Gate onboarding Next on three saved Regulars
Next: notifications counted optimistic chips, so a household with two saved Regulars could tap a third and leave step 2 before the server refused it. Next now waits until no add is pending and three Regulars have server ids. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -234,7 +234,7 @@ describe('onboarding step 2 (mock 1h)', () => {
|
|||||||
expect(regularNames()).toEqual(['Beef tacos', 'Pizza night'])
|
expect(regularNames()).toEqual(['Beef tacos', 'Pizza night'])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('enables "Next: notifications" once there are 3 Regulars', async () => {
|
it('enables "Next: notifications" once the server has saved 3 Regulars', async () => {
|
||||||
const server = await regularsStep([
|
const server = await regularsStep([
|
||||||
{ id: 'd1', name: 'Beef tacos' },
|
{ id: 'd1', name: 'Beef tacos' },
|
||||||
{ id: 'd2', name: 'Pizza night' },
|
{ id: 'd2', name: 'Pizza night' },
|
||||||
@@ -242,10 +242,13 @@ describe('onboarding step 2 (mock 1h)', () => {
|
|||||||
expect(next()).toBeDisabled()
|
expect(next()).toBeDisabled()
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole('button', { name: 'Add Burgers' }))
|
fireEvent.click(screen.getByRole('button', { name: 'Add Burgers' }))
|
||||||
await vi.waitFor(() => expect(next()).toBeEnabled())
|
await screen.findByRole('heading', { level: 2, name: 'Your regulars · 3' })
|
||||||
|
// The third chip shows at once, but Next waits for the server.
|
||||||
|
expect(next()).toBeDisabled()
|
||||||
await vi.waitFor(() => expect(server.held).toHaveLength(1))
|
await vi.waitFor(() => expect(server.held).toHaveLength(1))
|
||||||
created(server, server.held[0]!, 'd3')
|
created(server, server.held[0]!, 'd3')
|
||||||
await screen.findByRole('button', { name: 'Remove Burgers' })
|
await screen.findByRole('button', { name: 'Remove Burgers' })
|
||||||
|
expect(next()).toBeEnabled()
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole('button', { name: 'Remove Pizza night' }))
|
fireEvent.click(screen.getByRole('button', { name: 'Remove Pizza night' }))
|
||||||
await vi.waitFor(() => expect(next()).toBeDisabled())
|
await vi.waitFor(() => expect(next()).toBeDisabled())
|
||||||
@@ -256,4 +259,37 @@ describe('onboarding step 2 (mock 1h)', () => {
|
|||||||
fireEvent.click(next())
|
fireEvent.click(next())
|
||||||
expect(await screen.findByText('Step 3 of 3')).toBeInTheDocument()
|
expect(await screen.findByText('Step 3 of 3')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('keeps "Next: notifications" disabled when the third add is refused', async () => {
|
||||||
|
const server = await regularsStep([
|
||||||
|
{ id: 'd1', name: 'Beef tacos' },
|
||||||
|
{ id: 'd2', name: 'Pizza night' },
|
||||||
|
])
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Add Burgers' }))
|
||||||
|
await screen.findByRole('heading', { level: 2, name: 'Your regulars · 3' })
|
||||||
|
expect(next()).toBeDisabled()
|
||||||
|
|
||||||
|
await vi.waitFor(() => expect(server.held).toHaveLength(1))
|
||||||
|
server.held[0]!.answer(errorResponse(409, 'duplicate_dinner'))
|
||||||
|
expect(await screen.findByRole('alert')).toHaveTextContent('Could not add Burgers: duplicate_dinner message')
|
||||||
|
expect(regularNames()).toEqual(['Beef tacos', 'Pizza night'])
|
||||||
|
expect(next()).toBeDisabled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('keeps "Next: notifications" disabled while a fourth add is unanswered', async () => {
|
||||||
|
const server = await regularsStep([
|
||||||
|
{ id: 'd1', name: 'Beef tacos' },
|
||||||
|
{ id: 'd2', name: 'Pizza night' },
|
||||||
|
{ id: 'd3', name: 'Chili' },
|
||||||
|
])
|
||||||
|
expect(next()).toBeEnabled()
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Add Burgers' }))
|
||||||
|
await screen.findByRole('heading', { level: 2, name: 'Your regulars · 4' })
|
||||||
|
expect(next()).toBeDisabled()
|
||||||
|
await vi.waitFor(() => expect(server.held).toHaveLength(1))
|
||||||
|
server.held[0]!.answer(errorResponse(409, 'duplicate_dinner'))
|
||||||
|
await screen.findByRole('alert')
|
||||||
|
expect(next()).toBeEnabled()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ import { TextInput } from '../ui/TextInput.tsx'
|
|||||||
import { OnboardingStep } from './OnboardingStep.tsx'
|
import { OnboardingStep } from './OnboardingStep.tsx'
|
||||||
import styles from './Onboarding.module.css'
|
import styles from './Onboarding.module.css'
|
||||||
|
|
||||||
// MIN_REGULARS is how many Regulars the household adds before moving on.
|
// MIN_REGULARS is how many Regulars the server has saved before the
|
||||||
|
// household moves on.
|
||||||
export const MIN_REGULARS = 3
|
export const MIN_REGULARS = 3
|
||||||
|
|
||||||
// commonPicks are offered as one-tap Regulars until the household has them.
|
// commonPicks are offered as one-tap Regulars until the household has them.
|
||||||
@@ -41,6 +42,10 @@ export function RegularsStep() {
|
|||||||
const [draft, setDraft] = useState('')
|
const [draft, setDraft] = useState('')
|
||||||
const list = regulars.data ?? []
|
const list = regulars.data ?? []
|
||||||
const picks = commonPicks.filter((pick) => !list.some((regular) => sameName(regular.name, pick)))
|
const picks = commonPicks.filter((pick) => !list.some((regular) => sameName(regular.name, pick)))
|
||||||
|
// Next waits for every add to be answered, so a refused add's error shows
|
||||||
|
// here and an add the server takes back never counts towards the three.
|
||||||
|
const adding = list.some((regular) => regular.pending)
|
||||||
|
const saved = list.filter((regular) => !regular.pending).length
|
||||||
|
|
||||||
function onSubmit(event: FormEvent<HTMLFormElement>) {
|
function onSubmit(event: FormEvent<HTMLFormElement>) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
@@ -57,7 +62,7 @@ export function RegularsStep() {
|
|||||||
action={
|
action={
|
||||||
<Button
|
<Button
|
||||||
className={styles.action}
|
className={styles.action}
|
||||||
disabled={list.length < MIN_REGULARS}
|
disabled={adding || saved < MIN_REGULARS}
|
||||||
onClick={() => void navigate('/onboarding/notifications')}
|
onClick={() => void navigate('/onboarding/notifications')}
|
||||||
>
|
>
|
||||||
Next: notifications
|
Next: notifications
|
||||||
|
|||||||
Reference in New Issue
Block a user