# 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`):

```bash
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`:
   ```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`:
   ```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
