skills/testing/SKILL.md
Testing strategies and patterns for TypeScript/React/Next.js. Use when: writing unit tests, integration tests, e2e tests, setting up Vitest/Jest/Playwright, testing React components, testing API routes, mocking dependencies, or establishing testing patterns.
npx skillsauth add Awais16/skills-vault testingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
pnpm add -D vitest @testing-library/react @testing-library/jest-dom @testing-library/user-event jsdom
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: ['./src/test/setup.ts'],
include: ['src/**/*.test.{ts,tsx}'],
coverage: {
provider: 'v8',
reporter: ['text', 'html'],
exclude: ['node_modules/', 'src/test/'],
},
},
resolve: {
alias: { '@': path.resolve(__dirname, './src') },
},
});
// src/test/setup.ts
import '@testing-library/jest-dom/vitest';
// utils/format-currency.ts
export function formatCurrency(amount: number, currency = 'USD'): string {
return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount);
}
// utils/format-currency.test.ts
import { describe, it, expect } from 'vitest';
import { formatCurrency } from './format-currency';
describe('formatCurrency', () => {
it('formats USD by default', () => {
expect(formatCurrency(1234.5)).toBe('$1,234.50');
});
it('handles zero', () => {
expect(formatCurrency(0)).toBe('$0.00');
});
it('handles negative amounts', () => {
expect(formatCurrency(-50)).toBe('-$50.00');
});
it('supports other currencies', () => {
expect(formatCurrency(1000, 'EUR')).toBe('€1,000.00');
});
});
import { describe, it, expect, vi } from 'vitest';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { SearchFilter } from './search-filter';
describe('SearchFilter', () => {
it('renders with initial query', () => {
render(<SearchFilter initialQuery="hello" onSearch={vi.fn()} />);
expect(screen.getByRole('textbox')).toHaveValue('hello');
});
it('calls onSearch when form is submitted', async () => {
const user = userEvent.setup();
const onSearch = vi.fn();
render(<SearchFilter initialQuery="" onSearch={onSearch} />);
await user.type(screen.getByRole('textbox'), 'test query');
await user.click(screen.getByRole('button', { name: /search/i }));
expect(onSearch).toHaveBeenCalledWith('test query');
});
it('shows error for empty search', async () => {
const user = userEvent.setup();
render(<SearchFilter initialQuery="" onSearch={vi.fn()} />);
await user.click(screen.getByRole('button', { name: /search/i }));
expect(screen.getByRole('alert')).toHaveTextContent('Please enter a search term');
});
});
import { describe, it, expect } from 'vitest';
import { renderHook, act } from '@testing-library/react';
import { useCounter } from './use-counter';
describe('useCounter', () => {
it('starts with initial value', () => {
const { result } = renderHook(() => useCounter(10));
expect(result.current.count).toBe(10);
});
it('increments', () => {
const { result } = renderHook(() => useCounter(0));
act(() => result.current.increment());
expect(result.current.count).toBe(1);
});
});
// Mock a module
vi.mock('@/lib/db', () => ({
db: {
user: {
findMany: vi.fn(),
create: vi.fn(),
},
},
}));
// Mock fetch
vi.stubGlobal('fetch', vi.fn());
// Mock implementation for a specific test
it('handles API error', async () => {
vi.mocked(fetch).mockResolvedValueOnce(
new Response(JSON.stringify({ error: 'Not found' }), { status: 404 }),
);
const result = await fetchUser('invalid-id');
expect(result.ok).toBe(false);
});
// src/test/handlers.ts
import { http, HttpResponse } from 'msw';
export const handlers = [
http.get('/api/users', () => {
return HttpResponse.json([
{ id: '1', name: 'Alice' },
{ id: '2', name: 'Bob' },
]);
}),
http.post('/api/users', async ({ request }) => {
const body = await request.json();
return HttpResponse.json({ id: '3', ...body }, { status: 201 });
}),
];
// src/test/server.ts
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);
// src/test/setup.ts
import { server } from './server';
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
pnpm add -D @playwright/test
pnpm exec playwright install
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
webServer: {
command: 'pnpm dev',
port: 3000,
reuseExistingServer: !process.env.CI,
},
});
// e2e/auth.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Authentication', () => {
test('user can log in', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('[email protected]');
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page).toHaveURL('/dashboard');
await expect(page.getByText('Welcome back')).toBeVisible();
});
test('shows error for invalid credentials', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('[email protected]');
await page.getByLabel('Password').fill('wrong');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page.getByRole('alert')).toContainText('Invalid credentials');
await expect(page).toHaveURL('/login');
});
});
<module>.test.ts or <component>.test.tsx (co-located)e2e/<feature>.spec.tsit('describes the expected behavior', () => {
// Arrange — set up preconditions
const input = { name: 'Alice', age: 30 };
// Act — perform the action
const result = formatUser(input);
// Assert — verify the outcome
expect(result).toBe('Alice (30)');
});
"shows error when email is invalid"describe blocks by function or feature{
"scripts": {
"test": "vitest",
"test:run": "vitest run",
"test:coverage": "vitest run --coverage",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui"
}
}
tools
TypeScript best practices, advanced type patterns, and strict typing. Use when: writing TypeScript code, creating type definitions, fixing type errors, designing type-safe APIs, using generics, creating utility types, or migrating from JavaScript to TypeScript.
development
Web application security best practices and OWASP patterns. Use when: implementing authentication, authorization, input validation, sanitization, CSRF/XSS prevention, securing API endpoints, managing secrets, handling file uploads, configuring CORS, or auditing code for security vulnerabilities.
development
React best practices, component patterns, hooks, and state management. Use when: building React components, managing state with Zustand or Context API, writing custom hooks, optimizing renders, handling forms, implementing accessibility, or structuring component architecture.
development
Next.js App Router best practices and patterns. Use when: building Next.js applications, creating pages/layouts/routes, implementing Server Components, setting up API routes, configuring middleware, handling SSR/SSG/ISR, managing metadata/SEO, or optimizing Next.js performance.