Skip to content

Testing & CI

Tests use Vitest + @testing-library/react. They live in tests/ mirroring the src/ structure. Coverage targets: schemas, the registry, the loader (mergeSiteContent), nav generation, and key components.

Terminal window
pnpm vitest run # run all tests once
pnpm vitest run --watch # watch mode
pnpm vitest run tests/lib/auth # specific directory

Unit tests run in jsdom. They mock modules via vi.mock("@/...") and are structurally unable to catch SSR bundling issues (registry singleton splits, tree-shaken registration). That gap is covered by the build smoke tests.

scripts/smoke-build-check.mjs scans the SSR output directory (.netlify/build) for two regressions:

  1. Registry singleton splitcreateRegistry appearing in more than one SSR chunk means Rollup produced two independent registry instances. Sections registered in one are invisible to the other at runtime (causes “At least 2 section schemas must be registered” in production).
  2. Registration tree-shaken away — if registerSection(...) or registerSchema(...) calls are absent from the SSR output entirely, the registry is empty at runtime and no sections will render.

The script also accepts --expect <token> flags to assert that specific strings appear in the SSR output (used by the apps/dev custom-section acceptance test).

Run manually after building:

Terminal window
pnpm build
node scripts/smoke-build-check.mjs

CI runs on every push and pull request via .github/workflows/ci.yml. Steps in order:

Terminal window
node scripts/check-admin-isolation.mjs

Fast static check that no file in apps/admin/src/ contains a runtime (value) import of @drawnagency/*. See apps/admin isolation for why this matters.

2. Build apps/admin with packages dist absent

Section titled “2. Build apps/admin with packages dist absent”
Terminal window
pnpm --filter portal-admin build

Runs before build:packages. With no dist/ in the workspace packages, any runtime import of @drawnagency/* in admin would fail module resolution — exactly the failure mode on Netlify. Type-only imports are erased by esbuild and pass. This is layer 2 of the admin isolation gate.

Terminal window
pnpm run build:packages

Builds all five packages in dependency order. Because each build includes tsc --emitDeclarationOnly, this also doubles as the workspace typecheck.

Terminal window
npx vitest run

All unit tests.

Terminal window
pnpm build

Builds the in-repo dev site against the freshly-built workspace packages. This is the SSR build whose output is scanned next.

Terminal window
node scripts/smoke-build-check.mjs

Asserts: single createRegistry chunk, registerSection(...) present, registerSchema(...) present.

7. Build apps/dev (custom-section acceptance)

Section titled “7. Build apps/dev (custom-section acceptance)”
Terminal window
pnpm --filter portal-dev build

apps/dev hosts a ProductCard custom section. Building it verifies the custom-section registration channel works end-to-end. astro build bundles (does not execute) portal.config.mjs, so this builds without Supabase secrets.

Terminal window
cd apps/dev && node ../../scripts/smoke-build-check.mjs \
--expect product_card \
--expect data-portal-product-card

Asserts the custom section’s type string and rendered HTML attribute are present in the SSR output — confirming the section is registered and renders to markup.

Terminal window
pnpm --filter portal-docs build

Starlight and Pagefind fail the build on broken internal links and malformed frontmatter. This step is the docs content gate — it catches broken cross-references and schema errors in doc frontmatter. The docs app has no @drawnagency/* runtime imports, so it builds without the package dist.

The following gaps are documented in scripts/smoke-build-check.mjs and CLAUDE.md as known TODOs:

  • Empty import.meta.env.* values for required env vars at build time.
  • Container-query layout collapse at specific @-breakpoints (jsdom cannot compute container queries; a Playwright check rendering a container section at multiple widths would cover this).