Testing & CI
Unit tests
Section titled “Unit tests”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.
pnpm vitest run # run all tests oncepnpm vitest run --watch # watch modepnpm vitest run tests/lib/auth # specific directoryUnit 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.
Build smoke tests
Section titled “Build smoke tests”scripts/smoke-build-check.mjs scans the SSR output directory (.netlify/build) for two regressions:
- Registry singleton split —
createRegistryappearing 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). - Registration tree-shaken away — if
registerSection(...)orregisterSchema(...)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:
pnpm buildnode scripts/smoke-build-check.mjsCI pipeline
Section titled “CI pipeline”CI runs on every push and pull request via .github/workflows/ci.yml. Steps in order:
1. Check apps/admin isolation (static)
Section titled “1. Check apps/admin isolation (static)”node scripts/check-admin-isolation.mjsFast 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”pnpm --filter portal-admin buildRuns 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.
3. Build packages
Section titled “3. Build packages”pnpm run build:packagesBuilds all five packages in dependency order. Because each build includes tsc --emitDeclarationOnly, this also doubles as the workspace typecheck.
4. Run tests
Section titled “4. Run tests”npx vitest runAll unit tests.
5. Build site (SSR smoke test)
Section titled “5. Build site (SSR smoke test)”pnpm buildBuilds the in-repo dev site against the freshly-built workspace packages. This is the SSR build whose output is scanned next.
6. Check SSR build output
Section titled “6. Check SSR build output”node scripts/smoke-build-check.mjsAsserts: single createRegistry chunk, registerSection(...) present, registerSchema(...) present.
7. Build apps/dev (custom-section acceptance)
Section titled “7. Build apps/dev (custom-section acceptance)”pnpm --filter portal-dev buildapps/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.
8. Check apps/dev SSR output
Section titled “8. Check apps/dev SSR output”cd apps/dev && node ../../scripts/smoke-build-check.mjs \ --expect product_card \ --expect data-portal-product-cardAsserts 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.
9. Build docs site
Section titled “9. Build docs site”pnpm --filter portal-docs buildStarlight 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.
What the smoke test does NOT yet catch
Section titled “What the smoke test does NOT yet catch”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 acontainersection at multiple widths would cover this).