.cursor/skills/test-react/SKILL.md
Writes unit tests for React components using Vitest and React Testing Library. Use when writing or reviewing tests for React components, hooks, forms, or pages in apps/client. Covers setup, mocking strategy, form validation testing, async interactions, and testing library best practices.
npx skillsauth add guimap01/notes_app test-reactInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Stack: Vitest + @testing-library/react + @testing-library/user-event + @testing-library/jest-dom.
Vitest is configured in vite.config.ts (environment: "jsdom", globals: true). All tests run through Vite's pipeline — no transform config needed.
| What | Where |
|------|-------|
| Page / component tests | src/<domain>/__tests__/<name>.test.tsx |
| Shared render helpers | src/test/utils.tsx |
| Global setup | src/test/setup.ts |
Run with pnpm test (watch) or pnpm test:run (CI).
Always wrap components that use <Link> or any router primitive in MemoryRouter:
import { renderWithRouter } from "@/test/utils";
renderWithRouter(<LoginPage />);
Mock at the module level with vi.mock, then set the return value per test with vi.mocked:
vi.mock("@/context/auth-context");
const mockLogin = vi.fn();
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(useAuth).mockReturnValue({
user: null, isLoading: false,
login: mockLogin, register: vi.fn(), logout: vi.fn(),
});
});
Always use userEvent.setup() — not fireEvent — for realistic browser simulation:
const user = userEvent.setup();
await user.type(screen.getByLabelText(/email/i), "[email protected]");
await user.click(screen.getByRole("button", { name: /sign in/i }));
Add noValidate to every <form> that uses a JS validation library. Without it, jsdom's native browser validation blocks the submit event before react-hook-form runs, causing validation tests to silently fail:
// In the component — required for tests to work
<form noValidate onSubmit={...}>
Then in the test:
// Submit → wait for the zod error to appear
await user.click(screen.getByRole("button", { name: /submit/i }));
expect(await screen.findByText("Invalid email address")).toBeInTheDocument();
Use findBy* (returns a promise) for elements that appear after async work. Use waitFor to assert something eventually becomes true:
// Element appears after async: findBy
expect(await screen.findByText(/invalid email/i)).toBeInTheDocument();
// Mock was called after async: waitFor
await waitFor(() => expect(mockLogin).toHaveBeenCalledWith({ ... }));
Use a never-resolving promise to hold the component in the pending state:
let resolve!: () => void;
mockLogin.mockReturnValue(new Promise<void>((r) => { resolve = r; }));
await user.click(submitButton);
expect(await screen.findByRole("button", { name: /signing in/i })).toBeDisabled();
resolve(); // clean up
mockLogin.mockRejectedValue(new Error("Unauthorized"));
// ... fill and submit ...
expect(await screen.findByText(/invalid email or password/i)).toBeInTheDocument();
key in dynamic listsfireEvent for form submission — use userEvent.setup() and user.clickvi.mock(...) for all hooks/contexts used by the componentvi.clearAllMocks() in beforeEachrenderWithRouter if the component contains any <Link>noValidate on <form> in the component under testfindBy* for async elements, getBy* for synchronousdevelopment
Reviews React and TypeScript code for correctness, security, hook usage, and multi-tenancy safety. Use when reviewing generated React components, hooks, or TypeScript files, or when asked to do a code review on frontend code.
development
Reviews NestJS TypeScript code for correctness, security, multi-tenancy safety, and architectural best practices. Use when reviewing generated NestJS controllers, services, guards, interceptors, pipes, or modules, or when asked to do a code review on backend API code.
tools
Use when work should span one or more detached tasks but still behave like one job with a single owner context. TaskFlow is the durable flow substrate under authoring layers like Lobster, ACPX, plugins, or plain code. Keep conditional logic in the caller; use TaskFlow for flow identity, child-task linkage, waiting state, revision-checked mutations, and user-facing emergence.
tools
# Lobster Lobster executes multi-step workflows with approval checkpoints. Use it when: - User wants a repeatable automation (triage, monitor, sync) - Actions need human approval before executing (send, post, delete) - Multiple tool calls should run as one deterministic operation ## When to use Lobster | User intent | Use Lobster? | | ------------------------------------------------------ | --------------------------