xtablo-source/.planning/codebase/TESTING.md
2026-05-14 16:01:31 +02:00

8.3 KiB

Testing

Last updated: 2026-05-14 Scope: Turborepo monorepo at /Users/arthur.belleville/Documents/perso/projects/xtablo-source

Frameworks

  • Vitest is the only JS/TS test runner across every app and package (no Jest)
  • React Testing Library (@testing-library/react) for component tests; @testing-library/jest-dom matchers loaded via setup files
  • happy-dom / jsdom for DOM emulation in frontend tests (apps/main/vite.config.ts configures environment: "jsdom")
  • Vitest with mocked Supabase for apps/api — node environment, real Hono router exercised, Supabase client stubbed (apps/api/src/__tests__/setup.ts + globalSetup.ts)
  • pgTAP SQL tests under supabase/tests/database/*.test.sql for schema/RLS/trigger correctness (run via the Supabase CLI, separate from Vitest)

Test Commands

From the repo root (all dispatched by Turborepo via turbo.json):

pnpm test                  # Run every package's `test` task
pnpm test:watch            # Watch mode across the workspace
pnpm test:api              # Run only @xtablo/api (turbo --filter)
cd apps/main && pnpm test  # Run a single package directly
cd apps/api && pnpm test:watch

Per-package script conventions (see e.g. apps/api/package.json):

  • "test": "NODE_ENV=test vitest run"
  • "test:watch": "NODE_ENV=test vitest"
  • Frontend apps similarly use vitest run / vitest (no NODE_ENV=test prefix required)

Test File Location

Tests are co-located with the source they exercise:

  • React components: Foo.tsx + Foo.test.tsx in the same folder
    • e.g. apps/main/src/components/CustomModal.test.tsx, apps/main/src/components/NavigationBar.test.tsx
  • Hooks / contexts: useFoo.ts + useFoo.test.ts(x)
    • e.g. apps/main/src/contexts/UpgradeBlockContext.test.tsx
  • API: tests under apps/api/src/__tests__/<area>/<area>.test.ts (grouped by router/concern), plus co-located helper tests like apps/api/src/helpers/orgIcons.test.ts
  • Expo app sometimes uses __tests__/ folders: xtablo-expo/components/__tests__/BillingPaywall.test.tsx

Vitest Configs

Two distinct flavors live in the repo:

  1. Frontend (Vite + Vitest) — config embedded inside vite.config.ts. Example, apps/main/vite.config.ts:

    test: {
      globals: true,
      environment: "jsdom",
      setupFiles: "./src/setupTests.ts",
    }
    

    The Cloudflare plugin is skipped when process.env.VITEST === "true". VITE_SUPABASE_URL / VITE_SUPABASE_ANON_KEY are stubbed via define when running under Vitest.

    Other apps with the same pattern:

    • apps/admin/vite.config.ts + apps/admin/src/setupTests.ts
    • apps/external/vite.config.ts + apps/external/src/setupTests.ts
    • apps/clients/vite.config.ts + apps/clients/src/setupTests.ts
  2. API (standalone Vitest) — dedicated apps/api/vitest.config.ts:

    test: {
      globals: true,
      environment: "node",
      setupFiles: ["./src/__tests__/setup.ts"],
      globalSetup: ["./src/__tests__/globalSetup.ts"],
      testTimeout: 30000,
      hookTimeout: 60000,
      include: ["src/__tests__/**/*.test.ts", "src/**/*.test.ts"],
      pool: "forks",
      fileParallelism: false,
    }
    

    fileParallelism: false is deliberate — tests share initialized middleware state and can't safely run concurrently across files.

Setup Files

  • apps/main/src/setupTests.ts — imports @testing-library/jest-dom, registers an afterEach(cleanup), mocks ResizeObserver, Element.prototype.scrollIntoView, Element.prototype.scrollTo, and window.matchMedia. Also imports ./i18n.test to bootstrap i18next for tests.
  • apps/admin/src/setupTests.ts, apps/external/src/setupTests.ts, apps/clients/src/setupTests.ts — analogous per-app setup
  • apps/api/src/__tests__/setup.ts — minimal per-test-file setup (DB init handled by globalSetup.ts)
  • apps/api/src/__tests__/globalSetup.ts — boots the test middleware manager via createConfig() reading .env.test

Mocking Patterns

  • Frontend component tests import the component and render it with a renderWithProviders helper (see apps/main/src/utils/testHelpers.tsx and apps/clients/src/test/testHelpers.test.tsx) that wires QueryClient, router, i18n, and Zustand stores.
  • React Query mocking: integration tests mock the hook return values directly with vi.mock(...) and inject { data, isLoading, error } shapes.
  • Supabase client: API tests stub supabase.from(...) chains via vi.fn() — fixtures live in apps/api/src/__tests__/fixtures/.
  • Auth: API middleware tests bypass real Supabase auth by overriding the Bearer-token validator and asserting on 401 paths (see docs/MIDDLEWARE_TESTS.md).
  • Toast / window APIs: stubbed globally in setupTests.ts so individual tests don't have to.
  • i18n: each frontend test setup imports ./i18n.test so useTranslation() works without network.

API Test Patterns

Documented in docs/API_TESTS.md and docs/MIDDLEWARE_TESTS.md. Key conventions:

  • Tests live under apps/api/src/__tests__/<area>/<area>.test.ts (e.g. notes/notes.test.ts, tasks/tasks.test.ts)
  • Each router has at minimum a smoke test that verifies the endpoint is reachable and returns the right shape
  • Middleware is tested independently in apps/api/src/__tests__/middlewares/middlewares.test.ts — auth header validation, Supabase client injection, R2/Stream/email middleware behavior
  • Test mode is detected via NODE_ENV=test in createConfig() so secrets load from .env.test instead of Google Secret Manager — no GCP credentials required to run the suite
  • Fixtures (sample DB rows, sample Stripe payloads) live in apps/api/src/__tests__/fixtures/
  • Helpers (token mocking, app builders) live in apps/api/src/__tests__/helpers/
  • The __tests__/README.md documents the suite layout in-tree

Snapshot from docs/MIDDLEWARE_TESTS.md: 116 passing tests across the API suite at last documented run (2025-11-10), with explicit coverage for the Supabase, Auth, Stream, R2, and email middlewares.

Coverage

  • No coverage threshold is enforced in CI today — vitest.config.ts files do not declare coverage blocks and package.json has no test:coverage script at the root.
  • Coverage can still be produced ad-hoc with vitest run --coverage in any individual package, but it's not part of the normal workflow.
  • The repo relies on (a) Biome lint errors blocking CI, (b) pnpm typecheck (tsc -b) blocking CI, and (c) pnpm test blocking CI — coverage % is currently advisory only.

Existing Test Inventory

Total Vitest/RTL test files (*.test.ts(x), excluding node_modules, dist): 147

Breakdown by area (approximate):

  • apps/main/src/**/*.test.tsx — ~64 (components, contexts, providers, hooks)
  • apps/api/src/**/*.test.ts — ~31 (routers, middlewares, helpers)
  • apps/admin/src/**/*.test.tsx — pages, components, lib (e.g. AnalyticsStudioPage.test.tsx, PrivilegedGate.test.tsx)
  • apps/clients/src/**/*.test.ts(x) — env + Vite config sanity checks plus page tests
  • apps/external/src/viteConfig.test.ts, apps/main/src/viteConfig.test.ts — Vite build config smoke tests
  • xtablo-expo/**/*.test.ts(x) — React Native (Expo) tests (e.g. auth.test.ts, BillingPaywall.test.tsx)
  • supabase/tests/database/*.test.sql — 13 pgTAP files covering schema, RLS, triggers, indexes, Stripe + Apple billing functions

Representative examples worth reading first:

  • apps/main/src/components/CustomModal.test.tsx — minimal RTL test
  • apps/main/src/components/NavigationBar.test.tsx — uses renderWithProviders
  • apps/main/src/providers/UserStoreProvider.test.tsx — Zustand + React Query store test
  • apps/main/src/contexts/UpgradeBlockContext.test.tsx — context provider test
  • apps/api/src/helpers/orgIcons.test.ts — pure-helper unit test
  • apps/api/src/__tests__/middlewares/middlewares.test.ts — middleware integration suite
  • supabase/tests/database/02_rls_policies_core.test.sql — pgTAP RLS coverage

Reference Documents

  • docs/API_TESTS.md — API router test catalog, env setup, known limitations
  • docs/MIDDLEWARE_TESTS.md — middleware-by-middleware coverage notes
  • docs/ENV_TEST_SETUP.md.env.test structure
  • docs/TEST_FIXES.md, docs/TEST_ROUTER_REFACTOR.md — historical notes on test infrastructure changes
  • docs/TESTING_WITH_FAKE_ACCOUNTS.md — temporary-account flow for manual + integration testing