feat(claim-drawer): payer name opens PeekModal on top of drawer
This commit is contained in:
@@ -6,13 +6,23 @@
|
||||
|
||||
import React, { act } from "react";
|
||||
import { createRoot, type Root } from "react-dom/client";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, vi, beforeEach } from "vitest";
|
||||
import { PartiesGrid } from "./PartiesGrid";
|
||||
import { DrillStackProvider } from "@/components/drill/DrillStackProvider";
|
||||
import type {
|
||||
ClaimDetailAddress,
|
||||
ClaimDetailParties,
|
||||
} from "@/types";
|
||||
|
||||
// Mock the api module so PayerPeekContent's usePayerSummary call
|
||||
// doesn't make a real network request. The spy is set per-test in
|
||||
// beforeEach.
|
||||
vi.mock("@/hooks/usePayerSummary", () => ({
|
||||
usePayerSummary: vi.fn(),
|
||||
}));
|
||||
|
||||
import { usePayerSummary } from "@/hooks/usePayerSummary";
|
||||
|
||||
function renderIntoContainer(element: React.ReactElement): {
|
||||
container: HTMLDivElement;
|
||||
unmount: () => void;
|
||||
@@ -20,8 +30,11 @@ function renderIntoContainer(element: React.ReactElement): {
|
||||
const container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
const root: Root = createRoot(container);
|
||||
// SP21 Phase 5 Task 5.8: PartiesGrid now uses useDrillStack() to
|
||||
// open payer peeks. Wrap every render in a DrillStackProvider so
|
||||
// the hook has a context to read from.
|
||||
act(() => {
|
||||
root.render(element);
|
||||
root.render(<DrillStackProvider>{element}</DrillStackProvider>);
|
||||
});
|
||||
return {
|
||||
container,
|
||||
@@ -279,4 +292,112 @@ describe("PartiesGrid", () => {
|
||||
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("SP21 Task 5.8: payer name is drillable (renders as a button)", () => {
|
||||
// The payer card's name now wraps in a button with the
|
||||
// `drillable` affordance + `data-testid="party-payer-name-drill"`.
|
||||
// The other two cards (billing-provider, subscriber) keep the
|
||||
// name as a plain div.
|
||||
const { container, unmount } = renderIntoContainer(
|
||||
<PartiesGrid parties={makeParties()} />
|
||||
);
|
||||
|
||||
const payerNameBtn = container.querySelector(
|
||||
'[data-testid="party-payer-name-drill"]',
|
||||
);
|
||||
expect(payerNameBtn).not.toBeNull();
|
||||
expect(payerNameBtn?.textContent).toContain("Aetna");
|
||||
|
||||
// The billing-provider and subscriber names stay plain divs.
|
||||
expect(
|
||||
container.querySelector(
|
||||
'[data-testid="party-billing-provider-name-drill"]',
|
||||
),
|
||||
).toBeNull();
|
||||
expect(
|
||||
container.querySelector(
|
||||
'[data-testid="party-subscriber-name-drill"]',
|
||||
),
|
||||
).toBeNull();
|
||||
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("SP21 Task 5.8: clicking the payer name opens the PeekModal", async () => {
|
||||
// Mock the payer summary fetch so PayerPeekContent resolves
|
||||
// without a real network call. The peek modal renders
|
||||
// regardless of fetch state (loading skeleton → content).
|
||||
(usePayerSummary as unknown as ReturnType<typeof vi.fn>).mockReturnValue({
|
||||
data: null,
|
||||
isLoading: true,
|
||||
});
|
||||
|
||||
const { container, unmount } = renderIntoContainer(
|
||||
<PartiesGrid parties={makeParties()} />
|
||||
);
|
||||
|
||||
const payerNameBtn = container.querySelector(
|
||||
'[data-testid="party-payer-name-drill"]',
|
||||
) as HTMLButtonElement | null;
|
||||
expect(payerNameBtn).not.toBeNull();
|
||||
await act(async () => {
|
||||
payerNameBtn!.click();
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
// The PeekModal is a Radix Dialog that portals into document.body.
|
||||
// Look for the [role="dialog"] (modal) with our eyebrow "Payer".
|
||||
const dialogs = document.body.querySelectorAll('[role="dialog"]');
|
||||
// One dialog is the modal itself (the peek). PartiesGrid doesn't
|
||||
// open a drawer, so we expect exactly one.
|
||||
expect(dialogs.length).toBeGreaterThanOrEqual(1);
|
||||
const peekDialog = Array.from(dialogs).find((d) =>
|
||||
d.textContent?.includes("Payer"),
|
||||
);
|
||||
expect(peekDialog).not.toBeNull();
|
||||
expect(peekDialog?.textContent).toContain("Aetna");
|
||||
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("SP21 Task 5.8: peek uses the X12 payer id, not the human name", () => {
|
||||
// The PayerPeekContent is given the X12 payer id from
|
||||
// `payer.id`. Verify the click flow passes the right id by
|
||||
// checking usePayerSummary was called with "PAYER01".
|
||||
const mockUsePayerSummary = vi.fn().mockReturnValue({
|
||||
data: null,
|
||||
isLoading: true,
|
||||
});
|
||||
(usePayerSummary as unknown as ReturnType<typeof vi.fn>).mockImplementation(
|
||||
mockUsePayerSummary,
|
||||
);
|
||||
|
||||
const { container, unmount } = renderIntoContainer(
|
||||
<PartiesGrid parties={makeParties()} />
|
||||
);
|
||||
|
||||
// No peek yet → no usePayerSummary call.
|
||||
expect(mockUsePayerSummary).not.toHaveBeenCalled();
|
||||
|
||||
const payerNameBtn = container.querySelector(
|
||||
'[data-testid="party-payer-name-drill"]',
|
||||
) as HTMLButtonElement | null;
|
||||
act(() => {
|
||||
payerNameBtn!.click();
|
||||
});
|
||||
|
||||
// After the click, PayerPeekContent mounts and calls
|
||||
// usePayerSummary("PAYER01") — NOT the human name "Aetna".
|
||||
expect(mockUsePayerSummary).toHaveBeenCalledWith("PAYER01");
|
||||
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset the mock between tests so per-test implementations stick.
|
||||
(usePayerSummary as unknown as ReturnType<typeof vi.fn>).mockReturnValue({
|
||||
data: null,
|
||||
isLoading: true,
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user