Add unit, integration and e2e test suites

tests/{unit,integration,e2e}/, at the repo root (not under frontend/,
since e2e exercises the frontend and PocketBase together) — all
against real code rather than mocks:

- unit/: Vitest (nuxt environment) — pure functions in server/utils
  and shared/utils, plus components/composables via
  @nuxt/test-utils/runtime's mountSuspended.
- integration/: Vitest (node environment) — @nuxt/test-utils/e2e's
  setup() builds and runs the real Nitro server against a real
  ephemeral PocketBase instance, exercising the actual /content/* and
  /admin routes end to end.
- e2e/: Playwright, a real browser against the built app and another
  ephemeral PocketBase, covering the user-facing flows (home, blog
  archive, article detail incl. JSON-LD, category filter, 404s, the
  /admin redirect).

The Vitest/Playwright tooling (and node_modules) stays in frontend/,
the repo's only npm project; its configs just point at ../tests/.
Playwright resolves packages from the node_modules nearest each spec
file, so `npm run test:e2e` first symlinks tests/node_modules to
frontend/node_modules (pretest:e2e, idempotent, gitignored).

tests/support/pocketbase.ts is the shared piece behind integration
and e2e: it downloads the same pinned PocketBase binary
pocketbase/Dockerfile uses, starts it against the real
pocketbase/pb_migrations/ (not a copy), and seeds it from
tests/support/fixtures.ts — so these tests run against the exact
schema and API rules that ship to production, catching the class of
bug that only shows up when PocketBase actually enforces them (e.g. a
wrong filter/fields query string silently returning nothing, or too
much).

CLAUDE.md and docs/frontend.md document the new npm scripts and the
single-test commands for each layer.
This commit is contained in:
2026-09-11 11:48:43 +02:00
parent 6609e3ae28
commit 91181f2170
23 changed files with 1767 additions and 75 deletions
+26
View File
@@ -87,3 +87,29 @@ JSON-LD `BlogPosting` structured data, with the article body already present in
HTML (no client-only content). Accessibility requirements (focus visibility, labeled inputs,
meaningful alt text, descriptive links, full keyboard navigation) apply across all pages/components
— see `CLAUDE.md`.
## Tests (`tests/`, repo root)
Kept at the repo root, not under `frontend/`, since e2e exercises the frontend *and* PocketBase
together — but the tooling (Vitest/Playwright configs, `node_modules`) still lives in `frontend/`,
the only npm project in the repo; the configs there just point `include`/`testDir` at `../tests/`.
Three layers, all against real code (no mocked PocketBase):
- `tests/unit/` — Vitest, `environment: 'nuxt'` (`frontend/vitest.unit.config.ts`). Pure functions
in `frontend/server/utils/*` and `frontend/shared/utils/*` (imported directly, not via
auto-import) plus components and composables via `@nuxt/test-utils/runtime`'s `mountSuspended`.
- `tests/integration/` — Vitest, node environment (`frontend/vitest.integration.config.ts`).
`@nuxt/test-utils/e2e`'s `setup()` builds and runs the real Nitro server, pointed (via
`nuxtConfig.runtimeConfig` overrides) at a real ephemeral PocketBase instance from
`tests/support/pocketbase.ts`. Exercises the actual `/content/*` and `/admin` routes.
- `tests/e2e/` — Playwright (`frontend/playwright.config.ts`), a real browser against the built
app (`node .output/server/index.mjs`) and another ephemeral PocketBase on a fixed port (needed
so the Nuxt server's env and Playwright's `globalSetup` can reference each other without an
async hand-off — see the comments in `playwright.config.ts`/`tests/e2e/global-setup.ts`).
`tests/support/pocketbase.ts` is the shared piece: it downloads the same pinned PocketBase binary
`pocketbase/Dockerfile` uses (cached in `.cache/pocketbase/` at the repo root, gitignored), starts
it against the real `pocketbase/pb_migrations/`, and seeds it from `tests/support/fixtures.ts`
so integration and e2e tests run against the exact schema/rules that ship to production, not a
hand-maintained approximation of them.