From 201d1c96dd6005d10a3a43d58537c2f668f1fd23 Mon Sep 17 00:00:00 2001 From: "codegen-sh[bot]" <131295404+codegen-sh[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 02:57:54 +0000 Subject: [PATCH 1/5] feat: update docs with Lambda Curry branding and remove testing file - Remove storybook-testing.mdc file as requested - Update README.md with Lambda Curry branding inspired by homepage - Add hero section with logo, badges, and visual elements - Include Lambda Curry contact information and expertise callout - Keep content focused on the form library while showcasing LC expertise --- .cursor/rules/storybook-testing.mdc | 455 ---------------------------- README.md | 40 ++- 2 files changed, 36 insertions(+), 459 deletions(-) delete mode 100644 .cursor/rules/storybook-testing.mdc diff --git a/.cursor/rules/storybook-testing.mdc b/.cursor/rules/storybook-testing.mdc deleted file mode 100644 index 9ec9b0b..0000000 --- a/.cursor/rules/storybook-testing.mdc +++ /dev/null @@ -1,455 +0,0 @@ ---- -description: -globs: **/*.stories.tsx,apps/docs/**/*.mdx -alwaysApply: false ---- - -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. - -# Project Context -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. - -## Key Technologies -- Storybook 9.0.6 with React and Vite -- @storybook/test for interaction testing utilities (userEvent, expect, canvas) -- @storybook/test-runner for Playwright automation -- react-hook-form + @medusajs/ui for Medusa Forms components -- Zod validation for form validation testing -- Yarn 4.7.0 with corepack -- TypeScript throughout - -### Local Development Workflow -```bash -# Local development commands -cd apps/docs -yarn dev # Start Storybook for development -yarn test:local # Run tests against running Storybook (if available) - -# Local testing of built Storybook -yarn build # Build static Storybook -node simple-server.js & # Start custom server -npx test-storybook --url http://127.0.0.1:45678 # Test built version -``` - -### Codegen Testing Workflow -This setup is optimized for Codegen agents and local development testing: -```bash -# Codegen workflow for testing built Storybook -cd apps/docs -yarn install -npx playwright install -npx playwright install-deps -yarn build -node simple-server.js & -npx test-storybook --url http://127.0.0.1:45678 -``` - -## Project Structure -``` -lambda-curry/medusa-forms/ -├── apps/docs/ # Storybook app -│ ├── .storybook/ # Storybook configuration -│ ├── src/medusa-forms/ # Medusa Forms story files with tests -│ ├── simple-server.js # Custom static server for testing -│ └── package.json # Test scripts -├── packages/medusa-forms/ # Medusa Forms component library -│ └── src/ -│ ├── controlled/ # Controlled components using react-hook-form -│ └── ui/ # UI components using @medusajs/ui -└── .cursor/rules/ # Cursor rules directory -``` - -# Modern Storybook Interaction Testing - -## Core Principles -- **Stories as Tests**: Every story can be a render test; complex stories include interaction tests -- **Play Functions**: Use play functions to simulate user behavior and assert on results -- **Canvas Queries**: Use Testing Library queries through the canvas parameter -- **User Events**: Simulate real user interactions with userEvent -- **Step Grouping**: Organize complex interactions with the step function -- **Visual Debugging**: Debug tests visually in the Storybook UI - -## Essential Imports for Interaction Testing -```typescript -import type { Meta, StoryObj } from '@storybook/react'; -import { expect, userEvent, within } from '@storybook/test'; -import { FormProvider, useForm } from 'react-hook-form'; -``` - -## Story Structure with Play Functions - -### Basic Interaction Test Pattern -```typescript -export const FilledForm: Story = { - play: async ({ canvas, userEvent }) => { - // 👇 Simulate interactions with the component - await userEvent.type(canvas.getByTestId('email'), 'email@provider.com'); - await userEvent.type(canvas.getByTestId('password'), 'a-random-password'); - - // 👇 Trigger form submission - await userEvent.click(canvas.getByRole('button', { name: 'Submit' })); - - // 👇 Assert DOM structure - await expect( - canvas.getByText('Form submitted successfully!') - ).toBeInTheDocument(); - }, -}; -``` - -### Advanced Pattern with Step Grouping -```typescript -export const CompleteWorkflow: Story = { - play: async ({ canvas, userEvent, step }) => { - await step('Fill out form fields', async () => { - await userEvent.type(canvas.getByLabelText('Email'), 'user@example.com'); - await userEvent.type(canvas.getByLabelText('Password'), 'securepassword'); - }); - - await step('Submit form', async () => { - await userEvent.click(canvas.getByRole('button', { name: 'Submit' })); - }); - - await step('Verify success state', async () => { - await expect( - canvas.getByText('Welcome! Your account is ready.') - ).toBeInTheDocument(); - }); - }, -}; -``` - -## Testing Patterns - -### User Interaction Best Practices -```typescript -// ✅ ALWAYS click before clearing inputs -await userEvent.click(input); -await userEvent.clear(input); -await userEvent.type(input, 'new value'); - -// ✅ Use findBy* for async elements -const message = await canvas.findByText('Success message'); -expect(message).toBeInTheDocument(); - -// ✅ Use queryBy* to check non-existence -expect(canvas.queryByText('Should not exist')).not.toBeInTheDocument(); -``` - -### Three-Phase Test Structure -```typescript -export const Default: Story = { - play: async (storyContext) => { - // Phase 1: Test initial state - testDefaultValues(storyContext); - - // Phase 2: Test validation/error states - await testInvalidSubmission(storyContext); - - // Phase 3: Test success scenarios - await testValidSubmission(storyContext); - }, - decorators: [withReactRouterStubDecorator({ /* config */ })], -}; -``` - -### React Router Stub Decorator -```typescript -withReactRouterStubDecorator({ - routes: [{ - path: '/', - Component: ControlledComponentExample, - action: async ({ request }) => { - const { data, errors } = await getValidatedFormData( - request, - zodResolver(formSchema) - ); - if (errors) return { errors }; - return { message: 'Form submitted successfully' }; - }, - }], -}) -``` - -## Deprecated Patterns - DO NOT USE - -❌ **Never use getBy* for async elements** -```typescript -// BAD - will fail for async content -const message = canvas.getByText('Success message'); -``` - -❌ **Never clear inputs without clicking first** -```typescript -// BAD - unreliable -await userEvent.clear(input); -``` - -❌ **Never use regular forms instead of fetcher.Form** -```typescript -// BAD - won't work with React Router stub -
-``` - -❌ **Never test multiple unrelated scenarios in one story** -```typescript -// BAD - stories should be focused -export const AllScenarios: Story = { /* testing everything */ }; -``` - -## File Naming and Organization -- Story files: `component-name.stories.tsx` in `apps/docs/src/remix-hook-form/` -- Use kebab-case for file names -- Group related test functions together -- Export individual test functions for reusability - -## Testing Utilities and Helpers - -### Canvas Queries -```typescript -// Form elements -const input = canvas.getByLabelText('Field Label'); -const button = canvas.getByRole('button', { name: 'Submit' }); -const select = canvas.getByRole('combobox'); - -// Async content -const errorMessage = await canvas.findByText('Error message'); -const successMessage = await canvas.findByText('Success'); -``` - -### Common Test Patterns -```typescript -// Form validation testing -const testInvalidSubmission = async ({ canvas }: StoryContext) => { - const submitButton = canvas.getByRole('button', { name: 'Submit' }); - await userEvent.click(submitButton); - expect(await canvas.findByText('Field is required')).toBeInTheDocument(); -}; - -// Conditional field testing -const testConditionalFields = async ({ canvas }: StoryContext) => { - const trigger = canvas.getByLabelText('Show advanced options'); - expect(canvas.queryByLabelText('Advanced Field')).not.toBeInTheDocument(); - await userEvent.click(trigger); - expect(canvas.getByLabelText('Advanced Field')).toBeInTheDocument(); -}; -``` - -## Performance and Best Practices - -### Test Execution Optimization -- **Fast Feedback**: Tests should complete in under 10 seconds -- **Parallel Execution**: Leverage Playwright's parallel test execution -- **Focused Testing**: Each story should test one primary workflow -- **Efficient Selectors**: Use semantic queries (role, label) over CSS selectors - -### Local Development Workflow -```bash -# Local development commands -cd apps/docs -yarn dev # Start Storybook for development -yarn test:local # Run tests against running Storybook (if available) - -# Local testing of built Storybook -yarn build # Build static Storybook -node simple-server.js & # Start custom server -npx test-storybook --url http://127.0.0.1:45678 # Test built version -``` - -### Codegen Testing Workflow -This setup is optimized for Codegen agents and local development testing: -```bash -# Codegen workflow for testing built Storybook -cd apps/docs -yarn install -npx playwright install -npx playwright install-deps -yarn build -node simple-server.js & -npx test-storybook --url http://127.0.0.1:45678 -``` - -## Advanced Testing Patterns - -### Accessibility Testing -```typescript -// Include accessibility checks in stories -import { checkA11y } from '@storybook/addon-a11y'; - -export const AccessibilityTest: Story = { - play: async (storyContext) => { - await checkA11y(storyContext.canvasElement); - // Additional accessibility-specific tests - }, -}; -``` - -### Visual Regression Testing -```typescript -// Use Playwright's screenshot capabilities -export const VisualTest: Story = { - play: async ({ canvasElement }) => { - // Interact with component to desired state - // Screenshot will be taken automatically by test-runner - }, -}; -``` - -### Cross-Browser Testing -```typescript -// Configure multiple browsers in test-runner config -// Tests run automatically across Chromium, Firefox, WebKit -``` - -## Error Handling and Debugging - -### Debug Mode -```bash -# Run tests with debug output -npx test-storybook --url http://127.0.0.1:45678 --verbose - -# Run specific story -npx test-storybook --url http://127.0.0.1:45678 --testNamePattern="ComponentName" -``` - -### Common Error Patterns -1. **Element not found**: Use proper async queries (`findBy*`) -2. **Timing issues**: Add appropriate waits for async operations -3. **Form submission failures**: Verify fetcher setup and router configuration -4. **Port conflicts**: Use the custom static server solution - -## Verification Checklist -When creating or modifying Storybook tests, ensure: - -1. ✅ Story includes all three test phases (default, invalid, valid) -2. ✅ Uses React Router stub decorator for form handling -3. ✅ Follows click-before-clear pattern for inputs -4. ✅ Uses findBy* for async assertions -5. ✅ Tests both client-side and server-side validation -6. ✅ Includes proper error handling and success scenarios -7. ✅ Story serves as both documentation and test -8. ✅ Component is properly isolated and focused -9. ✅ Tests complete in reasonable time (< 10 seconds) -10. ✅ Uses semantic queries for better maintainability - -## Team Workflow Integration - -### Code Review Guidelines -- Verify test coverage includes happy path and error scenarios -- Ensure stories are self-documenting and demonstrate component usage -- Check that tests follow established patterns and conventions -- Validate that new tests don't introduce flaky behavior - -### Local Development Focus -- This setup is designed for local development and Codegen testing -- Tests run against built Storybook static files for consistency -- Custom server resolves common port conflicts in development environments -- Fast feedback loop optimized for developer productivity - -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. - -## Canvas Queries - Testing Library Integration - -### Query Types and When to Use Them -| Query Type | 0 Matches | 1 Match | >1 Matches | Awaited | Use Case | -|------------|-----------|---------|------------|---------|----------| -| `getBy*` | Throw error | Return element | Throw error | No | Elements that should exist | -| `queryBy*` | Return null | Return element | Throw error | No | Elements that may not exist | -| `findBy*` | Throw error | Return element | Throw error | Yes | Async elements | -| `getAllBy*` | Throw error | Return array | Return array | No | Multiple elements | -| `queryAllBy*` | Return [] | Return array | Return array | No | Multiple elements (optional) | -| `findAllBy*` | Throw error | Return array | Return array | Yes | Multiple async elements | - -### Query Priority (Recommended Order) -1. **ByRole** - Find elements by accessible role (most user-like) -2. **ByLabelText** - Find form elements by associated label -3. **ByPlaceholderText** - Find inputs by placeholder -4. **ByText** - Find elements by text content -5. **ByDisplayValue** - Find inputs by current value -6. **ByAltText** - Find images by alt text -7. **ByTitle** - Find elements by title attribute -8. **ByTestId** - Find by data-testid (last resort) - -### Common Query Examples -```typescript -// Semantic queries (preferred) -const submitButton = canvas.getByRole('button', { name: 'Submit' }); -const emailInput = canvas.getByLabelText('Email Address'); -const dropdown = canvas.getByRole('combobox', { name: 'Country' }); - -// Async queries for dynamic content -const successMessage = await canvas.findByText('Form submitted successfully'); -const errorList = await canvas.findAllByRole('alert'); - -// Conditional queries -const optionalField = canvas.queryByLabelText('Optional Field'); -expect(optionalField).not.toBeInTheDocument(); -``` - -## UserEvent Interactions - -### Common UserEvent Methods -```typescript -// Clicking elements -await userEvent.click(element); -await userEvent.dblClick(element); - -// Typing and input -await userEvent.type(input, 'text to type'); -await userEvent.clear(input); -await userEvent.paste(input, 'pasted text'); - -// Keyboard interactions -await userEvent.keyboard('{Enter}'); -await userEvent.tab(); - -// Selection -await userEvent.selectOptions(select, 'option-value'); -await userEvent.deselectOptions(select, 'option-value'); - -// File uploads -await userEvent.upload(fileInput, file); - -// Hover interactions -await userEvent.hover(element); -await userEvent.unhover(element); -``` - -### Form Interaction Best Practices -```typescript -// ✅ ALWAYS click before clearing inputs (for focus) -await userEvent.click(input); -await userEvent.clear(input); -await userEvent.type(input, 'new value'); - -// ✅ Use proper selection for dropdowns -await userEvent.click(canvas.getByRole('combobox')); -await userEvent.click(canvas.getByRole('option', { name: 'Option Text' })); - -// ✅ Handle file uploads properly -const file = new File(['content'], 'test.txt', { type: 'text/plain' }); -await userEvent.upload(canvas.getByLabelText('Upload File'), file); -``` - -## Component Wrapper Pattern for Medusa Forms - -### Controlled Component Setup -```typescript -const ControlledComponentExample = () => { - const form = useForm({ - resolver: zodResolver(formSchema), - defaultValues: { /* defaults */ }, - }); - - return ( - - console.log(data))}> - {/* Medusa Forms components */} - - - - - - ); -}; -``` diff --git a/README.md b/README.md index 65e3097..720e5f7 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,37 @@ # Lambda Curry Medusa Forms +
+ +![Lambda Curry Logo](https://lambdacurry.dev/favicon.ico) + +**Digital products made easy** + +*Controlled form components for Medusa Admin and Medusa UI applications* + +[![Storybook](https://img.shields.io/badge/Storybook-FF4785?style=for-the-badge&logo=storybook&logoColor=white)](https://lambda-curry.github.io/medusa-forms/?path=/docs/0-1-hello-world-start-here--docs) +[![TypeScript](https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white)](https://www.typescriptlang.org/) +[![React](https://img.shields.io/badge/React-20232A?style=for-the-badge&logo=react&logoColor=61DAFB)](https://reactjs.org/) + +--- + +*Built with expertise by [Lambda Curry](https://lambdacurry.dev) - your trusted Medusa development partners* + +
+ +## 🚀 About This Library + A controlled form components library for Medusa Admin and Medusa UI applications. Checkout our [Storybook Documentation](https://lambda-curry.github.io/medusa-forms/?path=/docs/0-1-hello-world-start-here--docs) to see the components in action and get started. -## Features +## ✨ Features - **Controlled Components**: All form components are controlled and work seamlessly with react-hook-form - **Medusa UI Integration**: Built specifically for Medusa Admin and Medusa UI design system - **TypeScript Support**: Full TypeScript support with proper type definitions - **Storybook Documentation**: Comprehensive documentation and examples -## Components +## 📦 Components - `ControlledInput` - Text input with validation - `ControlledTextArea` - Multi-line text input @@ -20,7 +40,7 @@ Checkout our [Storybook Documentation](https://lambda-curry.github.io/medusa-for - `ControlledDatePicker` - Date selection - `ControlledCurrencyInput` - Currency input with formatting -## Getting Started +## 🏁 Getting Started Step 1: Install dependencies @@ -34,7 +54,7 @@ Step 2: Start Storybook yarn storybook ``` -## Development +## 🔧 Development ### PR Previews @@ -83,3 +103,15 @@ The PR preview is deployed to the `gh-pages` branch in a directory structure lik /pr-preview/pr-[PR_NUMBER]/ ``` +--- + +
+ +**Need help with your Medusa project?** + +Lambda Curry specializes in Medusa development and can help you build amazing e-commerce experiences. + +[Contact Lambda Curry](https://lambdacurry.dev/#contact-us) | [View Our Work](https://lambdacurry.dev) + +
+ From 19e21ddd3e6f31a1bd8a128d7e1c752bdeaed7ef Mon Sep 17 00:00:00 2001 From: "codegen-sh[bot]" <131295404+codegen-sh[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 03:27:07 +0000 Subject: [PATCH 2/5] fix: address PR feedback - Restore storybook-testing.mdc file as requested - Update command from 'yarn storybook' to 'yarn dev' - Remove 0.2 Writing Tests.mdx file - Add Lambda Curry branding to Hello World Storybook page with logo, tagline, and contact info --- .cursor/rules/storybook-testing.mdc | 455 ++++++++++++++++++ README.md | 2 +- .../docs/src/0.1 Hello World (start here).mdx | 19 +- apps/docs/src/0.2 Writing Tests.mdx | 85 ---- 4 files changed, 474 insertions(+), 87 deletions(-) create mode 100644 .cursor/rules/storybook-testing.mdc delete mode 100644 apps/docs/src/0.2 Writing Tests.mdx diff --git a/.cursor/rules/storybook-testing.mdc b/.cursor/rules/storybook-testing.mdc new file mode 100644 index 0000000..9ec9b0b --- /dev/null +++ b/.cursor/rules/storybook-testing.mdc @@ -0,0 +1,455 @@ +--- +description: +globs: **/*.stories.tsx,apps/docs/**/*.mdx +alwaysApply: false +--- + +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. + +# Project Context +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. + +## Key Technologies +- Storybook 9.0.6 with React and Vite +- @storybook/test for interaction testing utilities (userEvent, expect, canvas) +- @storybook/test-runner for Playwright automation +- react-hook-form + @medusajs/ui for Medusa Forms components +- Zod validation for form validation testing +- Yarn 4.7.0 with corepack +- TypeScript throughout + +### Local Development Workflow +```bash +# Local development commands +cd apps/docs +yarn dev # Start Storybook for development +yarn test:local # Run tests against running Storybook (if available) + +# Local testing of built Storybook +yarn build # Build static Storybook +node simple-server.js & # Start custom server +npx test-storybook --url http://127.0.0.1:45678 # Test built version +``` + +### Codegen Testing Workflow +This setup is optimized for Codegen agents and local development testing: +```bash +# Codegen workflow for testing built Storybook +cd apps/docs +yarn install +npx playwright install +npx playwright install-deps +yarn build +node simple-server.js & +npx test-storybook --url http://127.0.0.1:45678 +``` + +## Project Structure +``` +lambda-curry/medusa-forms/ +├── apps/docs/ # Storybook app +│ ├── .storybook/ # Storybook configuration +│ ├── src/medusa-forms/ # Medusa Forms story files with tests +│ ├── simple-server.js # Custom static server for testing +│ └── package.json # Test scripts +├── packages/medusa-forms/ # Medusa Forms component library +│ └── src/ +│ ├── controlled/ # Controlled components using react-hook-form +│ └── ui/ # UI components using @medusajs/ui +└── .cursor/rules/ # Cursor rules directory +``` + +# Modern Storybook Interaction Testing + +## Core Principles +- **Stories as Tests**: Every story can be a render test; complex stories include interaction tests +- **Play Functions**: Use play functions to simulate user behavior and assert on results +- **Canvas Queries**: Use Testing Library queries through the canvas parameter +- **User Events**: Simulate real user interactions with userEvent +- **Step Grouping**: Organize complex interactions with the step function +- **Visual Debugging**: Debug tests visually in the Storybook UI + +## Essential Imports for Interaction Testing +```typescript +import type { Meta, StoryObj } from '@storybook/react'; +import { expect, userEvent, within } from '@storybook/test'; +import { FormProvider, useForm } from 'react-hook-form'; +``` + +## Story Structure with Play Functions + +### Basic Interaction Test Pattern +```typescript +export const FilledForm: Story = { + play: async ({ canvas, userEvent }) => { + // 👇 Simulate interactions with the component + await userEvent.type(canvas.getByTestId('email'), 'email@provider.com'); + await userEvent.type(canvas.getByTestId('password'), 'a-random-password'); + + // 👇 Trigger form submission + await userEvent.click(canvas.getByRole('button', { name: 'Submit' })); + + // 👇 Assert DOM structure + await expect( + canvas.getByText('Form submitted successfully!') + ).toBeInTheDocument(); + }, +}; +``` + +### Advanced Pattern with Step Grouping +```typescript +export const CompleteWorkflow: Story = { + play: async ({ canvas, userEvent, step }) => { + await step('Fill out form fields', async () => { + await userEvent.type(canvas.getByLabelText('Email'), 'user@example.com'); + await userEvent.type(canvas.getByLabelText('Password'), 'securepassword'); + }); + + await step('Submit form', async () => { + await userEvent.click(canvas.getByRole('button', { name: 'Submit' })); + }); + + await step('Verify success state', async () => { + await expect( + canvas.getByText('Welcome! Your account is ready.') + ).toBeInTheDocument(); + }); + }, +}; +``` + +## Testing Patterns + +### User Interaction Best Practices +```typescript +// ✅ ALWAYS click before clearing inputs +await userEvent.click(input); +await userEvent.clear(input); +await userEvent.type(input, 'new value'); + +// ✅ Use findBy* for async elements +const message = await canvas.findByText('Success message'); +expect(message).toBeInTheDocument(); + +// ✅ Use queryBy* to check non-existence +expect(canvas.queryByText('Should not exist')).not.toBeInTheDocument(); +``` + +### Three-Phase Test Structure +```typescript +export const Default: Story = { + play: async (storyContext) => { + // Phase 1: Test initial state + testDefaultValues(storyContext); + + // Phase 2: Test validation/error states + await testInvalidSubmission(storyContext); + + // Phase 3: Test success scenarios + await testValidSubmission(storyContext); + }, + decorators: [withReactRouterStubDecorator({ /* config */ })], +}; +``` + +### React Router Stub Decorator +```typescript +withReactRouterStubDecorator({ + routes: [{ + path: '/', + Component: ControlledComponentExample, + action: async ({ request }) => { + const { data, errors } = await getValidatedFormData( + request, + zodResolver(formSchema) + ); + if (errors) return { errors }; + return { message: 'Form submitted successfully' }; + }, + }], +}) +``` + +## Deprecated Patterns - DO NOT USE + +❌ **Never use getBy* for async elements** +```typescript +// BAD - will fail for async content +const message = canvas.getByText('Success message'); +``` + +❌ **Never clear inputs without clicking first** +```typescript +// BAD - unreliable +await userEvent.clear(input); +``` + +❌ **Never use regular forms instead of fetcher.Form** +```typescript +// BAD - won't work with React Router stub +
+``` + +❌ **Never test multiple unrelated scenarios in one story** +```typescript +// BAD - stories should be focused +export const AllScenarios: Story = { /* testing everything */ }; +``` + +## File Naming and Organization +- Story files: `component-name.stories.tsx` in `apps/docs/src/remix-hook-form/` +- Use kebab-case for file names +- Group related test functions together +- Export individual test functions for reusability + +## Testing Utilities and Helpers + +### Canvas Queries +```typescript +// Form elements +const input = canvas.getByLabelText('Field Label'); +const button = canvas.getByRole('button', { name: 'Submit' }); +const select = canvas.getByRole('combobox'); + +// Async content +const errorMessage = await canvas.findByText('Error message'); +const successMessage = await canvas.findByText('Success'); +``` + +### Common Test Patterns +```typescript +// Form validation testing +const testInvalidSubmission = async ({ canvas }: StoryContext) => { + const submitButton = canvas.getByRole('button', { name: 'Submit' }); + await userEvent.click(submitButton); + expect(await canvas.findByText('Field is required')).toBeInTheDocument(); +}; + +// Conditional field testing +const testConditionalFields = async ({ canvas }: StoryContext) => { + const trigger = canvas.getByLabelText('Show advanced options'); + expect(canvas.queryByLabelText('Advanced Field')).not.toBeInTheDocument(); + await userEvent.click(trigger); + expect(canvas.getByLabelText('Advanced Field')).toBeInTheDocument(); +}; +``` + +## Performance and Best Practices + +### Test Execution Optimization +- **Fast Feedback**: Tests should complete in under 10 seconds +- **Parallel Execution**: Leverage Playwright's parallel test execution +- **Focused Testing**: Each story should test one primary workflow +- **Efficient Selectors**: Use semantic queries (role, label) over CSS selectors + +### Local Development Workflow +```bash +# Local development commands +cd apps/docs +yarn dev # Start Storybook for development +yarn test:local # Run tests against running Storybook (if available) + +# Local testing of built Storybook +yarn build # Build static Storybook +node simple-server.js & # Start custom server +npx test-storybook --url http://127.0.0.1:45678 # Test built version +``` + +### Codegen Testing Workflow +This setup is optimized for Codegen agents and local development testing: +```bash +# Codegen workflow for testing built Storybook +cd apps/docs +yarn install +npx playwright install +npx playwright install-deps +yarn build +node simple-server.js & +npx test-storybook --url http://127.0.0.1:45678 +``` + +## Advanced Testing Patterns + +### Accessibility Testing +```typescript +// Include accessibility checks in stories +import { checkA11y } from '@storybook/addon-a11y'; + +export const AccessibilityTest: Story = { + play: async (storyContext) => { + await checkA11y(storyContext.canvasElement); + // Additional accessibility-specific tests + }, +}; +``` + +### Visual Regression Testing +```typescript +// Use Playwright's screenshot capabilities +export const VisualTest: Story = { + play: async ({ canvasElement }) => { + // Interact with component to desired state + // Screenshot will be taken automatically by test-runner + }, +}; +``` + +### Cross-Browser Testing +```typescript +// Configure multiple browsers in test-runner config +// Tests run automatically across Chromium, Firefox, WebKit +``` + +## Error Handling and Debugging + +### Debug Mode +```bash +# Run tests with debug output +npx test-storybook --url http://127.0.0.1:45678 --verbose + +# Run specific story +npx test-storybook --url http://127.0.0.1:45678 --testNamePattern="ComponentName" +``` + +### Common Error Patterns +1. **Element not found**: Use proper async queries (`findBy*`) +2. **Timing issues**: Add appropriate waits for async operations +3. **Form submission failures**: Verify fetcher setup and router configuration +4. **Port conflicts**: Use the custom static server solution + +## Verification Checklist +When creating or modifying Storybook tests, ensure: + +1. ✅ Story includes all three test phases (default, invalid, valid) +2. ✅ Uses React Router stub decorator for form handling +3. ✅ Follows click-before-clear pattern for inputs +4. ✅ Uses findBy* for async assertions +5. ✅ Tests both client-side and server-side validation +6. ✅ Includes proper error handling and success scenarios +7. ✅ Story serves as both documentation and test +8. ✅ Component is properly isolated and focused +9. ✅ Tests complete in reasonable time (< 10 seconds) +10. ✅ Uses semantic queries for better maintainability + +## Team Workflow Integration + +### Code Review Guidelines +- Verify test coverage includes happy path and error scenarios +- Ensure stories are self-documenting and demonstrate component usage +- Check that tests follow established patterns and conventions +- Validate that new tests don't introduce flaky behavior + +### Local Development Focus +- This setup is designed for local development and Codegen testing +- Tests run against built Storybook static files for consistency +- Custom server resolves common port conflicts in development environments +- Fast feedback loop optimized for developer productivity + +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. + +## Canvas Queries - Testing Library Integration + +### Query Types and When to Use Them +| Query Type | 0 Matches | 1 Match | >1 Matches | Awaited | Use Case | +|------------|-----------|---------|------------|---------|----------| +| `getBy*` | Throw error | Return element | Throw error | No | Elements that should exist | +| `queryBy*` | Return null | Return element | Throw error | No | Elements that may not exist | +| `findBy*` | Throw error | Return element | Throw error | Yes | Async elements | +| `getAllBy*` | Throw error | Return array | Return array | No | Multiple elements | +| `queryAllBy*` | Return [] | Return array | Return array | No | Multiple elements (optional) | +| `findAllBy*` | Throw error | Return array | Return array | Yes | Multiple async elements | + +### Query Priority (Recommended Order) +1. **ByRole** - Find elements by accessible role (most user-like) +2. **ByLabelText** - Find form elements by associated label +3. **ByPlaceholderText** - Find inputs by placeholder +4. **ByText** - Find elements by text content +5. **ByDisplayValue** - Find inputs by current value +6. **ByAltText** - Find images by alt text +7. **ByTitle** - Find elements by title attribute +8. **ByTestId** - Find by data-testid (last resort) + +### Common Query Examples +```typescript +// Semantic queries (preferred) +const submitButton = canvas.getByRole('button', { name: 'Submit' }); +const emailInput = canvas.getByLabelText('Email Address'); +const dropdown = canvas.getByRole('combobox', { name: 'Country' }); + +// Async queries for dynamic content +const successMessage = await canvas.findByText('Form submitted successfully'); +const errorList = await canvas.findAllByRole('alert'); + +// Conditional queries +const optionalField = canvas.queryByLabelText('Optional Field'); +expect(optionalField).not.toBeInTheDocument(); +``` + +## UserEvent Interactions + +### Common UserEvent Methods +```typescript +// Clicking elements +await userEvent.click(element); +await userEvent.dblClick(element); + +// Typing and input +await userEvent.type(input, 'text to type'); +await userEvent.clear(input); +await userEvent.paste(input, 'pasted text'); + +// Keyboard interactions +await userEvent.keyboard('{Enter}'); +await userEvent.tab(); + +// Selection +await userEvent.selectOptions(select, 'option-value'); +await userEvent.deselectOptions(select, 'option-value'); + +// File uploads +await userEvent.upload(fileInput, file); + +// Hover interactions +await userEvent.hover(element); +await userEvent.unhover(element); +``` + +### Form Interaction Best Practices +```typescript +// ✅ ALWAYS click before clearing inputs (for focus) +await userEvent.click(input); +await userEvent.clear(input); +await userEvent.type(input, 'new value'); + +// ✅ Use proper selection for dropdowns +await userEvent.click(canvas.getByRole('combobox')); +await userEvent.click(canvas.getByRole('option', { name: 'Option Text' })); + +// ✅ Handle file uploads properly +const file = new File(['content'], 'test.txt', { type: 'text/plain' }); +await userEvent.upload(canvas.getByLabelText('Upload File'), file); +``` + +## Component Wrapper Pattern for Medusa Forms + +### Controlled Component Setup +```typescript +const ControlledComponentExample = () => { + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { /* defaults */ }, + }); + + return ( + + console.log(data))}> + {/* Medusa Forms components */} + + + + + + ); +}; +``` diff --git a/README.md b/README.md index 720e5f7..143a265 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ yarn install Step 2: Start Storybook ```bash -yarn storybook +yarn dev ``` ## 🔧 Development diff --git a/apps/docs/src/0.1 Hello World (start here).mdx b/apps/docs/src/0.1 Hello World (start here).mdx index 5e4090c..b0d1027 100644 --- a/apps/docs/src/0.1 Hello World (start here).mdx +++ b/apps/docs/src/0.1 Hello World (start here).mdx @@ -1,4 +1,10 @@ -# Welcome to Medusa Forms! 🎯 +# Welcome to Lambda Curry Medusa Forms! 🎯 + +
+ Lambda Curry Logo +
Digital products made easy
+
Built with expertise by Lambda Curry - your trusted Medusa development partners
+
**Controlled form components designed specifically for Medusa Admin and Medusa UI** @@ -137,4 +143,15 @@ Check out our **Form Integration Examples** story to see complete working forms --- +
+
Need help with your Medusa project?
+
Lambda Curry specializes in Medusa development and can help you build amazing e-commerce experiences.
+ +
+ **Ready to build better forms?** Start exploring the components in the sidebar! 👈 + diff --git a/apps/docs/src/0.2 Writing Tests.mdx b/apps/docs/src/0.2 Writing Tests.mdx deleted file mode 100644 index c1cea90..0000000 --- a/apps/docs/src/0.2 Writing Tests.mdx +++ /dev/null @@ -1,85 +0,0 @@ -# Writing Tests with Storybook Test Runner - -Storybook Test Runner is a powerful tool that allows you to write and execute tests directly within your Storybook stories. This approach provides a seamless integration between your component documentation and testing, ensuring that your components behave as expected in various scenarios. Below, we'll explore how to write tests using the Storybook Test Runner, using the `TextField` component as a guide. - -## Writing Tests - -### Define Your Component and Story - -Create a Storybook story for your component. For example, the `TextField` component: - -```typescript -import type { Meta, StoryObj } from '@storybook/react'; -import { TextField } from './TextField'; - -const meta: Meta = { - component: TextField, - args: { - label: 'Username', - placeholder: 'Enter your username', - }, -}; - -export default meta; - -type Story = StoryObj; - -export const Tests: Story = {}; -``` - -### Write Test Scenarios - -Define test scenarios as functions that interact with the component using `@storybook/test` and `@testing-library/dom`. These functions simulate user interactions and assert expected outcomes. - -Example: - -```typescript -import { expect } from '@storybook/jest'; -import { userEvent } from '@storybook/testing-library'; -import type { StoryContext } from '@storybook/react'; - -const testValidSubmission = async ({ canvas }: StoryContext) => { - const input = canvas.getByLabelText('Username'); - await userEvent.type(input, 'validuser'); - await userEvent.tab(); - expect(input).toHaveValue('validuser'); - expect(canvas.queryByText('Username is required')).not.toBeInTheDocument(); -}; - -const testInvalidSubmission = async ({ canvas }: StoryContext) => { - const input = canvas.getByLabelText('Username'); - await userEvent.type(input, 'a'); - await userEvent.tab(); - expect(canvas.getByText('Username must be at least 3 characters')).toBeInTheDocument(); -}; -``` - -### Integrate Tests into Stories - -Use the `play` function in your Storybook story to execute the test scenarios. This function runs after the story renders, allowing you to interact with the component and verify its behavior. - -Example: - -```typescript -export const Tests: Story = { - play: async (storyContext) => { - await testValidSubmission(storyContext); - await testInvalidSubmission(storyContext); - }, -}; -``` - -## Testing Philosophy - -1. **Component Behavior**: Focus on testing the behavior of your component in isolation. Ensure that it handles user interactions, displays the correct UI, and manages state as expected. - -2. **User-Centric Scenarios**: Write tests that mimic real user interactions. This includes filling out forms, clicking buttons, and verifying that the UI responds correctly. - -3. **Edge Cases and Validation**: Test edge cases and validation logic to ensure your component handles invalid input gracefully and provides meaningful feedback to users. - -4. **Integration with Backend**: If your component interacts with a backend (e.g., form submission), simulate these interactions and verify that the component handles responses correctly. - -5. **Maintainability**: Write clear and concise tests that are easy to understand and maintain. Use descriptive function names and comments to explain the purpose of each test. - -By following these guidelines, you can leverage Storybook Test Runner to create comprehensive and reliable tests for your components, ensuring they meet both functional and user experience requirements. - From 98e2ab59703d86a0089d7ddd1d6c7d459ac03267 Mon Sep 17 00:00:00 2001 From: "codegen-sh[bot]" <131295404+codegen-sh[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 03:35:36 +0000 Subject: [PATCH 3/5] fix: update README layout and branding per feedback - Move logo to the top of the README - Change title to 'Welcome to Medusa Forms by Lambda Curry!' - Add link to https://lambdacurry.dev in Lambda Curry subtext --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 143a265..efc395e 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -# Lambda Curry Medusa Forms -
![Lambda Curry Logo](https://lambdacurry.dev/favicon.ico) +# Welcome to Medusa Forms by Lambda Curry! + **Digital products made easy** *Controlled form components for Medusa Admin and Medusa UI applications* From b1bbc68fb627be313d98ec6569a2c4731816a362 Mon Sep 17 00:00:00 2001 From: "codegen-sh[bot]" <131295404+codegen-sh[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 03:50:31 +0000 Subject: [PATCH 4/5] fix: update Hello World MDX with consistent branding layout - Move logo to the top of the Storybook documentation - Change title to 'Welcome to Medusa Forms by Lambda Curry!' - Add link to https://lambdacurry.dev in Lambda Curry subtext - Maintain consistent branding with README layout --- apps/docs/src/0.1 Hello World (start here).mdx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/docs/src/0.1 Hello World (start here).mdx b/apps/docs/src/0.1 Hello World (start here).mdx index b0d1027..ca7aa0d 100644 --- a/apps/docs/src/0.1 Hello World (start here).mdx +++ b/apps/docs/src/0.1 Hello World (start here).mdx @@ -1,9 +1,10 @@ -# Welcome to Lambda Curry Medusa Forms! 🎯 -
Lambda Curry Logo + + # Welcome to Medusa Forms by Lambda Curry! +
Digital products made easy
-
Built with expertise by Lambda Curry - your trusted Medusa development partners
+
Built with expertise by Lambda Curry - your trusted Medusa development partners
**Controlled form components designed specifically for Medusa Admin and Medusa UI** From 4fa0a8e578d48e4ebb21cf7996edf8a175cbe7be Mon Sep 17 00:00:00 2001 From: "codegen-sh[bot]" <131295404+codegen-sh[bot]@users.noreply.github.com> Date: Tue, 10 Jun 2025 00:48:58 +0000 Subject: [PATCH 5/5] feat: add Lambda Curry theming to Storybook sidebar - Create custom Storybook theme with Lambda Curry branding - Replace Storybook logo with Lambda Curry logo in sidebar - Add Lambda Curry brand colors (#6366f1) throughout the UI - Configure theme for both manager and docs - Set brand title to 'Medusa Forms by Lambda Curry' - Link brand logo to lambdacurry.dev website --- apps/docs/.storybook/manager.ts | 7 +++++++ apps/docs/.storybook/preview.ts | 5 +++++ apps/docs/.storybook/theme.ts | 37 +++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 apps/docs/.storybook/manager.ts create mode 100644 apps/docs/.storybook/theme.ts diff --git a/apps/docs/.storybook/manager.ts b/apps/docs/.storybook/manager.ts new file mode 100644 index 0000000..7809d60 --- /dev/null +++ b/apps/docs/.storybook/manager.ts @@ -0,0 +1,7 @@ +import { addons } from '@storybook/manager-api'; +import theme from './theme'; + +addons.setConfig({ + theme, +}); + diff --git a/apps/docs/.storybook/preview.ts b/apps/docs/.storybook/preview.ts index ab220a8..bde35e5 100644 --- a/apps/docs/.storybook/preview.ts +++ b/apps/docs/.storybook/preview.ts @@ -1,5 +1,6 @@ import type { Preview } from '@storybook/react-vite'; import '../src/main.css'; +import theme from './theme'; const preview: Preview = { parameters: { @@ -9,6 +10,9 @@ const preview: Preview = { date: /Date$/i, }, }, + docs: { + theme, + }, options: { storySort: { method: 'alphabetical', @@ -18,3 +22,4 @@ const preview: Preview = { }; export default preview; + diff --git a/apps/docs/.storybook/theme.ts b/apps/docs/.storybook/theme.ts new file mode 100644 index 0000000..db2d714 --- /dev/null +++ b/apps/docs/.storybook/theme.ts @@ -0,0 +1,37 @@ +import { create } from '@storybook/theming/create'; + +export default create({ + base: 'light', + + // Brand + brandTitle: 'Medusa Forms by Lambda Curry', + brandUrl: 'https://lambdacurry.dev', + brandImage: 'https://lambdacurry.dev/favicon.ico', + brandTarget: '_blank', + + // Colors + colorPrimary: '#6366f1', // Lambda Curry brand color + colorSecondary: '#6366f1', + + // UI + appBg: '#ffffff', + appContentBg: '#ffffff', + appBorderColor: '#e2e8f0', + appBorderRadius: 8, + + // Text colors + textColor: '#1f2937', + textInverseColor: '#ffffff', + + // Toolbar default and active colors + barTextColor: '#6b7280', + barSelectedColor: '#6366f1', + barBg: '#f8fafc', + + // Form colors + inputBg: '#ffffff', + inputBorder: '#d1d5db', + inputTextColor: '#1f2937', + inputBorderRadius: 6, +}); +