// @vitest-environment happy-dom import { afterEach, describe, expect, it, vi } from "vitest"; import { act, cleanup, fireEvent, render, waitFor } from "@testing-library/react"; import Inbox from "./Inbox"; import * as inboxApi from "@/lib/inbox-api"; import * as downloadModule from "@/lib/download"; // The Inbox page now gates the Resubmit/Dismiss BulkBars behind // . Tests need an "admin" // principal so the bulk bar still renders — otherwise the // SP8 resubmit-with-download assertion below has nothing to click. vi.mock("@/auth/useAuth", () => ({ useAuth: () => ({ user: { id: 1, username: "test-admin", role: "admin" }, status: "authenticated", }), })); afterEach(() => { cleanup(); vi.unstubAllGlobals(); vi.restoreAllMocks(); }); describe("Inbox page", () => { it("renders the four lane headers", async () => { vi.stubGlobal( "fetch", vi.fn().mockResolvedValue({ ok: true, json: async () => ({ rejected: [], candidates: [], unmatched: [], done_today: [], }), }), ); const { container } = render(); await waitFor(() => { expect(container.textContent).toContain("REJECTED"); expect(container.textContent).toContain("CANDIDATES"); expect(container.textContent).toContain("UNMATCHED"); expect(container.textContent).toContain("DONE"); }); }); it("SP8: multi-select resubmit opens bundle modal; Resubmit + Download triggers api + downloadBlob", async () => { // Two rejected claims → user selects both → Re-submit → modal appears // → user clicks Resubmit + Download → backend is hit with // ?download=true and the returned blob is handed to downloadBlob. const fetchMock = vi.fn().mockImplementation(async (url: string) => { if (url.includes("/api/inbox/lanes")) { return { ok: true, json: async () => ({ rejected: [ { id: "C1", kind: "claim", payer_claim_control_number: "C1", charge_amount: 100, payer_id: "P1", provider_npi: "N1", state: "rejected", rejection_reason: "old", service_date: null, score: null }, { id: "C2", kind: "claim", payer_claim_control_number: "C2", charge_amount: 200, payer_id: "P1", provider_npi: "N1", state: "rejected", rejection_reason: "old", service_date: null, score: null }, ], candidates: [], unmatched: [], done_today: [], }), }; } if (url.includes("/api/inbox/rejected/resubmit")) { return { ok: true, blob: async () => new Blob(["PK\u0003\u0004fake-zip-bytes"], { type: "application/zip" }), headers: { get: (name: string) => name.toLowerCase() === "content-disposition" ? 'attachment; filename="resubmit-2-claims.zip"' : name.toLowerCase() === "x-cyclone-serialize-errors" ? null : null, }, }; } return { ok: true, json: async () => ({}) }; }); vi.stubGlobal("fetch", fetchMock); // Spy on the two side-effects we care about: the api call + the // browser download. Both are module-scope so we install spies and // reset between tests (afterEach already calls vi.restoreAllMocks // but we mockReset to clear call history from other tests). const resubmitDownloadSpy = vi .spyOn(inboxApi, "resubmitRejectedWithDownload") .mockResolvedValue({ blob: new Blob(["PK\u0003\u0004fake-zip-bytes"], { type: "application/zip" }), filename: "resubmit-2-claims.zip", serializeErrors: [], }); const downloadSpy = vi .spyOn(downloadModule, "downloadBlob") .mockImplementation(() => {}); const { container, getByText } = render(); await waitFor(() => { expect(container.textContent).toContain("C1"); expect(container.textContent).toContain("C2"); }); // Select both rejected rows (the checkbox is the only per row). const checkboxes = container.querySelectorAll('input[type="checkbox"]'); expect(checkboxes.length).toBeGreaterThanOrEqual(2); await act(async () => { fireEvent.click(checkboxes[0]); fireEvent.click(checkboxes[1]); }); // BulkBar shows a Re-submit button for the rejected lane. const resubmitBtn = getByText(/Re-submit \(2\)/); await act(async () => { fireEvent.click(resubmitBtn); }); // Modal appears with both options. const modal = container.querySelector( '[data-testid="resubmit-bundle-modal"]', ); expect(modal).not.toBeNull(); const downloadBtn = container.querySelector( '[data-testid="resubmit-modal-download"]', ) as HTMLButtonElement | null; expect(downloadBtn).not.toBeNull(); await act(async () => { fireEvent.click(downloadBtn!); }); // The api was called with the selected ids, and the blob was // handed to downloadBlob with the right filename. expect(resubmitDownloadSpy).toHaveBeenCalledTimes(1); expect(resubmitDownloadSpy).toHaveBeenCalledWith(["C1", "C2"]); expect(downloadSpy).toHaveBeenCalledTimes(1); const [filename, blob] = downloadSpy.mock.calls[0]; expect(filename).toBe("resubmit-2-claims.zip"); expect(blob).toBeInstanceOf(Blob); }); });