// @vitest-environment happy-dom // Pure prop-driven component; match the act() convention used by the // other ClaimDrawer test files (ClaimDrawerHeader / ClaimDrawerSkeleton / // ClaimDrawerError / ValidationPanel). (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 { ServiceLinesTable } from "./ServiceLinesTable"; import type { ClaimDetailServiceLine } 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 makeLine( overrides: Partial = {} ): ClaimDetailServiceLine { return { lineNumber: 1, procedureQualifier: "HC", procedureCode: "99213", modifiers: [], charge: 100.0, units: 1, unitType: "UN", serviceDate: "2026-01-10", ...overrides, }; } describe("ServiceLinesTable", () => { it("test_section_label_includes_count", () => { const lines = [ makeLine({ lineNumber: 1 }), makeLine({ lineNumber: 2, procedureCode: "99214" }), makeLine({ lineNumber: 3, procedureCode: "90834" }), ]; const { container, unmount } = renderIntoContainer( ); 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 "Service Lines"; assert case-insensitively and // confirm the visual transform via the className sniff below. const text = (label?.textContent ?? "").toLowerCase(); expect(text).toContain("service lines"); expect(text).toContain("(3)"); // Eyebrow style (uppercase + tracking) — sniff the className. const cls = label?.className ?? ""; expect(cls).toContain("uppercase"); expect(cls).toContain("tracking-[0.18em]"); unmount(); }); it("test_empty_state_renders_when_no_lines", () => { const { container, unmount } = renderIntoContainer( ); const empty = container.querySelector('[data-testid="service-lines-empty"]'); expect(empty).not.toBeNull(); expect(empty?.textContent ?? "").toContain("No service lines"); // No table rendered in the empty state. expect(container.querySelector('[data-testid="service-lines-table"]')).toBeNull(); expect(container.querySelectorAll('[data-testid="service-line-row"]').length).toBe(0); // Section label still present, but with count "(0)". const label = container.querySelector('[data-testid="section-label"]'); expect(label?.textContent ?? "").toContain("(0)"); unmount(); }); it("test_single_line_renders_one_row", () => { const line = makeLine({ lineNumber: 1, procedureQualifier: "HC", procedureCode: "99213", modifiers: [], charge: 150.0, units: 1, unitType: "UN", serviceDate: "2026-01-10", }); const { container, unmount } = renderIntoContainer( ); const rows = container.querySelectorAll('[data-testid="service-line-row"]'); expect(rows.length).toBe(1); // Row carries the line number as a data attribute for test stability. expect(rows[0].getAttribute("data-line-number")).toBe("1"); // Cells: line #, procedure, charge, units, date. const cells = rows[0].querySelectorAll("td"); expect(cells.length).toBe(5); const cellText = Array.from(cells).map((c) => c.textContent ?? ""); expect(cellText[0]).toContain("1"); // line # expect(cellText[1]).toContain("HC 99213"); // procedure expect(cellText[2]).toContain("$150.00"); // charge expect(cellText[3]).toContain("1"); // units count expect(cellText[3]).toContain("UN"); // units type expect(cellText[4]).toContain("2026"); // date (year at minimum) unmount(); }); it("test_multiple_lines_render_in_order", () => { const lines = [ makeLine({ lineNumber: 1, procedureCode: "99213" }), makeLine({ lineNumber: 2, procedureCode: "99214" }), makeLine({ lineNumber: 3, procedureCode: "90834" }), ]; const { container, unmount } = renderIntoContainer( ); const rows = Array.from( container.querySelectorAll('[data-testid="service-line-row"]') ); expect(rows.length).toBe(3); const lineNumbers = rows.map((r) => r.getAttribute("data-line-number")); expect(lineNumbers).toEqual(["1", "2", "3"]); // The procedure cell preserves insertion order across rows. const procedureCells = rows.map((r) => r.querySelectorAll("td")[1]?.textContent ?? "" ); expect(procedureCells[0]).toContain("99213"); expect(procedureCells[1]).toContain("99214"); expect(procedureCells[2]).toContain("90834"); unmount(); }); it("test_procedure_column_shows_qualifier_code_and_modifiers", () => { const line = makeLine({ lineNumber: 1, procedureQualifier: "HC", procedureCode: "99213", modifiers: ["25", "59"], }); const { container, unmount } = renderIntoContainer( ); const procedureCell = container.querySelector( '[data-testid="service-line-row"] td:nth-child(2)' ); expect(procedureCell).not.toBeNull(); const text = procedureCell?.textContent ?? ""; // Base: qualifier + code. expect(text).toContain("HC 99213"); // Modifiers present (separated by middle dots per plan). expect(text).toContain("25"); expect(text).toContain("59"); // The two modifiers should both appear after the procedure code; the // separator used in the impl is the middle dot "·". expect(text).toContain("· 25"); expect(text).toContain("· 59"); unmount(); }); it("test_charge_column_is_formatted_and_mono", () => { const line = makeLine({ lineNumber: 1, charge: 123.45 }); const { container, unmount } = renderIntoContainer( ); const chargeCell = container.querySelector( '[data-testid="service-line-row"] td:nth-child(3)' ); expect(chargeCell).not.toBeNull(); // Currency formatting: fmt.usdPrecise → "$123.45". expect(chargeCell?.textContent ?? "").toContain("$123.45"); // Mono styling — either via Tailwind class or inline style. const cls = chargeCell?.className ?? ""; const style = chargeCell?.getAttribute("style") ?? ""; const isMono = cls.includes("font-mono") || style.includes("--m-font-mono") || style.includes("font-mono"); expect(isMono).toBe(true); // Right-aligned (charge is the headline number, anchored to the right). expect(cls).toContain("text-right"); // Tabular nums so charges align across rows. expect(cls).toContain("tabular-nums"); unmount(); }); it("test_units_and_date_columns", () => { const line = makeLine({ lineNumber: 1, units: 2, unitType: "UN", serviceDate: "2026-01-10", }); const { container, unmount } = renderIntoContainer( ); const cells = container.querySelectorAll( '[data-testid="service-line-row"] td' ); const unitsCell = cells[3]; const dateCell = cells[4]; // Units column: " ". const unitsText = unitsCell?.textContent ?? ""; expect(unitsText).toContain("2"); expect(unitsText).toContain("UN"); // Date column: fmt.date formats "2026-01-10" as a localized // "MMM D, YYYY" string. `new Date("YYYY-MM-DD")` is parsed as UTC // midnight, so the day can roll over by ±1 in non-UTC test // environments — assert only the year and month to stay // timezone-robust without forking fmt.date. const dateText = dateCell?.textContent ?? ""; expect(dateText).toContain("2026"); expect(dateText).toContain("Jan"); // The date cell should NOT be empty (i.e. fmt.date was actually invoked). expect(dateText.trim().length).toBeGreaterThan(0); unmount(); }); });