--- name: manage-react-component-tests description: Create or update test files for React components. Use when user asks to "create component tests", "test MyComponent", "generate tests for component", "update component tests", or mentions needing tests for a React component. Generates vitest test files with render, mocked sub-components, and proper test structure focusing on logic and prop validation. --- # Manage React Component Tests Skill This skill helps you create or update test files for React components. Tests follow a specific pattern with vitest, `render` from React Testing Library, and mocked sub-components to test logic and prop passing in isolation. ## When to Use This Skill Use this skill when you need to: - **Create** a new test file for a React component - **Update** an existing test file when the component changes - **Generate** test coverage for component logic and prop validation The skill will generate/update: - A test file with vitest and React Testing Library imports - Mocked versions of all imported sub-components - A `renderMyComponent` helper function using `render` - Individual test cases for different component behaviors and prop permutations ## Usage Invoke this skill when the user asks to: - "Create tests for [MyComponent]" - "Generate a test file for [MyComponent]" - "I need tests for [MyComponent]" - "Update tests for [MyComponent]" - "The [MyComponent] changed, update its tests" - "Test [MyComponent]" ## Core Principles ### Testing Philosophy 1. **Logic-Focused**: Test component logic, not UI appearance 2. **Prop Validation**: Verify sub-components receive correct props 3. **Isolation**: Mock all sub-components to test the component in isolation 4. **Callback Testing**: Simulate callbacks to test handlers 5. **Unit Testing**: Each component has its own tests; dependencies should have separate tests ## Prerequisites Before creating/updating component tests: 1. **Verify the component exists** - The component you're testing must already be defined 2. **Check for existing test file** - Use Glob to search for existing `.spec.tsx` file 3. **Identify sub-components** - Determine what components are imported and rendered 4. **Verify @testing-library/react** - Ensure it's installed (for `render`) ## Create vs Update Decision **If test file exists:** Update mode - Read the existing test file - Read the component definition - Compare and identify what's missing or outdated - Update the test to match current implementation **If test file does NOT exist:** Create mode - Read the component definition - Identify all sub-components to mock - Generate complete test file from scratch ## Test File Location **CRITICAL**: Test files MUST be in the `__tests__` folder, which is a **SIBLING** of the `/src` folder, NOT inside it. ### Directory Structure ``` packages/ my-package/ src/ components/ MyComponent.tsx __tests__/ # Sibling to src/, NOT inside src/ MyComponent.spec.tsx # .tsx extension for React components ``` ### Naming Convention For a component named `MyComponent`: - Test file: `MyComponent.spec.tsx` (matches component name exactly, with `.tsx` extension) - Located in: `packages/my-package/__tests__/MyComponent.spec.tsx` ## Test File Structure ### 1. Imports **Import Rules:** - Import React (required for this project's JSX transform) - Import vitest utilities from `'vitest'` - Import `render` and `screen` from `'@testing-library/react'` - Import the component being tested - DO NOT import sub-components (they will be mocked) **Example:** ```typescript import React from 'react'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen } from '@testing-library/react'; import { MyComponent } from '../src/components/MyComponent'; ``` ### 2. Mocking Sub-Components **All imported sub-components must be mocked** to test the component in isolation. **Pattern:** ```typescript // Mock all sub-component modules vi.mock('../src/components/SubComponentA', () => ({ SubComponentA: vi.fn(() =>
SubComponentA
) })); vi.mock('../src/components/SubComponentB', () => ({ SubComponentB: vi.fn(() =>
SubComponentB
) })); ``` **Rules:** - Place mocks at the top of the file, after imports - Use `vi.fn()` to create a mock function that returns a simple `
` - Add `data-testid` for easy querying in tests - Mock EVERY component imported by the component under test ### 3. Main Describe Block ```typescript describe('MyComponent', () => { // Helper function function renderMyComponent(props?: Partial) { // ... } // Reset mocks before each test beforeEach(() => { vi.clearAllMocks(); }); // Test cases it('should render sub-components with correct props', () => { // Test... }); }); ``` **Structure Rules:** - Main describe uses the component name - Contains one `renderMyComponent` helper function at the top - Includes `beforeEach` to clear mocks - All test cases use the `renderMyComponent` helper ### 4. renderMyComponent Helper Function **Purpose:** Factory function that renders the component with default or custom props. **Pattern:** ```typescript function renderMyComponent(props?: Partial) { const defaultProps: MyComponentProps = { title: 'Test Title', onSubmit: vi.fn(), items: [], }; return render(); } ``` **Rules:** - Accept partial props (optional) - Define sensible defaults for all required props - Use `vi.fn()` for callback props - Spread defaults first, then custom props - Return the result of `render()` ### 5. Testing Sub-Component Props **Access the mock to verify props:** ```typescript import { SubComponentA } from '../src/components/SubComponentA'; it('should pass correct props to SubComponentA', () => { renderMyComponent({ title: 'My Title', count: 5 }); expect(SubComponentA).toHaveBeenCalledWith( expect.objectContaining({ title: 'My Title', count: 5 }), expect.anything() // React context ); }); ``` **Rules:** - Import the mocked component at the top - Use `expect(MockedComponent).toHaveBeenCalledWith()` - Use `expect.objectContaining()` to match props - Use `expect.anything()` as second arg (React context) ### 6. Testing Callbacks **Simulate callback invocation to test handlers:** ```typescript it('should call onSubmit when button is clicked', () => { const mockOnSubmit = vi.fn(); const { SubButton } = require('../src/components/SubButton'); renderMyComponent({ onSubmit: mockOnSubmit }); // Get the onClick prop passed to SubButton const onClickProp = SubButton.mock.calls[0][0].onClick; // Simulate the click onClickProp(); expect(mockOnSubmit).toHaveBeenCalled(); }); ``` **Rules:** - Pass `vi.fn()` as callback props - Extract callback from mock's call arguments - Invoke the callback to test the handler - Assert the handler was called correctly ### 7. Testing Conditional Rendering **Test different branches:** ```typescript it('should render ErrorMessage when error prop is provided', () => { const { ErrorMessage } = require('../src/components/ErrorMessage'); renderMyComponent({ error: 'Something went wrong' }); expect(ErrorMessage).toHaveBeenCalledWith( expect.objectContaining({ message: 'Something went wrong' }), expect.anything() ); }); it('should not render ErrorMessage when no error', () => { const { ErrorMessage } = require('../src/components/ErrorMessage'); renderMyComponent({ error: null }); expect(ErrorMessage).not.toHaveBeenCalled(); }); ``` ### 8. Testing Lists and Iterations **Test components rendered in loops:** ```typescript it('should render ListItem for each item', () => { const { ListItem } = require('../src/components/ListItem'); const items = [ { id: '1', name: 'Item 1' }, { id: '2', name: 'Item 2' }, { id: '3', name: 'Item 3' } ]; renderMyComponent({ items }); expect(ListItem).toHaveBeenCalledTimes(3); expect(ListItem).toHaveBeenNthCalledWith( 1, expect.objectContaining({ id: '1', name: 'Item 1' }), expect.anything() ); expect(ListItem).toHaveBeenNthCalledWith( 2, expect.objectContaining({ id: '2', name: 'Item 2' }), expect.anything() ); expect(ListItem).toHaveBeenNthCalledWith( 3, expect.objectContaining({ id: '3', name: 'Item 3' }), expect.anything() ); }); ``` ## Workflow ### Create Workflow 1. **Identify the component** - Determine which component to test 2. **Locate the component file** - Use Glob to find the component definition 3. **Read the component** - Understand props, sub-components, logic branches 4. **Identify sub-components** - List all imported components to mock 5. **Ensure `__tests__` exists** - Create folder if needed (sibling to `/src`) 6. **Verify @testing-library/react** - Check package.json 7. **Create test file** in `__tests__/MyComponent.spec.tsx` with: - All required imports (including React) - Mock declarations for all sub-components - Main describe block - `renderMyComponent` helper function - `beforeEach` to clear mocks - Test cases covering all scenarios ### Update Workflow 1. **Read existing test and component** - Compare current state 2. **Identify changes**: - New sub-components → Add mocks - Changed props → Update test cases - New logic branches → Add test cases - Removed functionality → Remove tests 3. **Apply updates using Edit tool** - Targeted changes only 4. **Verify coverage** - Ensure all logic branches tested ### Update Guidelines - **Preserve structure** - Use Edit tool, not Write - **Maintain consistency** - Follow existing patterns - **Keep descriptive names** - Clear, behavior-focused - **Don't delete passing tests** - Only update broken/obsolete tests - **Add missing coverage** - Test new logic and props ## Best Practices 1. **Test Logic, Not UI** - Focus on props passed and callbacks invoked 2. **Mock All Sub-Components** - Test the component in complete isolation 3. **Descriptive Test Names** - Explain expected behavior clearly 4. **Clear Mocks Between Tests** - Use `beforeEach` with `vi.clearAllMocks()` 5. **Test All Branches** - Cover conditional rendering, loops, error states 6. **Test Callbacks** - Simulate sub-component callbacks to test handlers 7. **Use expect.objectContaining** - Match specific props without over-specifying ## Common Pitfalls to Avoid 1. ❌ **Don't Import Sub-Components Normally** - They should be mocked, not imported 2. ❌ **Don't Forget React Import** - Required for JSX in this project 3. ❌ **Don't Forget beforeEach** - Mocks persist between tests 4. ❌ **Don't Test UI Appearance** - Focus on logic and prop passing 5. ❌ **Don't Skip expect.anything()** - It's needed as the second argument for React context 6. ❌ **Don't Forget to Mock Everything** - ALL sub-components must be mocked ## Example Reference See `examples.md` in the same directory for complete working examples of: - Simple component with sub-components - Component with conditional rendering - Component with lists and iterations - Component with callbacks and handlers - Component with complex prop passing - Component with multiple branches ## Important Notes ### File Organization - Tests in `__tests__/` at package root (sibling to `/src`) - Use `.tsx` extension - Match component name exactly ### Dependencies - Ensure `@testing-library/react` is installed - Use `vi.mock()` for all sub-components - Mock at the module level, not inside tests ### Test Quality - Many small tests > few large tests - Test happy path and error cases - Test all conditional branches - Descriptive test names - Simple, focused tests ### Running Tests After creating/updating: 1. Run `pnpm test` to verify tests pass 2. Run `pnpm lint` to check linting 3. Run `pnpm build` to verify TypeScript compiles ### Mocking Best Practices - Always use `vi.fn()` for mock components - Return simple `
` elements with `data-testid` - Clear mocks in `beforeEach` - Import mocked components when you need to assert on them - Use `expect.objectContaining()` to verify props