[TERMINAL · SKILLS]
> mounting /skills...
> indexing 295 manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Terminal.skills
Use Cases/Set Up a Comprehensive Test Suite for a React Application

Set Up a Comprehensive Test Suite for a React Application

Build a multi-layer test suite with Testing Library for components, fast-check for edge cases, Happy DOM for speed, and Vitest for orchestration.

Development#testing#react#components#accessibility#testing-library
Works with:claude-codeopenai-codexgemini-clicursor

Skills stack · 4 skills

Avg quality 93/100·All SAFE
>

testing-library

v1.0.0

Test UI components the way users interact with them using Testing Library — query by role, text, and label instead of implementation details. Use when someone asks to "test React components", "Testing Library", "user-centric testing", "test accessibility", "test without implementation details", or "render and query components in tests". Covers React Testing Library, queries, user events, async testing, and accessibility assertions.

100/100 quality
1.71× impact
SAFE
View skill
>

happy-dom

v1.0.0

Run DOM tests without a browser using Happy DOM — fast JavaScript DOM implementation. Use when someone asks to "test without a browser", "Happy DOM", "fast DOM environment for tests", "replace jsdom", "Vitest DOM environment", or "server-side DOM". Covers test environment setup, Vitest/Jest integration, SSR testing, and performance comparison with jsdom.

93/100 quality
4.13× impact
SAFE
View skill
>

fast-check

v1.0.0

Find edge-case bugs with property-based testing using fast-check — generate thousands of random inputs automatically. Use when someone asks to "find edge cases", "fast-check", "property-based testing", "fuzz testing in TypeScript", "generate random test data", "QuickCheck for JavaScript", or "test with random inputs". Covers property definitions, arbitraries, shrinking, model-based testing, and integration with Vitest/Jest.

100/100 quality
3.45× impact
SAFE
View skill
>

vitest

v1.0.0

You are an expert in Vitest, the Vite-native testing framework. You help developers write and run unit tests, integration tests, and component tests with native TypeScript support, Jest-compatible API, built-in mocking, code coverage, snapshot testing, and watch mode — leveraging Vite's transform pipeline for instant test execution without separate compilation.

80/100 quality
1.64× impact
SAFE
View skill
$

The Problem

Noa's team has a React application with 200+ components and growing, but only 30 hand-written tests — mostly snapshot tests that break on every CSS change and catch nothing. The team skips writing tests because the existing suite is slow (jsdom takes 40 seconds) and the tests don't catch real bugs. Last month, a pricing calculation bug shipped to production because nobody thought to test negative quantities. The quarter before that, a form refactor broke accessibility for screen reader users — no test caught it because they were all testing CSS classes.

The Solution

Build a three-layer testing strategy: Testing Library for user-centric component tests (catches accessibility issues by default), fast-check for property-based testing of business logic (finds edge cases like negative quantities automatically), and Happy DOM as the test environment (3-5x faster than jsdom). Vitest orchestrates everything with parallel execution and watch mode.

Step-by-Step Walkthrough

Step 1: Test Infrastructure

bash
npm install -D vitest happy-dom @testing-library/react @testing-library/jest-dom \
  @testing-library/user-event fast-check @vitejs/plugin-react msw
typescript
// vitest.config.ts — Test configuration optimized for speed
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [react()],
  test: {
    environment: "happy-dom",         // 3-5x faster than jsdom
    globals: true,                     // No need to import describe/it/expect
    setupFiles: ["./tests/setup.ts"],
    css: false,                        // Don't process CSS in tests
    coverage: {
      provider: "v8",
      include: ["src/**/*.{ts,tsx}"],
      exclude: ["src/**/*.test.*", "src/**/*.stories.*"],
      thresholds: { branches: 80, functions: 80, lines: 80 },
    },
  },
  resolve: {
    alias: { "@": "./src" },
  },
});
typescript
// tests/setup.ts — Global test setup
import "@testing-library/jest-dom/vitest";
import { cleanup } from "@testing-library/react";
import { afterEach } from "vitest";

// Auto-cleanup after each test
afterEach(() => cleanup());

Step 2: Component Tests with Testing Library

Test components the way users interact with them — by role, text, and label.

tsx
// src/components/PricingCalculator.test.tsx
/**
 * Tests the pricing calculator from the user's perspective.
 * Finds elements by accessible roles — if a screen reader can't find
 * the button, neither can this test.
 */
import { render, screen, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { PricingCalculator } from "./PricingCalculator";

describe("PricingCalculator", () => {
  it("calculates total for a standard order", async () => {
    const user = userEvent.setup();
    render(<PricingCalculator />);

    // Find by accessible label — tests accessibility for free
    await user.type(screen.getByLabelText("Quantity"), "5");
    await user.selectOptions(screen.getByLabelText("Plan"), "pro");

    // Verify the calculated total is displayed
    const summary = screen.getByRole("region", { name: "Order Summary" });
    expect(within(summary).getByText("$245.00")).toBeInTheDocument();
  });

  it("applies discount code and shows savings", async () => {
    const user = userEvent.setup();
    render(<PricingCalculator />);

    await user.type(screen.getByLabelText("Quantity"), "10");
    await user.type(screen.getByLabelText("Discount Code"), "SAVE20");
    await user.click(screen.getByRole("button", { name: "Apply" }));

    // Discount applied
    expect(screen.getByText(/20% off/i)).toBeInTheDocument();
    expect(screen.getByText("$392.00")).toBeInTheDocument();  // 490 * 0.8
    expect(screen.getByText("You save $98.00")).toBeInTheDocument();
  });

  it("shows validation error for zero quantity", async () => {
    const user = userEvent.setup();
    render(<PricingCalculator />);

    await user.clear(screen.getByLabelText("Quantity"));
    await user.type(screen.getByLabelText("Quantity"), "0");
    await user.click(screen.getByRole("button", { name: "Calculate" }));

    expect(screen.getByRole("alert")).toHaveTextContent("Quantity must be at least 1");
  });

  it("is keyboard navigable", async () => {
    const user = userEvent.setup();
    render(<PricingCalculator />);

    // Tab through the form
    await user.tab();
    expect(screen.getByLabelText("Quantity")).toHaveFocus();

    await user.tab();
    expect(screen.getByLabelText("Plan")).toHaveFocus();

    await user.tab();
    expect(screen.getByLabelText("Discount Code")).toHaveFocus();

    // Submit with Enter
    await user.tab();
    expect(screen.getByRole("button", { name: "Calculate" })).toHaveFocus();
    await user.keyboard("{Enter}");
  });
});

Step 3: Property-Based Testing for Business Logic

Hand-written tests only cover cases you think of. fast-check generates thousands of random inputs to find the ones you didn't.

typescript
// src/lib/pricing.test.ts — Property-based pricing logic tests
/**
 * These tests found 3 bugs in the first run:
 * 1. Negative quantity produced negative prices
 * 2. Discount > 100% produced negative totals
 * 3. Floating point rounding gave $49.999999 instead of $50.00
 */
import fc from "fast-check";
import { calculatePrice, applyDiscount, formatCurrency } from "./pricing";

describe("calculatePrice — property-based", () => {
  it("price is always non-negative", () => {
    fc.assert(
      fc.property(
        fc.integer({ min: -1000, max: 1000 }),  // Include negative to test validation
        fc.constantFrom("free", "starter", "pro", "enterprise"),
        (quantity, plan) => {
          const price = calculatePrice(quantity, plan);
          expect(price).toBeGreaterThanOrEqual(0);
        }
      )
    );
  });

  it("price increases with quantity (for positive quantities)", () => {
    fc.assert(
      fc.property(
        fc.integer({ min: 1, max: 10000 }),
        fc.integer({ min: 1, max: 10000 }),
        fc.constantFrom("starter", "pro", "enterprise"),
        (q1, q2, plan) => {
          if (q1 < q2) {
            expect(calculatePrice(q1, plan)).toBeLessThanOrEqual(calculatePrice(q2, plan));
          }
        }
      )
    );
  });

  it("discount never makes price higher", () => {
    fc.assert(
      fc.property(
        fc.integer({ min: 1, max: 1000 }),
        fc.constantFrom("starter", "pro", "enterprise"),
        fc.integer({ min: 0, max: 200 }),  // Include >100% to test validation
        (quantity, plan, discountPercent) => {
          const original = calculatePrice(quantity, plan);
          const discounted = applyDiscount(original, discountPercent);
          expect(discounted).toBeLessThanOrEqual(original);
          expect(discounted).toBeGreaterThanOrEqual(0);
        }
      )
    );
  });

  it("formatCurrency roundtrips correctly", () => {
    fc.assert(
      fc.property(
        fc.integer({ min: 0, max: 99999999 }),  // Cents
        (cents) => {
          const formatted = formatCurrency(cents);  // "$123.45"
          const parsed = parseCurrency(formatted);   // 12345
          expect(parsed).toBe(cents);
        }
      )
    );
  });
});

Step 4: API Mocking with MSW

typescript
// tests/mocks/handlers.ts — Mock API responses
import { http, HttpResponse } from "msw";

export const handlers = [
  http.get("/api/plans", () =>
    HttpResponse.json([
      { id: "free", name: "Free", price: 0, limit: 1000 },
      { id: "pro", name: "Pro", price: 4900, limit: 100000 },
    ])
  ),

  http.post("/api/checkout", async ({ request }) => {
    const body = await request.json();
    if (body.quantity <= 0) {
      return HttpResponse.json({ error: "Invalid quantity" }, { status: 400 });
    }
    return HttpResponse.json({ sessionId: "cs_test_123", url: "https://checkout..." });
  }),
];
typescript
// tests/mocks/server.ts
import { setupServer } from "msw/node";
import { handlers } from "./handlers";
export const server = setupServer(...handlers);
typescript
// tests/setup.ts — Add MSW to test setup
import { server } from "./mocks/server";

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

The Outcome

Noa's test suite goes from 30 brittle snapshot tests to 180 meaningful tests across three layers. Component tests with Testing Library catch accessibility regressions automatically — the keyboard navigation test would have caught last quarter's screen reader bug. Property-based tests with fast-check found 3 pricing bugs on the first run: negative quantity prices, discounts over 100%, and floating-point rounding errors. The full suite runs in 3.2 seconds on Happy DOM (down from 40 seconds on jsdom). Watch mode re-runs affected tests in 200ms. Coverage sits at 87%, with the property-based tests covering thousands of input combinations that hand-written tests never would.