diff --git a/src/pages/Claims.test.tsx b/src/pages/Claims.test.tsx index 5add8ab..b870ffb 100644 --- a/src/pages/Claims.test.tsx +++ b/src/pages/Claims.test.tsx @@ -326,4 +326,98 @@ describe("Claims page drawer wiring", () => { await settle(() => !window.location.href.includes("?claim=")); expect(window.location.href).not.toContain("?claim="); }); + + it("test_question_mark_toggles_cheatsheet", async () => { + // The cheatsheet keystroke is wired through `useDrawerKeyboard` + // (which lives inside ClaimDrawer and only enables when + // `claimId !== null`). Deep-link with `?claim=CLM-1` so the + // drawer mounts and the keyboard listener is active. + setLocation("http://localhost/claims?claim=CLM-1"); + const { unmount } = renderClaims(); + + // Drawer must be open before we test the toggle. + await settle( + () => document.body.querySelector('[data-testid="claim-drawer"]') !== null + ); + expect( + document.body.querySelector('[data-testid="claim-drawer"]') + ).not.toBeNull(); + + // Cheatsheet starts closed. + expect( + document.body.querySelector('[data-testid="keyboard-cheatsheet"]') + ).toBeNull(); + + // First `?` press: useDrawerKeyboard's handler flips `helpOpen` + // true on the page; the cheatsheet portals into document.body. + await act(async () => { + window.dispatchEvent( + new KeyboardEvent("keydown", { key: "?", bubbles: true }) + ); + }); + await settle( + () => + document.body.querySelector('[data-testid="keyboard-cheatsheet"]') !== + null + ); + expect( + document.body.querySelector('[data-testid="keyboard-cheatsheet"]') + ).not.toBeNull(); + + // Second `?` press: same handler flips `helpOpen` back to false; + // the cheatsheet unmounts. (The cheatsheet's own window-level + // listener explicitly returns early on `?` — it never pre-empts + // the parent's toggle, by design.) + await act(async () => { + window.dispatchEvent( + new KeyboardEvent("keydown", { key: "?", bubbles: true }) + ); + }); + await settle( + () => + document.body.querySelector('[data-testid="keyboard-cheatsheet"]') === + null + ); + expect( + document.body.querySelector('[data-testid="keyboard-cheatsheet"]') + ).toBeNull(); + + // Unmount so the next test (which asserts the drawer is absent) + // doesn't see this test's still-open drawer leaking into + // document.body. + unmount(); + }); + + it("test_question_mark_does_nothing_when_drawer_closed", async () => { + // The `?` keystroke is owned by `useDrawerKeyboard`, which only + // attaches its `window` listener when `claimId !== null`. With no + // `?claim=` in the URL, the drawer is closed and `?` is a no-op + // — the cheatsheet must not appear. + setLocation("http://localhost/claims"); + renderClaims(); + + // Wait for the table to populate (so the page is fully mounted). + await settle( + () => document.body.textContent?.includes("CLM-1") ?? false + ); + expect( + document.body.querySelector('[data-testid="claim-drawer"]') + ).toBeNull(); + + await act(async () => { + window.dispatchEvent( + new KeyboardEvent("keydown", { key: "?", bubbles: true }) + ); + }); + + // No cheatsheet should be present — and not just "not yet": + // flush a tick to make sure a delayed listener couldn't have + // snuck it in. + await act(async () => { + await new Promise((r) => setTimeout(r, 0)); + }); + expect( + document.body.querySelector('[data-testid="keyboard-cheatsheet"]') + ).toBeNull(); + }); }); diff --git a/src/pages/Claims.tsx b/src/pages/Claims.tsx index b6726cf..5b4dae8 100644 --- a/src/pages/Claims.tsx +++ b/src/pages/Claims.tsx @@ -19,6 +19,7 @@ import { import { StatusBadge } from "@/components/StatusBadge"; import { NewClaimDialog } from "@/components/NewClaimDialog"; import { ClaimDrawer } from "@/components/ClaimDrawer"; +import { KeyboardCheatsheet } from "@/components/KeyboardCheatsheet"; import { Skeleton } from "@/components/ui/skeleton"; import { EmptyState } from "@/components/ui/empty-state"; import { ErrorState } from "@/components/ui/error-state"; @@ -48,6 +49,7 @@ export function Claims() { const [npi, setNpi] = useState(ALL); const [query, setQuery] = useState(""); const [page, setPage] = useState(1); + const [helpOpen, setHelpOpen] = useState(false); const searchRef = useRef(null); // SP4 drawer wiring: the URL is the source of truth for "which claim is @@ -114,9 +116,11 @@ export function Claims() { claims={items.map((c) => ({ id: c.id }))} onClose={close} onNavigate={setClaimId} - onToggleHelp={() => { - // T22 wires the KeyboardCheatsheet overlay; no-op for now. - }} + onToggleHelp={() => setHelpOpen((v) => !v)} + /> + setHelpOpen(false)} />