diff --git a/src/pages/Acks.test.tsx b/src/pages/Acks.test.tsx index 3a3d076..8029b57 100644 --- a/src/pages/Acks.test.tsx +++ b/src/pages/Acks.test.tsx @@ -9,13 +9,17 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { Acks } from "./Acks"; import { api } from "@/lib/api"; -vi.mock("@/lib/api", () => ({ - api: { - isConfigured: true, - listAcks: vi.fn(), - getAck: vi.fn(), - }, -})); +vi.mock("@/lib/api", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + api: { + isConfigured: true, + listAcks: vi.fn(), + getAck: vi.fn(), + }, + }; +}); function renderIntoContainer(element: React.ReactElement): { container: HTMLDivElement; @@ -105,6 +109,10 @@ function hasExactlyOneSelectedRow(): boolean { describe("Acks", () => { beforeEach(() => { vi.clearAllMocks(); + // Reset URL state between tests so a previous `?ack=` doesn't leak. + (window as unknown as { happyDOM: { setURL: (u: string) => void } }).happyDOM.setURL( + "http://localhost/acks" + ); }); it("renders a single ack row with counts and ack code", async () => { @@ -448,4 +456,115 @@ describe("Acks", () => { unmount(); }); + + it("test_clicking_a_row_opens_the_ack_drawer", async () => { + // SP21 Phase 5 Task 5.3: clicking an acks row drills into the + // matching ack via `?ack=ID` URL state. The AckDrawer mounts + // but the actual content depends on `useAckDetail` — we don't + // need to verify drawer internals here, just that the URL got + // pushed and the drawer portal opens. + (api.listAcks as unknown as ReturnType).mockResolvedValue({ + items: [ + { + id: 42, + sourceBatchId: "b-uuid-1", + acceptedCount: 3, + rejectedCount: 1, + receivedCount: 4, + ackCode: "P", + parsedAt: "2026-06-20T12:00:00Z", + }, + ], + total: 1, + returned: 1, + has_more: false, + }); + // Stub the per-ack fetch so `useAckDetail` resolves cleanly + // (avoids TanStack Query's "Query data cannot be undefined" + // warning). We only assert on the drawer's presence, so the + // shape doesn't need to be precise. + (api.getAck as unknown as ReturnType).mockResolvedValue({ + id: 42, + sourceBatchId: "b-uuid-1", + acceptedCount: 3, + rejectedCount: 1, + receivedCount: 4, + ackCode: "P", + parsedAt: "2026-06-20T12:00:00Z", + raw_999_text: "ISA*~\n", + }); + + const { unmount } = renderIntoContainer(React.createElement(Acks)); + await waitForText("b-uuid-1"); + + // No drawer yet. + expect( + document.body.querySelector('[data-testid="ack-drawer"]') + ).toBeNull(); + + // Click the row. + const row = rowAt(0); + expect(row).not.toBeNull(); + await act(async () => { + row!.click(); + await Promise.resolve(); + }); + await settle( + () => document.body.querySelector('[data-testid="ack-drawer"]') !== null + ); + + // The drawer is now in the DOM. + expect( + document.body.querySelector('[data-testid="ack-drawer"]') + ).not.toBeNull(); + + unmount(); + }); + + it("test_deep_link_with_ack_param_opens_drawer_on_mount", async () => { + // /acks?ack=42 deep link → drawer opens on mount without a click. + (api.listAcks as unknown as ReturnType).mockResolvedValue({ + items: [ + { + id: 42, + sourceBatchId: "b-uuid-1", + acceptedCount: 3, + rejectedCount: 1, + receivedCount: 4, + ackCode: "P", + parsedAt: "2026-06-20T12:00:00Z", + }, + ], + total: 1, + returned: 1, + has_more: false, + }); + (api.getAck as unknown as ReturnType).mockResolvedValue({ + id: 42, + sourceBatchId: "b-uuid-1", + acceptedCount: 3, + rejectedCount: 1, + receivedCount: 4, + ackCode: "P", + parsedAt: "2026-06-20T12:00:00Z", + raw_999_text: "ISA*~\n", + }); + + (window as unknown as { happyDOM: { setURL: (u: string) => void } }).happyDOM.setURL( + "http://localhost/acks?ack=42" + ); + + const { unmount } = renderIntoContainer(React.createElement(Acks)); + await waitForText("b-uuid-1"); + await settle( + () => document.body.querySelector('[data-testid="ack-drawer"]') !== null + ); + + // The drawer is in the DOM on first render. + expect( + document.body.querySelector('[data-testid="ack-drawer"]') + ).not.toBeNull(); + + unmount(); + }); }); \ No newline at end of file diff --git a/src/pages/Acks.tsx b/src/pages/Acks.tsx index d748abc..42663af 100644 --- a/src/pages/Acks.tsx +++ b/src/pages/Acks.tsx @@ -12,6 +12,8 @@ import { Skeleton } from "@/components/ui/skeleton"; import { EmptyState } from "@/components/ui/empty-state"; import { ErrorState } from "@/components/ui/error-state"; import { KeyboardCheatsheet } from "@/components/KeyboardCheatsheet"; +import { AckDrawer } from "@/components/AckDrawer"; +import { useAckDrawerUrlState } from "@/hooks/useAckDrawerUrlState"; import { useAcks } from "@/hooks/useAcks"; import { useRowKeyboard } from "@/hooks/useRowKeyboard"; import { api } from "@/lib/api"; @@ -91,7 +93,14 @@ function DownloadButton({ id, sourceBatchId }: { id: number; sourceBatchId: stri return (