# 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__//.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__//.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