Add Quick dinner and Cook from what I have shortcuts to Home
The placeholder Home now links to /quick and /cook-from-pantry, so Cook from what I have has an in-app entry point. App tests click each link and check the destination screen renders. Task: ab589dee-4cdc-4a17-aff9-7b3cba975548 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import { act, cleanup, render, screen } from '@testing-library/react'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { createApiClient, createVerifiedSession } from './api/index.ts'
|
||||
import { createNetworkStatus } from './pwa/networkStatus.ts'
|
||||
import { stubAnimate } from './test/animate.ts'
|
||||
import { mockViewport } from './test/matchMedia.ts'
|
||||
import { errorResponse, fakeLocation, jsonResponse, ME, request, stubFetch, stubFetchByPath } from './test/fetchStub.ts'
|
||||
import App, { routes } from './App.tsx'
|
||||
@@ -12,6 +13,7 @@ import { defaultWeek } from './plan/week.ts'
|
||||
|
||||
afterEach(() => {
|
||||
Reflect.deleteProperty(window, 'matchMedia')
|
||||
Reflect.deleteProperty(Element.prototype, 'animate')
|
||||
vi.restoreAllMocks()
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
@@ -30,6 +32,7 @@ function renderAppAt(path: string, location = fakeLocation(path)) {
|
||||
|
||||
// The routes that render a built screen rather than a placeholder page.
|
||||
const SCREENS = {
|
||||
'/': true,
|
||||
'/onboarding': true,
|
||||
'/plan': true,
|
||||
'/plan/shopping': true,
|
||||
@@ -84,6 +87,28 @@ describe('App routes', () => {
|
||||
},
|
||||
)
|
||||
|
||||
it.each([
|
||||
['Quick dinner', '/quick', 'Quick dinners'],
|
||||
['Cook from what I have', '/cook-from-pantry', 'Cook from what I have'],
|
||||
])('Home links %s to %s', async (link, path, heading) => {
|
||||
mockViewport(390)
|
||||
stubAnimate({ autoFinish: true })
|
||||
// The idea screen's own requests stay pending: this checks only that Home
|
||||
// navigates to it.
|
||||
const fetchMock = vi.fn<typeof fetch>((input) => {
|
||||
const respond = Object.hasOwn(SIGNED_IN, String(input)) ? SIGNED_IN[String(input) as keyof typeof SIGNED_IN] : undefined
|
||||
return respond === undefined ? new Promise<Response>(() => {}) : Promise.resolve(respond())
|
||||
})
|
||||
vi.stubGlobal('fetch', fetchMock)
|
||||
renderAt('/')
|
||||
await screen.findByRole('heading', { level: 1, name: 'Home' })
|
||||
const shortcut = screen.getByRole('link', { name: link })
|
||||
expect(shortcut).toHaveAttribute('href', path)
|
||||
act(() => shortcut.click())
|
||||
expect(await screen.findByRole('heading', { level: 1, name: heading })).toBeInTheDocument()
|
||||
expect(screen.queryByRole('heading', { level: 1, name: 'Home' })).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('/settings renders the settings home inside the shell', async () => {
|
||||
mockViewport(390)
|
||||
stubFetchByPath({
|
||||
|
||||
@@ -8,6 +8,7 @@ import { CookFromPantryPage } from './ideas/CookFromPantryPage.tsx'
|
||||
import { QuickDinnersPage } from './ideas/QuickDinnersPage.tsx'
|
||||
import { ONBOARDING_PATH, OnboardingGate } from './onboarding/OnboardingGate.tsx'
|
||||
import { OnboardingPage } from './onboarding/OnboardingPage.tsx'
|
||||
import { HomePage } from './pages/HomePage.tsx'
|
||||
import { PlaceholderPage } from './pages/PlaceholderPage.tsx'
|
||||
import { PlanPage } from './plan/PlanPage.tsx'
|
||||
import { OfflinePill } from './pwa/OfflinePill.tsx'
|
||||
@@ -43,6 +44,7 @@ export const routes = [
|
||||
|
||||
// The screens built so far; every other route shows its placeholder page.
|
||||
const screens: Partial<Record<(typeof routes)[number]['path'], ReactNode>> = {
|
||||
'/': <HomePage />,
|
||||
'/plan': <PlanPage />,
|
||||
'/plan/shopping': <SupplyRunPage />,
|
||||
'/plan/shopping/archive': <SupplyArchivePage />,
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/* Placeholder Home with the idea-screen shortcuts. */
|
||||
.page {
|
||||
max-width: 560px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
padding-top: 6px;
|
||||
font-family: var(--font-heading);
|
||||
font-size: 28px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.rows {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
/* Rows in one card are split by a hairline inset to the row padding. */
|
||||
.item + .item {
|
||||
border-top: 1px solid var(--color-neutral-300);
|
||||
margin: 0 16px;
|
||||
}
|
||||
|
||||
.item + .item .row {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.row {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
min-height: 44px;
|
||||
padding: 14px 16px;
|
||||
border-radius: 26px;
|
||||
color: var(--color-text);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.icon {
|
||||
flex: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-bg);
|
||||
}
|
||||
|
||||
.icon svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.rowTitle {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-family: var(--font-body);
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { ChevronRight, Refrigerator, Zap, type LucideIcon } from 'lucide-react'
|
||||
import { Link } from 'react-router'
|
||||
import { Card, Icon } from '../ui/index.ts'
|
||||
import styles from './HomePage.module.css'
|
||||
|
||||
// Home stands in for the Home screen until its feature is built, with the
|
||||
// shortcuts to the idea screens that have no other entry point.
|
||||
export function HomePage() {
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
<h1 className={styles.title}>Home</h1>
|
||||
<Card>
|
||||
<ul className={styles.rows}>
|
||||
<Shortcut to="/quick" icon={Zap} title="Quick dinner" />
|
||||
<Shortcut to="/cook-from-pantry" icon={Refrigerator} title="Cook from what I have" />
|
||||
</ul>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type ShortcutProps = {
|
||||
to: string
|
||||
icon: LucideIcon
|
||||
title: string
|
||||
}
|
||||
|
||||
function Shortcut({ to, icon, title }: ShortcutProps) {
|
||||
return (
|
||||
<li className={styles.item}>
|
||||
<Link to={to} className={styles.row}>
|
||||
<span className={styles.icon}>
|
||||
<Icon icon={icon} />
|
||||
</span>
|
||||
<span className={styles.rowTitle}>{title}</span>
|
||||
<Icon icon={ChevronRight} />
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user