From harness-claude
Tests React components with Testing Library and userEvent using accessible queries, user interactions, async utilities like waitFor/findBy, and conditional rendering verification.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Test React components with Testing Library using user-centric queries and async utilities
Provides React Testing Library best practices with 43 rules for writing, reviewing, refactoring maintainable user-centric tests. Applies to queries, async handling, userEvent, waitFor in test files.
Enforces React Testing Library best practices with 43 rules across 9 categories including query selection, async handling, anti-patterns, user interactions, and accessibility for writing and reviewing tests.
Writes, reviews, and fixes React Native component tests using @testing-library/react-native v13 (React 18, sync) and v14 (React 19+, async). Covers render, screen queries, userEvent, fireEvent, waitFor.
Share bugs, ideas, or general feedback.
Test React components with Testing Library using user-centric queries and async utilities
render and screen:import { render, screen } from '@testing-library/react';
import { UserCard } from './user-card';
it('displays the user name', () => {
render(<UserCard user={{ name: 'Alice', email: 'alice@test.com' }} />);
expect(screen.getByText('Alice')).toBeInTheDocument();
expect(screen.getByText('alice@test.com')).toBeInTheDocument();
});
getByRole — buttons, links, headings, form elementsgetByLabelText — form inputs by labelgetByPlaceholderText — inputs by placeholdergetByText — visible text contentgetByTestId — last resort, for elements without accessible namesit('renders a submit button', () => {
render(<LoginForm />);
expect(screen.getByRole('button', { name: 'Submit' })).toBeInTheDocument();
expect(screen.getByLabelText('Email')).toBeInTheDocument();
});
userEvent:import userEvent from '@testing-library/user-event';
it('submits the form with entered data', async () => {
const onSubmit = vi.fn();
render(<LoginForm onSubmit={onSubmit} />);
const user = userEvent.setup();
await user.type(screen.getByLabelText('Email'), 'alice@test.com');
await user.type(screen.getByLabelText('Password'), 'secret123');
await user.click(screen.getByRole('button', { name: 'Log in' }));
expect(onSubmit).toHaveBeenCalledWith({
email: 'alice@test.com',
password: 'secret123',
});
});
waitFor and findBy:it('shows user data after loading', async () => {
render(<UserProfile userId="123" />);
expect(screen.getByText('Loading...')).toBeInTheDocument();
// findBy waits for the element to appear (default 1000ms timeout)
expect(await screen.findByText('Alice')).toBeInTheDocument();
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
});
it('shows error message for invalid input', async () => {
render(<EmailInput />);
const user = userEvent.setup();
await user.type(screen.getByLabelText('Email'), 'not-an-email');
await user.tab(); // Trigger blur validation
expect(screen.getByText('Please enter a valid email')).toBeInTheDocument();
});
queryBy (returns null instead of throwing):it('does not show admin panel for regular users', () => {
render(<Dashboard user={{ role: 'user' }} />);
expect(screen.queryByText('Admin Panel')).not.toBeInTheDocument();
});
function renderWithProviders(ui: React.ReactElement) {
return render(
<ThemeProvider theme={defaultTheme}>
<AuthProvider value={mockAuth}>
{ui}
</AuthProvider>
</ThemeProvider>
);
}
it('uses theme colors', () => {
renderWithProviders(<Button>Click me</Button>);
expect(screen.getByRole('button')).toHaveStyle({ color: 'blue' });
});
userEvent over fireEvent — userEvent simulates real browser behavior (focus, keyboard events, click sequence):// Good — fires focus, keydown, keypress, input, keyup for each character
await user.type(input, 'hello');
// Less realistic — fires only the change event
fireEvent.change(input, { target: { value: 'hello' } });
Testing Library's philosophy is "test the way users interact with your app." Tests should not know about component internals (state, props, hooks) — they should only interact through the rendered DOM.
Query types:
getBy — returns element or throws. Use when the element must be presentqueryBy — returns element or null. Use when asserting absencefindBy — returns a Promise. Use for elements that appear asynchronouslyAllBy variants that return arraysuserEvent.setup(): Always call userEvent.setup() before interactions. This creates a user session with proper event sequencing. Do not use the older userEvent.click() static methods.
jsdom limitations: Testing Library runs in jsdom, which does not implement layout. getComputedStyle, getBoundingClientRect, and scroll behavior do not work. Use Playwright for visual and layout testing.
Trade-offs:
getByRole encourages accessible markup — but can be frustrating when role names are not obvioususerEvent is realistic — but slower than fireEvent. Use fireEvent for simple cases in large test suiteswaitFor and findBy handle async — but can mask slow components. Set explicit timeouts in CIhttps://testing-library.com/docs/react-testing-library/intro/