diff --git a/src/pages/BatchDiff.test.tsx b/src/pages/BatchDiff.test.tsx index 37cb37b..c58c612 100644 --- a/src/pages/BatchDiff.test.tsx +++ b/src/pages/BatchDiff.test.tsx @@ -29,6 +29,7 @@ vi.mock("@/lib/api", () => ({ isConfigured: true, listBatches: vi.fn(), getBatchDiff: vi.fn(), + getRemittance: vi.fn(), }, ApiError: class ApiError extends Error { constructor(public status: number, message: string) { @@ -180,6 +181,13 @@ function makeDiffPayload(): BatchDiffResponse { describe("BatchDiff page", () => { beforeEach(() => { vi.clearAllMocks(); + // Default for the per-remit detail fetch — the drawer fetches + // this whenever `?remit=` is in the URL. Return a never-resolving + // promise so the drawer stays in the loading state; the smoke + // test only asserts the drawer mounts, not the loaded data. + ( + api.getRemittance as unknown as ReturnType + ).mockReturnValue(new Promise(() => {})); }); afterEach(() => { @@ -189,6 +197,42 @@ describe("BatchDiff page", () => { .happyDOM.setURL("http://localhost/batch-diff"); }); + it("SP21 Task 4.5: deep-link ?remit=ID opens the RemitDrawer on mount", async () => { + // Pre-set the URL with `?remit=`. The page doesn't surface any + // per-remit rows (the diff payload is claim-level), but the + // drawer must still mount so the URL contract is honored. + // + // `useRemitDrawerUrlState` reads `window.location.search` + // (NOT React Router's search params) so we have to set the + // global window URL via happyDOM AND give MemoryRouter an + // initial entry — both stay in lockstep. + (window as unknown as { happyDOM: { setURL: (u: string) => void } }) + .happyDOM.setURL("http://localhost/batch-diff?remit=REM-7"); + ( + api.listBatches as unknown as ReturnType + ).mockResolvedValue([BATCH_A, BATCH_B]); + + const { unmount } = renderIntoContainer( + , + ["/batch-diff?remit=REM-7"], + ); + + // Page header must be present + drawer must be open. + await waitFor( + () => !!document.querySelector('[data-testid="batch-diff-page"]'), + "page header mounted", + ); + await waitFor( + () => !!document.querySelector('[data-testid="remit-drawer"]'), + "remit drawer mounted via deep link", + ); + expect( + document.querySelector('[data-testid="remit-drawer"]'), + ).not.toBeNull(); + + unmount(); + }); + it("renders the awaiting-picks empty state when no batches are selected", async () => { (api.listBatches as unknown as ReturnType).mockResolvedValue([ BATCH_A, BATCH_B, diff --git a/src/pages/BatchDiff.tsx b/src/pages/BatchDiff.tsx index 14607ea..b4762cf 100644 --- a/src/pages/BatchDiff.tsx +++ b/src/pages/BatchDiff.tsx @@ -21,8 +21,10 @@ import { BatchDiffView, BatchDiffViewSkeleton, } from "@/components/BatchDiffView"; +import { RemitDrawer } from "@/components/RemitDrawer"; import { useBatches } from "@/hooks/useBatches"; import { useBatchDiff } from "@/hooks/useBatchDiff"; +import { useRemitDrawerUrlState } from "@/hooks/useRemitDrawerUrlState"; import type { BatchSummary as ApiBatchSummary } from "@/lib/api"; import { cn } from "@/lib/utils"; @@ -269,6 +271,14 @@ export function BatchDiff() { // BrowserRouter (production) symmetric: both update the // useSearchParams hook on every navigation. const [searchParams, setSearchParams] = useSearchParams(); + // SP21 Phase 4 Task 4.5: mount the RemitDrawer so a deep link to + // /batch-diff?remit=REM-1 opens the drawer in-place. The BatchDiff + // data model (BatchClaimDiffSummary) is a claim-level projection — + // there are no per-remit IDs in the diff payload to wrap, so no + // click-to-drill is wired here. The drawer still mounts so the + // `?remit=` URL contract is honored when the user lands on this + // page with the param set (e.g. from an external link). + const { remitId, open, close } = useRemitDrawerUrlState(); const { a, b } = useMemo( () => readIdsFromParams(searchParams), [searchParams], @@ -835,6 +845,24 @@ export function BatchDiff() { {ready ? "A vs B" : "awaiting picks"} + + {/* SP21 Phase 4 Task 4.5: RemitDrawer mount. There are no + per-remit rows in the diff payload (the page is a + claim-level projection), so this drawer is for deep-link + support only — clicking a claim row does not open it. + When the user lands on /batch-diff?remit=REM-1, the drawer + opens in-place. The `remits` list is empty so j/k is a + no-op while the drawer is open. */} + { + // BatchDiff has no cheatsheet surface; `?` is a no-op + // here, but the prop is required by the drawer's contract. + }} + /> ); }