feat(frontend): PartiesGrid with billing provider/subscriber/payer cards

This commit is contained in:
Tyler
2026-06-20 11:38:51 -06:00
parent 042597217f
commit 94a39e9dd6
2 changed files with 437 additions and 0 deletions
@@ -0,0 +1,281 @@
// @vitest-environment happy-dom
// Pure prop-driven component; match the act() convention used by the
// other ClaimDrawer test files (ClaimDrawerHeader / ClaimDrawerSkeleton /
// ClaimDrawerError / ValidationPanel / ServiceLinesTable / DiagnosesList).
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
import React, { act } from "react";
import { createRoot, type Root } from "react-dom/client";
import { describe, expect, it } from "vitest";
import { PartiesGrid } from "./PartiesGrid";
import type {
ClaimDetailAddress,
ClaimDetailParties,
} from "@/types";
function renderIntoContainer(element: React.ReactElement): {
container: HTMLDivElement;
unmount: () => void;
} {
const container = document.createElement("div");
document.body.appendChild(container);
const root: Root = createRoot(container);
act(() => {
root.render(element);
});
return {
container,
unmount: () => {
act(() => root.unmount());
container.remove();
},
};
}
function makeAddress(
overrides: Partial<ClaimDetailAddress> = {}
): ClaimDetailAddress {
return {
line1: "100 Main St",
line2: null,
city: "Springfield",
state: "IL",
zip: "62701",
...overrides,
};
}
function makeParties(
overrides: Partial<ClaimDetailParties> = {}
): ClaimDetailParties {
return {
billingProvider: {
name: "Acme Medical Group",
npi: "1234567890",
taxId: "12-3456789",
address: makeAddress(),
},
subscriber: {
firstName: "Jane",
lastName: "Doe",
memberId: "MEM12345",
dob: "1980-05-15",
gender: "F",
},
payer: {
name: "Aetna",
id: "PAYER01",
},
...overrides,
};
}
describe("PartiesGrid", () => {
it("test_section_label_is_parties", () => {
const { container, unmount } = renderIntoContainer(
<PartiesGrid parties={makeParties()} />
);
const label = container.querySelector('[data-testid="section-label"]');
expect(label).not.toBeNull();
// `uppercase` is applied via Tailwind CSS, so textContent gives the
// source-case string "Parties"; assert case-insensitively and
// confirm the visual transform via the className sniff below.
const text = (label?.textContent ?? "").toLowerCase();
expect(text).toContain("parties");
// Eyebrow style (uppercase + tracking) — sniff the className.
const cls = label?.className ?? "";
expect(cls).toContain("uppercase");
expect(cls).toContain("tracking-[0.18em]");
unmount();
});
it("test_three_cards_rendered_for_each_party", () => {
const { container, unmount } = renderIntoContainer(
<PartiesGrid parties={makeParties()} />
);
const billing = container.querySelector('[data-testid="party-billing-provider"]');
const subscriber = container.querySelector('[data-testid="party-subscriber"]');
const payer = container.querySelector('[data-testid="party-payer"]');
expect(billing).not.toBeNull();
expect(subscriber).not.toBeNull();
expect(payer).not.toBeNull();
unmount();
});
it("test_billing_provider_card_shows_name_npi_taxId_and_address", () => {
const { container, unmount } = renderIntoContainer(
<PartiesGrid parties={makeParties()} />
);
const card = container.querySelector(
'[data-testid="party-billing-provider"]'
);
expect(card).not.toBeNull();
const text = card?.textContent ?? "";
expect(text).toContain("Acme Medical Group");
expect(text).toContain("1234567890");
expect(text).toContain("12-3456789");
// Address components.
expect(text).toContain("100 Main St");
expect(text).toContain("Springfield");
expect(text).toContain("IL");
expect(text).toContain("62701");
unmount();
});
it("test_subscriber_card_shows_first_last_memberId_and_gender", () => {
const { container, unmount } = renderIntoContainer(
<PartiesGrid parties={makeParties()} />
);
const card = container.querySelector('[data-testid="party-subscriber"]');
expect(card).not.toBeNull();
const text = card?.textContent ?? "";
expect(text).toContain("Jane");
expect(text).toContain("Doe");
expect(text).toContain("MEM12345");
expect(text).toContain("F");
// DOB is also present in this fixture.
expect(text).toContain("1980");
unmount();
});
it("test_subscriber_card_omits_dob_when_null", () => {
const { container, unmount } = renderIntoContainer(
<PartiesGrid
parties={makeParties({
subscriber: {
firstName: "John",
lastName: "Smith",
memberId: "MEM99999",
dob: null,
gender: "M",
},
})}
/>
);
const card = container.querySelector('[data-testid="party-subscriber"]');
expect(card).not.toBeNull();
const text = (card?.textContent ?? "").toLowerCase();
// Still renders the identity pieces.
expect(text).toContain("john");
expect(text).toContain("smith");
expect(text).toContain("mem99999");
expect(text).toContain("m");
// No "undefined" or "null" stringification for the absent DOB.
expect(text).not.toContain("undefined");
expect(text).not.toContain("null");
unmount();
});
it("test_payer_card_shows_name_and_id", () => {
const { container, unmount } = renderIntoContainer(
<PartiesGrid parties={makeParties()} />
);
const card = container.querySelector('[data-testid="party-payer"]');
expect(card).not.toBeNull();
const text = card?.textContent ?? "";
expect(text).toContain("Aetna");
expect(text).toContain("PAYER01");
unmount();
});
it("test_billing_provider_address_line2_rendered_when_present", () => {
const { container, unmount } = renderIntoContainer(
<PartiesGrid
parties={makeParties({
billingProvider: {
name: "Suite Clinic",
npi: "1111111111",
taxId: "99-9999999",
address: makeAddress({
line1: "200 Oak Ave",
line2: "Suite 300",
city: "Portland",
state: "OR",
zip: "97201",
}),
},
})}
/>
);
const card = container.querySelector(
'[data-testid="party-billing-provider"]'
);
const text = card?.textContent ?? "";
expect(text).toContain("200 Oak Ave");
expect(text).toContain("Suite 300");
expect(text).toContain("Portland");
expect(text).toContain("OR");
expect(text).toContain("97201");
unmount();
});
it("test_empty_address_object_does_not_render_garbage", () => {
// The backend can return `{}` for a missing address (per the
// ClaimDetailBillingProvider type's `Record<string, never>` branch).
// The component must not stringify an empty object.
const { container, unmount } = renderIntoContainer(
<PartiesGrid
parties={makeParties({
billingProvider: {
name: "Addrless Clinic",
npi: "2222222222",
taxId: "11-1111111",
address: {},
},
})}
/>
);
const card = container.querySelector(
'[data-testid="party-billing-provider"]'
);
const text = (card?.textContent ?? "").toLowerCase();
expect(text).toContain("addrless clinic");
expect(text).toContain("2222222222");
expect(text).not.toContain("[object object]");
expect(text).not.toContain("undefined");
expect(text).not.toContain("null");
unmount();
});
it("test_responsive_grid_uses_3_columns_on_desktop", () => {
const { container, unmount } = renderIntoContainer(
<PartiesGrid parties={makeParties()} />
);
// The section follows the established `flex flex-col gap-3 px-6 py-4`
// pattern; the responsive grid is the inner wrapper that holds the
// three party cards. Sniff its className.
const grid = container.querySelector('[data-testid="parties-grid-inner"]');
expect(grid).not.toBeNull();
const cls = grid?.className ?? "";
expect(cls).toContain("grid");
expect(cls).toContain("grid-cols-1");
expect(cls).toContain("md:grid-cols-3");
expect(cls).toContain("gap-4");
unmount();
});
});