Skip to content

Commit 1b6dc5a

Browse files
Update storybook test patterns to match modern standards
- Update imports from 'storybook/test' to '@storybook/test' - Add 'within' import for canvas element querying - Update play function signatures to use destructured parameters - Add step-based test organization for better debugging - Preserve existing test logic while modernizing structure - Apply patterns from date-picker and calendar components Files updated: - checkbox.stories.tsx: Added step organization and modern patterns - switch.stories.tsx: Updated imports and step structure - checkbox-custom.stories.tsx: Updated imports - switch-custom.stories.tsx: Updated imports and type fixes - checkbox-list.stories.tsx: Updated imports and dependencies - radio-group.stories.tsx: Updated imports and streamlined dependencies - radio-group-custom.stories.tsx: Updated imports and type fixes - text-field.stories.tsx: Updated imports - text-field-custom.stories.tsx: Updated imports and type fixes - textarea.stories.tsx: Updated imports - textarea-custom.stories.tsx: Updated imports - dropdown-menu-select.stories.tsx: Updated imports and dependencies - otp-input.stories.tsx: Updated imports and dependencies
1 parent 3b10bda commit 1b6dc5a

13 files changed

Lines changed: 127 additions & 103 deletions

apps/docs/src/remix-hook-form/checkbox-custom.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { FormLabel, FormMessage } from '@lambdacurry/forms/remix-hook-form/
44
import { Button } from '@lambdacurry/forms/ui/button';
55
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
66
import type { Meta, StoryObj } from '@storybook/react-vite';
7-
import { expect, userEvent, within } from 'storybook/test';
7+
import { expect, userEvent, within } from '@storybook/test';
88
import type * as React from 'react';
99
import type { ActionFunctionArgs } from 'react-router';
1010
import { useFetcher } from 'react-router';

apps/docs/src/remix-hook-form/checkbox-list.stories.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import { zodResolver } from '@hookform/resolvers/zod';
22
import { Checkbox } from '@lambdacurry/forms/remix-hook-form/checkbox';
33
import { Button } from '@lambdacurry/forms/ui/button';
4-
import { FormMessage } from '@lambdacurry/forms/ui/form';
5-
import type { Meta, StoryContext, StoryObj } from '@storybook/react-vite';
6-
import { expect, userEvent } from 'storybook/test';
7-
import type {} from '@testing-library/dom';
8-
import { type ActionFunctionArgs, Form, useFetcher } from 'react-router';
9-
import { RemixFormProvider, createFormData, getValidatedFormData, useRemixForm } from 'remix-hook-form';
4+
import type { Meta, StoryObj } from '@storybook/react-vite';
5+
import { expect, userEvent, within } from '@storybook/test';
6+
import { type ActionFunctionArgs, useFetcher } from 'react-router';
7+
import { RemixFormProvider, getValidatedFormData, useRemixForm } from 'remix-hook-form';
108
import { z } from 'zod';
119
import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub';
1210

apps/docs/src/remix-hook-form/checkbox.stories.tsx

Lines changed: 45 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { zodResolver } from '@hookform/resolvers/zod';
22
import { Checkbox } from '@lambdacurry/forms/remix-hook-form/checkbox';
33
import { Button } from '@lambdacurry/forms/ui/button';
4-
import type { Meta, StoryContext, StoryObj } from '@storybook/react-vite';
5-
import { expect, userEvent } from 'storybook/test';
4+
import type { Meta, StoryObj } from '@storybook/react-vite';
5+
import { expect, userEvent, within } from '@storybook/test';
66
import { type ActionFunctionArgs, useFetcher } from 'react-router';
77
import { RemixFormProvider, getValidatedFormData, useRemixForm } from 'remix-hook-form';
88
import { z } from 'zod';
@@ -84,34 +84,6 @@ const meta: Meta<typeof Checkbox> = {
8484
export default meta;
8585
type Story = StoryObj<typeof meta>;
8686

87-
const testDefaultValues = ({ canvas }: StoryContext) => {
88-
const termsCheckbox = canvas.getByLabelText('Accept terms and conditions');
89-
const marketingCheckbox = canvas.getByLabelText('Receive marketing emails');
90-
const requiredCheckbox = canvas.getByLabelText('This is a required checkbox');
91-
expect(termsCheckbox).not.toBeChecked();
92-
expect(marketingCheckbox).not.toBeChecked();
93-
expect(requiredCheckbox).not.toBeChecked();
94-
};
95-
96-
const testInvalidSubmission = async ({ canvas }: StoryContext) => {
97-
const submitButton = canvas.getByRole('button', { name: 'Submit' });
98-
await userEvent.click(submitButton);
99-
await expect(await canvas.findByText('You must accept the terms and conditions')).toBeInTheDocument();
100-
await expect(await canvas.findByText('This field is required')).toBeInTheDocument();
101-
};
102-
103-
const testValidSubmission = async ({ canvas }: StoryContext) => {
104-
const termsCheckbox = canvas.getByLabelText('Accept terms and conditions');
105-
const requiredCheckbox = canvas.getByLabelText('This is a required checkbox');
106-
await userEvent.click(termsCheckbox);
107-
await userEvent.click(requiredCheckbox);
108-
109-
const submitButton = canvas.getByRole('button', { name: 'Submit' });
110-
await userEvent.click(submitButton);
111-
112-
await expect(await canvas.findByText('Form submitted successfully')).toBeInTheDocument();
113-
};
114-
11587
export const Default: Story = {
11688
parameters: {
11789
docs: {
@@ -161,9 +133,48 @@ const ControlledCheckboxExample = () => {
161133
},
162134
},
163135
},
164-
play: async (storyContext) => {
165-
testDefaultValues(storyContext);
166-
await testInvalidSubmission(storyContext);
167-
await testValidSubmission(storyContext);
136+
play: async ({ canvasElement, step }) => {
137+
const canvas = within(canvasElement);
138+
139+
await step('Verify initial state', async () => {
140+
// Verify all checkboxes are unchecked initially
141+
const termsCheckbox = canvas.getByLabelText('Accept terms and conditions');
142+
const marketingCheckbox = canvas.getByLabelText('Receive marketing emails');
143+
const requiredCheckbox = canvas.getByLabelText('This is a required checkbox');
144+
145+
expect(termsCheckbox).not.toBeChecked();
146+
expect(marketingCheckbox).not.toBeChecked();
147+
expect(requiredCheckbox).not.toBeChecked();
148+
149+
// Verify submit button is present
150+
const submitButton = canvas.getByRole('button', { name: 'Submit' });
151+
expect(submitButton).toBeInTheDocument();
152+
});
153+
154+
await step('Test validation errors on invalid submission', async () => {
155+
// Submit form without checking required checkboxes
156+
const submitButton = canvas.getByRole('button', { name: 'Submit' });
157+
await userEvent.click(submitButton);
158+
159+
// Verify validation error messages appear
160+
await expect(canvas.findByText('You must accept the terms and conditions')).resolves.toBeInTheDocument();
161+
await expect(canvas.findByText('This field is required')).resolves.toBeInTheDocument();
162+
});
163+
164+
await step('Test successful form submission', async () => {
165+
// Check required checkboxes
166+
const termsCheckbox = canvas.getByLabelText('Accept terms and conditions');
167+
const requiredCheckbox = canvas.getByLabelText('This is a required checkbox');
168+
169+
await userEvent.click(termsCheckbox);
170+
await userEvent.click(requiredCheckbox);
171+
172+
// Submit form
173+
const submitButton = canvas.getByRole('button', { name: 'Submit' });
174+
await userEvent.click(submitButton);
175+
176+
// Verify success message
177+
await expect(canvas.findByText('Form submitted successfully')).resolves.toBeInTheDocument();
178+
});
168179
},
169180
};

apps/docs/src/remix-hook-form/dropdown-menu-select.stories.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { DropdownMenuSelect } from '@lambdacurry/forms/remix-hook-form/dropdown-
33
import { Button } from '@lambdacurry/forms/ui/button';
44
import { DropdownMenuSelectItem } from '@lambdacurry/forms/ui/dropdown-menu-select-field';
55
import type { Meta, StoryObj } from '@storybook/react-vite';
6-
import { expect, screen, userEvent, within } from 'storybook/test';
7-
import { type ActionFunctionArgs, Form, useFetcher } from 'react-router';
6+
import { expect, screen, userEvent, within } from '@storybook/test';
7+
import { type ActionFunctionArgs, useFetcher } from 'react-router';
88
import { RemixFormProvider, createFormData, getValidatedFormData, useRemixForm } from 'remix-hook-form';
99
import { z } from 'zod';
1010
import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub';

apps/docs/src/remix-hook-form/otp-input.stories.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { zodResolver } from '@hookform/resolvers/zod';
22
import { OTPInput } from '@lambdacurry/forms/remix-hook-form/otp-input';
33
import { Button } from '@lambdacurry/forms/ui/button';
44
import type { Meta, StoryObj } from '@storybook/react-vite';
5-
import { expect, userEvent, within } from 'storybook/test';
6-
import { type ActionFunctionArgs, Form, useFetcher } from 'react-router';
5+
import { expect, userEvent, within } from '@storybook/test';
6+
import { type ActionFunctionArgs, useFetcher } from 'react-router';
77
import { RemixFormProvider, createFormData, getValidatedFormData, useRemixForm } from 'remix-hook-form';
88
import { z } from 'zod';
99
import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub';

apps/docs/src/remix-hook-form/radio-group-custom.stories.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
import { zodResolver } from '@hookform/resolvers/zod';
2-
import { RadioGroup } from '@lambdacurry/forms/remix-hook-form/radio-group';
2+
import { RadioGroup, type RadioOption } from '@lambdacurry/forms/remix-hook-form/radio-group';
33
import { RadioGroupItem } from '@lambdacurry/forms/remix-hook-form/radio-group-item';
4+
import type { FormLabel, FormMessage } from '@lambdacurry/forms/remix-hook-form/form';
45
import { Button } from '@lambdacurry/forms/ui/button';
5-
import { FormLabel, FormMessage } from '@lambdacurry/forms/ui/form';
6-
import { cn } from '@lambdacurry/forms/ui/utils';
6+
import { Label } from '@lambdacurry/forms/ui/label';
77
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
88
import type { Meta, StoryObj } from '@storybook/react-vite';
9-
import { expect, userEvent, within } from 'storybook/test';
10-
import type * as React from 'react';
11-
import type { ActionFunctionArgs } from 'react-router';
12-
import { Form, useFetcher } from 'react-router';
9+
import { expect, userEvent, within } from '@storybook/test';
10+
import type { ComponentPropsWithoutRef, ComponentType } from 'react';
11+
import { type ActionFunctionArgs, useFetcher } from 'react-router';
1312
import { RemixFormProvider, getValidatedFormData, useRemixForm } from 'remix-hook-form';
1413
import { z } from 'zod';
1514
import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub';

apps/docs/src/remix-hook-form/radio-group.stories.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
import { zodResolver } from '@hookform/resolvers/zod';
2-
import { RadioGroup, type RadioOption } from '@lambdacurry/forms/remix-hook-form/radio-group';
3-
import { RadioGroupItem } from '@lambdacurry/forms/remix-hook-form/radio-group-item';
2+
import { RadioGroup } from '@lambdacurry/forms/remix-hook-form/radio-group';
43
import { Button } from '@lambdacurry/forms/ui/button';
5-
import { Label } from '@lambdacurry/forms/ui/label';
64
import type { Meta, StoryObj } from '@storybook/react-vite';
7-
import { expect, userEvent, within } from 'storybook/test';
8-
import type { ComponentPropsWithoutRef, ComponentType } from 'react';
9-
import { type ActionFunctionArgs, Form, useFetcher } from 'react-router';
10-
import { RemixFormProvider, getValidatedFormData, useRemixForm } from 'remix-hook-form';
5+
import { expect, userEvent, within } from '@storybook/test';
6+
import { type ActionFunctionArgs, useFetcher } from 'react-router';
7+
import { RemixFormProvider, createFormData, getValidatedFormData, useRemixForm } from 'remix-hook-form';
118
import { z } from 'zod';
129
import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub';
1310

apps/docs/src/remix-hook-form/switch-custom.stories.tsx

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import { zodResolver } from '@hookform/resolvers/zod';
22
import { Switch } from '@lambdacurry/forms/remix-hook-form/switch';
3+
import type { FormLabel, FormMessage } from '@lambdacurry/forms/remix-hook-form/form';
34
import { Button } from '@lambdacurry/forms/ui/button';
4-
import { FormLabel, FormMessage } from '@lambdacurry/forms/ui/form';
5-
import * as SwitchPrimitives from '@radix-ui/react-switch';
5+
import * as SwitchPrimitive from '@radix-ui/react-switch';
66
import type { Meta, StoryObj } from '@storybook/react-vite';
7-
import { expect, userEvent, within } from 'storybook/test';
7+
import { expect, userEvent, within } from '@storybook/test';
88
import type * as React from 'react';
99
import type { ActionFunctionArgs } from 'react-router';
1010
import { useFetcher } from 'react-router';
11-
import { RemixFormProvider, getValidatedFormData, useRemixForm } from 'remix-hook-form';
11+
import { RemixFormProvider, createFormData, getValidatedFormData, useRemixForm } from 'remix-hook-form';
1212
import { z } from 'zod';
1313
import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub';
1414

@@ -21,19 +21,19 @@ const formSchema = z.object({
2121
type FormData = z.infer<typeof formSchema>;
2222

2323
// Custom Switch component
24-
const PurpleSwitch = (props: React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>) => (
25-
<SwitchPrimitives.Root
24+
const PurpleSwitch = (props: React.ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>) => (
25+
<SwitchPrimitive.Root
2626
{...props}
2727
className="peer inline-flex h-8 w-16 shrink-0 cursor-pointer items-center rounded-full border-2 border-purple-300 bg-purple-100 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-purple-500 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-purple-600 data-[state=unchecked]:bg-purple-200"
2828
>
2929
{props.children}
30-
</SwitchPrimitives.Root>
30+
</SwitchPrimitive.Root>
3131
);
3232
PurpleSwitch.displayName = 'PurpleSwitch';
3333

3434
// Custom Switch Thumb component
35-
const PurpleSwitchThumb = (props: React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Thumb>) => (
36-
<SwitchPrimitives.Thumb
35+
const PurpleSwitchThumb = (props: React.ComponentPropsWithoutRef<typeof SwitchPrimitive.Thumb>) => (
36+
<SwitchPrimitive.Thumb
3737
{...props}
3838
className="pointer-events-none flex h-7 w-7 items-center justify-center rounded-full bg-white shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-8 data-[state=unchecked]:translate-x-0 relative overflow-hidden data-[state=checked]:[--on-opacity:1] data-[state=checked]:[--off-opacity:0] data-[state=unchecked]:[--on-opacity:0] data-[state=unchecked]:[--off-opacity:1]"
3939
>
@@ -43,7 +43,7 @@ const PurpleSwitchThumb = (props: React.ComponentPropsWithoutRef<typeof SwitchPr
4343
<span className="absolute inset-0 opacity-[var(--off-opacity)] flex items-center justify-center text-xs font-bold text-purple-600 transition-opacity duration-200 z-10">
4444
OFF
4545
</span>
46-
</SwitchPrimitives.Thumb>
46+
</SwitchPrimitive.Thumb>
4747
);
4848
PurpleSwitchThumb.displayName = 'PurpleSwitchThumb';
4949

@@ -60,19 +60,19 @@ const PurpleMessage = (props: React.ComponentPropsWithoutRef<typeof FormMessage>
6060
PurpleMessage.displayName = 'PurpleMessage';
6161

6262
// Green Switch component
63-
const GreenSwitch = (props: React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>) => (
64-
<SwitchPrimitives.Root
63+
const GreenSwitch = (props: React.ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>) => (
64+
<SwitchPrimitive.Root
6565
{...props}
6666
className="peer inline-flex h-7 w-14 shrink-0 cursor-pointer items-center rounded-full border-2 border-green-300 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-green-500 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-green-600 data-[state=unchecked]:bg-white"
6767
>
6868
{props.children}
69-
</SwitchPrimitives.Root>
69+
</SwitchPrimitive.Root>
7070
);
7171
GreenSwitch.displayName = 'GreenSwitch';
7272

7373
// Green Switch Thumb component
74-
const GreenSwitchThumb = (props: React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Thumb>) => (
75-
<SwitchPrimitives.Thumb
74+
const GreenSwitchThumb = (props: React.ComponentPropsWithoutRef<typeof SwitchPrimitive.Thumb>) => (
75+
<SwitchPrimitive.Thumb
7676
{...props}
7777
className="pointer-events-none flex h-6 w-6 items-center justify-center rounded-full bg-green-100 shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-7 data-[state=unchecked]:translate-x-0 data-[state=checked]:bg-white"
7878
>
@@ -86,7 +86,7 @@ const GreenSwitchThumb = (props: React.ComponentPropsWithoutRef<typeof SwitchPri
8686
<title>Check mark</title>
8787
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
8888
</svg>
89-
</SwitchPrimitives.Thumb>
89+
</SwitchPrimitive.Thumb>
9090
);
9191
GreenSwitchThumb.displayName = 'GreenSwitchThumb';
9292

@@ -234,17 +234,17 @@ The \`components\` prop allows you to override any of the internal components us
234234
235235
// APPROACH 2: Custom Switch with purple styling and ON/OFF text
236236
const PurpleSwitch = React.forwardRef((props, ref) => (
237-
<SwitchPrimitives.Root
237+
<SwitchPrimitive.Root
238238
ref={ref}
239239
{...props}
240240
className="peer inline-flex h-8 w-16 shrink-0 cursor-pointer items-center rounded-full border-2 border-purple-300 bg-purple-100 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-purple-500 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-purple-600 data-[state=unchecked]:bg-purple-200"
241241
>
242242
{props.children}
243-
</SwitchPrimitives.Root>
243+
</SwitchPrimitive.Root>
244244
));
245245
246246
const PurpleSwitchThumb = React.forwardRef((props, ref) => (
247-
<SwitchPrimitives.Thumb
247+
<SwitchPrimitive.Thumb
248248
ref={ref}
249249
{...props}
250250
className="pointer-events-none flex h-7 w-7 items-center justify-center rounded-full bg-white shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-8 data-[state=unchecked]:translate-x-0 relative overflow-hidden data-[state=checked]:[--on-opacity:1] data-[state=checked]:[--off-opacity:0] data-[state=unchecked]:[--on-opacity:0] data-[state=unchecked]:[--off-opacity:1]"
@@ -255,7 +255,7 @@ const PurpleSwitchThumb = React.forwardRef((props, ref) => (
255255
<span className="absolute inset-0 opacity-[var(--off-opacity)] flex items-center justify-center text-xs font-bold text-purple-600 transition-opacity duration-200 z-10">
256256
OFF
257257
</span>
258-
</SwitchPrimitives.Thumb>
258+
</SwitchPrimitive.Thumb>
259259
));
260260
261261
<Switch
@@ -270,17 +270,17 @@ const PurpleSwitchThumb = React.forwardRef((props, ref) => (
270270
271271
// APPROACH 3: Fully customized switch with green styling, checkmark icon, and custom form components
272272
const GreenSwitch = React.forwardRef((props, ref) => (
273-
<SwitchPrimitives.Root
273+
<SwitchPrimitive.Root
274274
ref={ref}
275275
{...props}
276276
className="peer inline-flex h-7 w-14 shrink-0 cursor-pointer items-center rounded-full border-2 border-green-300 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-green-500 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-green-600 data-[state=unchecked]:bg-white"
277277
>
278278
{props.children}
279-
</SwitchPrimitives.Root>
279+
</SwitchPrimitive.Root>
280280
));
281281
282282
const GreenSwitchThumb = React.forwardRef((props, ref) => (
283-
<SwitchPrimitives.Thumb
283+
<SwitchPrimitive.Thumb
284284
ref={ref}
285285
{...props}
286286
className="pointer-events-none flex h-6 w-6 items-center justify-center rounded-full bg-green-100 shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-7 data-[state=unchecked]:translate-x-0 data-[state=checked]:bg-white"
@@ -294,7 +294,7 @@ const GreenSwitchThumb = React.forwardRef((props, ref) => (
294294
>
295295
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
296296
</svg>
297-
</SwitchPrimitives.Thumb>
297+
</SwitchPrimitive.Thumb>
298298
));
299299
300300
const PurpleLabel = React.forwardRef((props, ref) => (

0 commit comments

Comments
 (0)