marathon-ralph/skills/setup-vitest/SKILL.md
Configure Vitest for unit and integration testing. Use when setting up a test framework, when no test runner is detected, or when the user asks to configure testing.
npx skillsauth add gruckion/chief-wiggum-anthropic-agent-harness setup-vitestInstall 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.
Configure Vitest as the unit and integration test framework with Testing Library integration.
Use ni to auto-detect the package manager:
# Core Vitest packages
ni -D vitest @vitest/ui @vitest/coverage-v8
# For React projects
ni -D @testing-library/react @testing-library/dom @testing-library/user-event @testing-library/jest-dom
# For Vue projects
ni -D @testing-library/vue @testing-library/dom @testing-library/user-event @testing-library/jest-dom
# For Svelte projects
ni -D @testing-library/svelte @testing-library/dom @testing-library/user-event @testing-library/jest-dom
Create or update vitest.config.ts at the project root:
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react' // For React projects
export default defineConfig({
plugins: [react()], // Add framework plugin as needed
test: {
// Test file patterns
include: ['**/*.{test,spec}.{js,ts,jsx,tsx}'],
exclude: ['**/node_modules/**', '**/dist/**', '**/e2e/**'],
// Environment - use 'jsdom' or 'happy-dom' for DOM testing
environment: 'jsdom',
// Enable global test APIs (describe, it, expect)
globals: true,
// Setup files run before each test file
setupFiles: ['./tests/setup.ts'],
// Mock behavior
clearMocks: true,
restoreMocks: true,
// Coverage configuration
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html', 'lcov'],
reportsDirectory: './coverage',
include: ['src/**/*.{ts,tsx}'],
exclude: [
'**/*.test.{ts,tsx}',
'**/*.spec.{ts,tsx}',
'**/*.d.ts',
'**/types/**',
],
thresholds: {
lines: 80,
functions: 80,
branches: 80,
statements: 80,
},
},
// Timeouts
testTimeout: 5000,
hookTimeout: 10000,
},
})
Add Vitest types to tsconfig.json:
{
"compilerOptions": {
"types": ["vitest/globals"]
}
}
Create tests/setup.ts for global test configuration:
import '@testing-library/jest-dom/vitest'
import { cleanup } from '@testing-library/react'
import { afterEach, vi } from 'vitest'
// Cleanup after each test
afterEach(() => {
cleanup()
})
// Mock window.matchMedia (common requirement)
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation((query: string) => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
})),
})
Add test scripts to the workspace package.json (where the code lives):
{
"scripts": {
"test": "vitest",
"test:run": "vitest run",
"test:ui": "vitest --ui",
"test:coverage": "vitest run --coverage"
}
}
For monorepo projects (Turborepo, Nx, Lerna, etc.), additional setup is required.
Read .claude/marathon-ralph.json to get the project configuration:
project.monorepo.type - The monorepo type (turbo, nx, lerna, etc.)project.packageManager - The package manager (bun, pnpm, yarn, npm)If using Turborepo (turbo.json exists), add the test task to the pipeline:
turbo.json:
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"test": {
"dependsOn": ["^build"],
"outputs": [],
"cache": false
},
"test:run": {
"dependsOn": ["^build"],
"outputs": [],
"cache": false
}
}
}
Root package.json - add script to run tests across all workspaces:
{
"scripts": {
"test": "turbo run test",
"test:run": "turbo run test:run"
}
}
For pnpm workspaces without Turborepo:
Root package.json:
{
"scripts": {
"test": "pnpm -r test",
"test:run": "pnpm -r test:run"
}
}
For npm or yarn workspaces:
Root package.json:
{
"scripts": {
"test": "npm run test --workspaces",
"test:run": "npm run test:run --workspaces"
}
}
To run tests for a specific workspace, use the package manager's filter:
# Turborepo + bun
bun run --filter=web test
# pnpm
pnpm --filter web test
# npm workspaces
npm run test --workspace=web
Follow Testing Library's query priority:
getByRole - Best choice, tests accessibilitygetByLabelText - For form fieldsgetByPlaceholderText - If no label availablegetByText - For non-interactive elementsgetByDisplayValue - For filled form valuesgetByAltText - For imagesgetByTitle - Rarely neededgetByTestId - Last resort onlyimport { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, it, expect, vi } from 'vitest'
import { LoginForm } from './LoginForm'
describe('LoginForm', () => {
it('submits with valid credentials', async () => {
const user = userEvent.setup()
const onSubmit = vi.fn()
render(<LoginForm onSubmit={onSubmit} />)
// Use accessible queries
await user.type(screen.getByLabelText(/email/i), '[email protected]')
await user.type(screen.getByLabelText(/password/i), 'password123')
await user.click(screen.getByRole('button', { name: /sign in/i }))
expect(onSubmit).toHaveBeenCalledWith({
email: '[email protected]',
password: 'password123',
})
})
it('shows error for invalid email', async () => {
const user = userEvent.setup()
render(<LoginForm onSubmit={vi.fn()} />)
await user.type(screen.getByLabelText(/email/i), 'invalid')
await user.click(screen.getByRole('button', { name: /sign in/i }))
expect(screen.getByRole('alert')).toHaveTextContent(/valid email/i)
})
})
Follow Kent C. Dodds' testing principles:
screen for all queriesgetByRole with accessible namesuserEvent over fireEventfindBy* for async elementsqueryBy* ONLY for asserting non-existencecontainer.querySelectorimport { vi } from 'vitest'
const mockFn = vi.fn()
mockFn.mockReturnValue('value')
mockFn.mockResolvedValue('async value')
// Automatic mock
vi.mock('./api')
// Manual mock with factory
vi.mock('./api', () => ({
fetchUser: vi.fn(() => ({ id: 1, name: 'Test' })),
}))
// Partial mock
vi.mock('./utils', async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
specificFunction: vi.fn(),
}
})
After setup, verify with:
# Run tests
nr test
# Run with coverage
nr test:coverage
# Open UI mode
nr test:ui
project/
├── src/
│ ├── components/
│ │ ├── Button.tsx
│ │ └── Button.test.tsx # Colocated tests
│ └── utils/
│ ├── helpers.ts
│ └── helpers.test.ts
├── tests/
│ └── setup.ts # Global setup
├── vitest.config.ts
└── package.json
tools
# SQLite Skill for better-t-stack ## Overview SQLite database implementation using LibSQL client and Drizzle ORM. This skill covers local development, Turso cloud, and Cloudflare D1 deployments. --- ## CRITICAL WARNING **NEVER use `bun:sqlite` with Next.js applications.** Next.js runs on Node.js, not Bun runtime. Using `bun:sqlite` causes: ``` Cannot find module 'bun:sqlite' ``` **Always use `@libsql/client`** - it works in both Node.js and Bun environments. --- ## Library Stack | Pack
tools
Write Playwright E2E tests using fixtures and best practices. Use when creating E2E tests, writing browser automation tests, or testing user flows.
development
Visually verify implemented features work correctly before marking complete. Use when testing UI changes, verifying web features, or checking user flows work in the browser.
tools
Programmatically update marathon-ralph state file using deterministic jq commands. Use this instead of manually editing the JSON file.