Skip to content

PA cockpit โ€” tests

The Public Affairs cockpit is the most heavily tested surface in the product, and the only board with its own end-to-end suite.

These tests moved into a package

As of v2026.08.27 the cockpit lives in @ronl/pa-cockpit, and its tests travelled with it โ€” they are no longer part of the packages/frontend suite. That is why the frontend's own totals fell between v2026.08.23 and v2026.08.33 without anything being deleted.

Package: 43 files ยท 476 tests. Backend: 16 files ยท 578 tests in src/pa-monitoring โ€” the largest single area in the repository. E2E: 2 specs ยท 7 tests, still in the frontend package.

Measured with npm test --workspace=@ronl/pa-cockpit on 24 September 2026 against main at 86af73e (v2026.09.11), after a clean npm ci in a separate clone on Node 24.14.1 / npm 11.11.0: 476 of 476 passing, Duration 37.58s. Coverage 90.11 % statements ยท 88.52 % branches ยท 86.52 % functions ยท 91.33 % lines โ€” branches up 12.97 points from v2026.08.36 under the per-file 80% floor adopted in v2026.09.2, which v2026.09.6 wrote into this package's vitest.config.ts as thresholds: { branches: 80, perFile: true }. The run passed it without naming a file.

Four releases, four identical counts

43 files and 476 tests at v2026.09.7, v2026.09.9 and v2026.09.11, and the only src/ change in the latest window was to src/scaffold.test.ts, whose three tests did not change in number. The coverage figures above are the 12 September ones re-measured: the 20 September pass read 90.07 statements and 86.39 functions, and this one reads what 12 September did. That is the second-decimal noise described on Coverage, not a change in the package.

This package holds 8 of the 26 files an 80% functions floor would fail โ€” the second-largest share after the frontend โ€” while none of its 38 source files is below the 80% branch floor that is actually configured.

Until v2026.09.6, these 476 tests ran nowhere in CI

@ronl/pa-cockpit is a library: it has no deploy workflow of its own, and no other workflow ran its suite. The most heavily tested surface in the product was therefore covered on developer machines and nowhere else, while every deployable package had had a pipeline running its tests since 20 August. Both frontend workflows now run it, as a step placed before the frontend's own โ€” the frontend imports the package, so there is no point testing the consumer while the library is broken. The pa-demo workflows consume it too and watch packages/pa-cockpit/** in their path filters, but run only pa-demo's own suite.

The E2E figures above are older than the rest

The package counts and coverage, and the src/pa-monitoring backend figure, are all from 24 September 2026. The two E2E specs were last measured on 29โ€“30 August and were not re-run in this pass โ€” running them needs the full local stack. See E2E & live smoke.


The package suite

File Tests Covers
services/pa.api.test.ts 66 The cockpit's API client โ€” the single largest test file in the package
pages/public-affairs-v2/PaDataProvider.test.tsx 21 The context every cockpit screen consumes
pages/public-affairs-v2/NotificationsPanel.test.tsx 19 Previously 0% โ€” notifications were hardcoded to empty
services/dossierbeheer.api.test.ts 17 The dossier API client
components/PADashboardV2/dossierbeheer/Dossierbeheer.test.tsx 17 Dossier list, filters, status transitions
pages/PADashboardV2.test.tsx 15 The page container and the host contract it requires
components/PADashboardV2/PaSectionsRouter.test.tsx 15 The section-id grammar that replaced a fourteen-export surface
pages/public-affairs-v2/Monitoring.test.tsx 14 Monitoring view
pages/public-affairs-v2/Issuekaart.test.tsx 14 The issue map
components/PADashboardV2/dossierbeheer/DossierEditor.test.tsx 14 Authoring a dossier
services/mock-demo.store.test.ts 11 The mock store both hosts drive
pages/public-affairs-v2/Kompas.test.tsx + kompas.test.ts 20 The kompas view and its pure data module
components/PADashboardV2/dossierbeheer/MdEditor.test.tsx 9 The Markdown editor
components/PADashboardV2/ZoekcriteriaSection.test.tsx 9 Saved-search criteria
pages/public-affairs-v2/Vandaag.test.tsx 8 The 30-second start screen
pages/public-affairs-v2/FeitenCijfers.test.tsx 8 Facts and figures
components/PADashboardV2/dossierbeheer/DossierRow.test.tsx 8 Row rendering and actions
pages/public-affairs-v2/AgendaView.test.tsx 7 The agenda
components/PADashboardV2/PACommandPalette.test.tsx 7 The โŒ˜K palette

By directory: pages/public-affairs-v2 119, services 94, components/PADashboardV2/dossierbeheer 67, components/PADashboardV2 53, pages 15, modes 7, package root 10, test 3.

The guards that came with the extraction

Seven of the smallest files are not feature tests at all โ€” they are structural guards protecting the package boundary, and they matter out of proportion to their test counts:

File Tests Guards
host.test.ts 3 That a host must configure the package before render, and fails with a named error if it does not
index.test.ts 2 The public surface โ€” so a re-export added by accident is caught
modes/no-module-scope-modes.test.ts 2 That the unfiltered mode helpers stay unreachable, which is what keeps the demo's curation honest
no-tailwind.test.ts 1 That no Tailwind utility class returns to the package
no-host-protocol.test.ts 1 That no host session-storage key, IdP literal or router path is hardcoded back in

The last one is the residue of a real defect class. The package had previously hardcoded five facts belonging to a host application; the guard exists so the seam cannot silently close again.

Two of these guards were themselves defective when written

The modes guard matched raw text, so a single line of documentation-shaped string could disarm the only rule covering dynamic imports โ€” and a decoy function declared in an inner scope disarmed it for a whole file while compiling with zero type and lint errors. It now parses the source, and its accusing and excusing inputs are split so one can no longer feed the other. A guard is code, and needs its own red/green like anything else.

Backend

src/pa-monitoring โ€” 533 tests across 16 files:

File Tests
pa.routes.test.ts 131
pa-dossiers.routes.test.ts 96
curation.service.test.ts 61
rules.test.ts 38 (pure scoring)
pa-dossiers.db.test.ts 30
pa-monitoring.db.test.ts 10
pa-cache.test.ts 9
notifications.service.test.ts 8
query-match.test.ts 6
rss.test.ts 4

Plus the TK, OB, EU, agenda and media source clients under pa-monitoring/sources. Coverage: pa-monitoring 98.32%, pa-monitoring/sources 97.07%.


E2E

Two Playwright specs, run with the same playwright.config.ts as the rest of the frontend suite.

Re-measured 30 August 2026 against acc at 15dfbf9: 7 tests, all passing, as part of the full frontend run of 27 tests in 1.9m. The count is unchanged since 22 August, when the two together ran in 18.9s and the live spec was additionally run six consecutive times while chasing a flake, passing 2/2 each time in 7.3โ€“11.7s.

See Coverage per board for how these seven sit against the other twenty.

Spec Tests Covers
pa-mock-journey.spec.ts 5 Mock mode driven against the real store with no mocking: curating moves the rail badges and the move survives a reload; an ignored signal stays ignored; Reset demodata restores every source to its fixture baseline; the reset control is offered in mock mode only; every signaalbron carries a watchlist orphan that can be linked to a dossier
pa-live-authoring.spec.ts 2 Authoring against the live backend and a real database โ€” a dossier and a zoekcriterium survive a genuine cold reload; live shows authored work while mock shows fixtures, from the same screen

Why mock mode is worth an E2E suite

Every mock-mode defect found by hand in this window was invisible to the unit suites by construction. They were not logic errors โ€” a saved-search write that was a bare return;, a confirm that built a new object and discarded it, notifications hardcoded to empty, a resource fetched once at mount and never again. Component tests mock the very seam that was broken, so they cannot see any of it, and each one passed throughout.

Mock mode makes an unmocked end-to-end run affordable: the fixtures are deterministic and nothing depends on what happens to be in the database.

Why the live spec only covers authoring

Curation depends on TK OData, the EU RSS feed and the media aggregator, and TK alone measured 10s and 48s for the same query minutes apart โ€” an assertion about signals arriving would be flaky by construction.

The live spec creates everything with a run-unique stamp and removes it again in afterEach, including when the test fails part-way. Nothing global is reset: pa:reset-data is a feature of the product, not test tooling.

Three lessons came out of building this suite

A throttled run that looks exactly like an outage, locator.count() not auto-waiting, and a hand-written mock that passed vacuously. All three are written up on Writing tests, because they generalise well beyond this board.