agentleFS
jayeshsojitra103jayeshsojitra103/claude-frontend-skills/skills/component-test-writer/SKILL.md

component-test-writer

Skill0 starsChanged 3 months agoMITView on GitHub

Write React component, hook, and end-to-end tests using Vitest, React Testing Library, MSW, and Playwright — chosen for behaviour coverage rather than line coverage, with accessible queries and no implementation-detail assertions. Use this skill whenever the user asks for tests, mentions Vitest, Jest, React Testing Library, MSW, Playwright, Cypress, test coverage, flaky tests, mocking API calls, or testing hooks, forms, async UI, or user flows — including "add tests for this component" and "why is this test flaky".

The file

We split this skill into the rules it gives an agent

4 rules. About 0 are common, 1 is distinctive, and 3 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 3

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Query priority: Use getByRole first, then getByLabelText for form fields, then getByText.

    line 27
  2. Distinctive:

    Assert on output, never internals: No assertions on state variables, hook call counts, class names, or child component props.

    line 43
  3. Very specific:

    Test through a component when the hook is used by one, since that is how it will actually run.

    line 90
  4. Very specific:

    Use web-first assertions (toBeVisible, toHaveText) — they retry automatically.

    line 114

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Component Test Writer

A test suite is worth what it catches minus what it costs to maintain. Tests coupled to implementation break on every refactor while catching nothing, which is how teams end up with 90% coverage and no confidence. This skill writes tests against behaviour the user can observe.

Choose the level first

LevelToolUse forKeep it
UnitVitestpure functions, reducers, formatters, validationmany, fast
ComponentVitest + RTLrendering, interaction, conditional UI, a11y contractmost of the suite
IntegrationRTL + MSWdata fetching, forms with real submit, routinga solid layer
E2EPlaywrightcritical revenue paths across real pagesfew, high value

The common failure is inversion: a hundred shallow component tests and no test that a user can actually check out. Ask what breaking would be most expensive, and start there.

Component tests

Query priority. Use getByRole first, then getByLabelText for form fields, then getByText. getByTestId is the last resort. This is not stylistic — role-based queries fail when the component becomes inaccessible, so the suite doubles as an accessibility regression net.

Interact as a user. userEvent over fireEvent: it dispatches the full event sequence (pointerdown, focus, keydown, input) that real interaction produces, so it catches bugs fireEvent.change walks straight past.

const user = userEvent.setup();
await user.type(screen.getByLabelText(/email/i), 'test@example.com');
await user.click(screen.getByRole('button', { name: /sign in/i }));
expect(await screen.findByRole('alert')).toHaveTextContent(/invalid credentials/i);

Assert on output, never internals. No assertions on state variables, hook call counts, class names, or child component props. If the rendered output and the callbacks are correct, the internals are free to change — that is the entire value of the test.

Async. findBy* for appearance, waitForElementToBeRemoved for disappearance, and waitFor only when neither fits. Never await new Promise(r => setTimeout(r, 500)); a fixed sleep is either flaky on slow CI or wasted time on fast CI.

Network with MSW

Mock at the network boundary, not the module boundary. Mocking axios or a useQuery hook tests your mock; intercepting HTTP tests your code.

export const server = setupServer(
  http.get('/api/users/:id', ({ params }) =>
    HttpResponse.json({ id: params.id, name: 'Ada Lovelace' }),
  ),
);
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

onUnhandledRequest: 'error' is worth setting from day one — it turns a silently missing handler into a clear failure instead of a mysterious empty render.

Override per test for the paths that matter more than the happy one:

server.use(http.get('/api/users/:id', () => new HttpResponse(null, { status: 500 })));

What to test in a component

Cover these five and stop; anything beyond is usually restating the implementation:

  1. It renders the meaningful content for representative props
  2. Interaction produces the expected output or callback
  3. Conditional branches — empty, loading, error, permission-denied
  4. Accessibility contract — roles, names, and keyboard operability
  5. Edge inputs — empty list, very long string, zero, null

Skip: prop-type validation, that a library works, styling, and trivially derived values.

Hooks

Test through a component when the hook is used by one, since that is how it will actually run. Use renderHook only for genuinely standalone hooks:

const { result } = renderHook(() => useCounter(5));
act(() => result.current.increment());
expect(result.current.count).toBe(6);

Playwright

Reserve for flows where failure costs real money: signup, checkout, payment, the primary task of the app. Each one is expensive to run and to maintain.

test('completes checkout', async ({ page }) => {
  await page.goto('/cart');
  await page.getByRole('button', { name: 'Checkout' }).click();
  await page.getByLabel('Card number').fill('4242424242424242');
  await page.getByRole('button', { name: 'Pay' }).click();
  await expect(page.getByText('Order confirmed')).toBeVisible();
});

Use web-first assertions (toBeVisible, toHaveText) — they retry automatically. Never waitForTimeout. Seed state through the API rather than the UI so a login page change does not break twenty unrelated tests.

Diagnosing flaky tests

Flakiness is almost always one of five causes. Check in this order:

  1. Fixed timeouts → replace with findBy* or a web-first assertion
  2. Shared state between tests → reset handlers, storage, and the query cache in afterEach
  3. Unawaited async → a missing await on a user interaction or an assertion
  4. Real timers with animations → vi.useFakeTimers() or disable animations in E2E
  5. Test order dependence → run with --shuffle; a suite that fails when shuffled has hidden coupling

A quarantined flaky test is worse than a deleted one — it trains the team to ignore red.

Setup

references/setup.md has the Vitest config, the shared setup file, a provider-aware custom render, MSW wiring, and Playwright config with auth-state reuse. Read it before writing the first test in a repo that has no test infrastructure yet — the provider render and the per-test QueryClient in particular prevent a class of order-dependent failures that is painful to debug later.

Output format

Deliver: the test file, any MSW handlers or fixtures it needs, the run command, and one short note on what is deliberately not covered and why. That last line is what makes a reviewer trust the rest.

Aim for coverage of behaviour, and treat a line-coverage percentage as a diagnostic rather than a target — chasing the number produces tests that assert nothing.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.