Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 5590508

Browse files
Merge pull request #5 from lambda-curry/codegen-bot/update-storybook-to-latest-patterns
2 parents dcd8e37 + 71867c0 commit 5590508

File tree

6 files changed

+426
-214
lines changed

6 files changed

+426
-214
lines changed

‎.cursor/rules/storybook-testing.mdc‎

Lines changed: 185 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -1,187 +1,121 @@
11
---
2-
type: Always
3-
description: Rules for writing Storybook Playwright tests in the lambda-curry/forms repository
2+
description:
3+
globs: **/*.stories.tsx,apps/docs/**/*.mdx
4+
alwaysApply: false
45
---
56

6-
You are an expert in Storybook, Playwright testing, React, TypeScript, Remix Hook Form, react-hook-form, @medusajs/ui, Zod validation, and the lambda-curry/forms monorepo architecture.
7+
You are an expert in Storybook, Playwright testing, React, TypeScript, react-hook-form, @medusajs/ui, Zod validation, and the lambda-curry/medusa-forms monorepo architecture.
78

89
# 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+
This is a monorepo containing form components with comprehensive Storybook interaction testing. The testing setup combines Storybook's component isolation with modern interaction testing patterns using play functions and the @storybook/test utilities.
1011

1112
## Key Technologies
12-
- Storybook 8.6.7 with React and Vite
13+
- Storybook 9.0.6 with React and Vite
14+
- @storybook/test for interaction testing utilities (userEvent, expect, canvas)
1315
- @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 (main components)
1716
- react-hook-form + @medusajs/ui for Medusa Forms components
17+
- Zod validation for form validation testing
1818
- Yarn 4.7.0 with corepack
1919
- TypeScript throughout
2020

21-
## Project Structure
22-
```
23-
lambda-curry/forms/
24-
├── apps/docs/ # Storybook app
25-
│ ├── .storybook/ # Storybook configuration
26-
│ ├── src/remix-hook-form/ # Remix Hook Form story files with tests
27-
│ ├── src/medusa-forms/ # Medusa Forms story files with tests
28-
│ ├── simple-server.js # Custom static server for testing
29-
│ └── package.json # Test scripts
30-
├── packages/components/ # Main component library (Remix Hook Form)
31-
│ └── src/
32-
│ ├── remix-hook-form/ # Form components
33-
│ └── ui/ # UI components
34-
├── packages/medusa-forms/ # Medusa Forms component library
35-
│ └── src/
36-
│ ├── controlled/ # Controlled components using react-hook-form
37-
│ └── ui/ # UI components using @medusajs/ui
38-
└── .cursor/rules/ # Cursor rules directory
39-
```
40-
41-
# Environment Setup and Testing Infrastructure
42-
43-
## Prerequisites
44-
Before running Playwright tests locally, ensure the following setup is complete:
45-
46-
### 1. System Dependencies
47-
```bash
48-
# Install Node.js dependencies
49-
cd apps/docs
50-
yarn install
51-
52-
# Install Playwright browsers
53-
npx playwright install
54-
55-
# Install system dependencies for Playwright
56-
npx playwright install-deps
57-
```
58-
59-
### 2. Build Storybook Static Files
60-
```bash
61-
cd apps/docs
62-
yarn build # Creates storybook-static directory
63-
```
64-
65-
### 3. Server Setup for Local Testing
66-
Due to common port conflicts in development environments, use the custom static server for local testing:
67-
21+
### Local Development Workflow
6822
```bash
69-
# Start the custom static server (handles port conflicts)
23+
# Local development commands
7024
cd apps/docs
71-
node simple-server.js & # Runs on port 45678
72-
```
73-
74-
The `simple-server.js` file provides:
75-
- Static file serving with proper MIME types
76-
- CORS headers for cross-origin requests
77-
- SPA routing fallback to index.html
78-
- Conflict-free port allocation (45678)
25+
yarn dev # Start Storybook for development
26+
yarn test:local # Run tests against running Storybook (if available)
7927

80-
### 4. Run Tests Locally
81-
```bash
82-
# Run tests against the static server
83-
cd apps/docs
84-
npx test-storybook --url http://127.0.0.1:45678
28+
# Local testing of built Storybook
29+
yarn build # Build static Storybook
30+
node simple-server.js & # Start custom server
31+
npx test-storybook --url http://127.0.0.1:45678 # Test built version
8532
```
8633

87-
## Complete Local Testing Workflow
34+
### Codegen Testing Workflow
35+
This setup is optimized for Codegen agents and local development testing:
8836
```bash
89-
# Full local testing workflow from scratch
37+
# Codegen workflow for testing built Storybook
9038
cd apps/docs
91-
92-
# 1. Install dependencies (if needed)
9339
yarn install
9440
npx playwright install
9541
npx playwright install-deps
96-
97-
# 2. Build Storybook
9842
yarn build
99-
100-
# 3. Start static server
10143
node simple-server.js &
102-
103-
# 4. Run tests
10444
npx test-storybook --url http://127.0.0.1:45678
105-
106-
# 5. Stop server when done
107-
pkill -f "simple-server.js"
10845
```
10946

110-
## Troubleshooting Common Issues
111-
112-
### Port Conflicts
113-
If you encounter "EADDRINUSE" errors:
114-
- **Problem**: Default ports (6006, 6007, 8080, etc.) are occupied
115-
- **Solution**: Use the custom static server on port 45678
116-
- **Alternative**: Find available ports with `netstat -tulpn | grep :PORT`
117-
118-
### Browser Installation Issues
119-
If Playwright can't find browsers:
120-
```bash
121-
# Reinstall browsers
122-
npx playwright install chromium
123-
124-
# Install system dependencies
125-
npx playwright install-deps
47+
## Project Structure
12648
```
127-
128-
### Build Issues
129-
If Storybook build fails:
130-
```bash
131-
# Clean and rebuild
132-
rm -rf storybook-static
133-
yarn build
49+
lambda-curry/medusa-forms/
50+
├── apps/docs/ # Storybook app
51+
│ ├── .storybook/ # Storybook configuration
52+
│ ├── src/medusa-forms/ # Medusa Forms story files with tests
53+
│ ├── simple-server.js # Custom static server for testing
54+
│ └── package.json # Test scripts
55+
├── packages/medusa-forms/ # Medusa Forms component library
56+
│ └── src/
57+
│ ├── controlled/ # Controlled components using react-hook-form
58+
│ └── ui/ # UI components using @medusajs/ui
59+
└── .cursor/rules/ # Cursor rules directory
13460
```
13561

136-
### Test Execution Issues
137-
- **Timeout errors**: Increase timeout in test configuration
138-
- **Element not found**: Ensure proper async handling with `findBy*`
139-
- **Server not responding**: Verify static server is running on correct port
140-
141-
# Core Principles for Storybook Testing
62+
# Modern Storybook Interaction Testing
14263

143-
## Story Structure Pattern
144-
- Follow the three-phase testing pattern: Default state → Invalid submission → Valid submission
145-
- Each story serves dual purposes: documentation AND automated tests
146-
- Use play functions for comprehensive interaction testing
147-
- Test complete user workflows, not isolated units
64+
## Core Principles
65+
- **Stories as Tests**: Every story can be a render test; complex stories include interaction tests
66+
- **Play Functions**: Use play functions to simulate user behavior and assert on results
67+
- **Canvas Queries**: Use Testing Library queries through the canvas parameter
68+
- **User Events**: Simulate real user interactions with userEvent
69+
- **Step Grouping**: Organize complex interactions with the step function
70+
- **Visual Debugging**: Debug tests visually in the Storybook UI
14871

149-
## Essential Code Elements
150-
Always include these in Storybook test stories:
151-
152-
### Required Imports
72+
## Essential Imports for Interaction Testing
15373
```typescript
154-
import type { Meta, StoryContext, StoryObj } from '@storybook/react';
155-
import { expect, userEvent } from '@storybook/test';
156-
import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub';
74+
import type { Meta, StoryObj } from '@storybook/react';
75+
import { expect, userEvent, within } from '@storybook/test';
76+
import { FormProvider, useForm } from 'react-hook-form';
15777
```
15878

159-
### Form Schema Setup
79+
## Story Structure with Play Functions
80+
81+
### Basic Interaction Test Pattern
16082
```typescript
161-
const formSchema = z.object({
162-
fieldName: z.string().min(1, 'Field is required'),
163-
});
164-
type FormData = z.infer<typeof formSchema>;
83+
export const FilledForm: Story = {
84+
play: async ({ canvas, userEvent }) => {
85+
// 👇 Simulate interactions with the component
86+
await userEvent.type(canvas.getByTestId('email'), 'email@provider.com');
87+
await userEvent.type(canvas.getByTestId('password'), 'a-random-password');
88+
89+
// 👇 Trigger form submission
90+
await userEvent.click(canvas.getByRole('button', { name: 'Submit' }));
91+
92+
// 👇 Assert DOM structure
93+
await expect(
94+
canvas.getByText('Form submitted successfully!')
95+
).toBeInTheDocument();
96+
},
97+
};
16598
```
16699

167-
### Component Wrapper Pattern
100+
### Advanced Pattern with Step Grouping
168101
```typescript
169-
const ControlledComponentExample = () => {
170-
const fetcher = useFetcher<{ message: string }>();
171-
const methods = useRemixForm<FormData>({
172-
resolver: zodResolver(formSchema),
173-
defaultValues: { /* defaults */ },
174-
fetcher,
175-
submitConfig: { action: '/', method: 'post' },
176-
});
177-
178-
return (
179-
<RemixFormProvider {...methods}>
180-
<fetcher.Form onSubmit={methods.handleSubmit}>
181-
{/* Component and form elements */}
182-
</fetcher.Form>
183-
</RemixFormProvider>
184-
);
102+
export const CompleteWorkflow: Story = {
103+
play: async ({ canvas, userEvent, step }) => {
104+
await step('Fill out form fields', async () => {
105+
await userEvent.type(canvas.getByLabelText('Email'), 'user@example.com');
106+
await userEvent.type(canvas.getByLabelText('Password'), 'securepassword');
107+
});
108+
109+
await step('Submit form', async () => {
110+
await userEvent.click(canvas.getByRole('button', { name: 'Submit' }));
111+
});
112+
113+
await step('Verify success state', async () => {
114+
await expect(
115+
canvas.getByText('Welcome! Your account is ready.')
116+
).toBeInTheDocument();
117+
});
118+
},
185119
};
186120
```
187121

@@ -413,3 +347,109 @@ When creating or modifying Storybook tests, ensure:
413347
- Fast feedback loop optimized for developer productivity
414348

415349
Remember: Every story should test real user workflows and serve as living documentation. Focus on behavior, not implementation details. The testing infrastructure should be reliable, fast, and easy to maintain for local development and Codegen workflows.
350+
351+
## Canvas Queries - Testing Library Integration
352+
353+
### Query Types and When to Use Them
354+
| Query Type | 0 Matches | 1 Match | >1 Matches | Awaited | Use Case |
355+
|------------|-----------|---------|------------|---------|----------|
356+
| `getBy*` | Throw error | Return element | Throw error | No | Elements that should exist |
357+
| `queryBy*` | Return null | Return element | Throw error | No | Elements that may not exist |
358+
| `findBy*` | Throw error | Return element | Throw error | Yes | Async elements |
359+
| `getAllBy*` | Throw error | Return array | Return array | No | Multiple elements |
360+
| `queryAllBy*` | Return [] | Return array | Return array | No | Multiple elements (optional) |
361+
| `findAllBy*` | Throw error | Return array | Return array | Yes | Multiple async elements |
362+
363+
### Query Priority (Recommended Order)
364+
1. **ByRole** - Find elements by accessible role (most user-like)
365+
2. **ByLabelText** - Find form elements by associated label
366+
3. **ByPlaceholderText** - Find inputs by placeholder
367+
4. **ByText** - Find elements by text content
368+
5. **ByDisplayValue** - Find inputs by current value
369+
6. **ByAltText** - Find images by alt text
370+
7. **ByTitle** - Find elements by title attribute
371+
8. **ByTestId** - Find by data-testid (last resort)
372+
373+
### Common Query Examples
374+
```typescript
375+
// Semantic queries (preferred)
376+
const submitButton = canvas.getByRole('button', { name: 'Submit' });
377+
const emailInput = canvas.getByLabelText('Email Address');
378+
const dropdown = canvas.getByRole('combobox', { name: 'Country' });
379+
380+
// Async queries for dynamic content
381+
const successMessage = await canvas.findByText('Form submitted successfully');
382+
const errorList = await canvas.findAllByRole('alert');
383+
384+
// Conditional queries
385+
const optionalField = canvas.queryByLabelText('Optional Field');
386+
expect(optionalField).not.toBeInTheDocument();
387+
```
388+
389+
## UserEvent Interactions
390+
391+
### Common UserEvent Methods
392+
```typescript
393+
// Clicking elements
394+
await userEvent.click(element);
395+
await userEvent.dblClick(element);
396+
397+
// Typing and input
398+
await userEvent.type(input, 'text to type');
399+
await userEvent.clear(input);
400+
await userEvent.paste(input, 'pasted text');
401+
402+
// Keyboard interactions
403+
await userEvent.keyboard('{Enter}');
404+
await userEvent.tab();
405+
406+
// Selection
407+
await userEvent.selectOptions(select, 'option-value');
408+
await userEvent.deselectOptions(select, 'option-value');
409+
410+
// File uploads
411+
await userEvent.upload(fileInput, file);
412+
413+
// Hover interactions
414+
await userEvent.hover(element);
415+
await userEvent.unhover(element);
416+
```
417+
418+
### Form Interaction Best Practices
419+
```typescript
420+
// ✅ ALWAYS click before clearing inputs (for focus)
421+
await userEvent.click(input);
422+
await userEvent.clear(input);
423+
await userEvent.type(input, 'new value');
424+
425+
// ✅ Use proper selection for dropdowns
426+
await userEvent.click(canvas.getByRole('combobox'));
427+
await userEvent.click(canvas.getByRole('option', { name: 'Option Text' }));
428+
429+
// ✅ Handle file uploads properly
430+
const file = new File(['content'], 'test.txt', { type: 'text/plain' });
431+
await userEvent.upload(canvas.getByLabelText('Upload File'), file);
432+
```
433+
434+
## Component Wrapper Pattern for Medusa Forms
435+
436+
### Controlled Component Setup
437+
```typescript
438+
const ControlledComponentExample = () => {
439+
const form = useForm<FormData>({
440+
resolver: zodResolver(formSchema),
441+
defaultValues: { /* defaults */ },
442+
});
443+
444+
return (
445+
<FormProvider {...form}>
446+
<form onSubmit={form.handleSubmit((data) => console.log(data))}>
447+
{/* Medusa Forms components */}
448+
<ControlledInput name="email" label="Email" />
449+
<ControlledSelect name="country" label="Country" options={countryOptions} />
450+
<Button type="submit">Submit</Button>
451+
</form>
452+
</FormProvider>
453+
);
454+
};
455+
```

‎apps/docs/package.json‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,17 @@
1414
"@hookform/error-message": "^2.0.0",
1515
"@hookform/resolvers": "^3.9.1",
1616
"@lambdacurry/medusa-forms": "*",
17-
"@storybook/addon-links": "^9.0.1",
18-
"@storybook/react-vite": "^9.0.1",
17+
"@storybook/addon-links": "^9.0.6",
18+
"@storybook/react-vite": "^9.0.6",
1919
"react": "^19.0.0",
2020
"react-hook-form": "^7.51.0",
21-
"storybook": "^9.0.1",
21+
"storybook": "^9.0.6",
2222
"zod": "^3.23.8"
2323
},
2424
"devDependencies": {
25-
"@storybook/addon-docs": "^9.0.1",
26-
"@storybook/test-runner": "^0.22.0",
25+
"@storybook/addon-docs": "^9.0.6",
26+
"@storybook/test": "^8.6.14",
27+
"@storybook/test-runner": "^0.22.1",
2728
"@storybook/testing-library": "^0.2.2",
2829
"@tailwindcss/postcss": "^4.1.8",
2930
"@tailwindcss/vite": "^4.0.0",

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /