Skip to content

test: pin FilterToolbar's object-option labels against the value/label ternary - #1675

Merged
mrbobbytables merged 1 commit into
mainfrom
quality/test-filter-toolbar-options
Oct 20, 2026
Merged

mrbobbytables merged 1 commit into
mainfrom
quality/test-filter-toolbar-options

Conversation

@hivecommons-hive

Copy link
Copy Markdown
Contributor

Test Improvement

Adds tests/filter-toolbar-options.test.mjs, a contract for the selects[].options handling of src/components/shared/FilterToolbar.js. No production file changes.

FilterToolbar (landed in #1659) accepts two option shapes: a bare string, where value and visible label are the same token, and a { value, label } pair, where they deliberately differ. Only MemberDirectory passes the pair shape — MEMBERSHIP_FILTERS in src/components/MemberDirectory/hooks.js:3, where value unknown is displayed as "Membership not specified".

Why the existing suite does not cover this. Collapsing

const optionLabel = typeof option === 'string' ? option : option.label;

to the value arm leaves the entire unit suite green at main a621619 on node v26.10.0 — TZ=UTC node --test reports zero failures, including tests/member-directory.test.mjs, tests/member-directory-membership-vocabulary.test.mjs, tests/case-studies.test.mjs and tests/architecture-filters-ui.test.mjs (83 tests, 83 pass). Every assertion over a rendered <select> reads option values (optionValues(), tests/member-directory.test.mjs:157) or drives onChange directly; none reads an option's visible text. The coverage gates do not catch it either: npm run test:unit:coverage already reports src files | 100.00 | 100.00 | 9967/9967 lines | 2904/2904 regions, so both arms execute. The gap is behavioural, not a counter gap.

With this file applied, that same mutation makes tests/filter-toolbar-options.test.mjs the only failing file in the suite.

What it pins, calling the component as a plain function the way its callers do (no hook dispatcher needed):

  • string options render the same token as value and as visible label
  • { value, label } options render the label, not the value — asserted against the real MEMBERSHIP_FILTERS
  • one selects entry may mix both shapes
  • each <option> keys off its value, so two options may share a label
  • onSearchChange / onChange / onClear receive the changed value, not the event
  • "Clear filters" renders only while activeCount > 0, with type="button"

Verification at this branch head, node v26.10.0:

  • TZ=UTC node --test — green.
  • npm run test:unit:coverage:check (the CI gate thresholds) — exit 0; the new file reports 100.00 | 100.00, src files and harness files unchanged at their floors.
  • npx prettier --check on the new file — clean.
  • node scripts/check-duplication.mjs — ok, 264 clones against a ceiling of 264 (unchanged).

Scope / overlap. Touches exactly one new file under tests/. Disjoint from every open hold-gated PR: #1673 (MDX active-content link definitions), #1664 (CONTRIBUTING data model), #1666 (CI exemption file references), #1668 (open-automation-pr policy), #1671 (activeScheme scheme boundary). It adds no assertion to tests/member-directory.test.mjs or any other existing file, so it cannot conflict with work in those.

Related Issue

Fixes #1674


Filed by quality agent (hold-gated mode). Human review required.

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

…l ternary

src/components/shared/FilterToolbar.js accepts select options as either a
bare string, where value and visible label are the same token, or a
{ value, label } pair, where they deliberately differ. Only MemberDirectory
passes the pair shape (MEMBERSHIP_FILTERS, e.g. value 'unknown' shown as
'Membership not specified').

Every existing assertion over a rendered <select> reads option values or
drives onChange directly, so collapsing

  const optionLabel = typeof option === 'string' ? option : option.label;

to the value arm leaves the entire unit suite green while the membership
dropdown silently renders raw values. Line and region coverage of src/ is
already 100%, so no coverage gate catches it either.

Add tests/filter-toolbar-options.test.mjs, which owns the shared component's
option contract directly: both option shapes, a mixed list, per-value keying
so two options may share a label, the onSearchChange/onChange/onClear
wiring, and the activeCount gate on 'Clear filters'. Under the mutation
above it is the only failing file in the suite.

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

netlify Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for endusers ready!

Name Link
🔨 Latest commit 0b3bc54
🔍 Latest deploy log https://app.netlify.com/projects/endusers/deploys/6ad6edc1e2d644b4458162b0
😎 Deploy Preview https://deploy-preview-1675--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

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.

@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 the component and ran the suite locally — this looks correct to me.

Verified at head 0b3bc54, node v26.10.0:

  • The six assertions in tests/filter-toolbar-options.test.mjs match src/components/shared/FilterToolbar.js exactly: the value/label ternary (FilterToolbar.js:82-85), key={optionValue} (FilterToolbar.js:87), onChange(event.target.value) for both the search input and selects (FilterToolbar.js:63, 77), and the activeCount > 0 guard on "Clear filters" (FilterToolbar.js:105).
  • The MEMBERSHIP_FILTERS expectations match src/components/MemberDirectory/hooks.js:3-10 verbatim.
  • All 6 tests pass. Mutation check confirms the PR's claim: collapsing the label arm of the ternary to option.value fails 2 of these tests, so the gap this file pins is real.

Test-only change, one new file, no production edits, CI green. No findings.

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

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[quality] FilterToolbar object-option labels are unpinned: collapsing the value/label ternary leaves the whole unit suite green

1 participant