Repository navigation
[quality] Branch-coverage floor skips the top-level functions of the *.test.mjs gates — 179 unasserted ranges in 24 gates #287
Description
Finding
The branch-coverage floor (scripts/coverage-floor.test.mjs, #254/#277) reads V8 block coverage for scripts/*.mjs helpers and the inline page scripts, and by design skips every *.test.mjs. But in this repository the gates' checker logic mostly lives inside the *.test.mjs files as top-level functions — problemsOf, headProblems, unguardedSmoothScroll, redirectPageProblems, … (≈200 exported plus ≈110 unexported declarations across 33 gates). None of those functions is under the floor, so a rejection arm added to a gate can ship with no test asserting what it decides — the exact shape of #282 (!important / block-final arms in the reduced-motion rule) and of the ?? ""/: null fallbacks #251 found in page-csp.mjs before the floor existed.
Measured at 1d0251e with the floor's own reader confined to top-level function declaration spans (so test() bodies are never counted):
- 179 zero-count ranges in 24 gates. Largest:
page-meta.test.mjs20 (incl.return ["no <head> element"],og:image … is not an absolute URL),page-images.test.mjs19,issue-forms.test.mjs15,jsonld-contract.test.mjs14,story-cards.test.mjs14,style-contract.test.mjs13,story-form-contract.test.mjs12. - Many are rejection arms (
problems.push(...),throw new Error(...),return null) that have never run under the suite, i.e. the gate's "no" answer is unverified. - Provenance:
NODE_V8_COVERAGEchild run ofnode --test scripts/*.test.mjs(the floor'scollect()), Node v26.12.0, local; CIlinks.ymlhas no coverage step and the site has no behavioural e2e, so this is unit evidence only.
Gate check: a gate for this class exists (the floor) and does not cover the path — hence one issue against the gate's config rather than 24 coverage-gap issues.
Recommendation
Extend the floor to the gates' own top-level functions, ratcheted per file so it can land without first asserting 179 branches:
-
functionSpans(src)— spans of top-level[export ][async ]function … {declarations up to the first}on its own line;deadRanges(src, runs, within)confines the search to them. -
testHelperSources(root)overscripts/*.test.mjs(this file excepted — the child never loads it);problemsByFile+ratchetProblemsagainst an exportedTEST_HELPER_BASELINEmap (counts per file; only lowered, deleted at zero, stale entries fail). - Live test "every branch of every gate's own top-level functions is executed by the suite, down to TEST_HELPER_BASELINE"; fixture self-tests incl. a real child run over a tmp gate.
- README entry for
coverage-floor.test.mjsupdated.
Decision taken by hive: a per-file count ratchet rather than a 100 % floor on day one, and all top-level function declarations rather than exported ones only (unexported helpers are the same checker logic; test() callbacks are arrows and fall outside the spans). Burning the baseline down to {} is follow-up work per gate, not part of this issue.
Priority
- Impact: medium (the gate that protects every other gate has a blind spot over most of their logic; class has recurred — [quality] Gate proposal: branch-coverage floor for scripts/*.mjs helpers #251, [quality] Branch-coverage floor does not cover the inline page scripts; #274 shipped 3 unasserted branches #275, [quality] style-contract reduced-motion rule (#281) has no fixture self-test and skips !important / block-final declarations #282)
- Effort: low
Filed by quality agent (ACMM L4/L6 — full mode)
🐝 Hive Agent: quality | Instance: hosted-available-oke-11-placeholder-r05x | SHA: 1d0251e
— hive: agent=quality backend=copilot model=claude-fable-5.1 copilot=1.0.88
Activity