Claude Skill

testing-react

Writes React/TypeScript tests using Vitest and React Testing Library. Use when "write react tests", "vitest", "component test", "hook test", "RTL", "testing library", "snapshot test", or testing React components, hooks, and utilities.

LLM Mart · 0 points · 10 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download iliaal-whetstone-distillery_generated-skills_testing-react-bccd699.zip · 4 KB
Part of iliaal/whetstone — 62 skills

Install

skills CLI npx skills add https://github.com/iliaal/whetstone/tree/master/distillery/generated-skills/testing-react
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install iliaal-whetstone@llmmart
Git git clone https://github.com/iliaal/whetstone.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole iliaal/whetstone collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Test Classification

Type Tool Target File pattern
Unit Vitest Pure functions, utilities, services Co-located *.test.ts
Component Vitest + RTL React components Co-located *.test.tsx
Hook Vitest + RTL Custom hooks Co-located *.test.ts
E2E Playwright User flows, critical paths Separate e2e/ directory

Default to component tests for React components. Unit tests for pure functions and service classes. See e2e-testing for Playwright patterns.

Setup

Vitest config: environment: 'jsdom', globals: true, setupFiles pointing to a file that imports @testing-library/jest-dom/vitest. Use @vitejs/plugin-react and mirror path aliases from tsconfig.json.

Critical Rules

  • Query priority: getByRole > getByLabelText > getByPlaceholderText > getByText > getByTestId. Use data-testid only when no accessible query works.
  • Mock boundaries: Mock API services, navigation, and external providers. Render child components and UI libraries real for integration confidence.
  • One behavior per test with AAA structure. Name tests should <behavior> when <condition>.
  • Async: Use findBy* for async elements, waitFor after state-triggering actions, vi.useFakeTimers() for debounce/timer logic.
  • User events: Prefer userEvent over fireEvent for realistic interactions.
  • Cleanup: vi.clearAllMocks() in beforeEach. Recreate test state per test instead of sharing mutable variables.
  • Incremental workflow: When testing a directory, process one file at a time (simplest first). Run and verify each before proceeding.
  • Tests expose bugs, not the reverse: If a test uncovers broken or buggy behavior, highlight the issue and propose a fix to the source code. Never adjust the test to match incorrect behavior.

Component Test

import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

vi.mock('@/api/client');

describe('UserForm', () => {
  beforeEach(() => { vi.clearAllMocks(); });

  it('should submit valid form data', async () => {
    const onSubmit = vi.fn();
    render(<UserForm onSubmit={onSubmit} />);

    await userEvent.type(screen.getByLabelText(/email/i), 'test@example.com');
    await userEvent.click(screen.getByRole('button', { name: /submit/i }));

    await waitFor(() => {
      expect(onSubmit).toHaveBeenCalledWith(
        expect.objectContaining({ email: 'test@example.com' }),
      );
    });
  });
});

Hook Test

import { renderHook, act } from '@testing-library/react';

it('should debounce value updates', () => {
  vi.useFakeTimers();
  const { result, rerender } = renderHook(
    ({ value }) => useDebounce(value, 300),
    { initialProps: { value: 'initial' } },
  );
  rerender({ value: 'updated' });
  expect(result.current).toBe('initial');
  act(() => { vi.advanceTimersByTime(300); });
  expect(result.current).toBe('updated');
  vi.useRealTimers();
});

Mocking Patterns

// Service mock — mock the module, not the transport layer
vi.mock('@/server-api/me/me.service', () => ({
  MeService: { retrieveMe: vi.fn() },
}));

// QueryClient wrapper for components using TanStack Query
const createWrapper = () => {
  const queryClient = new QueryClient({
    defaultOptions: { queries: { retry: false } },
  });
  return ({ children }: { children: React.ReactNode }) => (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  );
};
render(<Component />, { wrapper: createWrapper() });

Running Tests

npx vitest                         # Watch mode
npx vitest run                     # Single run (CI)
npx vitest run src/features/       # Test specific directory
npx vitest --coverage              # Coverage report
Files (whetstone)
  • references
    • e2e-testing.md 4.7 KB
      # E2E Testing with Playwright
      
      ## Directory Structure
      
      ```
      e2e/
      ├── playwright.config.ts
      ├── fixtures/
      │   ├── auth.fixture.ts
      │   └── test-data.fixture.ts
      ├── pages/
      │   ├── base.page.ts
      │   └── <page-name>.page.ts
      ├── tests/
      │   ├── auth/
      │   │   └── login.spec.ts
      │   └── smoke/
      │       └── critical-paths.spec.ts
      └── utils/
          └── api-helpers.ts
      ```
      
      Naming: tests `<feature>.spec.ts`, page objects `<page>.page.ts`, fixtures `<concern>.fixture.ts`.
      
      ## Configuration
      
      ```typescript
      import { defineConfig, devices } from '@playwright/test';
      
      export default defineConfig({
        testDir: './e2e/tests',
        fullyParallel: true,
        forbidOnly: !!process.env.CI,
        retries: process.env.CI ? 2 : 0,
        workers: process.env.CI ? 1 : undefined,
        reporter: 'html',
        use: {
          baseURL: 'http://localhost:5173',
          trace: 'on-first-retry',
          screenshot: 'only-on-failure',
        },
        projects: [
          { name: 'setup', testDir: './e2e/fixtures', testMatch: 'auth.fixture.ts' },
          {
            name: 'chromium',
            use: { ...devices['Desktop Chrome'], storageState: 'e2e/.auth/user.json' },
            dependencies: ['setup'],
          },
        ],
        webServer: {
          command: 'npm run dev',
          url: 'http://localhost:5173',
          reuseExistingServer: !process.env.CI,
        },
      });
      ```
      
      ## Page Object Model
      
      Tests never use selectors directly — page objects encapsulate all locators and actions.
      
      ```typescript
      // e2e/pages/base.page.ts
      import { type Page, type Locator } from '@playwright/test';
      
      export abstract class BasePage {
        constructor(protected readonly page: Page) {}
        abstract goto(): Promise<void>;
        async waitForLoad() { await this.page.waitForLoadState('networkidle'); }
        get toast(): Locator { return this.page.getByRole('alert'); }
      }
      
      // e2e/pages/users.page.ts
      export class UsersPage extends BasePage {
        readonly createButton: Locator;
        readonly searchInput: Locator;
      
        constructor(page: Page) {
          super(page);
          this.createButton = page.getByRole('button', { name: /create/i });
          this.searchInput = page.getByRole('searchbox', { name: /search/i });
        }
      
        async goto() {
          await this.page.goto('/users');
          await this.waitForLoad();
        }
      
        async searchFor(query: string) {
          await this.searchInput.fill(query);
          await this.page.waitForResponse('**/api/users?*');
        }
      }
      ```
      
      Rules: locators as public readonly properties, actions as async methods with internal waits, no assertions in page objects, one PO per page.
      
      ## Selector Priority
      
      | Priority | Method | Use when |
      |----------|--------|----------|
      | 1 | `getByRole` | Buttons, links, headings, inputs |
      | 2 | `getByLabel` | Form inputs with labels |
      | 3 | `getByPlaceholder` | Search inputs |
      | 4 | `getByText` | Static text content |
      | 5 | `getByTestId` | No accessible selector available |
      
      Never use CSS selectors, XPath, or DOM structure selectors. When adding `data-testid`, use `<action>-<entity>-<type>` pattern: `create-user-btn`.
      
      ## Wait Strategies
      
      Never use `waitForTimeout` or `setTimeout`. Use explicit conditions:
      
      ```typescript
      await page.getByRole('heading', { name: 'Dashboard' }).waitFor();
      await page.waitForURL('/dashboard');
      await page.waitForResponse(
        (r) => r.url().includes('/api/users') && r.status() === 200,
      );
      await page.getByTestId('spinner').waitFor({ state: 'hidden' });
      ```
      
      ## Auth State Reuse
      
      Save auth state once, reuse across all tests:
      
      ```typescript
      // e2e/fixtures/auth.fixture.ts
      import { test as setup } from '@playwright/test';
      
      setup('authenticate', async ({ page }) => {
        await page.goto('/login');
        await page.getByLabel('Email').fill('testuser@example.com');
        await page.getByLabel('Password').fill('TestPassword123!');
        await page.getByRole('button', { name: /sign in/i }).click();
        await page.waitForURL('/dashboard');
        await page.context().storageState({ path: 'e2e/.auth/user.json' });
      });
      ```
      
      Tests receive auth state via `storageState` in config projects.
      
      ## Test Data & Network
      
      - Tests create own data via API helpers (faster than UI), clean up in `finally` blocks
      - Mock responses with `page.route('**/api/path', route => route.fulfill({ ... }))`
      - Simulate errors with `route.abort('failed')`
      - Wait for responses: `const resp = page.waitForResponse('**/api/users'); await click; await resp;`
      
      ## Flaky Test Fixes
      
      | Cause | Fix |
      |-------|-----|
      | Hardcoded waits | Explicit wait conditions |
      | Shared test data | Each test creates its own |
      | Animations | `animations: 'disabled'` in config |
      | Race conditions | Wait for API responses before assertions |
      
      ```bash
      npx playwright test --headed --debug  # Debug mode
      npx playwright show-trace trace.zip   # Trace viewer
      npx playwright test --ui              # Interactive UI
      ```
      
  • manifest.json 1.4 KB
    {
      "query": "testing-react",
      "search_queries": [
        "react testing",
        "javascript testing",
        "frontend testing",
        "vitest",
        "playwright e2e testing"
      ],
      "generated": "2026-02-20",
      "token_count": 1132,
      "instructions": "E2E testing patterns go in references/e2e-testing.md, not in main body. Must not conflict with testing-laravel skill triggers.",
      "sources": [
        {
          "id": "antfu/skills/vitest",
          "installs": 5253,
          "sha1": "a205a2386c18d57dc658249b13600035e76ee6f6"
        },
        {
          "id": "wshobson/agents/javascript-testing-patterns",
          "installs": 2916,
          "sha1": "c7753a42ad58898b607bdf70d9c98c690e5cbe84"
        },
        {
          "id": "hieutrtr/ai1-skills/e2e-testing",
          "installs": 1388,
          "sha1": "ed2d000328b7038689b2aeb3660ae653a96740d9"
        },
        {
          "id": "sergiodxa/agent-skills/frontend-testing-best-practices",
          "installs": 1236,
          "sha1": "121c019dcf7fa7bc1cc3fd3475e744c9d9e55d5a"
        },
        {
          "id": "langgenius/dify/frontend-testing",
          "installs": 1152,
          "sha1": "87a164e4ef5947b86f7907b7c8008d391eb32844"
        },
        {
          "id": "jeffallan/claude-skills/playwright-expert",
          "installs": 764,
          "sha1": "f53e2a86f8cbcb4dd87b293cdffc4c9e426e865e"
        },
        {
          "id": "bobmatnyc/claude-mpm-skills/playwright-e2e-testing",
          "installs": 489,
          "sha1": "55631c9c746457a2e0b583675345f22af3a2d06e"
        }
      ]
    }
    
  • SKILL.md 4.1 KB
    ---
    name: testing-react
    description: Writes React/TypeScript tests using Vitest and React Testing Library. Use when "write react tests", "vitest", "component test", "hook test", "RTL", "testing library", "snapshot test", or testing React components, hooks, and utilities.
    ---
    
    ## Test Classification
    
    | Type | Tool | Target | File pattern |
    |------|------|--------|-------------|
    | Unit | Vitest | Pure functions, utilities, services | Co-located `*.test.ts` |
    | Component | Vitest + RTL | React components | Co-located `*.test.tsx` |
    | Hook | Vitest + RTL | Custom hooks | Co-located `*.test.ts` |
    | E2E | Playwright | User flows, critical paths | Separate `e2e/` directory |
    
    Default to component tests for React components. Unit tests for pure functions and service classes. See [e2e-testing](references/e2e-testing.md) for Playwright patterns.
    
    ## Setup
    
    Vitest config: `environment: 'jsdom'`, `globals: true`, `setupFiles` pointing to a file that imports `@testing-library/jest-dom/vitest`. Use `@vitejs/plugin-react` and mirror path aliases from `tsconfig.json`.
    
    ## Critical Rules
    
    - **Query priority**: `getByRole` > `getByLabelText` > `getByPlaceholderText` > `getByText` > `getByTestId`. Use `data-testid` only when no accessible query works.
    - **Mock boundaries**: Mock API services, navigation, and external providers. Render child components and UI libraries real for integration confidence.
    - **One behavior per test** with AAA structure. Name tests `should <behavior> when <condition>`.
    - **Async**: Use `findBy*` for async elements, `waitFor` after state-triggering actions, `vi.useFakeTimers()` for debounce/timer logic.
    - **User events**: Prefer `userEvent` over `fireEvent` for realistic interactions.
    - **Cleanup**: `vi.clearAllMocks()` in `beforeEach`. Recreate test state per test instead of sharing mutable variables.
    - **Incremental workflow**: When testing a directory, process one file at a time (simplest first). Run and verify each before proceeding.
    - **Tests expose bugs, not the reverse**: If a test uncovers broken or buggy behavior, highlight the issue and propose a fix to the source code. Never adjust the test to match incorrect behavior.
    
    ## Component Test
    
    ```tsx
    import { render, screen, waitFor } from '@testing-library/react';
    import userEvent from '@testing-library/user-event';
    
    vi.mock('@/api/client');
    
    describe('UserForm', () => {
      beforeEach(() => { vi.clearAllMocks(); });
    
      it('should submit valid form data', async () => {
        const onSubmit = vi.fn();
        render(<UserForm onSubmit={onSubmit} />);
    
        await userEvent.type(screen.getByLabelText(/email/i), 'test@example.com');
        await userEvent.click(screen.getByRole('button', { name: /submit/i }));
    
        await waitFor(() => {
          expect(onSubmit).toHaveBeenCalledWith(
            expect.objectContaining({ email: 'test@example.com' }),
          );
        });
      });
    });
    ```
    
    ## Hook Test
    
    ```typescript
    import { renderHook, act } from '@testing-library/react';
    
    it('should debounce value updates', () => {
      vi.useFakeTimers();
      const { result, rerender } = renderHook(
        ({ value }) => useDebounce(value, 300),
        { initialProps: { value: 'initial' } },
      );
      rerender({ value: 'updated' });
      expect(result.current).toBe('initial');
      act(() => { vi.advanceTimersByTime(300); });
      expect(result.current).toBe('updated');
      vi.useRealTimers();
    });
    ```
    
    ## Mocking Patterns
    
    ```typescript
    // Service mock — mock the module, not the transport layer
    vi.mock('@/server-api/me/me.service', () => ({
      MeService: { retrieveMe: vi.fn() },
    }));
    
    // QueryClient wrapper for components using TanStack Query
    const createWrapper = () => {
      const queryClient = new QueryClient({
        defaultOptions: { queries: { retry: false } },
      });
      return ({ children }: { children: React.ReactNode }) => (
        <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
      );
    };
    render(<Component />, { wrapper: createWrapper() });
    ```
    
    ## Running Tests
    
    ```bash
    npx vitest                         # Watch mode
    npx vitest run                     # Single run (CI)
    npx vitest run src/features/       # Test specific directory
    npx vitest --coverage              # Coverage report
    ```
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related