Skip to content

Testing

RONL Business API is an npm-workspaces monorepo with five tested packages: the Express/TypeScript backend on Jest, and four on Vitest β€” the caseworker portal (packages/frontend) with React Testing Library, the Public Affairs cockpit (packages/pa-cockpit), the public cockpit demo (packages/pa-demo) and the public search site (packages/public-site) with jsdom. All five run with coverage by default.

Figures on this page are measured, not estimated

Every count and percentage below was produced by running npm run test:serial against v2026.08.33 on 29 August 2026, on acc at 1e7fb19. Rerun the commands in Running the tests to reproduce them.

Two things were not re-run for this release and say so where they appear: the frontend and public-site Playwright suites, and the four live-smoke shell scripts, which remain described from their configuration only. See E2E & live smoke.

At a glance:

Package Runner Files Tests Result Wall timeΒΉ Statements Branches Functions Lines
packages/backend Jest + ts-jest 74 1576 all passing ~86s 98.35% 91.49% 96.65% 98.75%
packages/frontend Vitest + RTL 103 842 all passing ~276s 88.07% 80.09% 83.53% 88.96%
packages/pa-cockpit Vitest + RTL 41 368 all passing ~92s 86.16% 75.55% 83.46% 88.53%
packages/pa-demo Vitest + jsdom 19 104 all passing ~27s 91.30% 86.95% 85.00% 91.66%
packages/public-site Vitest + jsdom 28 140 all passing ~108s 86.82% 70.39% 87.63% 88.76%

265 files Β· 3030 tests, plus one performance spec run separately β€” 3031 in total. See Coverage for what those percentages mean and where the remaining gaps are.

The frontend did not shrink β€” the cockpit moved

The frontend reads 103 files / 842 tests here against 133 / 1155 at v2026.08.23. Nothing was deleted: 41 files and 368 tests moved into packages/pa-cockpit when the cockpit was extracted into a package. Taken together the pair grew by 11 files and 55 tests. See PA-Cockpit package.

ΒΉ These are serial wall times, measured with test:serial, and are much longer than a parallel run β€” the frontend suite measures ~61s parallel on an idle machine. Treat them as an order of magnitude rather than a figure to match, and compare like with like.


Where to look

Page Covers
Coverage Headline and per-area coverage for all five packages, and why the last two decimals are noise
Backend suite The 74 files and 1576 tests in packages/backend, by area
Public site suite The 28 files and 140 tests in packages/public-site, plus its own Playwright suite
PA-demo suite The 19 files and 104 tests in packages/pa-demo, and the one Playwright suite that runs in CI
Caseworker Β· PA cockpit Β· Infra-board Β· Woo-dashboard The frontend and cockpit suites, split the way the product is β€” one page per board
E2E & live smoke The Playwright suites, what they need running, and the four cross-app shell scripts
Writing tests Conventions for adding tests here, and the traps that have already cost time

The four board pages do not add up to the frontend total, by design

They account for 841 of the 1155 frontend tests. The other 314 are not board-specific and so have no board page to live on:

  • 223 in src/services (211), App.test.tsx (5), src/hooks (4) and src/test (3)
  • 49 in shared widgets reused across boards β€” ProcessStartFormViewer, TimeLine, DecisionViewer, LoginChoice, AltchaWidget, SessionExpiryWarning, PersonalDataPanel
  • 42 in pages that belong to no board β€” AuthCallback, Dashboard, ChangelogPanel, LoginChoice

Note also that components/CaseworkerDashboard/ is counted under Caseworker but is the shared section-component library, reused across three of the four V2 dashboards. Its 185 tests protect more than that one board.


Running the tests

Run from the repository root after npm install (node_modules must already be installed in every workspace).

Command Scope Files Tests
npm test Every workspace with a test script (see below) 265 3030
npm run test:serial The same, without file parallelism 265 3030
npm test --workspace=@ronl/backend Backend only (Jest, coverage on by default) 74 1576
npm test --workspace=@ronl/frontend Frontend only (Vitest, coverage on by default) 103 842
npm test --workspace=@ronl/pa-cockpit The cockpit package 41 368
npm test --workspace=@ronl/pa-demo The public demo 19 104
npm test --workspace=@ronl/public-site Public site only (Vitest, coverage on by default) 28 140
npm run test:perf --workspace=@ronl/frontend The wall-clock budget, run without file parallelism 1 1
# Everything
npm test

# Everything, serially β€” see the warning below
npm run test:serial

# One workspace
npm test --workspace=@ronl/backend
npm test --workspace=@ronl/pa-cockpit

# Single file / pattern
npx jest --config packages/backend/jest.config.js --no-coverage --testPathPattern=rules
npx vitest run --config packages/frontend/vite.config.ts session
npx vitest run --config packages/public-site/vite.config.ts SectionIndex

Reach for test:serial, not for a flag

This repository runs two test runners behind one command shape β€” the backend on Jest, the other four on Vitest. Any serial flag you reach for is right in four places and wrong in the fifth: --no-file-parallelism is Vitest's, --runInBand is Jest's, and Jest rejects the Vitest flags outright. Every workspace defines a test:serial script for exactly this reason, so the runner's identity stops being something the caller has to know.

Note that a parallel-run failure is not a finding until it fails serially. The flakiness chased through August turned out to be timeouts at Vitest's 5000ms default under machine load, not a defect in the code under test; testTimeout is now 20s in all four Vitest workspaces.

What npm test at the root actually runs. The root script is npm run test --workspaces --if-present, fanning out over every package under packages/*. Five of the six have a test script β€” @ronl/backend, @ronl/frontend, @ronl/pa-cockpit, @ronl/pa-demo, @ronl/public-site β€” and --if-present silently skips the sixth, @ronl/shared, which has no test script at all (only build, prepare, clean, type-check). That is expected, not a gap: shared is a types-only package with nothing to unit test.

Clear the Jest cache before trusting a green backend run

ts-jest caches type diagnostics per file, so a warm cache can skip re-checking a file that no longer compiles and report the suite green. That is not hypothetical: nine backend test files were latently broken for weeks β€” every top-level declaration landing in the global scope, colliding across files β€” while npm test passed locally every time. The first CI run on a cold runner failed immediately.

npx jest --config packages/backend/jest.config.js --clearCache

Which files fail also varies between runs, because it depends on the order the type program reaches them. See Writing tests.

The performance budget

simEngine.ts carries a real budget: run(cfg) must process the default 3,150-application population in under 250ms. Its source comment is emphatic that if the assertion ever fails the threshold must not be loosened β€” the intended remedy is a web worker, not a bigger number.

The problem was never the threshold. A single performance.now() call measures the machine as much as the engine: on a contended host the assertion was observed at 302ms, then 837ms, then 1297ms across three consecutive runs, and inside a full npm test β€” where Vitest saturates every core with 133 parallel test files β€” even the fastest of three CPU-time samples came out at 468ms, against ~100ms in isolation. Wiring the CI test gate would have made that a permanently red pipeline.

So the budget moved rather than moved up:

  • The assertion lives in simEngine.perf.test.ts, still asserting < 250ms, now with a warm-up run and the fastest of three samples so a JIT pause or a stray GC cannot decide it.
  • vite.config.ts excludes src/**/*.perf.test.ts from the default run.
  • vitest.perf.config.ts mirrors it β€” same plugins, aliases and setup, but the perf specs are the only thing included, and fileParallelism is off. It spreads and overrides the base test block rather than using mergeConfig, which concatenates arrays and would have kept the base exclude, hiding the perf specs from their own run.
  • npm run test:perf runs it, and both frontend workflows run that as their own blocking CI step.

ChangelogPanel.test.tsx was the other casualty of the same contention: its 15-second timeout sufficed in isolation but not inside a full run, where it was observed taking 22s. changelog-data.ts renders every real version entry, so the test is genuinely slow rather than unreliable β€” and a timeout exists to catch a hang, not to assert a speed. It was raised to 60s, which costs no coverage; the perf spec remains the one place a real budget is asserted.


Linting, formatting, git hooks, and CI

Command What it does Result (measured)
npm run lint npm run lint --workspaces --if-present β€” eslint . in backend, frontend, public-site exit 0, no errors
npm run check-format prettier --check "**/*.{ts,tsx,json,md}" --ignore-path .gitignore β€” one repo-wide glob, not a per-workspace fan-out exit 0, no violations

npm run lint skips @ronl/shared the same way test does β€” no lint script there. npm run check-format, by contrast, is not scoped by workspace scripts at all: it is a single Prettier invocation over the whole tree (minus .gitignored paths), so it reaches packages/shared too even though shared defines no format-check script of its own.

Git hooks

Hook Runs Scope
pre-commit npx lint-staged Staged files only
pre-push build @ronl/shared β†’ npm run type-check β†’ npm run lint β†’ npm run check-format All workspaces (type-check, lint) / whole tree (check-format)

The hooks do not run the tests

Neither pre-commit nor pre-push invokes any test script, so nothing client-side stops a push that breaks a suite β€” run npm test yourself before pushing anything nontrivial. Since 20 August 2026 every pipeline does run them, so a broken push no longer reaches acceptance; the client-side gap is now a matter of feedback speed rather than of what ships.

CI

Nine workflows under .github/workflows/ on acc β€” an acc/prod pair per package, plus the supply-chain audit gate:

Workflow Lint Type-check Tests E2E Perf budget Build Deploys?
azure-backend-acc.yml / -prod.yml βœ… – βœ… – – βœ… No β€” packages and uploads an artifact
azure-frontend-acc.yml / -prod.yml βœ… – βœ… – βœ… βœ… Yes
azure-pa-demo-acc.yml / -prod.yml βœ… βœ… βœ… βœ… (acc only) – βœ… Yes
azure-publicsite-acc.yml / -prod.yml βœ… βœ… βœ… – – βœ… Yes
zizmor.yml – – – – – – No β€” blocking audit gate

Every package has a real CI test gate, as of 20 August 2026 β€” a failing test blocks the pipeline in all eight Azure workflows. Public-site had one already. Backend CI now runs Jest alongside its existing lint step; frontend CI previously ran neither lint nor test, going straight from npm ci to vite build.

azure-pa-demo-acc.yml is the only workflow that runs an end-to-end suite β€” the first Playwright in CI anywhere in this repository. See PA-demo suite.

The backend workflows are the exception to "deploy": they end at Create deployment zip β†’ Upload deployment artifact. Nothing in them calls a deploy action, and there is no post-deployment health check β€” the artifact is deployed separately, from a developer machine. See CI/CD and the supply-chain gate.

The gap was deliberate while it lasted: gating on coverage that was still climbing was judged theatre, the same call the Linked Data Explorer made and has since reversed for the same reason.


Roadmap

E2E CI wiring is done for one of the three Playwright suites. The PA-demo suite runs as a blocking step of azure-pa-demo-acc.yml, which was possible because that demo needs no backend, database or Keycloak β€” Playwright starts its own dev server and that is the whole environment.

The other two still run locally only. The frontend suite needs a webServer-style auto-boot for Keycloak/Postgres/Redis/backend/LDE-backend β€” there is no human to start the stack manually in CI β€” plus a known Node v24-on-Windows exit crash in globalSetup and an unbounded local-Operaton-history-growth gap to close first. The public-site suite has no such blocker and is the obvious next candidate.

Two boards have no end-to-end coverage. Infra-board and Woo-dashboard are well covered by unit tests and have no Playwright specs at all. That is a gap rather than a decision β€” the PA cockpit work showed exactly which class of defect unit tests cannot see.

Doccle has no live-tested results yet. test-doccle-live.sh exists and is ready to run, but every run so far has been in DOCCLE_STUB_MODE=true β€” see Doccle β€” Live Testing.

Branch-coverage depth is the natural next target across the frontend and public site: defensive guards and catch-block edges inside already-tested files, not new files to reach.

Deliberately out of scope for now: visual regression and screenshot diffing, a cross-browser matrix (Chromium only in both Playwright suites), and parallel or sharded E2E execution tuning β€” none are blockers at current suite size.