fix(test): address Copilot coverage test quality on #401#402
Conversation
Address Copilot review on #401: use userEvent.tab() to verify focus advances when Tab is not trapped, and assert no React unmounted warnings in post-unmount async tests for billing, waitlist, and admin packages.
CI Coverage & Test Summary
Suites: 64 passed, 0 failed (64 total) · Tests: 666 passed, 0 failed (666 total) ✅ All reported test suites passed. Coverage artifacts: Updated at: June 3, 2026 at 5:14 PM PDT |
There was a problem hiding this comment.
Pull request overview
Improves the quality of recently added “coverage-oriented” tests by turning them into regression-catching assertions, primarily around focus handling and post-unmount async behavior (ensuring no React “unmounted component” warnings).
Changes:
AdminDetailDrawertest now usesuserEvent.tab()and asserts focus moves from “Notes” → “Save”.useSignupModetest now spies onconsole.errorto assert no unmounted-component warnings after resolving settings post-unmount.BillingProvidertests add similarconsole.errorassertions across multiple post-unmount async scenarios and useact()to flush microtasks.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| packages/admin/src/components/AdminDetailDrawer.web.test.tsx | Replaces manual Tab key dispatch with userEvent.tab() and adds a concrete focus assertion. |
| packages/waitlist/src/hooks/useSignupMode.test.tsx | Adds console warning assertions for late async resolution after unmount. |
| packages/billing/src/BillingProvider.test.tsx | Adds post-unmount console warning assertions to multiple async resolution/rejection paths, flushing with act(). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const unmountedWarnings = consoleSpy.mock.calls.filter(([message]) => | ||
| String(message).toLowerCase().includes('unmounted') | ||
| ); | ||
| expect(unmountedWarnings).toHaveLength(0); | ||
|
|
||
| consoleSpy.mockRestore(); | ||
| vi.restoreAllMocks(); |
There was a problem hiding this comment.
Fixed in f7e2714.
- Wrapped the spy + test body in
try/finallysoconsoleSpy.mockRestore()runs even when the assertion throws — no spy leakage into later tests. - Added
expectNoUnmountedConsoleWarnings()that scans every argument on eachconsole.errorcall for"unmounted", not just the first arg.
Intent unchanged: we still fail the test if React logs an unmounted-component warning after resolving settings post-unmount. The assertion is stricter (all args) and the setup is safer (guaranteed cleanup).
| const unmountedWarnings = consoleSpy.mock.calls.filter(([message]) => | ||
| String(message).toLowerCase().includes('unmounted') | ||
| ); | ||
| expect(unmountedWarnings).toHaveLength(0); | ||
| consoleSpy.mockRestore(); |
There was a problem hiding this comment.
Fixed in f7e2714.
- This test (plan query resolve after unmount) now uses
try/finallyaround the spy so cleanup always runs. expectNoUnmountedConsoleWarnings()checks allconsole.errorarguments per call.
We still assert no React unmounted warnings after resolvePlan fires post-unmount — same regression guard from #401, just with leak-proof cleanup and broader argument matching.
| const unmountedWarnings = consoleSpy.mock.calls.filter(([message]) => | ||
| String(message).toLowerCase().includes('unmounted') | ||
| ); | ||
| expect(unmountedWarnings).toHaveLength(0); | ||
| consoleSpy.mockRestore(); |
There was a problem hiding this comment.
Fixed in f7e2714.
- RPC resolve-after-unmount test: spy wrapped in
try/finally. - Warning detection uses
args.some(...)across the full argument list.
Still validates that a late ensure_billing_subscription resolution does not trigger setState-on-unmounted warnings — the postcondition is the same, only the harness is more reliable.
| const unmountedWarnings = consoleSpy.mock.calls.filter(([message]) => | ||
| String(message).toLowerCase().includes('unmounted') | ||
| ); | ||
| expect(unmountedWarnings).toHaveLength(0); | ||
| consoleSpy.mockRestore(); |
There was a problem hiding this comment.
Fixed in f7e2714.
- Plan query rejection after unmount:
try/finallyensures the spy is restored on failure. - All console.error args are scanned for unmounted warnings.
Intent preserved: a late plan rejection must not produce React unmounted warnings. We did not remove or soften the assertion — only made cleanup unconditional and matching more complete.
| const unmountedWarnings = consoleSpy.mock.calls.filter(([message]) => | ||
| String(message).toLowerCase().includes('unmounted') | ||
| ); | ||
| expect(unmountedWarnings).toHaveLength(0); | ||
| consoleSpy.mockRestore(); |
There was a problem hiding this comment.
Fixed in f7e2714.
- RPC rejection after unmount: same
try/finally+expectNoUnmountedConsoleWarnings()pattern as the other three billing tests.
Still enforces that rejectRpc after unmount does not log unmounted-component warnings. Broader arg matching makes the check more likely to catch real regressions, not less.
Wrap console.error spies in try/finally so failed assertions cannot leak mocks into later tests, and scan all console.error arguments when detecting React unmounted-component warnings.
diego-artificerinnovations
left a comment
There was a problem hiding this comment.
All six Copilot comments addressed correctly. Approving.
AdminDetailDrawer Tab test ✓ — Replacing the raw KeyboardEvent dispatch with userEvent.tab() and asserting document.activeElement === save is the right fix. The old test only confirmed focus was on notes after a synthetic event the component may have silently ignored; the new test verifies Tab actually advances focus to the next element, which is the real invariant.
BillingProvider four unmount tests ✓ — The consoleSpy + expectNoUnmountedConsoleWarnings pattern is correct:
vi.spyOn(console, 'error').mockImplementation(() => {})captures React's unmounted-component warnings without polluting test output.act(async () => { await Promise.resolve(); })correctly flushes pending microtasks so post-unmount state updates resolve before the assertion.consoleSpy.mockRestore()infinallyguarantees cleanup even on test failure.- Filter on
'unmounted'(case-insensitive) matches React's warning copy.
useSignupMode unmount test ✓ — Same pattern; bare await Promise.resolve() is fine for the renderHook path. Moving vi.restoreAllMocks() into finally is correct.
Pattern is consistent across all three files and matches the ObservabilityProvider precedent from #400.
Summary
Addresses all six Copilot review comments on the promotion PR #401. The coverage tests added in #400 exercised unmount/cancellation paths for coverage but lacked assertions that would catch regressions.
Changes
KeyboardEventTab dispatch withuserEvent.tab()and assert focus advances from Notes → Save (verifies Tab is not trapped when focus is not on the last element).console.errorand assert no unmounted-component warnings after resolving settings post-unmount.console.errorunmount-warning assertions to four post-unmount async tests (plan query resolve/reject, RPC resolve/reject), usingact()to flush updates.Pattern matches
ObservabilityProvider.web.test.tsxfrom #400.Test plan
packages/admin—AdminDetailDrawer.web.test.tsx(17 tests)packages/waitlist—useSignupMode.test.tsx(2 tests)packages/billing—BillingProvider.test.tsx(24 tests)Closes Copilot feedback on #401; merge this into
developbefore promoting tomain.