mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-06-16 05:01:32 +08:00
96819fa081
Three new slash commands invoking the React agents. - /react-review: invokes react-reviewer. Documents the routing rule with typescript-reviewer — both should run together on TSX/JSX PRs. Lists CRITICAL/HIGH/MEDIUM rule categories and the automated checks (eslint with react-hooks + jsx-a11y, tsc --noEmit, npm audit). - /react-build: invokes react-build-resolver. Documents bundler detection, common failure patterns, fix strategy, and stop conditions. - /react-test: enforces TDD with React Testing Library + Vitest or Jest, behavior-focused queries, userEvent + MSW patterns, axe accessibility assertions, coverage targets. Each command file has the required description: frontmatter and follows the per-language command convention (cpp-test, go-test, kotlin-test, etc.).
266 lines
7.2 KiB
Markdown
266 lines
7.2 KiB
Markdown
---
|
||
description: Enforce TDD workflow for React. Write React Testing Library tests first (behavior-focused, accessibility-first), then implement components. Detects Vitest or Jest and verifies coverage targets.
|
||
---
|
||
|
||
# React TDD Command
|
||
|
||
This command enforces test-driven development for React using React Testing Library plus Vitest or Jest, detected at runtime.
|
||
|
||
## What This Command Does
|
||
|
||
1. **Define Component Signature**: Scaffold the component, prop type, and exports
|
||
2. **Write Behavior Tests First**: RTL queries (role-first), `userEvent`, MSW for network — RED
|
||
3. **Run Tests**: Verify they fail for the right reason
|
||
4. **Implement Minimal Code**: Just enough to pass — GREEN
|
||
5. **Refactor**: Improve while keeping tests green
|
||
6. **Check Coverage**: Hit the targets in [rules/react/testing.md](../rules/react/testing.md)
|
||
|
||
## When to Use
|
||
|
||
Use `/react-test` when:
|
||
|
||
- Implementing a new React component or custom hook
|
||
- Adding test coverage to an untested component
|
||
- Fixing a bug (write failing test first that reproduces it)
|
||
- Building forms, state machines, or accessibility-critical UI
|
||
- Onboarding to RTL + Vitest/Jest workflow
|
||
|
||
## TDD Cycle
|
||
|
||
```
|
||
RED -> Write failing test for the next behavior
|
||
GREEN -> Implement minimal component code to pass
|
||
REFACTOR -> Improve component, tests stay green
|
||
REPEAT -> Next behavior
|
||
```
|
||
|
||
## Runner Detection
|
||
|
||
```bash
|
||
test -f vitest.config.ts -o -f vitest.config.js -o -f vite.config.ts # Vitest
|
||
grep -l '"jest"' package.json # Jest
|
||
```
|
||
|
||
Prefer Vitest for new Vite-based projects; respect Jest for existing setups.
|
||
|
||
## Example Session
|
||
|
||
```text
|
||
User: /react-test I need a SearchInput component with debounced search
|
||
|
||
Agent:
|
||
# TDD Session: SearchInput
|
||
|
||
## Step 1: Define Signature
|
||
|
||
```tsx
|
||
// src/components/SearchInput.tsx
|
||
type Props = {
|
||
onSearch: (query: string) => void;
|
||
placeholder?: string;
|
||
debounceMs?: number;
|
||
};
|
||
|
||
export function SearchInput({ onSearch, placeholder, debounceMs }: Props) {
|
||
throw new Error("not implemented");
|
||
}
|
||
```
|
||
|
||
## Step 2: Write Behavior Tests (RED)
|
||
|
||
```tsx
|
||
// src/components/SearchInput.test.tsx
|
||
import { describe, expect, test, vi } from "vitest";
|
||
import { render, screen } from "@testing-library/react";
|
||
import userEvent from "@testing-library/user-event";
|
||
import { SearchInput } from "./SearchInput";
|
||
|
||
describe("SearchInput", () => {
|
||
test("renders with placeholder", () => {
|
||
render(<SearchInput onSearch={() => {}} placeholder="Search users" />);
|
||
expect(screen.getByPlaceholderText("Search users")).toBeInTheDocument();
|
||
});
|
||
|
||
test("calls onSearch after typing", async () => {
|
||
vi.useFakeTimers();
|
||
const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime });
|
||
const onSearch = vi.fn();
|
||
render(<SearchInput onSearch={onSearch} debounceMs={300} />);
|
||
|
||
await user.type(screen.getByRole("textbox"), "alice");
|
||
|
||
expect(onSearch).not.toHaveBeenCalled(); // before debounce
|
||
vi.advanceTimersByTime(300);
|
||
expect(onSearch).toHaveBeenCalledWith("alice"); // after debounce
|
||
|
||
vi.useRealTimers();
|
||
});
|
||
|
||
test("does not call onSearch when typing pauses then continues", async () => {
|
||
vi.useFakeTimers();
|
||
const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime });
|
||
const onSearch = vi.fn();
|
||
render(<SearchInput onSearch={onSearch} debounceMs={300} />);
|
||
|
||
await user.type(screen.getByRole("textbox"), "ali");
|
||
vi.advanceTimersByTime(200); // mid-debounce
|
||
await user.type(screen.getByRole("textbox"), "ce");
|
||
vi.advanceTimersByTime(300);
|
||
|
||
expect(onSearch).toHaveBeenCalledTimes(1);
|
||
expect(onSearch).toHaveBeenCalledWith("alice");
|
||
|
||
vi.useRealTimers();
|
||
});
|
||
|
||
test("is keyboard reachable and accessible", () => {
|
||
render(<SearchInput onSearch={() => {}} />);
|
||
const input = screen.getByRole("textbox");
|
||
input.focus();
|
||
expect(input).toHaveFocus();
|
||
});
|
||
});
|
||
```
|
||
|
||
## Step 3: Run Tests — Verify FAIL
|
||
|
||
```bash
|
||
$ vitest run src/components/SearchInput.test.tsx
|
||
|
||
× src/components/SearchInput.test.tsx (4 tests) ✘ Error: not implemented
|
||
```
|
||
|
||
✓ Tests fail as expected.
|
||
|
||
## Step 4: Implement Minimal Code (GREEN)
|
||
|
||
```tsx
|
||
import { useEffect, useState } from "react";
|
||
|
||
export function SearchInput({ onSearch, placeholder, debounceMs = 300 }: Props) {
|
||
const [query, setQuery] = useState("");
|
||
|
||
useEffect(() => {
|
||
const id = setTimeout(() => onSearch(query), debounceMs);
|
||
return () => clearTimeout(id);
|
||
}, [query, onSearch, debounceMs]);
|
||
|
||
return (
|
||
<input
|
||
type="text"
|
||
value={query}
|
||
placeholder={placeholder}
|
||
onChange={(e) => setQuery(e.target.value)}
|
||
/>
|
||
);
|
||
}
|
||
```
|
||
|
||
## Step 5: Run Tests — Verify PASS
|
||
|
||
```bash
|
||
$ vitest run src/components/SearchInput.test.tsx
|
||
|
||
✓ src/components/SearchInput.test.tsx (4 tests) 47ms
|
||
```
|
||
|
||
## Step 6: Coverage
|
||
|
||
```bash
|
||
$ vitest run --coverage src/components/SearchInput.test.tsx
|
||
|
||
% Stmts: 100 % Branch: 100 % Funcs: 100 % Lines: 100
|
||
```
|
||
|
||
## TDD Complete!
|
||
```
|
||
|
||
## Test Patterns
|
||
|
||
### Behavior, not implementation
|
||
|
||
Use `getByRole`, `getByLabelText`, `getByText`. Avoid `container.querySelector` and asserting on component state.
|
||
|
||
### `userEvent.setup()` per test
|
||
|
||
```tsx
|
||
const user = userEvent.setup();
|
||
await user.click(screen.getByRole("button", { name: /save/i }));
|
||
```
|
||
|
||
### MSW for network
|
||
|
||
```tsx
|
||
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
|
||
afterEach(() => server.resetHandlers());
|
||
afterAll(() => server.close());
|
||
|
||
server.use(http.post("/api/users", () => HttpResponse.json({ id: "1" }, { status: 201 })));
|
||
```
|
||
|
||
### Custom hooks
|
||
|
||
```tsx
|
||
const { result } = renderHook(() => useCounter(0));
|
||
act(() => result.current.increment());
|
||
expect(result.current.count).toBe(1);
|
||
```
|
||
|
||
### Accessibility
|
||
|
||
```tsx
|
||
import { axe } from "vitest-axe";
|
||
expect(await axe(container)).toHaveNoViolations();
|
||
```
|
||
|
||
## Coverage Targets
|
||
|
||
| Layer | Target |
|
||
|---|---|
|
||
| Pure utilities | >=90% |
|
||
| Custom hooks | >=85% |
|
||
| Presentational components | >=80% |
|
||
| Container components | >=70% |
|
||
| Pages | E2E covered separately |
|
||
|
||
Configure in `vitest.config.ts` / `jest.config.js` to enforce thresholds in CI.
|
||
|
||
## Anti-Patterns to Avoid
|
||
|
||
- `container.querySelector(...)` — bypasses accessibility queries
|
||
- Asserting on render count
|
||
- Mocking `react` itself (`jest.mock("react", ...)`)
|
||
- Mocking child components by default (mock only when child has heavy side effects)
|
||
- Ignoring `act()` warnings — they signal real bugs
|
||
- Snapshot tests of rendered components (brittle, rubber-stamped) — use Playwright/Cypress visual diff instead
|
||
|
||
## Test Commands
|
||
|
||
```bash
|
||
# Vitest
|
||
vitest # watch
|
||
vitest run # one-shot
|
||
vitest run --coverage # with coverage
|
||
vitest run path/to/file.test.tsx # single file
|
||
|
||
# Jest
|
||
jest --watch
|
||
jest --coverage
|
||
jest path/to/file.test.tsx
|
||
|
||
# CI mode
|
||
CI=true vitest run --coverage
|
||
```
|
||
|
||
## Related Commands
|
||
|
||
- `/react-build` — fix build errors before running tests
|
||
- `/react-review` — review after implementation
|
||
- `verification-loop` skill — full verification loop
|
||
|
||
## Related
|
||
|
||
- Skills: `skills/react-testing/`, `skills/tdd-workflow/`, `skills/accessibility/`, `skills/e2e-testing/`
|
||
- Rules: `rules/react/testing.md`
|
||
- Agents: `react-reviewer` (reviews test quality), `tdd-guide` (enforces TDD process)
|