Skip to content

Commit 64d9059

Browse files
committed
test: exact wizard step reproduction with ego parents + sibling shared parents checkboxes
1 parent 5da5fee commit 64d9059

File tree

1 file changed

+146
-101
lines changed

1 file changed

+146
-101
lines changed
Lines changed: 146 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
import type { Meta, StoryObj } from '@storybook/nextjs-vite';
2-
import { useState } from 'react';
3-
import { expect, screen, userEvent, waitFor } from 'storybook/test';
2+
import { useEffect, useRef, useState } from 'react';
3+
import { expect, screen, userEvent, waitFor, within } from 'storybook/test';
4+
import Surface from '~/components/layout/Surface';
5+
import Heading from '~/components/typography/Heading';
6+
import Paragraph from '~/components/typography/Paragraph';
47
import Field from '~/lib/form/components/Field/Field';
58
import CheckboxGroupField from '~/lib/form/components/fields/CheckboxGroup';
9+
import InputField from '~/lib/form/components/fields/InputField';
10+
import RadioGroupField from '~/lib/form/components/fields/RadioGroup';
611
import useFormStore from '~/lib/form/hooks/useFormStore';
712
import FormStoreProvider from '~/lib/form/store/formStoreProvider';
813

14+
// ---- Simple controlled test ----
15+
916
function ControlledCheckboxGroup() {
1017
const [values, setValues] = useState<(string | number)[]>(['a', 'b', 'c']);
1118

@@ -27,140 +34,136 @@ function ControlledCheckboxGroup() {
2734
);
2835
}
2936

30-
const meta: Meta = {
31-
title: 'Form/Fields/CheckboxGroup Interaction',
32-
component: ControlledCheckboxGroup,
33-
};
34-
35-
export default meta;
36-
type Story = StoryObj;
37+
// ---- Wizard step reproduction ----
38+
// Mimics the SiblingsDetailStep exactly: FormStoreProvider, ego-parents
39+
// checkbox group, sibling name + sex fields, sibling shared-parents checkbox group
3740

38-
export const ClickCheckbox: Story = {
39-
play: async () => {
40-
// Find checkbox C and verify it starts checked
41-
const checkboxC = await screen.findByRole('checkbox', { name: 'Option C' });
42-
await expect(checkboxC).toHaveAttribute('aria-checked', 'true');
41+
const PARENTS = [
42+
{ name: 'Mom', value: '0' },
43+
{ name: 'Donor 1', value: '1' },
44+
{ name: 'Donor 2', value: '2' },
45+
];
4346

44-
// Click to uncheck
45-
await userEvent.click(checkboxC);
47+
const SEX_OPTIONS = [
48+
{ value: 'male', label: 'Male' },
49+
{ value: 'female', label: 'Female' },
50+
];
4651

47-
// Verify it unchecked
48-
await waitFor(async () => {
49-
await expect(checkboxC).toHaveAttribute('aria-checked', 'false');
50-
});
51-
52-
await waitFor(async () => {
53-
await expect(screen.getByTestId('result')).toHaveTextContent('["a","b"]');
54-
});
55-
},
56-
};
57-
58-
function FormFieldCheckboxGroup() {
52+
function WizardStepReproduction() {
5953
return (
6054
<FormStoreProvider>
61-
<FormFieldInner />
55+
<WizardStepInner />
6256
</FormStoreProvider>
6357
);
6458
}
6559

66-
function FormFieldInner() {
60+
function WizardStepInner() {
61+
const validateForm = useFormStore((s) => s.validateForm);
6762
const getFormValues = useFormStore((s) => s.getFormValues);
68-
const values = getFormValues();
63+
const [submitted, setSubmitted] = useState<string | null>(null);
64+
const getFormValuesRef = useRef(getFormValues);
65+
getFormValuesRef.current = getFormValues;
6966

70-
return (
71-
<div>
72-
<Field
73-
name="choices"
74-
label="Pick options"
75-
component={CheckboxGroupField}
76-
options={[
77-
{ value: 'a', label: 'Option A' },
78-
{ value: 'b', label: 'Option B' },
79-
{ value: 'c', label: 'Option C' },
80-
]}
81-
initialValue={['a', 'b', 'c']}
82-
/>
83-
<div data-testid="form-result">
84-
{JSON.stringify(values.choices ?? 'undefined')}
85-
</div>
86-
</div>
87-
);
88-
}
67+
const handleSubmit = async () => {
68+
const isValid = await validateForm();
69+
if (!isValid) return;
8970

90-
export const ClickCheckboxInFormField: Story = {
91-
render: () => <FormFieldCheckboxGroup />,
92-
play: async () => {
93-
const checkboxC = await screen.findByRole('checkbox', { name: 'Option C' });
94-
await expect(checkboxC).toHaveAttribute('aria-checked', 'true');
71+
const values = getFormValuesRef.current();
72+
const rawEgoParents = values['ego-parents'];
73+
const egoParentIndices = Array.isArray(rawEgoParents)
74+
? rawEgoParents.map((v) => Number(v))
75+
: [0, 1, 2];
9576

96-
await userEvent.click(checkboxC);
77+
const rawSharedParents = values['sibling-0-sharedParents'];
78+
const sharedParentIndices = Array.isArray(rawSharedParents)
79+
? rawSharedParents.map((v) => Number(v))
80+
: [];
9781

98-
await waitFor(async () => {
99-
await expect(checkboxC).toHaveAttribute('aria-checked', 'false');
100-
});
82+
setSubmitted(JSON.stringify({ egoParentIndices, sharedParentIndices }));
83+
};
10184

102-
await waitFor(async () => {
103-
await expect(screen.getByTestId('form-result')).toHaveTextContent(
104-
'["a","b"]',
105-
);
106-
});
107-
},
108-
};
85+
return (
86+
<div className="flex flex-col gap-6 p-4">
87+
<Surface level={1} spacing="sm">
88+
<Paragraph>
89+
Since you have multiple parents, please confirm which are specifically
90+
your parents.
91+
</Paragraph>
92+
<Field
93+
name="ego-parents"
94+
label="Which of these parents are YOUR parents?"
95+
data-testid="ego-parents-checkboxes"
96+
component={CheckboxGroupField}
97+
options={PARENTS.map((p) => ({ value: p.value, label: p.name }))}
98+
initialValue={PARENTS.map((p) => p.value)}
99+
/>
100+
</Surface>
101+
102+
<div className="flex flex-col gap-3 rounded border p-4">
103+
<Heading level="h3">Sibling 1</Heading>
104+
<Field
105+
name="sibling-0-name"
106+
label="Name"
107+
component={InputField}
108+
placeholder="Enter name"
109+
required
110+
/>
111+
<Field
112+
name="sibling-0-sex"
113+
label="Sex assigned at birth"
114+
component={RadioGroupField}
115+
options={SEX_OPTIONS}
116+
required
117+
/>
118+
<Field
119+
name="sibling-0-sharedParents"
120+
label="Which of your parents are also this sibling's parent?"
121+
component={CheckboxGroupField}
122+
options={PARENTS.map((p) => ({ value: p.value, label: p.name }))}
123+
initialValue={PARENTS.map((p) => p.value)}
124+
/>
125+
</div>
109126

110-
function DialogCheckboxGroup() {
111-
const [open, setOpen] = useState(true);
127+
<button onClick={() => void handleSubmit()}>Submit</button>
112128

113-
return (
114-
<div>
115-
<button onClick={() => setOpen(true)}>Open</button>
116-
{open && (
117-
<dialog open style={{ position: 'fixed', zIndex: 1000 }}>
118-
<FormStoreProvider>
119-
<DialogCheckboxInner onClose={() => setOpen(false)} />
120-
</FormStoreProvider>
121-
</dialog>
122-
)}
129+
{submitted && <div data-testid="wizard-result">{submitted}</div>}
123130
</div>
124131
);
125132
}
126133

127-
function DialogCheckboxInner({ onClose }: { onClose: () => void }) {
128-
return (
129-
<div>
130-
<Field
131-
name="choices"
132-
label="Pick options"
133-
data-testid="dialog-checkboxes"
134-
component={CheckboxGroupField}
135-
options={[
136-
{ value: 'a', label: 'Option A' },
137-
{ value: 'b', label: 'Option B' },
138-
{ value: 'c', label: 'Option C' },
139-
]}
140-
initialValue={['a', 'b', 'c']}
141-
/>
142-
<button onClick={onClose}>Close</button>
143-
</div>
144-
);
145-
}
134+
// ---- Meta ----
135+
136+
const meta: Meta = {
137+
title: 'Form/Fields/CheckboxGroup Interaction',
138+
component: ControlledCheckboxGroup,
139+
};
140+
141+
export default meta;
142+
type Story = StoryObj;
146143

147-
export const ClickCheckboxInDialog: Story = {
148-
render: () => <DialogCheckboxGroup />,
144+
// ---- Stories ----
145+
146+
export const ClickCheckbox: Story = {
149147
play: async () => {
150-
const checkboxC = await screen.findByRole('checkbox', { name: 'Option C' });
148+
const checkboxC = await screen.findByRole('checkbox', {
149+
name: 'Option C',
150+
});
151151
await expect(checkboxC).toHaveAttribute('aria-checked', 'true');
152152

153153
await userEvent.click(checkboxC);
154154

155155
await waitFor(async () => {
156156
await expect(checkboxC).toHaveAttribute('aria-checked', 'false');
157157
});
158+
159+
await waitFor(async () => {
160+
await expect(screen.getByTestId('result')).toHaveTextContent('["a","b"]');
161+
});
158162
},
159163
};
160164

161165
export const ClickLabel: Story = {
162166
play: async () => {
163-
// Find the text label and click it instead of the checkbox button
164167
const labelText = await screen.findByText('Option C');
165168
await expect(
166169
screen.getByRole('checkbox', { name: 'Option C' }),
@@ -179,3 +182,45 @@ export const ClickLabel: Story = {
179182
});
180183
},
181184
};
185+
186+
export const WizardStepRepro: Story = {
187+
render: () => <WizardStepReproduction />,
188+
play: async () => {
189+
// 1. Uncheck Donor 2 from ego's parents
190+
const egoContainer = await screen.findByTestId('ego-parents-checkboxes');
191+
const egoScope = within(egoContainer);
192+
const donor2Cb = await egoScope.findByRole('checkbox', {
193+
name: 'Donor 2',
194+
});
195+
await expect(donor2Cb).toHaveAttribute('aria-checked', 'true');
196+
await userEvent.click(donor2Cb);
197+
await waitFor(async () => {
198+
await expect(donor2Cb).toHaveAttribute('aria-checked', 'false');
199+
});
200+
201+
// 2. Fill sibling name + sex
202+
const nameInput = await screen.findByRole('textbox');
203+
await userEvent.click(nameInput);
204+
await userEvent.type(nameInput, 'Half Sib');
205+
await userEvent.click(await screen.findByRole('radio', { name: 'Female' }));
206+
207+
// 3. Uncheck Donor 1 from sibling's shared parents
208+
const donor1Cbs = await screen.findAllByRole('checkbox', {
209+
name: 'Donor 1',
210+
});
211+
// Index 0 = ego group, index 1 = sibling group
212+
await userEvent.click(donor1Cbs[1]!);
213+
await waitFor(async () => {
214+
await expect(donor1Cbs[1]).toHaveAttribute('aria-checked', 'false');
215+
});
216+
217+
// 4. Submit and verify
218+
await userEvent.click(screen.getByRole('button', { name: 'Submit' }));
219+
await waitFor(async () => {
220+
const result = screen.getByTestId('wizard-result');
221+
const data = JSON.parse(result.textContent!);
222+
await expect(data.egoParentIndices).toEqual([0, 1]);
223+
await expect(data.sharedParentIndices).toEqual([0, 2]);
224+
});
225+
},
226+
};

0 commit comments

Comments
 (0)