Jest Test Scaffolder Workflow
This skill creates comprehensive unit tests for React components using Jest, React Testing Library, and user-event.
Workflow Steps
Identify component to test:
- Ask for component file path (e.g.,
src/components/ui/Button/Button.tsx)
- Read component file to extract:
- Component name
- Props interface/types
- Event handlers
- Component role (button, input, form, etc.)
- Determine test location:
{{COMPONENT_DIR}}/__tests__/{{COMPONENT_NAME}}.test.tsx
Analyze component structure:
- Extract prop names and types from TypeScript interface
- Identify event handlers (onClick, onChange, onSubmit, etc.)
- Determine accessibility role (button, textbox, combobox, etc.)
- Identify state variations (disabled, loading, error, etc.)
- Check for conditional rendering
Generate test file:
- Read template:
.claude/skills/jest-test-scaffolder/templates/component.test.tsx.tpl
- Replace placeholders:
{{COMPONENT_NAME}} - Component name (PascalCase)
{{COMPONENT_PATH}} - Relative import path
{{COMPONENT_ROLE}} - Accessibility role (button, textbox, etc.)
{{DEFAULT_PROPS}} - Default props for rendering
{{EVENT_HANDLERS}} - Event handler tests
{{STATE_TESTS}} - State variation tests (disabled, loading, etc.)
- Write to:
{{COMPONENT_DIR}}/__tests__/{{COMPONENT_NAME}}.test.tsx
Include comprehensive test scenarios:
- ✅ Render Test: Component renders without errors
- ✅ Props Test: Component accepts and displays props correctly
- ✅ Interaction Test: User interactions trigger expected behavior
- ✅ State Test: Component handles state changes (disabled, loading, etc.)
- ✅ Accessibility Test: Component is accessible (role, labels)
- ⚠️ Edge Cases: Empty props, long text, null values (TODO placeholders)
- ⚠️ Snapshot Test: Visual regression (TODO placeholder)
Use React Testing Library best practices:
- Use
screen queries (getByRole, getByText, getByLabelText)
- Use
userEvent for realistic interactions
- Use
jest.fn() for Jest mocks
- Avoid implementation details (no enzyme shallow rendering)
- Test accessibility (roles, labels, keyboard navigation)
Report success:
- Show test file path
- Display test coverage (number of test cases)
- Provide command to run tests:
yarn test {{COMPONENT_NAME}}
- Remind user to add edge case tests
For React Hooks
When testing custom React hooks:
- Use template:
.claude/skills/jest-test-scaffolder/templates/hook.test.tsx.tpl
- Use
@testing-library/react renderHook:import { renderHook, act } from "@testing-library/react";
import { useCustomHook } from "./useCustomHook";
- Test hook behavior:
- Initial state
- State updates via
act()
- Return values
- Side effects
Template Structure
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { jest } from '@jest/globals';
import { {{COMPONENT_NAME}} } from './{{COMPONENT_NAME}}';
describe('{{COMPONENT_NAME}}', () => {
it('renders without errors', () => {
render(<{{COMPONENT_NAME}} {{DEFAULT_PROPS}} />);
expect(screen.getByRole('{{ROLE}}')).toBeInTheDocument();
});
it('handles user interactions', async () => {
const user = userEvent.setup();
const mock{{HANDLER}} = jest.fn();
render(<{{COMPONENT_NAME}} on{{HANDLER}}={mock{{HANDLER}}} />);
await user.click(screen.getByRole('{{ROLE}}'));
expect(mock{{HANDLER}}).toHaveBeenCalledTimes(1);
});
it('handles disabled state', async () => {
const user = userEvent.setup();
const mockHandler = jest.fn();
render(<{{COMPONENT_NAME}} disabled on{{HANDLER}}={mockHandler} />);
const element = screen.getByRole('{{ROLE}}');
expect(element).toBeDisabled();
await user.click(element);
expect(mockHandler).not.toHaveBeenCalled();
});
// TODO: Add accessibility tests
// TODO: Add snapshot tests
// TODO: Add edge case tests (empty props, long text, null values)
});
Example Usage
User: "Create tests for the Button component"
Claude (using jest-test-scaffolder):
1. Reads src/components/ui/Button/Button.tsx
2. Extracts props: { children, onClick, disabled, variant, size }
3. Generates src/components/ui/Button/__tests__/Button.test.tsx
4. Creates 5 test cases:
- Renders with correct text
- Calls onClick handler when clicked
- Is disabled when disabled prop is true
- Renders correct variant styles (TODO)
- Handles different sizes (TODO)
Files created:
- src/components/ui/Button/__tests__/Button.test.tsx (42 lines, 3 complete tests, 2 TODO tests)
Run tests:
yarn test Button
Test coverage:
✅ Render test
✅ Click interaction test
✅ Disabled state test
⚠️ Variant test (TODO)
⚠️ Size test (TODO)
Pattern Matching
This skill follows the existing test pattern from:
src/components/ui/Button/__tests__/Button.test.tsx (36 lines, 3 tests)
- Uses
describe and it blocks
- Uses
screen.getByRole for queries
- Uses
userEvent.setup() and await user.click()
- Uses
jest.fn() for Jest mocks
- Tests rendering, interactions, and state
Best Practices
Test user behavior, not implementation:
- Query by role, label, text (user-visible)
- Avoid querying by class names or test IDs (unless necessary)
- Test what users see and do
Use userEvent over fireEvent:
- More realistic user interactions
- Handles complex interactions (hover, tab, type)
- Follows browser behavior
Keep tests simple and focused:
- One assertion per test (when possible)
- Clear test descriptions
- Avoid complex setup
Test accessibility:
- Use semantic roles
- Verify labels and descriptions
- Test keyboard navigation
Mock external dependencies:
- Mock API calls
- Mock context providers
- Mock navigation (useNavigate, useRouter)
Jest vs Vitest
This skill uses Jest (not Vitest):
jest.fn() instead of vi.fn()
jest.mock() instead of vi.mock()
jest.spyOn() instead of vi.spyOn()
- Everything else is the same (React Testing Library patterns)
When NOT to Use This Skill
- ❌ E2E tests → Use
webapp-testing skill (Playwright)
- ❌ Integration tests → Use
api-integration-test-scaffolder
- ❌ Backend tests → Use
pytest-test-scaffolder
- ❌ Storybook stories → Use
storybook-scaffolder
1---2name: jest-test-scaffolder3description: Scaffolds Jest unit tests for React components and hooks. Use when creating tests for frontend components.4---5
6# Jest Test Scaffolder Workflow
7
8This skill creates comprehensive unit tests for React components using Jest, React Testing Library, and user-event.
9
10## Workflow Steps
11
121. **Identify component to test:**
13 - Ask for component file path (e.g., `src/components/ui/Button/Button.tsx`)
14 - Read component file to extract:
15 - Component name
16 - Props interface/types
17 - Event handlers
18 - Component role (button, input, form, etc.)
19 - Determine test location: `{{COMPONENT_DIR}}/__tests__/{{COMPONENT_NAME}}.test.tsx`
20
212. **Analyze component structure:**
22 - Extract prop names and types from TypeScript interface
23 - Identify event handlers (onClick, onChange, onSubmit, etc.)
24 - Determine accessibility role (button, textbox, combobox, etc.)
25 - Identify state variations (disabled, loading, error, etc.)
26 - Check for conditional rendering
27
283. **Generate test file:**
29 - Read template: `.claude/skills/jest-test-scaffolder/templates/component.test.tsx.tpl`
30 - Replace placeholders:
31 - `{{COMPONENT_NAME}}` - Component name (PascalCase)
32 - `{{COMPONENT_PATH}}` - Relative import path
33 - `{{COMPONENT_ROLE}}` - Accessibility role (button, textbox, etc.)
34 - `{{DEFAULT_PROPS}}` - Default props for rendering
35 - `{{EVENT_HANDLERS}}` - Event handler tests
36 - `{{STATE_TESTS}}` - State variation tests (disabled, loading, etc.)
37 - Write to: `{{COMPONENT_DIR}}/__tests__/{{COMPONENT_NAME}}.test.tsx`
38
394. **Include comprehensive test scenarios:**
40 - ✅ **Render Test**: Component renders without errors
41 - ✅ **Props Test**: Component accepts and displays props correctly
42 - ✅ **Interaction Test**: User interactions trigger expected behavior
43 - ✅ **State Test**: Component handles state changes (disabled, loading, etc.)
44 - ✅ **Accessibility Test**: Component is accessible (role, labels)
45 - ⚠️ **Edge Cases**: Empty props, long text, null values (TODO placeholders)
46 - ⚠️ **Snapshot Test**: Visual regression (TODO placeholder)
47
485. **Use React Testing Library best practices:**
49 - Use `screen` queries (getByRole, getByText, getByLabelText)
50 - Use `userEvent` for realistic interactions
51 - Use `jest.fn()` for Jest mocks
52 - Avoid implementation details (no enzyme shallow rendering)
53 - Test accessibility (roles, labels, keyboard navigation)
54
556. **Report success:**
56 - Show test file path
57 - Display test coverage (number of test cases)
58 - Provide command to run tests: `yarn test {{COMPONENT_NAME}}`
59 - Remind user to add edge case tests
60
61## For React Hooks
62
63When testing custom React hooks:
64
651. **Use template:** `.claude/skills/jest-test-scaffolder/templates/hook.test.tsx.tpl`
662. **Use `@testing-library/react` renderHook:**
67 ```typescript
68 import { renderHook, act } from "@testing-library/react";
69 import { useCustomHook } from "./useCustomHook";
70 ```
713. **Test hook behavior:**
72 - Initial state
73 - State updates via `act()`
74 - Return values
75 - Side effects
76
77## Template Structure
78
79```typescript
80import { render, screen } from '@testing-library/react';
81import userEvent from '@testing-library/user-event';
82import { jest } from '@jest/globals';
83import { {{COMPONENT_NAME}} } from './{{COMPONENT_NAME}}';
84
85describe('{{COMPONENT_NAME}}', () => {
86 it('renders without errors', () => {
87 render(<{{COMPONENT_NAME}} {{DEFAULT_PROPS}} />);
88 expect(screen.getByRole('{{ROLE}}')).toBeInTheDocument();
89 });
90
91 it('handles user interactions', async () => {
92 const user = userEvent.setup();
93 const mock{{HANDLER}} = jest.fn();
94 render(<{{COMPONENT_NAME}} on{{HANDLER}}={mock{{HANDLER}}} />);
95
96 await user.click(screen.getByRole('{{ROLE}}'));
97 expect(mock{{HANDLER}}).toHaveBeenCalledTimes(1);
98 });
99
100 it('handles disabled state', async () => {
101 const user = userEvent.setup();
102 const mockHandler = jest.fn();
103 render(<{{COMPONENT_NAME}} disabled on{{HANDLER}}={mockHandler} />);
104
105 const element = screen.getByRole('{{ROLE}}');
106 expect(element).toBeDisabled();
107
108 await user.click(element);
109 expect(mockHandler).not.toHaveBeenCalled();
110 });
111
112 // TODO: Add accessibility tests
113 // TODO: Add snapshot tests
114 // TODO: Add edge case tests (empty props, long text, null values)
115});
116```
117
118## Example Usage
119
120```
121User: "Create tests for the Button component"
122
123Claude (using jest-test-scaffolder):
1241. Reads src/components/ui/Button/Button.tsx
1252. Extracts props: { children, onClick, disabled, variant, size }
1263. Generates src/components/ui/Button/__tests__/Button.test.tsx
1274. Creates 5 test cases:
128 - Renders with correct text
129 - Calls onClick handler when clicked
130 - Is disabled when disabled prop is true
131 - Renders correct variant styles (TODO)
132 - Handles different sizes (TODO)
133
134Files created:
135- src/components/ui/Button/__tests__/Button.test.tsx (42 lines, 3 complete tests, 2 TODO tests)
136
137Run tests:
138yarn test Button
139
140Test coverage:
141✅ Render test
142✅ Click interaction test
143✅ Disabled state test
144⚠️ Variant test (TODO)
145⚠️ Size test (TODO)
146```
147
148## Pattern Matching
149
150This skill follows the existing test pattern from:
151
152- `src/components/ui/Button/__tests__/Button.test.tsx` (36 lines, 3 tests)
153- Uses `describe` and `it` blocks
154- Uses `screen.getByRole` for queries
155- Uses `userEvent.setup()` and `await user.click()`
156- Uses `jest.fn()` for Jest mocks
157- Tests rendering, interactions, and state
158
159## Best Practices
160
1611. **Test user behavior, not implementation:**
162 - Query by role, label, text (user-visible)
163 - Avoid querying by class names or test IDs (unless necessary)
164 - Test what users see and do
165
1662. **Use userEvent over fireEvent:**
167 - More realistic user interactions
168 - Handles complex interactions (hover, tab, type)
169 - Follows browser behavior
170
1713. **Keep tests simple and focused:**
172 - One assertion per test (when possible)
173 - Clear test descriptions
174 - Avoid complex setup
175
1764. **Test accessibility:**
177 - Use semantic roles
178 - Verify labels and descriptions
179 - Test keyboard navigation
180
1815. **Mock external dependencies:**
182 - Mock API calls
183 - Mock context providers
184 - Mock navigation (useNavigate, useRouter)
185
186## Jest vs Vitest
187
188This skill uses **Jest** (not Vitest):
189
190- `jest.fn()` instead of `vi.fn()`
191- `jest.mock()` instead of `vi.mock()`
192- `jest.spyOn()` instead of `vi.spyOn()`
193- Everything else is the same (React Testing Library patterns)
194
195## When NOT to Use This Skill
196
197- ❌ E2E tests → Use `webapp-testing` skill (Playwright)
198- ❌ Integration tests → Use `api-integration-test-scaffolder`
199- ❌ Backend tests → Use `pytest-test-scaffolder`
200- ❌ Storybook stories → Use `storybook-scaffolder`