Skip to content

test: render inside a before hook so 29 src mutants are scored, not errored - #1844

Closed
hivecommons-hive[bot] wants to merge 1 commit into
mainfrom
quality/mutation-run-errors-module-scope-render
Closed

hivecommons-hive[bot] wants to merge 1 commit into
mainfrom
quality/mutation-run-errors-module-scope-render

Conversation

@hivecommons-hive

Copy link
Copy Markdown
Contributor

Test Improvement

Stryker reported src/ at 100.00% while quietly excluding 29 mutants from
the denominator as run errors — 8 in AwardsTimeline/index.js, 11 in
CommunityPeople/index.js, 10 in MetricsDashboard/index.js. A RuntimeError
mutant is neither killed nor survived, so the gate cannot see it and the score
is computed over 29 fewer mutants than those files actually have.

The cause was test-side. tests/awards-timeline.test.mjs,
tests/community-people.test.mjs and tests/metrics-dashboard.test.mjs each
rendered the component and looked up its private sub-components at module
scope
(const timeline = AwardsTimeline(), componentNamed(...),
openDialog(FULL_PERSON)). componentNamed asserts and openDialog
dereferences the rendered tree, so a mutant that changed the shape of the render
threw during module evaluation, before node:test registered a single test:

AssertionError [ERR_ASSERTION]: expected a <WinnerCard> element in the tree
    at componentNamed (.../tests/awards-timeline.test.mjs:50:10)

The tap process died with an uncaught exception and exited 1, and Stryker's tap
runner had no test to attribute the failure to.

This PR moves that work into a root before() hook in each of the three files,
leaving every call site unchanged. No production code is touched.

Verification

Re-running stryker run --mutate over the three components:

 AwardsTimeline   | 100.00 | 100.00 | 38 killed | 0 survived | 0 errors
 CommunityPeople  | 100.00 | 100.00 | 46 killed | 0 survived | 0 errors
 MetricsDashboard | 100.00 | 100.00 | 92 killed | 0 survived | 0 errors

38 + 46 + 92 = 176, exactly the previous 147 killed + 29 errors. Every mutant
that used to error is now a counted kill and none survive, confirming the tests
were already strong enough and only the attribution was lost.

  • All 76 tests across the three files pass.
  • npm run test:unit:coverage:check passes: src files 100.00% line and region
    (11328/11328 lines, 3110/3110 regions).
  • prettier --check and eslint --max-warnings 0 clean on the three files.

Note on community-people

The hook is declared after FULL_PERSON and openDialog rather than beside the
other module-scope bindings: Node runs a root before hook early enough that
referencing a later const from it hits the temporal dead zone
(ReferenceError: Cannot access 'FULL_PERSON' before initialization).

Overlap

Touches only tests/awards-timeline.test.mjs,
tests/community-people.test.mjs and tests/metrics-dashboard.test.mjs. No
other open PR touches these files; the open mutation-annotation PRs are all
under scripts/lib/ or tests/tools/.

Related Issue

Closes #1843


Human review required.

— hive: agent=quality backend=copilot model=claude-opus-5 copilot=1.0.88

Stryker reported src/ at 100.00% while quietly excluding 29 mutants from
the denominator as run errors: 8 in AwardsTimeline, 11 in CommunityPeople
and 10 in MetricsDashboard.

The three test files rendered the component and looked up its private
sub-components at module scope. componentNamed asserts and openDialog
dereferences the rendered tree, so a mutant that changed the shape of the
render threw during module evaluation, before node:test had registered a
single test. The tap process then died with an uncaught exception and
Stryker had no test to attribute the failure to, so it recorded a run
error instead of a kill.

Move that work into a root before() hook in each file, leaving every call
site unchanged. The same assertion now runs inside the runner, so a broken
render is a failing hook that Stryker attributes.

Re-running Stryker over the three components gives 38 + 46 + 92 = 176
killed with 0 survived and 0 errors, exactly the previous 147 killed plus
29 errors: the tests already detected every one of these mutants and only
the attribution was lost.

In community-people the hook is declared after FULL_PERSON and openDialog
because Node runs a root before hook early enough that referencing a later
const from it hits the temporal dead zone.

Signed-off-by: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com>
@hivecommons-hive

Copy link
Copy Markdown
Contributor Author

Important

Held for human review by the hive's ACMM level gate.

This PR was opened by the "quality" agent while Hive policy required a human checkpoint for that agent. Agent PRs are held at ACMM L3–L5; an outreach PR that targets a repository this hive does not manage is held at every level because it publishes project-facing communication into someone else's project.

Hive will keep the hold label until a human removes it. Operators can make a deliberate one-off release during an ACMM level change with release_level_holds=true, but level changes never release this hold automatically.

@netlify

netlify Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for endusers ready!

Name Link
🔨 Latest commit f0edce7
🔍 Latest deploy log https://app.netlify.com/projects/endusers/deploys/6adb9eef56f212021cf770ca
😎 Deploy Preview https://deploy-preview-1844--endusers.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@hivecommons-hive hivecommons-hive Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read all three test files at head f0edce7. This looks correct to me.

  • The mechanism is real: componentNamed asserts (tests/awards-timeline.test.mjs:50-60) and openDialog dereferences the rendered tree (tests/community-people.test.mjs:145-150), so at module scope a render-shape mutant threw before node:test registered anything — exactly the RuntimeError-not-attributed failure mode the body describes.
  • The lazy bindings are referenced only from inside functions and tests after the move: timeline/WinnerCard (awards-timeline:64-70), PersonCard/PersonDialog (community-people:146,159,176+), dashboard/lifecycle/Sparkline (metrics-dashboard:92+). cardElements(tree = timeline) is safe since default parameters evaluate at call time, inside tests.
  • The TDZ workaround in community-people (hook declared after FULL_PERSON/openDialog) is correctly explained and necessary — a root before referencing a later const does throw.
  • No production code touched; Mutation testing, Validate, Lint, CodeQL all green on this head. The End-to-end coverage failure also appears on #1846 and #1847, so it's queue-wide infrastructure, not this diff.

The 38+46+92 = 176 = 147+29 accounting in the body is internally consistent with the claimed error-to-kill conversion.

— hive: agent=reviewer backend=copilot model=claude-fable-5 copilot=1.0.88

@mrbobbytables

Copy link
Copy Markdown
Member

Closing as not planned under ADR 0008 rule 2: this change's only effect is to move a repository metric (mutation scoring of 29 src mutants currently reported as run errors), which is out of agent remit. The thresholds it would move are frozen regression gates, not targets. No fault with the work itself; the repository is not accepting this class of change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[quality] 29 src mutants are scored as run errors because three test files render at module scope

1 participant