skills/vitest/SKILL.md
Vitest unit testing patterns with React Testing Library. Trigger: When writing unit tests for React components, hooks, or utilities.
npx skillsauth add johnnystefan/test-saas-business 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.
For E2E tests: Use
playwrightskill. This skill covers unit/integration tests with Vitest + React Testing Library.
Use Given/When/Then (AAA) pattern with comments:
it("should update user name when form is submitted", async () => {
// Given - Arrange
const user = userEvent.setup();
const onSubmit = vi.fn();
render(<UserForm onSubmit={onSubmit} />);
// When - Act
await user.type(screen.getByLabelText(/name/i), "John");
await user.click(screen.getByRole("button", { name: /submit/i }));
// Then - Assert
expect(onSubmit).toHaveBeenCalledWith({ name: "John" });
});
describe('ComponentName', () => {
describe('when [condition]', () => {
it('should [expected behavior]', () => {});
});
});
Group by behavior, NOT by method.
| Priority | Query | Use Case |
| -------- | ---------------------- | ------------------------- |
| 1 | getByRole | Buttons, inputs, headings |
| 2 | getByLabelText | Form fields |
| 3 | getByPlaceholderText | Inputs without label |
| 4 | getByText | Static text |
| 5 | getByTestId | Last resort only |
// ✅ GOOD
screen.getByRole('button', { name: /submit/i });
screen.getByLabelText(/email/i);
// ❌ BAD
container.querySelector('.btn-primary');
// ✅ ALWAYS use userEvent
const user = userEvent.setup();
await user.click(button);
await user.type(input, 'hello');
// ❌ NEVER use fireEvent for interactions
fireEvent.click(button);
// ✅ findBy for elements that appear async
const element = await screen.findByText(/loaded/i);
// ✅ waitFor for assertions
await waitFor(() => {
expect(screen.getByText(/success/i)).toBeInTheDocument();
});
// ✅ ONE assertion per waitFor
await waitFor(() => expect(mockFn).toHaveBeenCalled());
await waitFor(() => expect(screen.getByText(/done/i)).toBeVisible());
// ❌ NEVER multiple assertions in waitFor
await waitFor(() => {
expect(mockFn).toHaveBeenCalled();
expect(screen.getByText(/done/i)).toBeVisible(); // Slower failures
});
// Basic mock
const handleClick = vi.fn();
// Mock with return value
const fetchUser = vi.fn().mockResolvedValue({ name: 'John' });
// Always clean up
afterEach(() => {
vi.restoreAllMocks();
});
| Method | When to Use |
| ---------- | ------------------------------------- |
| vi.spyOn | Observe without replacing (PREFERRED) |
| vi.mock | Replace entire module (use sparingly) |
// Presence
expect(element).toBeInTheDocument();
expect(element).toBeVisible();
// State
expect(button).toBeDisabled();
expect(input).toHaveValue('text');
expect(checkbox).toBeChecked();
// Content
expect(element).toHaveTextContent(/hello/i);
expect(element).toHaveAttribute('href', '/home');
// Functions
expect(fn).toHaveBeenCalledWith(arg1, arg2);
expect(fn).toHaveBeenCalledTimes(2);
// ❌ Internal state
expect(component.state.isLoading).toBe(true);
// ❌ Third-party libraries
expect(axios.get).toHaveBeenCalled();
// ❌ Static content (unless conditional)
expect(screen.getByText('Welcome')).toBeInTheDocument();
// ✅ User-visible behavior
expect(screen.getByRole('button')).toBeDisabled();
components/
├── Button/
│ ├── Button.tsx
│ ├── Button.test.tsx # Co-located
│ └── index.ts
pnpm test # Watch mode
pnpm test:run # Single run
pnpm test:coverage # With coverage
pnpm test Button # Filter by name
tools
Zustand 5 state management patterns. Trigger: When implementing client-side state with Zustand (stores, selectors, persist middleware, slices).
databases
Zod 4 schema validation patterns. Trigger: When creating or updating Zod v4 schemas for validation/parsing (forms, request payloads, adapters), including v3 -> v4 migration patterns.
tools
Vite 8 (Rolldown-powered) build tool configuration, plugin API, SSR, and migration guide. Trigger: When working with vite.config.ts, Vite plugins, building libraries, or SSR apps with Vite.
development
TypeScript 6 strict patterns and best practices for this NX monorepo. Trigger: When implementing or refactoring TypeScript in .ts/.tsx (types, interfaces, generics, const maps, type guards, removing any, tightening unknown, declarative naming, RO-RO, guard clauses).