ワンクリックで
testing-patterns
Guide for writing tests. Vitest for unit tests, Playwright for E2E. Includes SEO and sitemap testing patterns.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Guide for writing tests. Vitest for unit tests, Playwright for E2E. Includes SEO and sitemap testing patterns.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Guide for adding environment variables. Use when adding new env vars to ensure all 5 locations are updated (code, env files, Coolify, workflow, GitHub secrets).
Enforce the internal API proxy layer pattern. Use when adding API endpoints, fetching data from backend, implementing HMAC signing, or working with lib/api/* or app/api/*.
Best practices for React 19 and Next.js 16 App Router. Use when creating components, hooks, or pages. Enforces server-first rendering, proper client boundaries, and modern React patterns.
MANDATORY checklist before writing ANY new code. Use this skill FIRST before implementing any feature, component, utility, type, or hook. Enforces DRY principle and existing pattern reuse.
Evaluate external PR suggestions (Gemini, CodeRabbit) against project conventions. Use when asked to review or agree with external code review comments.
Guide for CSP, security headers, and external scripts. Use fetchWithHmac for APIs, safeFetch for external services.
| name | testing-patterns |
| description | Guide for writing tests. Vitest for unit tests, Playwright for E2E. Includes SEO and sitemap testing patterns. |
Guide for writing unit tests (Vitest) and E2E tests (Playwright) following project conventions.
| Tool | Purpose | Config |
|---|---|---|
| Vitest | Unit/integration tests | vitest.config.ts |
| React Testing Library | Component tests | Via Vitest |
| Playwright | E2E tests | playwright.config.ts |
| jsdom | DOM environment | Vitest config |
yarn test # Run all unit tests
yarn test:watch # Watch mode
yarn test:coverage # With coverage report
yarn test:e2e # Playwright E2E tests
yarn test:e2e:ui # Playwright with UI
test/**/*.{test,spec}.{ts,tsx}<feature>.test.ts or <component>.spec.tsxtest/
├── filter-system.test.ts
├── url-parsing.test.ts
├── mocks/
│ ├── next-intl.ts
│ └── next-intl-server.ts
└── setup.ts
e2e/**/*.spec.ts<feature>.flow.spec.ts or <feature>.spec.tsimport { describe, it, expect, vi } from "vitest";
describe("featureName", () => {
describe("functionName", () => {
it("should do something specific", () => {
// Arrange
const input = "test";
// Act
const result = functionName(input);
// Assert
expect(result).toBe("expected");
});
});
});
import { describe, it, expect } from "vitest";
import { buildCanonicalUrlDynamic } from "@utils/url-filters";
describe("buildCanonicalUrlDynamic", () => {
it("should omit tots for date and category", () => {
expect(buildCanonicalUrlDynamic("barcelona", "tots", "tots")).toBe(
"/barcelona"
);
});
it("should include date when not tots", () => {
expect(buildCanonicalUrlDynamic("barcelona", "avui", "tots")).toBe(
"/barcelona/avui"
);
});
});
import { vi, describe, it, expect, beforeEach } from "vitest";
// Mock a module
vi.mock("@utils/api-helpers", () => ({
fetchWithHmac: vi.fn(),
}));
import { fetchWithHmac } from "@utils/api-helpers";
describe("myFunction", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("should call fetchWithHmac", async () => {
vi.mocked(fetchWithHmac).mockResolvedValue({ data: [] });
await myFunction();
expect(fetchWithHmac).toHaveBeenCalledWith(
expect.stringContaining("/api/events")
);
});
});
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { MyComponent } from "@components/ui/MyComponent";
describe("MyComponent", () => {
it("should render title", () => {
render(<MyComponent title="Hello" />);
expect(screen.getByText("Hello")).toBeInTheDocument();
});
it("should handle click", async () => {
const onClick = vi.fn();
render(<MyComponent onClick={onClick} />);
await userEvent.click(screen.getByRole("button"));
expect(onClick).toHaveBeenCalled();
});
});
The project has pre-built mocks for next-intl:
// test/mocks/next-intl.ts - For client hooks
// test/mocks/next-intl-server.ts - For server functions
// vitest.config.ts aliases these automatically
// Just import normally in tests:
import { useTranslations } from "next-intl";
import { test, expect } from "@playwright/test";
test.describe("Feature Name", () => {
test("should do something", async ({ page }) => {
await page.goto("/barcelona");
await expect(
page.getByRole("heading", { name: "Barcelona" })
).toBeVisible();
});
});
// Wait for navigation
await page.waitForURL(/\/barcelona\/avui/);
// Click and wait
await page.getByRole("link", { name: "Events" }).click();
await page.waitForLoadState("networkidle");
// Fill form
await page.getByLabel("Search").fill("concert");
await page.getByRole("button", { name: "Search" }).click();
// Assert element count
const cards = page.locator('[data-testid="event-card"]');
await expect(cards).toHaveCount(10);
// Check URL params
expect(page.url()).toContain("search=concert");
test.describe("Mobile", () => {
test.use({ viewport: { width: 375, height: 667 } });
test("should show mobile menu", async ({ page }) => {
await page.goto("/");
await expect(page.getByRole("button", { name: "Menu" })).toBeVisible();
});
});
For testing against deployed environments:
PLAYWRIGHT_TEST_BASE_URL=https://staging.example.com yarn test:e2e
Uses playwright.remote.config.ts configuration.
import { generateMetadata } from "@app/[place]/page";
describe("Page Metadata", () => {
it("should generate correct metadata for place page", async () => {
const metadata = await generateMetadata({
params: Promise.resolve({ place: "barcelona" }),
});
expect(metadata.title).toContain("Barcelona");
expect(metadata.description).toBeDefined();
expect(metadata.openGraph?.url).toContain("/barcelona");
});
it("should include canonical URL", async () => {
const metadata = await generateMetadata({
params: Promise.resolve({ place: "barcelona" }),
});
expect(metadata.alternates?.canonical).toBe(
"https://www.esdeveniments.cat/barcelona"
);
});
});
test("should have correct SEO meta tags", async ({ page }) => {
await page.goto("/barcelona");
// Canonical URL
const canonical = page.locator('link[rel="canonical"]');
await expect(canonical).toHaveAttribute(
"href",
"https://www.esdeveniments.cat/barcelona"
);
// Open Graph
const ogTitle = page.locator('meta[property="og:title"]');
await expect(ogTitle).toHaveAttribute("content", /Barcelona/);
const ogUrl = page.locator('meta[property="og:url"]');
await expect(ogUrl).toHaveAttribute("content", /\/barcelona/);
});
test("should have valid JSON-LD structured data", async ({ page }) => {
await page.goto("/barcelona");
// Get JSON-LD script content
const jsonLd = await page.evaluate(() => {
const script = document.querySelector('script[type="application/ld+json"]');
return script ? JSON.parse(script.textContent || "{}") : null;
});
expect(jsonLd).toBeDefined();
expect(jsonLd["@type"]).toBe("ItemList");
expect(jsonLd.itemListElement).toBeInstanceOf(Array);
});
test("should have correct hreflang tags", async ({ page }) => {
await page.goto("/barcelona");
// Check all locale variants
const hreflangCa = page.locator('link[hreflang="ca"]');
const hreflangEs = page.locator('link[hreflang="es"]');
const hreflangEn = page.locator('link[hreflang="en"]');
await expect(hreflangCa).toHaveAttribute("href", /\/barcelona/);
await expect(hreflangEs).toHaveAttribute("href", /\/es\/barcelona/);
await expect(hreflangEn).toHaveAttribute("href", /\/en\/barcelona/);
});
test.describe("Sitemaps", () => {
test("should return valid sitemap index", async ({ request }) => {
const response = await request.get("/sitemap.xml");
expect(response.status()).toBe(200);
expect(response.headers()["content-type"]).toContain("xml");
const body = await response.text();
expect(body).toContain("<sitemapindex");
expect(body).toContain("<sitemap>");
});
test("should include event URLs in server sitemap", async ({ request }) => {
const response = await request.get("/server-sitemap.xml");
expect(response.status()).toBe(200);
const body = await response.text();
expect(body).toContain("<urlset");
expect(body).toContain("<loc>");
});
test("should have correct cache headers", async ({ request }) => {
const response = await request.get("/server-sitemap.xml");
const cacheControl = response.headers()["cache-control"];
expect(cacheControl).toContain("s-maxage");
});
});
test("should have correct robots.txt", async ({ request }) => {
const response = await request.get("/robots.txt");
expect(response.status()).toBe(200);
const body = await response.text();
expect(body).toContain("Allow: /");
expect(body).toContain("Disallow: /_next/");
expect(body).toContain("Disallow: /api/");
expect(body).toContain("Sitemap:");
});
test("should return valid RSS feed", async ({ request }) => {
const response = await request.get("/rss.xml");
expect(response.status()).toBe(200);
expect(response.headers()["content-type"]).toContain("xml");
const body = await response.text();
expect(body).toContain("<rss");
expect(body).toContain("<channel>");
expect(body).toContain("<item>");
});
The test/setup.ts file:
HMAC_SECRET for HMAC utilitiesyarn test:coverage to checktest/ directory?e2e/ directory?yarn test before committing?