Require Home minutes_total and Supply run label for every list (review H1)
CI / ci (push) Successful in 8m2s
CI / ci (pull_request) Successful in 7m55s

parseTonight now rejects a tonight dinner without the minutes_total key
instead of reading it as null; an explicit null is still valid. The Home
API always emits the key. parseShoppingList checks the label against
weekStart for unready lists too, since the API supplies it regardless of
readiness.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-09 21:21:34 -04:00
co-authored by Claude Opus 5.5
parent 8e18c0b37c
commit d572ee192d
2 changed files with 41 additions and 11 deletions
+31
View File
@@ -227,6 +227,16 @@ describe('HomePage', () => {
expect(screen.queryByText(/min$/)).not.toBeInTheDocument()
})
it('reports tonight without a minutes_total rather than showing it as unknown', async () => {
const { minutes_total: _, ...tonight } = HOME.tonight
renderHome({ ...HOME, tonight })
expect(await screen.findByRole('alert')).toHaveTextContent('tonight has no minutes_total')
expect(screen.getByRole('button', { name: 'Retry' })).toBeInTheDocument()
expect(screen.queryByRole('region', { name: 'Tonight' })).not.toBeInTheDocument()
expect(screen.queryByText('Regular')).not.toBeInTheDocument()
})
it.each([
['a missing', undefined],
['an empty', ''],
@@ -248,6 +258,27 @@ describe('HomePage', () => {
expect(screen.queryByRole('link', { name: /Shopping list ready/ })).not.toBeInTheDocument()
})
it.each([
['a missing', undefined],
['an empty', ''],
])('reports an unready Supply run with %s label rather than showing the home', async (_, label) => {
renderHome({ ...HOME, shoppingList: { ...NOTHING_YET.shoppingList, label } })
expect(await screen.findByRole('alert')).toHaveTextContent('the unready shoppingList has no label')
expect(screen.getByRole('button', { name: 'Retry' })).toBeInTheDocument()
expect(screen.queryByRole('list', { name: 'Status' })).not.toBeInTheDocument()
})
it('reports an unready Supply run whose label is not its week rather than showing the home', async () => {
renderHome({ ...HOME, shoppingList: { ...NOTHING_YET.shoppingList, label: 'Week 41 · Oct 5 – 11' } })
expect(await screen.findByRole('alert')).toHaveTextContent(
'shoppingList label "Week 41 · Oct 5 – 11" is not the week of 2026-09-28',
)
expect(screen.getByRole('button', { name: 'Retry' })).toBeInTheDocument()
expect(screen.queryByRole('list', { name: 'Status' })).not.toBeInTheDocument()
})
it('reports a Supply run whose weekStart is not a date rather than showing it', async () => {
renderHome({ ...HOME, shoppingList: { ...HOME.shoppingList, weekStart: 'Oct 5' } })
+10 -11
View File
@@ -39,7 +39,8 @@ function parseTonight(where: string, body: unknown): Tonight | null {
if (!isRecord(body) || typeof body.id !== 'string' || typeof body.name !== 'string') {
throw new Error(`${where}: tonight is not a dinner`)
}
const minutes = body.minutes_total ?? null
if (!('minutes_total' in body)) throw new Error(`${where}: tonight has no minutes_total`)
const minutes = body.minutes_total
if (minutes !== null && (!Number.isInteger(minutes) || (minutes as number) <= 0)) {
throw new Error(`${where}: tonight's minutes_total ${String(minutes)} is not a positive whole number`)
}
@@ -52,21 +53,19 @@ function parseTonight(where: string, body: unknown): Tonight | null {
}
}
// parseShoppingList checks the shoppingList of a GET /api/v1/home body. A
// ready list must carry the API's label for its week, which must be the
// label weekStart has, since the status row shows dates from weekStart.
// parseShoppingList checks the shoppingList of a GET /api/v1/home body. The
// list, ready or not, must carry the API's label for its week, which must be
// the label weekStart has, since the status row shows dates from weekStart.
function parseShoppingList(where: string, list: unknown): ShoppingListStatus {
if (!isRecord(list) || typeof list.weekStart !== 'string' || typeof list.ready !== 'boolean') {
throw new Error(`${where}: shoppingList is not a week's Supply run status`)
}
const week = weekLabel(list.weekStart)
if (list.ready) {
if (typeof list.label !== 'string' || list.label === '') {
throw new Error(`${where}: the ready shoppingList has no label`)
}
if (list.label !== `${week.week} · ${week.dates}`) {
throw new Error(`${where}: shoppingList label "${list.label}" is not the week of ${list.weekStart}`)
}
if (typeof list.label !== 'string' || list.label === '') {
throw new Error(`${where}: the ${list.ready ? 'ready' : 'unready'} shoppingList has no label`)
}
if (list.label !== `${week.week} · ${week.dates}`) {
throw new Error(`${where}: shoppingList label "${list.label}" is not the week of ${list.weekStart}`)
}
return { weekStart: list.weekStart, dates: week.dates.replace(' – ', '–'), ready: list.ready }
}