feat(drill): PayerPeekContent + usePayerSummary + api.getPayerSummary
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
// @vitest-environment happy-dom
|
||||
// PayerPeekContent uses useQuery internally via usePayerSummary. We mock
|
||||
// that hook here so the component can be rendered without standing up a
|
||||
// real QueryClient — same pattern as useClaimDetail.test.ts.
|
||||
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
|
||||
import { afterEach, describe, it, expect, vi } from "vitest";
|
||||
import { cleanup, render, screen } from "@testing-library/react";
|
||||
import { MemoryRouter } from "react-router-dom";
|
||||
import type { ReactNode } from "react";
|
||||
import { PayerPeekContent } from "@/components/drill/PayerPeekContent";
|
||||
|
||||
// Mock the hook BEFORE the import above is resolved (vitest hoists
|
||||
// `vi.mock` to the top of the file regardless of where it appears
|
||||
// syntactically).
|
||||
vi.mock("@/hooks/usePayerSummary", () => ({
|
||||
usePayerSummary: vi.fn(),
|
||||
}));
|
||||
|
||||
// Importing after vi.mock so we get the mocked reference.
|
||||
import { usePayerSummary } from "@/hooks/usePayerSummary";
|
||||
|
||||
// happy-dom keeps `document.body` between tests; without cleanup,
|
||||
// `screen.getByText(...)` would find nodes from earlier renders.
|
||||
afterEach(() => cleanup());
|
||||
|
||||
// The component renders <Link>, which requires a router context. A bare
|
||||
// MemoryRouter with no initialEntries is enough — the test asserts on the
|
||||
// generated href, not on navigation.
|
||||
function withRouter(node: ReactNode) {
|
||||
return <MemoryRouter>{node}</MemoryRouter>;
|
||||
}
|
||||
|
||||
const SAMPLE_PAYER = {
|
||||
payer_id: "SKCO0",
|
||||
name: "CO Medicaid",
|
||||
claim_count: 1247,
|
||||
billed_total: 548000,
|
||||
received_total: 521000,
|
||||
denial_rate: 0.042,
|
||||
top_providers: [{ npi: "1881068062", count: 184 }],
|
||||
} as const;
|
||||
|
||||
describe("PayerPeekContent", () => {
|
||||
it("renders loading skeleton while fetching", () => {
|
||||
// Idle / in-flight state — `data` undefined, `isLoading` true.
|
||||
// The component renders <Skeleton variant="row" /> rows and no text,
|
||||
// so the regex match for /claims/i must come back null (the
|
||||
// "View all claims" link only renders once data is present).
|
||||
(usePayerSummary as unknown as ReturnType<typeof vi.fn>).mockReturnValue({
|
||||
data: undefined,
|
||||
isLoading: true,
|
||||
});
|
||||
|
||||
render(withRouter(<PayerPeekContent payerId="SKCO0" />));
|
||||
expect(screen.queryByText(/claims/i)).toBeNull();
|
||||
});
|
||||
|
||||
it("renders summary stats when data loads", () => {
|
||||
(usePayerSummary as unknown as ReturnType<typeof vi.fn>).mockReturnValue({
|
||||
data: SAMPLE_PAYER,
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
render(withRouter(<PayerPeekContent payerId="SKCO0" />));
|
||||
|
||||
expect(screen.getByText("CO Medicaid")).toBeTruthy();
|
||||
// fmt.num(1247) === "1,247" — the "184 claims" line also matches this
|
||||
// regex, but getByText with a regex is fine because we only assert
|
||||
// existence.
|
||||
expect(screen.getByText(/1,247/)).toBeTruthy();
|
||||
expect(screen.getByText("$548,000")).toBeTruthy();
|
||||
// denial_rate is a fraction (0.042); fmt.pct(payer.denial_rate * 100)
|
||||
// yields "4.2%".
|
||||
expect(screen.getByText("4.2%")).toBeTruthy();
|
||||
|
||||
const link = screen.getByRole("link", { name: /view all claims/i });
|
||||
expect(link.getAttribute("href")).toBe("/claims?payer=SKCO0");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user