// @vitest-environment happy-dom import { describe, expect, it, vi } from "vitest"; import { render, screen, fireEvent } from "@testing-library/react"; import { InboxRow } from "./InboxRow"; import type { InboxClaimRow, InboxCandidateRow } from "@/lib/inbox-api"; const baseClaim: InboxClaimRow = { id: "CLP-7741", kind: "claim", patient_control_number: "PCN-001", charge_amount: 1240, payer_id: "CO-MCD", provider_npi: "1234567890", state: "rejected", rejection_reason: "999 AK9 R", rejected_at: "2026-06-20T09:12:00Z", service_date_from: null, score: null, score_tier: null, score_breakdown: null, }; describe("InboxRow", () => { it("renders the claim id, payer, and formatted charge", () => { const { container } = render( {}} />
, ); expect(container.textContent).toContain("CLP-7741"); expect(container.textContent).toContain("CO-MCD"); expect(container.textContent).toContain("$1,240.00"); }); it("renders the rejection reason when present", () => { const { container } = render( {}} />
, ); expect(container.textContent).toContain("999 AK9 R"); }); it("calls onClick when the row is clicked", () => { const onClick = vi.fn(); const { container } = render(
, ); const row = container.querySelector("tr"); expect(row).not.toBeNull(); fireEvent.click(row!); expect(onClick).toHaveBeenCalledOnce(); }); it("renders the top candidate score for remit rows", () => { const candidateRow: InboxCandidateRow = { id: "R-1", kind: "remit", payer_claim_control_number: "PCN-X", charge_amount: 500, payer_id: "AETNA", rendering_provider_npi: "1234567890", service_date: null, candidates: [ { claim_id: "C-1", score: 92, tier: "strong", breakdown: { patient: 1, date: 1, amount: 1, provider: 1 }, }, ], }; const { container } = render( {}} />
, ); expect(container.textContent).toContain("92"); }); // --------------------------------------------------------------------- // SP29: per-AK2 chip evidence + per-row Resubmit gesture for the // rejected-lane drill. The page renders 999 AK2 set-response codes // inline on the row so the operator can spot "this batch had R/E/X // rejects" without opening the drawer. The per-row Resubmit button // downloads a single corrected 837. // --------------------------------------------------------------------- it("SP29: rejected row renders inline 999 ack evidence chips + Resubmit button", () => { // 4 linked 999 AK2 set-responses — 3 will render as chips, the // 4th triggers the "+N more" overflow indicator. const rejectedRow: InboxClaimRow = { ...baseClaim, id: "REJ1", claim_acks: { total: 4, rejected: 2, items: [ { ack_id: 11, set_control_number: "991102989", set_accept_reject_code: "A", ak2_index: 0, linked_at: "2026-07-02T10:03:00Z", }, { ack_id: 12, set_control_number: "991102989", set_accept_reject_code: "R", ak2_index: 1, linked_at: "2026-07-02T10:02:00Z", }, { ack_id: 13, set_control_number: "991102989", set_accept_reject_code: "E", ak2_index: 2, linked_at: "2026-07-02T10:01:00Z", }, ], }, }; const onResubmitOne = vi.fn(); const { container } = render( {}} onResubmitOne={onResubmitOne} />
, ); // Chip visible — the operator can read at a glance that the most // recent AK2 from this batch was an Accept, then a Reject, then // an Error. The "999 · A" / "999 · R" / "999 · E" text comes from // rendering "·". expect(container.textContent).toContain("991102989·A"); expect(container.textContent).toContain("991102989·R"); expect(container.textContent).toContain("991102989·E"); // Overflow "+1 more" — total=4 but only 3 chips fit. expect(container.textContent).toContain("+1 more"); // Resubmit button rendered with the per-row testid. const btn = screen.getByTestId("resubmit-REJ1") as HTMLButtonElement; expect(btn).toBeTruthy(); expect(btn.textContent).toBe("Resubmit"); // Click fires the per-row handler with the claim id; click does // NOT bubble to onClick (the row-onClick is for drilldown, the // button is a separate download gesture). fireEvent.click(btn); expect(onResubmitOne).toHaveBeenCalledTimes(1); expect(onResubmitOne).toHaveBeenCalledWith("REJ1"); }); it("SP29: rejected row with no linked 999 acks shows the '999 not linked' orphan marker", () => { // A rejected claim whose 999 ack didn't match any ST02 in the // batch — the operator needs to know the drill lacked ack // evidence. The "999 not linked" italic line is the marker. const rejectedRow: InboxClaimRow = { ...baseClaim, id: "REJ-ORPH", claim_acks: { total: 0, rejected: 0, items: [], }, }; const onResubmitOne = vi.fn(); const { container } = render( {}} onResubmitOne={onResubmitOne} />
, ); expect(container.textContent).toContain("999 not linked"); // The Resubmit button still renders so the operator can fix the // claim without first manually linking the orphan. expect(screen.getByTestId("resubmit-REJ-ORPH")).toBeTruthy(); }); });