Skip to content

test: cover the keys useFocusTrap deliberately ignores - #1126

Merged
mrbobbytables merged 1 commit into
mainfrom
quality/test-focus-trap-indifferent-keys
Oct 7, 2026
Merged

mrbobbytables merged 1 commit into
mainfrom
quality/test-focus-trap-indifferent-keys

Conversation

@hivecommons-hive

Copy link
Copy Markdown
Contributor

Test Improvement

Adds tests/e2e/focus-trap-indifferent-keys.spec.js (new file, nothing else
touched): two cases, one per lightbox, that assert
src/components/hooks/useFocusTrap.js ignores a key that is neither Tab nor
Escape.

The hook installs one document-level keydown listener for the lifetime of
an open dialog, so it sees every key pressed anywhere on the page. Its Escape
arm (community-people.spec.js:191, member-directory.spec.js) and both Tab
wrap arms (community-people.spec.js:202, interactions.spec.js:217) are
already driven in a browser. The early return at line 33 was not:

33  if (event.key !== 'Tab') return;

Without it, a keystroke made with focus on the dialog's last focusable
element satisfies !event.shiftKey && document.activeElement === last and pulls
focus back to the close button — typing in a dialog, or arrow-scrolling it,
would bounce focus on every keypress. Each case therefore Shift+Tabs to the last
focusable before pressing its indifferent key, which is the only position from
which the guard's removal is observable, reads focus back as an index into the
same button, a[href] list the trap queries (so the assertion is "focus did not
move", not "something is focused"), and presses Escape afterwards to show the
listener was still attached.

Verification

All run locally at 7ab301e against npm run build:e2e:coverage:

  • npx playwright test tests/e2e/focus-trap-indifferent-keys.spec.js — 2 passed.
  • Full npm run test:e2e:coverage with the spec in place — 324 passed.
  • npm run test:unit — 1966 passed, 0 failed.
  • npx prettier --check on the new file — clean.
  • Mutation check. Replacing line 33 with a comment and rebuilding, then
    running this spec plus community-people.spec.js, member-directory.spec.js
    and interactions.spec.js: 4 failed, 33 passed. Both new cases fail, as
    do the two pre-existing Shift+Tab wrap cases (the bare Shift keydown now
    falls through and fires the backward wrap before Tab is ever pressed) — which
    is the same defect seen from a different angle, not this guard's own coverage.

Region effect. Two full test:e2e:coverage runs against the same build:

without: src/components/hooks/useFocusTrap.js | 100.00 | 85.71 |  | 33 35 51
with:    src/components/hooks/useFocusTrap.js | 100.00 | 90.48 |  | 35 51
without: src files | 100.00 | 91.45 | 2099/2099 lines | 417/456 regions
with:    src files | 100.00 | 91.61 | 2099/2099 lines | 415/453 regions

Unlike #1097 and #1120, region 33 does leave the uncovered list: no second
build is involved, so there is no region-attribution mismatch (#1066, #1079).
The denominator still moves (456 → 453), the moving-denominator effect described
in #1105. Both figures clear --check-source-regions 80
(.github/workflows/ci.yml:233) and the 86 proposed in #1105.

Scope

Lines 35 and 51 of the hook stay uncovered and are out of scope: every
shipped dialog contains a close button, so !focusable?.length has no
browser-reachable case, and both call sites always pass a live triggerRef, so
the previousFocus fallback has none either. Nothing is left for #1125 to track
once this merges.

Coordination

Claims only tests/e2e/focus-trap-indifferent-keys.spec.js (new) and only
region src/components/hooks/useFocusTrap.js:33. Disjoint from #1120, which
claims tests/e2e/dialog-non-dismissing.spec.js and the backdrop onMouseDown
guards at MemberProfile.js:15 / CommunityPeople/index.js:32. No overlay
under tests/e2e/fixtures/ (#1104, #1117, #1124), no
tests/tools/e2e-coverage-report.mjs (#1040, #1070, #1114), no
tests/tools/e2e-data-fixtures.cjs (#1110), no workflow file.

Related Issue

Closes #1125


Human review required.

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

useFocusTrap installs one document-level keydown listener for the lifetime
of an open lightbox, so it sees every key pressed on the page. Its Escape
arm and both Tab wrap arms are driven in a browser already; the early
return at line 33 that makes it indifferent to every other key had no
end-to-end assertion, and the e2e coverage report listed region 33 as
uncovered.

Each case opens a lightbox, Shift+Tab's to the dialog's last focusable --
the only position from which removing the guard is observable, since a key
falling through would then satisfy the forward wrap and pull focus back to
the close button -- presses a non-Tab, non-Escape key, and asserts focus
has not moved. Escape afterwards shows the listener was still attached.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: quality <quality@hive.kubestellar.io>
@hivecommons-hive hivecommons-hive Bot added the hold label Oct 6, 2026
@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. Non-outreach agents are held at ACMM L3–L5; the outreach agent is always held because it publishes project-facing communication.

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.

@mrbobbytables
mrbobbytables added this pull request to the merge queue Oct 7, 2026
Merged via the queue into main with commit 1a0655d Oct 7, 2026
7 checks passed
@mrbobbytables
mrbobbytables deleted the quality/test-focus-trap-indifferent-keys branch October 7, 2026 15:19
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] useFocusTrap's non-Tab/non-Escape early return has no end-to-end coverage

1 participant