Skip to content

Cover useNotificationSettings, including its out-of-order-save guard - #805

Merged
selfcontained merged 1 commit into
mainfrom
agt_781723e901cf/job-test-enforcer-6c3be5a2
Jul 21, 2026
Merged

Cover useNotificationSettings, including its out-of-order-save guard#805
selfcontained merged 1 commit into
mainfrom
agt_781723e901cf/job-test-enforcer-6c3be5a2

Conversation

@selfcontained

Copy link
Copy Markdown
Owner

What

useNotificationSettings (302 lines, extracted from notification-settings.tsx by #799) had zero unit coverage. This adds 23 tests.

The hook's riskiest logic is the set of guards that keep overlapping browser-notification saves from clobbering each other:

  • webSaveRequestIdRef gates the success path, the catch, and the finally in persistWebNotificationSettings
  • the optimistic toggles roll back to the saved refs (savedWebEnabledRef / savedWebEventsRef) rather than the current ones

That is exactly the kind of logic that regresses silently — nothing else in the suite touches it.

Coverage added

  • initial load, and load failure leaving defaults intact
  • hasChanges across all four legs (webhook URL, Slack events with order-insensitive comparison, and both browser-notification legs observed mid-flight)
  • handleSave success (server response wins over typed value) and failure, incl. the non-Error rejection fallback
  • handleTest success / ok: false / empty-URL omission
  • optimistic enable + event toggles, and their rollback to the saved baseline on failure
  • three overlapping-save orderings: superseded response landing first, stale response landing after a newer one won, and a stale rejection landing after a newer one won
  • visibilitychange permission re-read, including the document.hidden gate and unmount teardown (with a positive control)
  • handleTestWebNotification gated on permission

A note on the overlapping-save tests

They drive the two toggles in separate act() calls so the ref-sync effect commits in between, matching what two real clicks produce, and they assert both POST bodies.

Batching both toggles into one act() instead leaves webNotifyEnabledRef stale, so the second request posts webNotifyEnabled: false — a state the UI cannot actually reach. The first draft did exactly that, and the tests passed anyway because the mocked response echoed back true. The review pass caught it; asserting the POST bodies is what pins the real contract down.

Verification

Tests verified non-vacuous by 10 source mutations, all 10 caught — including removal of each of the three webSaveRequestIdRef guards, swapping either rollback ref for its current-value counterpart, dropping the document.hidden gate, and the ref-staleness path above.

One mutation deliberately left unkilled: the .sort() on the webNotifyEvents / savedWebEvents comparison in hasChanges. Both values are always assigned from the same source, so their order cannot diverge — it's unreachable defensive code, and a test for it would be contrived.

pnpm run check, pnpm run test (server 2285 / web 268 / ext 51), pnpm run finalize:web, and pnpm run test:e2e (169 pass / 12 skip) all green.

🤖 Generated with Claude Code

useNotificationSettings (302 lines, extracted from notification-settings.tsx
by #799) had no unit coverage. Its riskiest logic is the pair of guards that
keep overlapping browser-notification saves from clobbering each other:
webSaveRequestIdRef gates the success path, the catch, and the finally, and the
optimistic toggles roll back to the *saved* refs rather than the current ones.
That is exactly the kind of logic that regresses silently.
Adds 23 tests covering initial load and load failure, hasChanges across all
four legs, handleSave success/failure, handleTest, the optimistic
enable/event toggles and their rollbacks, three overlapping-save orderings,
the visibilitychange permission re-read and its unmount teardown, and
handleTestWebNotification.
The overlapping-save tests drive the two toggles in separate act() calls so
the ref-sync effect commits in between, matching what two real clicks produce,
and assert both POST bodies. Batching them into one act() instead leaves
webNotifyEnabledRef stale and posts webNotifyEnabled: false — a state the UI
cannot actually reach, which would have made the tests certify the wrong
contract.
Verified non-vacuous: 10 source mutations, all 10 caught.
@selfcontained
selfcontained merged commit f666115 into mainJul 21, 2026
1 check passed
@selfcontained
selfcontained deleted the agt_781723e901cf/job-test-enforcer-6c3be5a2 branch July 21, 2026 02:23
selfcontained added a commit that referenced this pull request Jul 22, 2026
…wiring (#810)
PR #805 covered the useNotificationSettings hook; the component that wires it
into the browser/Slack sections had no direct unit coverage. Add a component
test that renders NotificationSettings with only its api + web-notifications
seams mocked, asserting the wiring rather than re-testing hook logic:
- Save gates on hasChanges and reflects the transient saving state.
- A browser-notification toggle drives the browser-settings POST and its
saved message renders in the Browser section.
- webMessage/webError render in the Browser section and message/error in the
Slack section — neither status slot cross-wires into the other (the silent
bug a mis-wired prop would produce).
- The enable toggle, event toggles, and test button stay behind the granted
permission gate, including when a saved enabled flag outlives its permission.
Verified non-vacuous against 6 source mutations (two cross-wire swaps,
Save-ignores-hasChanges, checkbox/test-button permission gates) — all caught.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for freeto 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.

1 participant

@selfcontained