From aai-stack-react
Provides React Testing Library patterns for component testing: rendering with providers, preferred queries by role/label/text, role-based selectors, and user interactions.
npx claudepluginhub bradtaylorsf/alphaagent-teamThis skill uses the workspace's default tool permissions.
Patterns for testing React components with React Testing Library.
Suggests manual /compact at logical task boundaries in long Claude Code sessions and multi-phase tasks to avoid arbitrary auto-compaction losses.
Share bugs, ideas, or general feedback.
Patterns for testing React components with React Testing Library.
React Testing Library encourages testing components the way users interact with them, not implementation details.
import { render, screen } from '@testing-library/react'
test('renders greeting', () => {
render(<Greeting name="World" />)
expect(screen.getByText('Hello, World!')).toBeInTheDocument()
})
function renderWithProviders(
ui: ReactElement,
{
preloadedState = {},
...renderOptions
} = {}
) {
function Wrapper({ children }: { children: ReactNode }) {
return (
<Provider store={setupStore(preloadedState)}>
<ThemeProvider>
<Router>
{children}
</Router>
</ThemeProvider>
</Provider>
)
}
return render(ui, { wrapper: Wrapper, ...renderOptions })
}
// Usage
test('renders with providers', () => {
renderWithProviders(<UserDashboard />)
expect(screen.getByText('Dashboard')).toBeInTheDocument()
})
Use queries in this order (most to least preferred):
// 1. Accessible queries (preferred)
screen.getByRole('button', { name: /submit/i })
screen.getByLabelText('Email')
screen.getByPlaceholderText('Enter email')
screen.getByText('Hello World')
screen.getByDisplayValue('current value')
// 2. Semantic queries
screen.getByAltText('Profile picture')
screen.getByTitle('Close')
// 3. Test IDs (last resort)
screen.getByTestId('custom-element')
// getBy* - Throws if not found (use for elements that should exist)
screen.getByRole('button')
// queryBy* - Returns null if not found (use for asserting absence)
expect(screen.queryByText('Loading')).not.toBeInTheDocument()
// findBy* - Returns promise, waits for element (use for async)
const button = await screen.findByRole('button')
// *AllBy* - Returns array of all matches
const items = screen.getAllByRole('listitem')
// Buttons
screen.getByRole('button', { name: /submit/i })
// Links
screen.getByRole('link', { name: /home/i })
// Form elements
screen.getByRole('textbox', { name: /email/i })
screen.getByRole('checkbox', { name: /remember me/i })
screen.getByRole('combobox', { name: /country/i })
// Headings
screen.getByRole('heading', { name: /welcome/i, level: 1 })
// Navigation
screen.getByRole('navigation')
// Lists
screen.getByRole('list')
screen.getAllByRole('listitem')
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
test('button click increments counter', async () => {
const user = userEvent.setup()
render(<Counter />)
const button = screen.getByRole('button', { name: /increment/i })
await user.click(button)
expect(screen.getByText('Count: 1')).toBeInTheDocument()
})
test('form submission', async () => {
const user = userEvent.setup()
const onSubmit = jest.fn()
render(<LoginForm onSubmit={onSubmit} />)
await user.type(screen.getByLabelText(/email/i), 'test@example.com')
await user.type(screen.getByLabelText(/password/i), 'password123')
await user.click(screen.getByRole('button', { name: /sign in/i }))
expect(onSubmit).toHaveBeenCalledWith({
email: 'test@example.com',
password: 'password123',
})
})
test('select option', async () => {
const user = userEvent.setup()
render(<CountrySelect />)
await user.selectOptions(
screen.getByRole('combobox', { name: /country/i }),
'US'
)
expect(screen.getByRole('combobox')).toHaveValue('US')
})
test('keyboard navigation', async () => {
const user = userEvent.setup()
render(<Form />)
await user.tab() // Focus first input
await user.type(screen.getByLabelText(/name/i), 'John')
await user.tab() // Focus next input
await user.keyboard('{Enter}') // Submit form
})
test('loads user data', async () => {
render(<UserProfile userId="123" />)
// Wait for loading to finish
expect(screen.getByText(/loading/i)).toBeInTheDocument()
// Wait for data to appear
expect(await screen.findByText('John Doe')).toBeInTheDocument()
})
import { waitFor } from '@testing-library/react'
test('form validation', async () => {
const user = userEvent.setup()
render(<Form />)
await user.click(screen.getByRole('button', { name: /submit/i }))
await waitFor(() => {
expect(screen.getByText(/email is required/i)).toBeInTheDocument()
})
})
test('disappearing element', async () => {
render(<Notification />)
expect(screen.getByText(/success/i)).toBeInTheDocument()
await waitFor(() => {
expect(screen.queryByText(/success/i)).not.toBeInTheDocument()
})
})
test('loading state disappears', async () => {
render(<DataLoader />)
await waitForElementToBeRemoved(() => screen.queryByText(/loading/i))
expect(screen.getByText('Data loaded')).toBeInTheDocument()
})
import { rest } from 'msw'
import { setupServer } from 'msw/node'
const server = setupServer(
rest.get('/api/user/:id', (req, res, ctx) => {
return res(ctx.json({ id: req.params.id, name: 'John Doe' }))
})
)
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
test('fetches user data', async () => {
render(<UserProfile userId="123" />)
expect(await screen.findByText('John Doe')).toBeInTheDocument()
})
test('handles error', async () => {
server.use(
rest.get('/api/user/:id', (req, res, ctx) => {
return res(ctx.status(500))
})
)
render(<UserProfile userId="123" />)
expect(await screen.findByText(/error loading user/i)).toBeInTheDocument()
})
jest.mock('../hooks/useUser', () => ({
useUser: jest.fn(),
}))
import { useUser } from '../hooks/useUser'
test('renders user when loaded', () => {
(useUser as jest.Mock).mockReturnValue({
user: { name: 'John' },
loading: false,
error: null,
})
render(<UserProfile />)
expect(screen.getByText('John')).toBeInTheDocument()
})
import { MemoryRouter, Route, Routes } from 'react-router-dom'
test('navigates on click', async () => {
const user = userEvent.setup()
render(
<MemoryRouter initialEntries={['/']}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</MemoryRouter>
)
await user.click(screen.getByRole('link', { name: /about/i }))
expect(screen.getByText(/about page/i)).toBeInTheDocument()
})
describe('RegistrationForm', () => {
test('shows validation errors for empty required fields', async () => {
const user = userEvent.setup()
render(<RegistrationForm />)
await user.click(screen.getByRole('button', { name: /register/i }))
expect(await screen.findByText(/name is required/i)).toBeInTheDocument()
expect(screen.getByText(/email is required/i)).toBeInTheDocument()
})
test('submits with valid data', async () => {
const user = userEvent.setup()
const onSubmit = jest.fn()
render(<RegistrationForm onSubmit={onSubmit} />)
await user.type(screen.getByLabelText(/name/i), 'John Doe')
await user.type(screen.getByLabelText(/email/i), 'john@example.com')
await user.click(screen.getByRole('button', { name: /register/i }))
expect(onSubmit).toHaveBeenCalledWith({
name: 'John Doe',
email: 'john@example.com',
})
})
})
test('opens and closes modal', async () => {
const user = userEvent.setup()
render(<App />)
// Modal not visible initially
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
// Open modal
await user.click(screen.getByRole('button', { name: /open modal/i }))
expect(screen.getByRole('dialog')).toBeInTheDocument()
// Close modal
await user.click(screen.getByRole('button', { name: /close/i }))
await waitForElementToBeRemoved(() => screen.queryByRole('dialog'))
})
test('renders and filters list items', async () => {
const user = userEvent.setup()
render(<TodoList />)
// Verify initial items
const items = screen.getAllByRole('listitem')
expect(items).toHaveLength(3)
// Filter items
await user.type(screen.getByRole('searchbox'), 'buy')
const filteredItems = screen.getAllByRole('listitem')
expect(filteredItems).toHaveLength(1)
expect(filteredItems[0]).toHaveTextContent('Buy groceries')
})
import { axe, toHaveNoViolations } from 'jest-axe'
expect.extend(toHaveNoViolations)
test('has no accessibility violations', async () => {
const { container } = render(<MyComponent />)
const results = await axe(container)
expect(results).toHaveNoViolations()
})
test('debugging', () => {
render(<Component />)
// Print current DOM
screen.debug()
// Print specific element
screen.debug(screen.getByRole('button'))
// Log accessible roles
screen.logTestingPlaygroundURL() // Opens Testing Playground with current DOM
})
Used by:
frontend-developer agente2e-test-specialist agentunit-test-specialist agent