feat(upload): streamed claim cards offer drill to persisted entity
This commit is contained in:
@@ -0,0 +1,270 @@
|
||||
// @vitest-environment happy-dom
|
||||
// Tell React this is an `act`-aware test environment so react-query's
|
||||
// internal state updates flush through without noisy console warnings.
|
||||
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT =
|
||||
true;
|
||||
|
||||
import React, { act, useEffect } from "react";
|
||||
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
||||
import { MemoryRouter, useLocation } from "react-router-dom";
|
||||
import { createRoot, type Root } from "react-dom/client";
|
||||
import { ClaimCard837, ClaimCard835 } from "./Upload";
|
||||
import { useAppStore } from "@/store";
|
||||
import type { ClaimOutput, ClaimPayment, ParsedBatch } from "@/types";
|
||||
|
||||
// Fixtures — kept tiny, just enough to exercise the drill logic.
|
||||
const CLAIM_837: ClaimOutput = {
|
||||
claim_id: "CLM-PERSISTED",
|
||||
subscriber: {
|
||||
first_name: "Jane",
|
||||
last_name: "Doe",
|
||||
member_id: "MEM-1",
|
||||
},
|
||||
payer: { name: "Test Payer", id: "P1" },
|
||||
billing_provider: { npi: "1234567890" },
|
||||
claim: {
|
||||
total_charge: 100,
|
||||
place_of_service: "11",
|
||||
frequency_code: "1",
|
||||
prior_auth: null,
|
||||
},
|
||||
service_lines: [
|
||||
{
|
||||
line_number: 1,
|
||||
procedure: { qualifier: "HC", code: "99213", modifiers: [] },
|
||||
charge: "100",
|
||||
units: "1",
|
||||
unit_type: "UN",
|
||||
service_date: "2026-06-01",
|
||||
},
|
||||
],
|
||||
diagnoses: [{ qualifier: "ABK", code: "J20.9" }],
|
||||
validation: { passed: true, errors: [], warnings: [] },
|
||||
};
|
||||
|
||||
const CLAIM_835: ClaimPayment = {
|
||||
payer_claim_control_number: "PCN-PERSISTED",
|
||||
status_code: "1",
|
||||
status_label: "Processed as Primary",
|
||||
claim_filing_indicator: "CI",
|
||||
facility_type: "11",
|
||||
frequency_code: "1",
|
||||
total_charge: "100",
|
||||
total_paid: "80",
|
||||
patient_responsibility: "20",
|
||||
service_payments: [
|
||||
{
|
||||
line_number: 1,
|
||||
procedure_qualifier: "HC",
|
||||
procedure_code: "99213",
|
||||
modifiers: [],
|
||||
service_date: "2026-06-01",
|
||||
units: "1",
|
||||
unit_type: "UN",
|
||||
charge: "100",
|
||||
payment: "80",
|
||||
adjustments: [],
|
||||
},
|
||||
],
|
||||
original_claim_id: null,
|
||||
};
|
||||
|
||||
// Mount the card inside a MemoryRouter so the useNavigate call has
|
||||
// a router context (without this, clicking the drill link would
|
||||
// throw "useNavigate may be used only in a Router").
|
||||
function renderCard(
|
||||
element: React.ReactElement,
|
||||
initialEntries: string[] = ["/upload"],
|
||||
): {
|
||||
container: HTMLDivElement;
|
||||
unmount: () => void;
|
||||
tracker: { pathname: string; search: string };
|
||||
} {
|
||||
const container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
const tracker = { pathname: "/upload", search: "" };
|
||||
const Tracker = () => {
|
||||
const loc = useLocation();
|
||||
useEffect(() => {
|
||||
tracker.pathname = loc.pathname;
|
||||
tracker.search = loc.search;
|
||||
}, [loc.pathname, loc.search]);
|
||||
return null;
|
||||
};
|
||||
const root: Root = createRoot(container);
|
||||
act(() => {
|
||||
root.render(
|
||||
React.createElement(
|
||||
MemoryRouter,
|
||||
{ initialEntries },
|
||||
React.createElement(Tracker, null),
|
||||
element,
|
||||
),
|
||||
);
|
||||
});
|
||||
return {
|
||||
container,
|
||||
unmount: () => {
|
||||
act(() => root.unmount());
|
||||
container.remove();
|
||||
},
|
||||
tracker,
|
||||
};
|
||||
}
|
||||
|
||||
describe("ClaimCard837 / ClaimCard835 drill link (SP21 Phase 5 Task 5.7)", () => {
|
||||
beforeEach(() => {
|
||||
// Reset parsedBatches to empty by default — individual tests
|
||||
// populate it as needed.
|
||||
useAppStore.setState({ parsedBatches: [] });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
useAppStore.setState({ parsedBatches: [] });
|
||||
});
|
||||
|
||||
it("ClaimCard837: renders the drill link when the claim_id is in a persisted batch", async () => {
|
||||
// Pre-populate the store with a parsed batch that contains this
|
||||
// claim id, then expand the card and verify the link is present.
|
||||
const persisted: ParsedBatch = {
|
||||
id: "batch-1",
|
||||
kind: "837p",
|
||||
inputFilename: "test.837",
|
||||
parsedAt: "2026-06-21T12:00:00Z",
|
||||
claimCount: 1,
|
||||
passed: 1,
|
||||
failed: 0,
|
||||
claimIds: ["CLM-PERSISTED"],
|
||||
summary: { total_claims: 1, passed: 1, failed: 0 },
|
||||
};
|
||||
useAppStore.setState({ parsedBatches: [persisted] });
|
||||
|
||||
const { container, unmount } = renderCard(
|
||||
React.createElement(ClaimCard837, { claim: CLAIM_837 }),
|
||||
);
|
||||
|
||||
// Expand the card.
|
||||
const header = container.querySelector("button[aria-expanded]");
|
||||
await act(async () => {
|
||||
(header as HTMLButtonElement).click();
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
// The drill link should now be visible.
|
||||
const link = container.querySelector('[data-testid="upload-claim-drill"]');
|
||||
expect(link).not.toBeNull();
|
||||
expect(link?.textContent).toContain("See claim in detail");
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("ClaimCard837: does NOT render the drill link when the claim_id is not persisted", async () => {
|
||||
// Streaming-only claim — the parsedBatches slice is empty, so
|
||||
// the link must be absent (clicking would 404 ClaimDrawer).
|
||||
const { container, unmount } = renderCard(
|
||||
React.createElement(ClaimCard837, { claim: CLAIM_837 }),
|
||||
);
|
||||
|
||||
const header = container.querySelector("button[aria-expanded]");
|
||||
await act(async () => {
|
||||
(header as HTMLButtonElement).click();
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(
|
||||
container.querySelector('[data-testid="upload-claim-drill"]'),
|
||||
).toBeNull();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("ClaimCard837: clicking the drill link navigates to /claims?claim=ID", async () => {
|
||||
const persisted: ParsedBatch = {
|
||||
id: "batch-1",
|
||||
kind: "837p",
|
||||
inputFilename: "test.837",
|
||||
parsedAt: "2026-06-21T12:00:00Z",
|
||||
claimCount: 1,
|
||||
passed: 1,
|
||||
failed: 0,
|
||||
claimIds: ["CLM-PERSISTED"],
|
||||
summary: { total_claims: 1, passed: 1, failed: 0 },
|
||||
};
|
||||
useAppStore.setState({ parsedBatches: [persisted] });
|
||||
|
||||
const { container, unmount, tracker } = renderCard(
|
||||
React.createElement(ClaimCard837, { claim: CLAIM_837 }),
|
||||
);
|
||||
|
||||
const header = container.querySelector("button[aria-expanded]");
|
||||
await act(async () => {
|
||||
(header as HTMLButtonElement).click();
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
const link = container.querySelector(
|
||||
'[data-testid="upload-claim-drill"]',
|
||||
) as HTMLButtonElement;
|
||||
await act(async () => {
|
||||
link.click();
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(tracker.pathname).toBe("/claims");
|
||||
expect(tracker.search).toBe("?claim=CLM-PERSISTED");
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("ClaimCard835: renders the drill link when the PCN is persisted", async () => {
|
||||
const persisted: ParsedBatch = {
|
||||
id: "batch-1",
|
||||
kind: "835",
|
||||
inputFilename: "test.835",
|
||||
parsedAt: "2026-06-21T12:00:00Z",
|
||||
claimCount: 1,
|
||||
passed: 1,
|
||||
failed: 0,
|
||||
claimIds: ["PCN-PERSISTED"],
|
||||
summary: { total_claims: 1, passed: 1, failed: 0 },
|
||||
};
|
||||
useAppStore.setState({ parsedBatches: [persisted] });
|
||||
|
||||
const { container, unmount, tracker } = renderCard(
|
||||
React.createElement(ClaimCard835, { claim: CLAIM_835 }),
|
||||
);
|
||||
|
||||
const header = container.querySelector("button[aria-expanded]");
|
||||
await act(async () => {
|
||||
(header as HTMLButtonElement).click();
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
const link = container.querySelector('[data-testid="upload-remit-drill"]');
|
||||
expect(link).not.toBeNull();
|
||||
await act(async () => {
|
||||
(link as HTMLButtonElement).click();
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
// 835 cards drill to /remittances?remit=PCN (not /claims), since
|
||||
// the RemitDrawer is the right surface for the payment side.
|
||||
expect(tracker.pathname).toBe("/remittances");
|
||||
expect(tracker.search).toBe("?remit=PCN-PERSISTED");
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("ClaimCard835: does NOT render the drill link when the PCN is not persisted", async () => {
|
||||
const { container, unmount } = renderCard(
|
||||
React.createElement(ClaimCard835, { claim: CLAIM_835 }),
|
||||
);
|
||||
|
||||
const header = container.querySelector("button[aria-expanded]");
|
||||
await act(async () => {
|
||||
(header as HTMLButtonElement).click();
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(
|
||||
container.querySelector('[data-testid="upload-remit-drill"]'),
|
||||
).toBeNull();
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user