git:20260131.7d8568c to git:20260131.b40f0a1

104 added, 176 removed. Audit A to A.

---
name: react-testing
- description: Testing Library for React - render, screen, userEvent, waitFor. Use when writing tests for React components with Vitest or Jest.
- user-invocable: false
+ description: Testing Library for React 19 - render, screen, userEvent, waitFor, Suspense. Use when writing tests for React components with Vitest.
+ versions:
+ "@testing-library/react": 16.1.0
+ "@testing-library/user-event": 14.5.2
+ vitest: 2.1.8
+ msw: 2.7.0
+ react: 19
+ user-invocable: true
+ references: references/installation.md, references/queries.md, references/user-events.md, references/async-testing.md, references/msw-setup.md, references/react-19-hooks.md, references/accessibility-testing.md, references/hooks-testing.md, references/vitest-config.md, references/mocking-patterns.md, references/templates/basic-setup.md, references/templates/component-basic.md, references/templates/component-async.md, references/templates/form-testing.md, references/templates/hook-basic.md, references/templates/api-integration.md, references/templates/suspense-testing.md, references/templates/error-boundary.md, references/templates/accessibility-audit.md
+ related-skills: react-19, solid-react, react-state, react-forms
---
# React Testing Library
Test React components the way users interact with them.
## Agent Workflow (MANDATORY)
Before ANY implementation, launch in parallel:
- 1. **fuse-ai-pilot:explore-codebase** - Analyze existing test patterns and setup
+ 1. **fuse-ai-pilot:explore-codebase** - Analyze existing test patterns
2. **fuse-ai-pilot:research-expert** - Verify latest Testing Library docs via Context7/Exa
- 3. **mcp__context7__query-docs** - Check userEvent, waitFor, and async patterns
+ 3. **mcp__context7__query-docs** - Check userEvent, waitFor patterns
After implementation, run **fuse-ai-pilot:sniper** for validation.
---
- ## Installation
-
- ```bash
- bun add -D @testing-library/react @testing-library/user-event @testing-library/jest-dom vitest jsdom
- ```
+ ## Overview
- ## Vitest Configuration
+ ### When to Use
- ```typescript
- // vite.config.ts
- import { defineConfig } from 'vitest/config'
- import react from '@vitejs/plugin-react'
+ - Testing React component behavior
+ - Validating user interactions
+ - Ensuring accessibility compliance
+ - Mocking API calls with MSW
+ - Testing custom hooks
+ - Testing React 19 features (useActionState, use())
- export default defineConfig({
- plugins: [react()],
- test: {
- environment: 'jsdom',
- globals: true,
- setupFiles: './src/test/setup.ts',
- },
- })
- ```
+ ### Why React Testing Library
- ```typescript
- // src/test/setup.ts
- import '@testing-library/jest-dom/vitest'
- ```
+ | Feature | Benefit |
+ |---------|---------|
+ | User-centric | Tests what users see |
+ | Accessible queries | Encourages a11y markup |
+ | No implementation details | Resilient to refactoring |
+ | Vitest integration | 10-20x faster than Jest |
---
- ## Basic Testing
-
- ```typescript
- // src/components/__tests__/Button.test.tsx
- import { render, screen } from '@testing-library/react'
- import userEvent from '@testing-library/user-event'
- import { Button } from '../Button'
-
- describe('Button', () => {
- it('renders with text', () => {
- render(<Button>Click me</Button>)
- expect(screen.getByRole('button', { name: /click me/i })).toBeInTheDocument()
- })
-
- it('calls onClick when clicked', async () => {
- const handleClick = vi.fn()
- render(<Button onClick={handleClick}>Click me</Button>)
-
- await userEvent.click(screen.getByRole('button'))
-
- expect(handleClick).toHaveBeenCalledTimes(1)
- })
+ ## Critical Rules
- it('is disabled when disabled prop is true', () => {
- render(<Button disabled>Click me</Button>)
- expect(screen.getByRole('button')).toBeDisabled()
- })
- })
- ```
+ 1. **Query by role first** - `getByRole` is most accessible
+ 2. **Use userEvent, not fireEvent** - Realistic interactions
+ 3. **waitFor for async** - Never `setTimeout`
+ 4. **MSW for API mocking** - Don't mock fetch
+ 5. **Test behavior, not implementation** - No internal state testing
---
- ## Queries
-
- ### Priority Order (Recommended)
-
- 1. **getByRole** - Most accessible
- 2. **getByLabelText** - Form inputs
- 3. **getByPlaceholderText** - Inputs
- 4. **getByText** - Text content
- 5. **getByTestId** - Last resort
-
- ```typescript
- // Accessible queries
- screen.getByRole('button', { name: /submit/i })
- screen.getByRole('textbox', { name: /email/i })
- screen.getByRole('heading', { level: 1 })
- screen.getByLabelText(/password/i)
-
- // Text queries
- screen.getByText(/welcome/i)
- screen.getByPlaceholderText(/search/i)
-
- // Test ID (avoid if possible)
- screen.getByTestId('custom-element')
- ```
-
- ### Query Variants
+ ## Reference Guide
- ```typescript
- // getBy - Throws if not found (sync)
- screen.getByRole('button')
+ ### Concepts
- // queryBy - Returns null if not found (sync)
- screen.queryByRole('button')
+ | Topic | Reference |
+ |-------|-----------|
+ | Setup & installation | `references/installation.md` |
+ | Query priority | `references/queries.md` |
+ | User interactions | `references/user-events.md` |
+ | Async patterns | `references/async-testing.md` |
+ | API mocking | `references/msw-setup.md` |
+ | React 19 hooks | `references/react-19-hooks.md` |
+ | Accessibility | `references/accessibility-testing.md` |
+ | Custom hooks | `references/hooks-testing.md` |
+ | Vitest config | `references/vitest-config.md` |
+ | Mocking patterns | `references/mocking-patterns.md` |
- // findBy - Returns promise (async)
- await screen.findByRole('button')
+ ### Templates
- // getAllBy, queryAllBy, findAllBy - Multiple elements
- screen.getAllByRole('listitem')
- ```
+ | Template | Use Case |
+ |----------|----------|
+ | `templates/basic-setup.md` | Vitest + RTL + MSW config |
+ | `templates/component-basic.md` | Simple component tests |
+ | `templates/component-async.md` | Loading/error/success |
+ | `templates/form-testing.md` | Forms + useActionState |
+ | `templates/hook-basic.md` | Custom hook tests |
+ | `templates/api-integration.md` | MSW integration tests |
+ | `templates/suspense-testing.md` | Suspense + use() |
+ | `templates/error-boundary.md` | Error boundary tests |
+ | `templates/accessibility-audit.md` | axe-core a11y audit |
---
- ## User Events
-
- ```typescript
- import userEvent from '@testing-library/user-event'
-
- describe('Form', () => {
- it('submits form data', async () => {
- const user = userEvent.setup()
- const handleSubmit = vi.fn()
- render(<LoginForm onSubmit={handleSubmit} />)
-
- // Type in inputs
- await user.type(screen.getByLabelText(/email/i), 'test@example.com')
- await user.type(screen.getByLabelText(/password/i), 'password123')
+ ## Forbidden Patterns
- // Click submit
- await user.click(screen.getByRole('button', { name: /login/i }))
+ | Pattern | Reason | Alternative |
+ |---------|--------|-------------|
+ | `fireEvent` | Not realistic | `userEvent` |
+ | `setTimeout` | Flaky | `waitFor`, `findBy` |
+ | `getByTestId` first | Not accessible | `getByRole` |
+ | Direct fetch mocking | Hard to maintain | MSW |
+ | Empty `waitFor` | No assertion | Add `expect()` |
- expect(handleSubmit).toHaveBeenCalledWith({
- email: 'test@example.com',
- password: 'password123',
- })
- })
+ ---
- it('shows error on invalid input', async () => {
- const user = userEvent.setup()
- render(<LoginForm />)
+ ## Quick Start
- await user.type(screen.getByLabelText(/email/i), 'invalid')
- await user.click(screen.getByRole('button', { name: /login/i }))
+ ### Install
- expect(await screen.findByText(/invalid email/i)).toBeInTheDocument()
- })
- })
+ ```bash
+ npm install -D vitest @testing-library/react \
+ @testing-library/user-event @testing-library/jest-dom \
+ jsdom msw
```
- ---
+ → See `templates/basic-setup.md` for complete configuration
- ## Async Testing
+ ### Basic Test
```typescript
- import { render, screen, waitFor } from '@testing-library/react'
-
- describe('UserProfile', () => {
- it('loads and displays user data', async () => {
- render(<UserProfile userId="1" />)
-
- // Wait for loading to finish
- expect(screen.getByText(/loading/i)).toBeInTheDocument()
-
- // Wait for data
- await waitFor(() => {
- expect(screen.getByText('John Doe')).toBeInTheDocument()
- })
- })
+ import { render, screen } from '@testing-library/react'
+ import userEvent from '@testing-library/user-event'
- it('shows error on failure', async () => {
- server.use(
- http.get('/api/users/:id', () => {
- return HttpResponse.error()
- })
- )
+ test('button click works', async () => {
+ const user = userEvent.setup()
+ render(<Button onClick={fn}>Click</Button>)
- render(<UserProfile userId="1" />)
+ await user.click(screen.getByRole('button'))
- expect(await screen.findByText(/error loading/i)).toBeInTheDocument()
- })
+ expect(fn).toHaveBeenCalled()
})
```
+ → See `templates/component-basic.md` for more examples
+
---
- ## Mocking
+ ## Best Practices
- ### Mock Functions
+ ### Query Priority
- ```typescript
- const mockFn = vi.fn()
- mockFn.mockReturnValue('value')
- mockFn.mockResolvedValue('async value')
- mockFn.mockImplementation((x) => x * 2)
- ```
+ 1. `getByRole` - Buttons, headings, inputs
+ 2. `getByLabelText` - Form inputs
+ 3. `getByText` - Static text
+ 4. `getByTestId` - Last resort
- ### Mock Modules
+ ### Async Pattern
```typescript
- vi.mock('../services/api', () => ({
- fetchUser: vi.fn().mockResolvedValue({ name: 'John' }),
- }))
+ // Preferred: findBy
+ await screen.findByText('Loaded')
+
+ // Alternative: waitFor
+ await waitFor(() => expect(...).toBeInTheDocument())
```
- ### MSW for API Mocking
+ → See `templates/component-async.md`
- ```typescript
- // src/test/mocks/handlers.ts
- import { http, HttpResponse } from 'msw'
+ ### userEvent Setup
- export const handlers = [
- http.get('/api/users/:id', ({ params }) => {
- return HttpResponse.json({ id: params.id, name: 'John' })
- }),
- ]
+ ```typescript
+ const user = userEvent.setup()
+ await user.click(button)
+ await user.type(input, 'text')
```
- ---
-
- ## Best Practices
-
- 1. **Query by role** - Most accessible and robust
- 2. **Use userEvent** - More realistic than fireEvent
- 3. **Avoid implementation details** - Test behavior, not internals
- 4. **Use async utilities** - waitFor, findBy for async
- 5. **Mock at network level** - Use MSW for API mocking
- 6. **Write descriptive test names** - Clear intent
+ → See `references/user-events.md`