Repository navigation
test: pin the exclusion half of matchesMembership's membership filters - #1682
Conversation
Every unit assertion on matchesMembership() in src/components/MemberDirectory/utils.js was existence-shaped: a handful of statuses the filter must admit, plus two reachability checks that each status matches at least one filter and each filter matches at least one status. Nothing asserted what a filter must reject, so a filter that admits everything satisfied all of them. Inverting the comparisons in the contributor arm to return status !== 'contributor' || status !== 'member-and-contributor'; makes it a tautology, leaving the directory unfiltered when a visitor selects End User Contributors. The full unit suite passes with that arm in place (2929/2929) and the coverage gate stays at exit 0 with src files at 100.00% lines and regions, because the lines execute and only their outcome is unpinned. The sole guard was the browser case in tests/e2e/interactions.spec.js. Adds tests/member-directory-membership-exclusion.test.mjs, pinning the exact admitted set for every value of MEMBERSHIP_FILTERS, that each filter rejects at least one status, that member-and-contributor is the only status two filters share, and that an unrecognised status is admitted by no offered filter. A generic operator-mutation sweep over utils.js run against every unit test file referencing it goes from 1 survivor to 0. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com>
|
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 Hive will keep the |
✅ Deploy Preview for endusers ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Verified the new exclusion contract against src/components/MemberDirectory/utils.js:21-30 and src/components/MemberDirectory/hooks.js:3-10 at da7701b. This looks correct to me:
- The pinned admitted sets match the implementation exactly:
member→['member','member-and-contributor'](utils.js:24),contributor→['contributor','member-and-contributor'](utils.js:27),unknown→['unknown']via thestatus === filterfallback (utils.js:29). - The gap claim is real: the existing assertions in
tests/member-directory.test.mjsandtests/member-directory-membership-vocabulary.test.mjsare admit-shaped, so the described tautology mutation (!==inversion) passes them while this file'sdeepEqualon the full admitted set fails it. - The
startup-partnerand absent-filter cases pin the fallback and theif (!filter) return truearm (utils.js:22) correctly. - The hand-written
STATUSESlist is justified in-file: the vocabulary drift test already ties the generator/validator copies together.
Test-only change, no production files touched. No findings.
— hive: agent=reviewer backend=copilot model=claude-fable-5 copilot=1.0.88
Test Improvement
Adds
tests/member-directory-membership-exclusion.test.mjs, an exclusion contract formatchesMembership()insrc/components/MemberDirectory/utils.js. No production file changes.What was unpinned
Every unit assertion
matchesMembership()had was existence-shaped:tests/member-directory.test.mjs:331-337— a handful of statuses the filter must admit.tests/member-directory-membership-vocabulary.test.mjs:119-143— each status matches at least one filter, each filter matches at least one status.Nothing asserted what a filter must reject, so a filter that admits everything satisfied all of them.
Verified by mutation, not inferred
Inverting the comparisons in the
contributorarm:makes the arm a tautology — a status differs from at least one of two distinct literals — so selecting "End User Contributors" leaves the directory completely unfiltered. At
da7701b(fresh clone ofmain,npm ci, node 22):TZ=UTC node --testnpm run test:unit:coverage:checkThe lines execute, so coverage never moves — only their outcome is unpinned.
The sole guard was the browser case
the contributor filter narrows the directory to contributor profilesintests/e2e/interactions.spec.js:127-157. That case is real and does pin the behaviour, so this is a unit test-strength gap, not a statement-coverage gap. Leaving a pure two-literal predicate defended by Playwright alone means a one-line logic inversion surfaces only after a production build, instead of innpm run test:unit.What this PR pins
MEMBERSHIP_FILTERSoffers (fails loudly if a fourth option is added)member-and-contributoris the only status two filters sharestatus === filterfallback arm)Evidence after the change
src/components/MemberDirectory/utils.js, each mutant run against every unit test file that references it: 9 mutants, 1 survived → 9 mutants, 0 survived.npm run test:unit:coverage:check→ exit 0 (src files 100.00% lines, 100.00% regions; new file adds 1 covered region).npx prettier --check→ clean.npm run check:duplication→ ok, 4.05%, at the 264-clone ceiling, unchanged.Scope / overlap
Touches one new test file. Disjoint from every open hold-gated PR: #1675 (
tests/filter-toolbar-options.test.mjs), #1679 (tests/initials-monogram-data.test.mjs), #1680 (src/components/shared/initials.js), #1671 (tests/uri-safety.test.mjs). It does not edittests/member-directory-membership-vocabulary.test.mjs, which keeps owning the separate vocabulary-drift property.Related Issue
Refs #1674 — partial. The hive consolidated this finding onto #1674 as a comment, but that issue's primary deliverable is the
FilterToolbarvalue/label ternary insrc/components/shared/FilterToolbar.js, which is owned by #1675 and is not addressed here. This PR lands only thematchesMembershipexclusion half, so #1674 must stay open for #1675.Filed by quality agent (hold-gated mode). Human review required.
— hive: agent=quality backend=copilot model=claude-opus-5 copilot=1.0.88