accessibility
Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android…
React component testing with React Testing Library, Vitest/Jest, MSW for network mocking, accessibility assertions with axe, and the decision boundary between component tests and Playwright/Cypress end-to-end runs. Use when writing or fixing tests for React components, hooks, or
$ npx -y skills add affaan-m/ECC --skill react-testing --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/react-testingContext preview
The summary Claude sees to decide when to auto-load this skill.
React component testing with React Testing Library, Vitest/Jest, MSW for network mocking, accessibility assertions with axe, and the decision boundary between component tests and Playwright/Cypress end-to-end runs. Use when writing or fixing tests for React components, hooks, or
name: react-testing description: React component testing with React Testing Library, Vitest/Jest, MSW for network mocking, accessibility assertions with axe, and the decision boundary between component tests and Playwright/Cypress end-to-end runs. Use when writing or fixing tests for React components, hooks, or pages. metadata: origin: ECC
Comprehensive React testing patterns for behavior-focused component tests, custom hook tests, accessibility assertions, and network-level mocking.
Test what the user sees and does, not implementation details.
A test should:
A test should NOT:
| Runner | When | Note | |---|---|---| | **Vitest** | Vite, Remix, modern setups | Faster, native ESM, Jest-compatible API | | **Jest** | Next.js, CRA, established repos | Default for many React projects | | **Playwright Component Testing** | Real browser engine needed | Use when JSDOM lacks the required feature | | **Cypress Component Testing** | Real browser, Cypress already in use | Alternative to Playwright CT |
Pick one. Do not run RTL + Vitest AND Playwright CT in the same repo unless you have a clear lane separation.
React Testing Library exposes queries in three tiers — use top-down:
1. **Accessible to everyone**: `getByRole`, `getByLabelText`, `getByPlaceholderText`, `getByText`, `getByDisplayValue` 2. **Semantic**: `getByAltText`, `getByTitle` 3. **Test IDs (escape hatch)**: `getByTestId`
// Best
screen.getByRole("button", { name: /save/i });
// OK for inputs
screen.getByLabelText("Email");
// Last resort
screen.getByTestId("save-btn");Variants:
import userEvent from "@testing-library/user-event";
test("submits the form", async () => {
const user = userEvent.setup();
const onSubmit = vi.fn();
render(<UserForm onSubmit={onSubmit} />);
await user.type(screen.getByLabelText("Email"), "user@example.com");
await user.click(screen.getByRole("button", { name: /save/i }));
expect(onSubmit).toHaveBeenCalledWith({ email: "user@example.com" });
});// Element that appears after async work
expect(await screen.findByText("Loaded")).toBeInTheDocument();
// Side effect assertion
await waitFor(() => expect(saveSpy).toHaveBeenCalled());
// Element that should disappear
await waitForElementToBeRemoved(() => screen.queryByText("Loading"));Never `setTimeout` + assertion — flaky. Use the matchers above.
Mock Service Worker mocks at the network layer. The component, hooks, and fetch library all behave exactly as in production.
// test/setup.ts
import { setupServer } from "msw/node";
import { http, HttpResponse } from "msw";
export const handlers = [
http.get("/api/users/:id", ({ params }) =>
HttpResponse.json({ id: params.id, name: "Alice" }),
),
http.post("/api/users", async ({ request }) => {
const body = await request.json();
return HttpResponse.json({ id: "new-id", ...body }, { status: 201 });
}),
];
export const server = setupServer(...handlers);
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());Configure `onUnhandledRequest: "error"` so any unmocked request fails the test loudly — silent passes are worse than red.
test("renders error on 500", async () => {
server.use(
http.get("/api/users/:id", () => new HttpResponse(null, { status: 500 })),
);
render(<UserPage id="1" />);
expect(await screen.findByText(/something went wrong/i)).toBeInTheDocument();
});Wrap providers once in a `test-utils.tsx`:
// test-utils.tsx
import { render, RenderOptions } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
export function renderWithProviders(
ui: React.ReactElement,
options?: RenderOptions,
) {
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
return render(
<QueryClientProvider client={queryClient}>
<ThemeProvider theme={lightTheme}>
<MemoryRouter>{ui}</MemoryRouter>
</ThemeProvider>
</QueryClientProvider>,
options,
);
}
export * from "@testing-library/react";Then `import { renderWithProviders, screen } from "test-utils"` in every test file.
import { renderHook, act } from "@testing-library/react";
test("useCounter increments and decrements", () => {
const { result } = renderHook(() => useCounter(0));
expect(result.current.count).toBe(0);
act(() => result.current.increment());
expect(rYour agent can write code, but ECC gives it a coordinated engineering system and toolbox: it plans before it builds, verifies changes with tests, reviews its own work from a fresh context, remembers what matters, and turns repeated wins into reusable skills
Repo: affaan-m/ECC
Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android…
Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for…
Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks…
Design and optimize AI agent action spaces, tool definitions, and observation formatting for…
Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained…
Use after completing any non-trivial task. The agent self-rates its output on 5 axes —…