How SpaceZero sets up Vitest for unit tests, where to add tests, and how to layer E2E coverage with Playwright when you need it.
SpaceZero ships Vitest (jsdom) for unit and component tests. There is no Playwright setup and no test:e2e script — E2E is opt-in.
vitest.config.ts # jsdom, globals, @ alias, coverage
src/test/setup.ts # jest-dom matchers, cleanup, matchMedia + serviceWorker mocks
src/components/__tests__/
└── button.test.tsx # the shipped example test
Scripts:
npm run test # vitest run (one-shot)
npm run test:watch # watch mode
npm run test:coverage # run with coverage
Colocate new tests near the code they cover:
src/
├── components/
│ ├── __tests__/
│ │ └── button.test.tsx
│ └── ui/
│ └── button.tsx
├── hooks/
│ └── useAuth.test.ts # add your own
└── lib/
└── utils.test.ts # add your own
// src/components/__tests__/button.test.tsx
import { describe, it, expect, vi } from 'vitest';
import { render, screen } from '@testing-library/react';
import { Button } from '@/components/ui/button';
describe('Button', () => {
it('renders with children text', () => {
render(<Button>Click me</Button>);
expect(screen.getByRole('button')).toHaveTextContent('Click me');
});
it('renders as anchor when href is provided', () => {
render(<Button href="/test">Link button</Button>);
const link = screen.getByRole('link');
expect(link).toHaveAttribute('href', '/test');
expect(link).toHaveTextContent('Link button');
});
it('calls onClick when clicked', () => {
const handleClick = vi.fn();
render(<Button onClick={handleClick}>Click</Button>);
screen.getByRole('button').click();
expect(handleClick).toHaveBeenCalledTimes(1);
});
});
// src/hooks/useAuth.test.ts (example)
import { describe, it, expect } from 'vitest';
import { renderHook } from '@testing-library/react';
import { useAuth } from './useAuth';
describe('useAuth', () => {
it('exposes the auth API', () => {
const { result } = renderHook(() => useAuth());
expect(result.current.signInWithEmail).toBeDefined();
expect(result.current.signOut).toBeDefined();
});
});
Edge functions are plain request/response handlers — call them directly with a mocked Supabase client:
// functions/api/auth/check-user.test.ts (example)
import { describe, it, expect, vi } from 'vitest';
vi.mock('@/lib/supabase', () => ({
getAdminSupabaseClient: () => ({
auth: { admin: { listUsers: vi.fn().mockResolvedValue({ data: { users: [{ email: '[email protected]' }] } }) } },
}),
}));
describe('check-user', () => {
it('returns exists=true for a known email', async () => {
const request = new Request('http://test.com', {
method: 'POST',
body: JSON.stringify({ email: '[email protected]' }),
});
const { onRequestPost } = await import('./check-user');
const response = await onRequestPost({ request, env: {} });
expect((await response.json()).exists).toBe(true);
});
});
Playwright is not installed. To add it:
npm init playwright@latest
# Select: TypeScript, tests folder, GitHub Actions
# then add to package.json:
# "test:e2e": "playwright test"
// test/e2e/auth.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Authentication', () => {
test('sign up with email', async ({ page }) => {
await page.goto('/');
await page.click('button:has-text("Login")');
await page.fill('input[type="email"]', '[email protected]');
await page.fill('input[type="password"]', 'password123');
await page.click('button:has-text("Create Account")');
await expect(page).toHaveURL('/profile');
});
});
# .github/workflows/test.yml
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npm run test
# add once Playwright is set up:
# - run: npm run test:e2e
getByRole, getByLabelText before data-testid