diff --git a/web/src/dinners/DinnerDetailPage.tsx b/web/src/dinners/DinnerDetailPage.tsx index 2bec389..ab21eb0 100644 --- a/web/src/dinners/DinnerDetailPage.tsx +++ b/web/src/dinners/DinnerDetailPage.tsx @@ -276,9 +276,10 @@ function statusName(dinner: DinnerDetail): string { } // PlannedDay is the kicker of a dinner on a plan, "Saturday, Oct 10 · Side -// quest": the first day, from today on in the household's timezone, that -// this week's or next week's plan has it. There is none while the plans are -// read, nor when neither has the dinner from today on. +// quest", whenever this week's or next week's plan has it. Of the days that +// do, it is the first from today on in the household's timezone; when every +// one is already past, it is the latest of them. There is none while the +// plans are read, nor when neither plan has the dinner. function PlannedDay({ dinner, timezone }: { dinner: DinnerDetail; timezone: string }) { const today = localNow(timezone, new Date()).date const thisWeek = weekStart(today) @@ -291,9 +292,8 @@ function PlannedDay({ dinner, timezone }: { dinner: DinnerDetail; timezone: stri ) } - const day = plans - .flatMap((plan) => plan.data?.days ?? []) - .find((day) => day.dinner.id === dinner.id && day.date >= today) + const planned = plans.flatMap((plan) => plan.data?.days ?? []).filter((day) => day.dinner.id === dinner.id) + const day = planned.find((day) => day.date >= today) ?? planned.at(-1) if (day === undefined) return null return (

diff --git a/web/src/dinners/KitchenReading.test.tsx b/web/src/dinners/KitchenReading.test.tsx index 38a33b7..4d53796 100644 --- a/web/src/dinners/KitchenReading.test.tsx +++ b/web/src/dinners/KitchenReading.test.tsx @@ -46,8 +46,8 @@ function detail(overrides: Record = {}) { } // planBody is a week's plan as GET /api/v1/plans/{weekStart} sends it: p0 to -// p6, except the bowls (d1) on day bowlsOn, counting from Monday = 0. -function planBody(weekStart: string, bowlsOn?: number) { +// p6, except the bowls (d1) on each day of bowlsOn, counting from Monday = 0. +function planBody(weekStart: string, ...bowlsOn: number[]) { const start = new Date(`${weekStart}T00:00:00Z`).getTime() return { id: `plan-${weekStart}`, @@ -58,10 +58,10 @@ function planBody(weekStart: string, bowlsOn?: number) { days: Array.from({ length: 7 }, (_, i) => ({ date: new Date(start + i * 86_400_000).toISOString().slice(0, 10), dinner: - i === bowlsOn + bowlsOn.includes(i) ? { id: 'd1', name: 'Korean beef bowls', photo: null, minutes: 30, method: 'stovetop' } : { id: `p${i}`, name: `Dinner ${i}`, photo: null, minutes: 30, method: 'stovetop' }, - is_new: i === bowlsOn, + is_new: bowlsOn.includes(i), start_by: null, })), } @@ -176,13 +176,34 @@ describe('Kitchen reading mode', () => { expect(await screen.findByText('Monday, Oct 12 · Regular')).toBeInTheDocument() }) - it('is absent when no plan has the dinner from today on', async () => { - // On Monday, already past, and on no day of next week. - const fetchMock = serve({ + it("names a day of this week's plan already past when that is the only day", async () => { + // Today is Wednesday; the bowls were on Monday, and are on no day of next week. + serve({ [THIS_WEEK]: () => jsonResponse(200, planBody('2026-10-05', 0)), [NEXT_WEEK]: () => jsonResponse(200, planBody('2026-10-12')), }) await renderPage() + expect(await screen.findByText('Monday, Oct 5 · Side quest')).toBeInTheDocument() + }) + + it('names the first day from today on over a day already past', async () => { + serve({ [THIS_WEEK]: () => jsonResponse(200, planBody('2026-10-05', 0, 1, 4, 6)) }) + await renderPage() + expect(await screen.findByText('Friday, Oct 9 · Side quest')).toBeInTheDocument() + expect(screen.queryByText(/Monday|Tuesday|Sunday/)).not.toBeInTheDocument() + }) + + it('names the latest day already past when every day is past', async () => { + serve({ [THIS_WEEK]: () => jsonResponse(200, planBody('2026-10-05', 0, 1)) }) + await renderPage() + expect(await screen.findByText('Tuesday, Oct 6 · Side quest')).toBeInTheDocument() + expect(screen.queryByText(/Monday/)).not.toBeInTheDocument() + }) + + it('is absent when neither week has a plan', async () => { + const fetchMock = serve({ [THIS_WEEK]: () => errorResponse(404, 'not_found') }) + await renderPage() + await waitFor(() => expect(reads(fetchMock, THIS_WEEK)).toBe(1)) await waitFor(() => expect(reads(fetchMock, NEXT_WEEK)).toBe(1)) await act(async () => {}) expect(screen.queryByText(/· Side quest/)).not.toBeInTheDocument()