| name | frontend-testing |
| description | Generates Vitest + React Testing Library tests for frontend components, hooks, and utilities. Use when writing tests, reviewing spec files, improving coverage, or when the user mentions Vitest, RTL, unit tests, or integration tests. |
| metadata | {"source":"https://github.com/langgenius/dify/blob/main/.agents/skills/frontend-testing"} |
Frontend Testing Skill
This skill enables Claude to generate high-quality, comprehensive frontend tests for projects following established conventions and best practices.
Use Vitest mock/timer APIs (vi.*).
When to Apply This Skill
Apply this skill when the user:
- Asks to write tests for a component, hook, or utility
- Asks to review existing tests for completeness
- Mentions Vitest, React Testing Library, RTL, or spec files
- Requests test coverage improvement
- Uses
pnpm analyze-component output as context
- Mentions testing, unit tests, or integration tests for frontend code
- Wants to understand testing patterns in the codebase
Do NOT apply when:
- User is asking about backend/API tests (Python/pytest)
- User is asking about E2E tests (Playwright/Cypress)
- User is only asking conceptual questions without code context
Quick Reference
Tech Stack
| Tool | Version | Purpose |
|---|
| Vitest | 4.0.16 | Test runner |
| React Testing Library | 16.0 | Component testing |
| jsdom | - | Test environment |
| nock | 14.0 | HTTP mocking |
| TypeScript | 5.x | Type safety |
Key Commands
pnpm test
pnpm test:watch
pnpm test path/to/file.spec.tsx
pnpm test:coverage
pnpm analyze-component <path>
pnpm analyze-component <path> --review
File Naming
- Test files:
ComponentName.spec.tsx (same directory as component)
- Integration tests:
web/__tests__/ directory
Test Structure Template
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import Component from './index'
vi.mock('@/service/api')
vi.mock('next/navigation', () => ({
useRouter: () => ({ push: vi.fn() }),
usePathname: () => '/test',
}))
let mockSharedState = false
describe('ComponentName', () => {
beforeEach(() => {
vi.clearAllMocks()
mockSharedState = false
})
describe('Rendering', () => {
it('should render without crashing', () => {
const props = { title: 'Test' }
render(<Component {...props} />)
expect(screen.getByText('Test')).toBeInTheDocument()
})
})
describe('Props', () => {
it('should apply custom className', () => {
render(<Component className="custom" />)
expect(screen.getByRole('button')).toHaveClass('custom')
})
})
describe('User Interactions', () => {
it('should handle click events', () => {
const handleClick = vi.fn()
render(<Component onClick={handleClick} />)
fireEvent.click(screen.getByRole('button'))
expect(handleClick).toHaveBeenCalledTimes(1)
})
})
describe('Edge Cases', () => {
it('should handle null data', () => {
render(<Component data={null} />)
expect(screen.getByText(/no data/i)).toBeInTheDocument()
})
it('should handle empty array', () => {
render(<Component items={[]} />)
expect(screen.getByText(/empty/i)).toBeInTheDocument()
})
})
})
Testing Workflow (CRITICAL)
⚠️ Incremental Approach Required
NEVER generate all test files at once. For complex components or multi-file directories:
- Analyze & Plan: List all files, order by complexity (simple → complex)
- Process ONE at a time: Write test → Run test → Fix if needed → Next
- Verify before proceeding: Do NOT continue to next file until current passes
For each file:
┌────────────────────────────────────────┐
│ 1. Write test │
│ 2. Run: pnpm test <file>.spec.tsx │
│ 3. PASS? → Mark complete, next file │
│ FAIL? → Fix first, then continue │
└────────────────────────────────────────┘
Complexity-Based Order
Process in this order for multi-file testing:
- 🟢 Utility functions (simplest)
- 🟢 Custom hooks
- 🟡 Simple components (presentational)
- 🟡 Medium components (state, effects)
- 🔴 Complex components (API, routing)
- 🔴 Integration tests (index files - last)
When to Refactor First
- Complexity > 50: Break into smaller pieces before testing
- 500+ lines: Consider splitting before testing
- Many dependencies: Extract logic into hooks first
📖 See references/workflow.md for complete workflow details and todo list format.
Testing Strategy
Path-Level Testing (Directory Testing)
When assigned to test a directory/path, test ALL content within that path:
- Test all components, hooks, utilities in the directory (not just
index file)
- Use incremental approach: one file at a time, verify each before proceeding
- Goal: 100% coverage of ALL files in the directory
Integration Testing First
Prefer integration testing when writing tests for a directory:
- ✅ Import real project components directly (including base components and siblings)
- ✅ Only mock: API services (
@/service/*), next/navigation, complex context providers
- ❌ DO NOT mock base components (
@/app/components/base/*)
- ❌ DO NOT mock sibling/child components in the same directory
See Test Structure Template for correct import/mock patterns.
nuqs Query State Testing (Required for URL State Hooks)
When a component or hook uses useQueryState / useQueryStates:
- ✅ Use
NuqsTestingAdapter (prefer shared helpers in web/test/nuqs-testing.tsx)
- ✅ Assert URL synchronization via
onUrlUpdate (searchParams, options.history)
- ✅ For custom parsers (
createParser), keep parse and serialize bijective and add round-trip edge cases (%2F, %25, spaces, legacy encoded values)
- ✅ Verify default-clearing behavior (default values should be removed from URL when applicable)
- ⚠️ Only mock
nuqs directly when URL behavior is explicitly out of scope for the test
Core Principles
1. AAA Pattern (Arrange-Act-Assert)
Every test should clearly separate:
- Arrange: Setup test data and render component
- Act: Perform user actions
- Assert: Verify expected outcomes
2. Black-Box Testing
- Test observable behavior, not implementation details
- Use semantic queries (getByRole, getByLabelText)
- Avoid testing internal state directly
- Prefer pattern matching over hardcoded strings in assertions:
expect(screen.getByText('Loading...')).toBeInTheDocument()
expect(screen.getByRole('status')).toBeInTheDocument()
expect(screen.getByText(/loading/i)).toBeInTheDocument()
3. Single Behavior Per Test
Each test verifies ONE user-observable behavior:
it('should disable button when loading', () => {
render(<Button loading />)
expect(screen.getByRole('button')).toBeDisabled()
})
it('should handle loading state', () => {
render(<Button loading />)
expect(screen.getByRole('button')).toBeDisabled()
expect(screen.getByText('Loading...')).toBeInTheDocument()
expect(screen.getByRole('button')).toHaveClass('loading')
})
4. Semantic Naming
Use should <behavior> when <condition>:
it('should show error message when validation fails')
it('should call onSubmit when form is valid')
it('should disable input when isReadOnly is true')
Required Test Scenarios
Always Required (All Components)
- Rendering: Component renders without crashing
- Props: Required props, optional props, default values
- Edge Cases: null, undefined, empty values, boundary conditions
Conditional (When Present)
| Feature | Test Focus |
|---|
useState | Initial state, transitions, cleanup |
useEffect | Execution, dependencies, cleanup |
| Event handlers | All onClick, onChange, onSubmit, keyboard |
| API calls | Loading, success, error states |
| Routing | Navigation, params, query strings |
useCallback/useMemo | Referential equality |
| Context | Provider values, consumer behavior |
| Forms | Validation, submission, error display |
Coverage Goals (Per File)
For each test file generated, aim for:
- ✅ 100% function coverage
- ✅ 100% statement coverage
- ✅ >95% branch coverage
- ✅ >95% line coverage
Note: For multi-file directories, process one file at a time with full coverage each. See references/workflow.md.
Detailed Guides
For more detailed information, refer to:
references/workflow.md - Incremental testing workflow (MUST READ for multi-file testing)
references/mocking.md - Mock patterns, Zustand store testing, and best practices
references/async-testing.md - Async operations and API calls
references/domain-components.md - Workflow, Dataset, Configuration testing
references/common-patterns.md - Frequently used testing patterns
references/checklist.md - Test generation checklist and validation steps
Authoritative References
Project Configuration
Look for these files in the project to understand the test setup:
vitest.config.ts - Vitest configuration
vitest.setup.ts - Test environment setup and global mocks
- Existing
*.spec.tsx / *.spec.ts files - Follow established patterns in the codebase