Skip to content

Commit 5cd18cb

Browse files
authored
Merge pull request #71 from lambda-curry/codegen-bot/storybook-testing-guide-1748113487
2 parents a8f493d + 58a76a8 commit 5cd18cb

File tree

1 file changed

+245
-0
lines changed

1 file changed

+245
-0
lines changed
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
---
2+
type: Always
3+
description: Rules for writing Storybook Playwright tests in the lambda-curry/forms repository
4+
---
5+
6+
You are an expert in Storybook, Playwright testing, React, TypeScript, Remix Hook Form, Zod validation, and the lambda-curry/forms monorepo architecture.
7+
8+
# Project Context
9+
This is a monorepo containing form components with comprehensive Storybook Playwright testing. The testing setup combines Storybook's component isolation with Playwright's browser automation to create real-world testing scenarios.
10+
11+
## Key Technologies
12+
- Storybook 8.6.7 with React and Vite
13+
- @storybook/test-runner for Playwright automation
14+
- @storybook/test for testing utilities (userEvent, expect, canvas)
15+
- React Router stub decorator for form handling
16+
- Remix Hook Form + Zod for validation testing
17+
- Yarn 4.7.0 with corepack
18+
- TypeScript throughout
19+
20+
## Project Structure
21+
```
22+
lambda-curry/forms/
23+
├── apps/docs/ # Storybook app
24+
│ ├── .storybook/ # Storybook configuration
25+
│ ├── src/remix-hook-form/ # Story files with tests
26+
│ └── package.json # Test scripts
27+
├── packages/components/ # Component library
28+
│ └── src/
29+
│ ├── remix-hook-form/ # Form components
30+
│ └── ui/ # UI components
31+
└── .cursor/rules/ # Cursor rules directory
32+
```
33+
34+
# Core Principles for Storybook Testing
35+
36+
## Story Structure Pattern
37+
- Follow the three-phase testing pattern: Default state → Invalid submission → Valid submission
38+
- Each story serves dual purposes: documentation AND automated tests
39+
- Use play functions for comprehensive interaction testing
40+
- Test complete user workflows, not isolated units
41+
42+
## Essential Code Elements
43+
Always include these in Storybook test stories:
44+
45+
### Required Imports
46+
```typescript
47+
import type { Meta, StoryContext, StoryObj } from '@storybook/react';
48+
import { expect, userEvent } from '@storybook/test';
49+
import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub';
50+
```
51+
52+
### Form Schema Setup
53+
```typescript
54+
const formSchema = z.object({
55+
fieldName: z.string().min(1, 'Field is required'),
56+
});
57+
type FormData = z.infer<typeof formSchema>;
58+
```
59+
60+
### Component Wrapper Pattern
61+
```typescript
62+
const ControlledComponentExample = () => {
63+
const fetcher = useFetcher<{ message: string }>();
64+
const methods = useRemixForm<FormData>({
65+
resolver: zodResolver(formSchema),
66+
defaultValues: { /* defaults */ },
67+
fetcher,
68+
submitConfig: { action: '/', method: 'post' },
69+
});
70+
71+
return (
72+
<RemixFormProvider {...methods}>
73+
<fetcher.Form onSubmit={methods.handleSubmit}>
74+
{/* Component and form elements */}
75+
</fetcher.Form>
76+
</RemixFormProvider>
77+
);
78+
};
79+
```
80+
81+
## Testing Patterns
82+
83+
### User Interaction Best Practices
84+
```typescript
85+
// ✅ ALWAYS click before clearing inputs
86+
await userEvent.click(input);
87+
await userEvent.clear(input);
88+
await userEvent.type(input, 'new value');
89+
90+
// ✅ Use findBy* for async elements
91+
const message = await canvas.findByText('Success message');
92+
expect(message).toBeInTheDocument();
93+
94+
// ✅ Use queryBy* to check non-existence
95+
expect(canvas.queryByText('Should not exist')).not.toBeInTheDocument();
96+
```
97+
98+
### Three-Phase Test Structure
99+
```typescript
100+
export const Default: Story = {
101+
play: async (storyContext) => {
102+
// Phase 1: Test initial state
103+
testDefaultValues(storyContext);
104+
105+
// Phase 2: Test validation/error states
106+
await testInvalidSubmission(storyContext);
107+
108+
// Phase 3: Test success scenarios
109+
await testValidSubmission(storyContext);
110+
},
111+
decorators: [withReactRouterStubDecorator({ /* config */ })],
112+
};
113+
```
114+
115+
### React Router Stub Decorator
116+
```typescript
117+
withReactRouterStubDecorator({
118+
routes: [{
119+
path: '/',
120+
Component: ControlledComponentExample,
121+
action: async ({ request }) => {
122+
const { data, errors } = await getValidatedFormData<FormData>(
123+
request,
124+
zodResolver(formSchema)
125+
);
126+
if (errors) return { errors };
127+
return { message: 'Form submitted successfully' };
128+
},
129+
}],
130+
})
131+
```
132+
133+
## Deprecated Patterns - DO NOT USE
134+
135+
❌ **Never use getBy* for async elements**
136+
```typescript
137+
// BAD - will fail for async content
138+
const message = canvas.getByText('Success message');
139+
```
140+
141+
❌ **Never clear inputs without clicking first**
142+
```typescript
143+
// BAD - unreliable
144+
await userEvent.clear(input);
145+
```
146+
147+
❌ **Never use regular forms instead of fetcher.Form**
148+
```typescript
149+
// BAD - won't work with React Router stub
150+
<form onSubmit={methods.handleSubmit}>
151+
```
152+
153+
❌ **Never test multiple unrelated scenarios in one story**
154+
```typescript
155+
// BAD - stories should be focused
156+
export const AllScenarios: Story = { /* testing everything */ };
157+
```
158+
159+
## File Naming and Organization
160+
- Story files: `component-name.stories.tsx` in `apps/docs/src/remix-hook-form/`
161+
- Use kebab-case for file names
162+
- Group related test functions together
163+
- Export individual test functions for reusability
164+
165+
## Testing Utilities and Helpers
166+
167+
### Canvas Queries
168+
```typescript
169+
// Form elements
170+
const input = canvas.getByLabelText('Field Label');
171+
const button = canvas.getByRole('button', { name: 'Submit' });
172+
const select = canvas.getByRole('combobox');
173+
174+
// Async content
175+
const errorMessage = await canvas.findByText('Error message');
176+
const successMessage = await canvas.findByText('Success');
177+
```
178+
179+
### Common Test Patterns
180+
```typescript
181+
// Form validation testing
182+
const testInvalidSubmission = async ({ canvas }: StoryContext) => {
183+
const submitButton = canvas.getByRole('button', { name: 'Submit' });
184+
await userEvent.click(submitButton);
185+
expect(await canvas.findByText('Field is required')).toBeInTheDocument();
186+
};
187+
188+
// Conditional field testing
189+
const testConditionalFields = async ({ canvas }: StoryContext) => {
190+
const trigger = canvas.getByLabelText('Show advanced options');
191+
expect(canvas.queryByLabelText('Advanced Field')).not.toBeInTheDocument();
192+
await userEvent.click(trigger);
193+
expect(canvas.getByLabelText('Advanced Field')).toBeInTheDocument();
194+
};
195+
```
196+
197+
## Environment Setup Requirements
198+
- Node.js (version in .nvmrc)
199+
- Yarn 4.7.0 via corepack
200+
- Playwright browsers: `npx playwright install chromium`
201+
202+
## Test Commands
203+
```bash
204+
# Development workflow
205+
cd apps/docs
206+
yarn dev # Start Storybook
207+
yarn test:local # Run tests against running Storybook
208+
209+
# CI/Production
210+
yarn test # Build, serve, and test
211+
```
212+
213+
## Error Handling and Debugging
214+
- Use Storybook UI at http://localhost:6006 for visual debugging
215+
- Add console.logs for test execution flow debugging
216+
- Use browser dev tools during test execution
217+
- Check network tab for form submission verification
218+
219+
## Verification Steps
220+
When creating or modifying Storybook tests, ensure:
221+
222+
1. ✅ Story includes all three test phases (default, invalid, valid)
223+
2. ✅ Uses React Router stub decorator for form handling
224+
3. ✅ Follows click-before-clear pattern for inputs
225+
4. ✅ Uses findBy* for async assertions
226+
5. ✅ Tests both client-side and server-side validation
227+
6. ✅ Includes proper error handling and success scenarios
228+
7. ✅ Story serves as both documentation and test
229+
8. ✅ Component is properly isolated and focused
230+
231+
## Common Pitfalls to Avoid
232+
- Port conflicts (6006 already in use) - kill existing processes
233+
- Missing Playwright system dependencies - run `npx playwright install-deps`
234+
- Test timeouts - add delays for complex async operations
235+
- Element not found errors - ensure proper async handling
236+
- Form submission issues - verify fetcher setup and decorator usage
237+
238+
## Advanced Patterns
239+
- Create reusable test utilities in `apps/docs/src/lib/test-utils.ts`
240+
- Use story composition for different scenarios
241+
- Implement mock data factories for consistent test data
242+
- Group related stories with shared decorators
243+
244+
Remember: Every story should test real user workflows and serve as living documentation. Focus on behavior, not implementation details.
245+

0 commit comments

Comments
 (0)