From tour-dev-toolkit
This skill should be used when the user asks to "scaffold a test", "create a test file", "generate tests for", "add tests to", or needs a vitest test file following project conventions.
npx claudepluginhub boburshoh122000/claude-plugins-bobur --plugin tour-dev-toolkitThis skill uses the workspace's default tool permissions.
Generate a Vitest test file for an existing source file following project conventions.
Verifies tests pass on completed feature branch, presents options to merge locally, create GitHub PR, keep as-is or discard; executes choice and cleans up worktree.
Guides root cause investigation for bugs, test failures, unexpected behavior, performance issues, and build failures before proposing fixes.
Writes implementation plans from specs for multi-step tasks, mapping files and breaking into TDD bite-sized steps before coding.
Generate a Vitest test file for an existing source file following project conventions.
src/app/lib/foo-actions.ts -> src/app/lib/foo-actions.test.tssrc/lib/bar.ts -> src/lib/bar.test.tssrc/app/api/baz/route.ts -> src/app/api/baz/route.test.ts (same directory)// @vitest-environment node -- the jsdom environment has a broken tldts dependency. Every test file in this project requires this pragma.@/lib/db if the source imports prisma -- use vi.mock('@/lib/db', () => ({ prisma: { ... } })) with vi.hoisted() mock functions for each Prisma model method used.__tests__ directory.vi.hoisted() for all mock function declarations to ensure they are hoisted above vi.mock() calls.next/cache if the source imports revalidatePath or revalidateTag.beforeEach with vi.spyOn(console, 'error').mockImplementation(() => {}).// @vitest-environment node
import { describe, it, expect, vi, beforeEach } from 'vitest';
// ── Mocks ────────────────────────────────────────────────────────────────────
const mocks = vi.hoisted(() => ({
// Auth mocks (include if source imports auth-guard or @/auth)
requireUser: vi.fn(),
// auth: vi.fn(),
// Prisma mocks (include if source imports @/lib/db)
// modelNameFindFirst: vi.fn(),
// modelNameCreate: vi.fn(),
// modelNameUpdate: vi.fn(),
// modelNameUpdateMany: vi.fn(),
// Activity logger (include if source imports @/lib/activity-logger)
// logActivity: vi.fn(),
// Cache (include if source imports next/cache)
// revalidatePath: vi.fn(),
// revalidateTag: vi.fn(),
// Cron guard (include if source imports @/lib/cron-guard)
// verifyCronRequest: vi.fn(),
// acquireCronLock: vi.fn(),
// releaseCronLock: vi.fn(),
}));
// Include each vi.mock() only for modules the source file actually imports:
// vi.mock('@/lib/db', () => ({
// prisma: {
// modelName: {
// findFirst: mocks.modelNameFindFirst,
// create: mocks.modelNameCreate,
// update: mocks.modelNameUpdate,
// updateMany: mocks.modelNameUpdateMany,
// },
// },
// }));
// vi.mock('@/lib/auth-guard', () => ({ requireUser: mocks.requireUser }));
// vi.mock('@/auth', () => ({ auth: mocks.auth }));
// vi.mock('@/lib/activity-logger', () => ({ logActivity: mocks.logActivity }));
// vi.mock('next/cache', () => ({
// revalidatePath: mocks.revalidatePath,
// revalidateTag: mocks.revalidateTag,
// }));
// vi.mock('@/lib/cron-guard', () => ({
// verifyCronRequest: mocks.verifyCronRequest,
// acquireCronLock: mocks.acquireCronLock,
// releaseCronLock: mocks.releaseCronLock,
// }));
// ── Imports ──────────────────────────────────────────────────────────────────
import {
// TODO: Import all exported functions from the source file
} from './SOURCE_FILE';
// ── Helpers ──────────────────────────────────────────────────────────────────
// function authedUser(orgId = 'org-1') {
// return { id: 'user-1', organization_id: orgId };
// }
// ── Setup ────────────────────────────────────────────────────────────────────
beforeEach(() => {
vi.clearAllMocks();
vi.spyOn(console, 'error').mockImplementation(() => {});
// TODO: Set default mock return values
// mocks.requireUser.mockResolvedValue(authedUser());
});
// ── Tests ────────────────────────────────────────────────────────────────────
// One describe() block per exported function:
// describe('functionName', () => {
// it('blocks unauthenticated users', async () => {
// // TODO: Test auth guard
// });
//
// it('returns error when no organization', async () => {
// // TODO: Test org check
// });
//
// it('performs the expected operation', async () => {
// // TODO: Test happy path
// });
//
// it('handles errors gracefully', async () => {
// // TODO: Test error handling
// });
// });
For route.ts files, use NextRequest to construct test requests:
import { NextRequest, NextResponse } from 'next/server';
import { GET, POST } from './route';
function makeReq(method = 'GET', body?: object) {
const url = 'http://localhost/api/path';
if (body) {
return new NextRequest(url, {
method,
body: JSON.stringify(body),
headers: { 'content-type': 'application/json' },
});
}
return new NextRequest(url, { method });
}
// For cron routes:
function makeCronReq() {
return new NextRequest('http://localhost/api/cron/job-name', {
headers: { authorization: 'Bearer test-secret' },
});
}
src/lib/financial-export.ts uses document.createElement -- not unit-testable in node environment. Skip or mark as integration-only.src/lib/google-places.ts imports @/lib/db at the top level -- mock @/lib/db even when testing pure functions from this module.src/lib/super-admin-guard.ts reads process.env at module load time -- vi.resetModules() fails due to the next-auth import chain. Set env vars before the module loads..gt(0) not .positive() in test data schemas. The .positive() method is deprecated.@/lib/numeric-schemas: If source imports monetaryAmount, mock it: vi.mock('@/lib/numeric-schemas', () => { const { z } = require('zod'); return { monetaryAmount: z.number().gt(0) }; }).