本文へ移動
cccskills
無料GitHub で公開

frontend-testing

Vitest + React Testing Library patterns for the Next.js 15 frontend. Co-located component tests, mocking lib/api.ts, testing Server Components, CartProvider harness. Use when writing or reviewing *.test.ts(x) files in frontend/.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md6.8 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Frontend Testing Skill

Version: 1.0 | Working Directory: frontend/

Write Vitest + React Testing Library tests for $ARGUMENTS following the conventions in this repo.

Stack

  • Vitest — runner and assertion library (vi.mock, expect, vi.fn)
  • @testing-library/react — render + DOM queries
  • @testing-library/jest-dom — matchers (toBeInTheDocument, toHaveTextContent, …)
  • @testing-library/user-event — user interactions
  • jsdom — DOM environment

No Jest. No Enzyme. No snapshot tests unless explicitly requested.

Constitution (Non-Negotiable Rules)

#RuleWhy
1Co-locate tests next to the file under test: Foo.tsx → Foo.test.tsx in the same folderKeeps tests discoverable; matches include: ['**/*.test.{ts,tsx}'] in vitest config
2Mock @/lib/api with vi.mock, not raw fetch (unless testing lib/api.ts itself)Tests the component, not the transport
3Server Components: call them as async functions (await Page({ params: Promise.resolve({slug}) })) then render(jsx)Next 15 + React 19 Server Components aren't rendered by next/navigation stubs
4Use userEvent over fireEvent for anything a user would do (click, type, select)userEvent fires realistic event sequences
5Query by role / label / text, not by test IDs unless there's no accessible alternativeCatches a11y regressions for free
6Cart tests use renderWithCart from @/test/utilsSingle source of truth for CartProvider setup
7Don't test: middleware.ts (edge runtime), Tailwind class presence (brittle), third-party libsLow value, high maintenance
8Strict TypeScript in tests — no any, no @ts-ignore without a comment explaining whySame bar as production code

Quick Start

Pure component (Client)

// frontend/components/AddToCartButton.test.tsx
import { describe, it, expect, vi } from "vitest";
import { screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { renderWithCart } from "@/test/utils";
import { AddToCartButton } from "./AddToCartButton";

const product = {
  id: "p1", slug: "shirt", name: "Tee", price_cents: 1000,
  currency: "USD", image_url: null, stock: 3, is_active: true,
};

describe("AddToCartButton", () => {
  it("adds the product to the cart on click", async () => {
    const user = userEvent.setup();
    renderWithCart(<AddToCartButton product={product} />);
    await user.click(screen.getByRole("button", { name: /add to cart/i }));
    // assert via a cart-reading sibling or by observing the re-render; don't peek into localStorage
  });

  it("is disabled when out of stock", () => {
    renderWithCart(<AddToCartButton product={{ ...product, stock: 0 }} />);
    expect(screen.getByRole("button", { name: /sold out/i })).toBeDisabled();
  });
});

Server Component page

// frontend/app/(store)/products/[slug]/page.test.tsx
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";

vi.mock("@/lib/api", () => ({
  getProduct: vi.fn(async (slug: string) => ({
    id: "p1", slug, name: "Tee", price_cents: 1000,
    currency: "USD", image_url: null, stock: 3, is_active: true,
  })),
}));

const { default: ProductPage } = await import("./page");

describe("ProductPage", () => {
  it("renders the product name", async () => {
    const jsx = await ProductPage({ params: Promise.resolve({ slug: "shirt" }) });
    render(jsx);
    expect(screen.getByRole("heading", { name: /tee/i })).toBeInTheDocument();
  });
});

lib/api.ts helper test (mock fetch here — this is the boundary)

// frontend/lib/api.test.ts
import { describe, it, expect, vi, beforeEach } from "vitest";
import { listProducts, ApiError } from "./api";

beforeEach(() => { vi.restoreAllMocks(); });

describe("listProducts", () => {
  it("returns items on 200", async () => {
    vi.stubGlobal("fetch", vi.fn(async () =>
      new Response(JSON.stringify({ items: [], total: 0 }), { status: 200 })
    ));
    const res = await listProducts();
    expect(res.items).toEqual([]);
  });

  it("throws ApiError on non-2xx", async () => {
    vi.stubGlobal("fetch", vi.fn(async () =>
      new Response(JSON.stringify({ detail: "not_found" }), { status: 404 })
    ));
    await expect(listProducts()).rejects.toBeInstanceOf(ApiError);
  });
});

File Organization

What to testWhere the test file goes
components/Foo.tsxcomponents/Foo.test.tsx (same folder)
app/(store)/products/[slug]/page.tsxapp/(store)/products/[slug]/page.test.tsx
lib/api.tslib/api.test.ts
Shared utilitieslib/<name>.test.ts

What To Cover

Read docs/features/<slug>/plan.md first. Derive tests from the plan's user flows + error mapping:

  • Happy path — one test per documented flow
  • Every error branch in plan.md — map ApiError.detail strings to rendered messages
  • Empty / loading / out-of-stock states — components must render without crashing
  • Gated UI — admin components should handle a missing session gracefully (render nothing or a login prompt)

What NOT To Cover

  • frontend/middleware.ts — runs in the edge runtime; integration-test via real auth flow
  • Tailwind class names — querying .className is brittle
  • CartProvider internals — treat as a black box via useCart()
  • Third-party lib internals (@supabase/ssr, next/image)

Common Patterns

Mocking the Supabase browser client

vi.mock("@/lib/supabase/client", () => ({
  createSupabaseBrowserClient: () => ({
    auth: {
      getSession: vi.fn(async () => ({ data: { session: { access_token: "t" } } })),
      signInWithPassword: vi.fn(async () => ({ data: {}, error: null })),
    },
  }),
}));

Asserting router.refresh() was called

const refresh = vi.fn();
vi.mock("next/navigation", () => ({ useRouter: () => ({ refresh, push: vi.fn() }) }));
// ... trigger the action
expect(refresh).toHaveBeenCalled();

Resetting module-level mocks between tests

import { beforeEach } from "vitest";
beforeEach(() => { vi.clearAllMocks(); });

Running

cd frontend
npm test                  # one-shot (CI mode)
npm run test:watch        # watch mode during dev
npm run test:coverage     # with coverage report

Exemplar Files

  • frontend/test/setup.ts — global test setup (jest-dom + env stubs)
  • frontend/test/utils.tsx — renderWithCart helper
  • frontend/vitest.config.ts — config with jsdom + @ alias

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Supabase Postgres + Storage + Auth operations from FastAPI — service-role client, RLS-aware queries, additive SQL migrations under backend/db/migrations, the create_order RPC, and the product-images bucket. Use when adding tables/columns, writing queries, calling RPCs, or working with Storage.

日本語の概要は準備中です。原文の説明を表示しています。

shanraisshan/learning-x-claude22026年5月7日 更新

Build FastAPI endpoints in the layered router → service → Supabase pattern, with Pydantic v2 schemas, JWT/admin gating, and consistent HTTPException-based domain errors. Use when adding/modifying any endpoint under backend/.

日本語の概要は準備中です。原文の説明を表示しています。

shanraisshan/learning-x-claude22026年5月7日 更新

pytest + httpx + TestClient patterns for the FastAPI backend. Dependency overrides for auth, FakeSupabase mocking, HTTPException assertions. Use when writing or reviewing backend/tests/test_*.py.

日本語の概要は準備中です。原文の説明を表示しています。

shanraisshan/learning-x-claude22026年5月7日 更新

Drive Chrome via the claude-in-chrome MCP to exercise plan.md::Verify steps for a shipped feature. Loads deferred MCP tool schemas, asserts console/network cleanliness, records a GIF for the PR. Used by the browser-verifier agent.

日本語の概要は準備中です。原文の説明を表示しています。

shanraisshan/learning-x-claude22026年5月7日 更新

Pre-PR code review checklist for this repo. Plan alignment, security gates, layering, dead code, API-surface completeness. Used by the code-reviewer agent.

日本語の概要は準備中です。原文の説明を表示しています。

shanraisshan/learning-x-claude22026年5月7日 更新

Integrate with the FastAPI backend through the typed wrapper in frontend/lib/api.ts, attach Supabase JWTs for admin calls, and handle errors via ApiError. Use when adding/modifying API calls or wiring auth.

日本語の概要は準備中です。原文の説明を表示しています。

shanraisshan/learning-x-claude22026年5月7日 更新

shanraisshan のスキルをすべて見る

このスキルの問題を報告する