Skip to main content

Overview

folksbase uses two testing layers:
  • Vitest for unit tests — fast, focused tests for business logic
  • Playwright for E2E tests — browser-based tests against the full running app

Unit Tests (Vitest)

Where Tests Live

Tests go in __tests__/ directories next to the file being tested:
src/
├── services/
│   ├── contacts.service.ts
│   └── __tests__/
│       └── contacts.service.test.ts
├── repositories/
│   ├── contacts.repository.ts
│   └── __tests__/
│       └── contacts.repository.test.ts

Running Tests

# Run all unit tests
pnpm test

# Run tests for a specific package
pnpm --filter @folksbase/api test

# Run a single test file
pnpm --filter @folksbase/api test src/services/__tests__/csv-ai.service.test.ts

Writing Tests

Mock all external dependencies. Tests should be fast and isolated — no real database, Redis, or API calls.
import { describe, it, expect, vi, beforeEach } from "vitest";

// Mock external dependencies
vi.mock("@folksbase/db");
vi.mock("@/lib/redis.js");

describe("contacts.service", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("creates a contact with normalized email", async () => {
    // Arrange
    const mockCreate = vi.fn().mockResolvedValue({ id: "abc", email: "test@example.com" });
    // ... setup mocks

    // Act
    const result = await createContact({ email: "TEST@Example.COM" });

    // Assert
    expect(result.email).toBe("test@example.com");
    expect(mockCreate).toHaveBeenCalledOnce();
  });
});

What to Test

  • Business logic in services (happy path + error cases)
  • Repository query behavior (mocked DB responses)
  • Edge cases: empty inputs, missing data, concurrent operations
  • Error handling: verify errors propagate correctly

What NOT to Test

  • Drizzle query builder syntax — trust the ORM
  • Zod schema validation — trust Zod
  • Framework internals — trust Next.js and Hono

Test Configuration

Vitest config lives in each app’s vitest.config.ts. The API config sets up path aliases so imports like @/lib/logger.js and @folksbase/db resolve correctly in tests:
resolve: {
  alias: {
    "@": resolve(__dirname, "src"),
    "@folksbase/db": resolve(__dirname, "../../packages/db/src/index.ts"),
    "@folksbase/types": resolve(__dirname, "../../packages/types/src/index.ts"),
  },
},

Important: Use Valid UUIDs in Tests

Route param validators enforce UUID format. Always use valid UUIDs in test fixtures, not arbitrary strings:
// ❌ Will fail validation
const contactId = "c-1";

// ✅ Use a real UUID
const contactId = "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11";

E2E Tests (Playwright)

Where Tests Live

E2E tests live in apps/web/e2e/ with config in apps/web/playwright.config.ts.

Test Projects

Playwright is configured with three projects:
ProjectPurpose
setupAuthenticates once, stores session in e2e/.auth/user.json
no-authRuns auth.spec.ts with empty storage (tests login flow)
chromiumRuns all other specs with stored auth (depends on setup)

Running E2E Tests

# Run all E2E tests
pnpm --filter @folksbase/web e2e

# Run with Playwright UI (interactive mode)
pnpm --filter @folksbase/web e2e:ui

# Run a single spec
pnpm --filter @folksbase/web exec playwright test e2e/auth.spec.ts --project=no-auth

Environment Variables

E2E tests require real Supabase credentials:
VariablePurpose
E2E_USER_EMAILTest user email for authentication
E2E_USER_PASSWORDTest user password
PLAYWRIGHT_BASE_URLApp URL (defaults to http://localhost:3000)
These are defined in apps/web/.env.example and read via process.env in the Playwright config.

Test Files

The E2E suite covers: auth, dashboard, navigation, contacts, imports, exports, tags, settings, and accessibility.

CI Integration

In CI, E2E tests run against the Vercel preview URL generated for each PR. The PLAYWRIGHT_BASE_URL is overridden to point to the preview deployment.

Storybook

UI components are documented with Storybook 8. Stories live alongside their components as *.stories.tsx files.
pnpm --filter @folksbase/web storybook        # Run locally on port 6006
pnpm --filter @folksbase/web build-storybook  # Build static output
When adding a new UI component, include a story file covering its variants and states. Storybook auto-deploys to Netlify on pushes to main when component or config files change.