feat(frontend): ClaimDrawer skeleton + error states
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
// @vitest-environment happy-dom
|
||||
// React Query's internal state updates need an `act`-aware environment or
|
||||
// it logs noisy warnings. Match the convention from useClaimDetail.test.ts.
|
||||
(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 { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { ClaimDrawerSkeleton } from "./ClaimDrawerSkeleton";
|
||||
|
||||
function renderIntoContainer(element: React.ReactElement): {
|
||||
container: HTMLDivElement;
|
||||
unmount: () => void;
|
||||
} {
|
||||
const container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||
const root: Root = createRoot(container);
|
||||
act(() => {
|
||||
root.render(
|
||||
React.createElement(QueryClientProvider, { client: qc }, element)
|
||||
);
|
||||
});
|
||||
return {
|
||||
container,
|
||||
unmount: () => {
|
||||
act(() => root.unmount());
|
||||
container.remove();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe("ClaimDrawerSkeleton", () => {
|
||||
it("renders_eight_to_twelve_skeleton_primitives", () => {
|
||||
// The Skeleton primitive marks itself aria-busy="true" for AT.
|
||||
// Counting aria-busy elements tells us how many primitives rendered,
|
||||
// independent of layout or copy.
|
||||
const { unmount } = renderIntoContainer(<ClaimDrawerSkeleton />);
|
||||
|
||||
const busyCount = document.querySelectorAll('[aria-busy="true"]').length;
|
||||
expect(busyCount).toBeGreaterThanOrEqual(8);
|
||||
expect(busyCount).toBeLessThanOrEqual(12);
|
||||
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("renders_one_primitives_per_drawer_section", () => {
|
||||
// The skeleton must mirror the drawer's section structure so the
|
||||
// layout doesn't jump when real data arrives. Each section gets a
|
||||
// labelled testid primitive.
|
||||
const { container, unmount } = renderIntoContainer(<ClaimDrawerSkeleton />);
|
||||
|
||||
const header = container.querySelector('[data-testid="skeleton-header"]');
|
||||
const validation = container.querySelector(
|
||||
'[data-testid="skeleton-validation"]'
|
||||
);
|
||||
const lines1 = container.querySelector('[data-testid="skeleton-lines-1"]');
|
||||
const lines2 = container.querySelector('[data-testid="skeleton-lines-2"]');
|
||||
const lines3 = container.querySelector('[data-testid="skeleton-lines-3"]');
|
||||
const dx1 = container.querySelector('[data-testid="skeleton-dx-1"]');
|
||||
const dx2 = container.querySelector('[data-testid="skeleton-dx-2"]');
|
||||
const parties1 = container.querySelector(
|
||||
'[data-testid="skeleton-parties-1"]'
|
||||
);
|
||||
const parties2 = container.querySelector(
|
||||
'[data-testid="skeleton-parties-2"]'
|
||||
);
|
||||
const history = container.querySelector('[data-testid="skeleton-history"]');
|
||||
|
||||
// Every section primitive is present.
|
||||
expect(header).not.toBeNull();
|
||||
expect(validation).not.toBeNull();
|
||||
expect(lines1).not.toBeNull();
|
||||
expect(lines2).not.toBeNull();
|
||||
expect(lines3).not.toBeNull();
|
||||
expect(dx1).not.toBeNull();
|
||||
expect(dx2).not.toBeNull();
|
||||
expect(parties1).not.toBeNull();
|
||||
expect(parties2).not.toBeNull();
|
||||
expect(history).not.toBeNull();
|
||||
|
||||
// Header band should be visibly taller than the validation row — it
|
||||
// hosts the claim id, status, billed amount etc.
|
||||
// happy-dom doesn't do layout, so we read the inline styles the
|
||||
// Skeleton primitive writes from `height`/`width` props.
|
||||
const headerHeight = Number(
|
||||
(header as HTMLElement).style.height.replace("px", "")
|
||||
);
|
||||
const validationHeight = Number(
|
||||
(validation as HTMLElement).style.height.replace("px", "") || "36"
|
||||
);
|
||||
expect(headerHeight).toBeGreaterThan(validationHeight);
|
||||
|
||||
// Header should be wider than the dx rows (60% / 45%).
|
||||
const headerWidth = (header as HTMLElement).style.width || "100%";
|
||||
const dx1Width = (dx1 as HTMLElement).style.width;
|
||||
// "100%" vs "60%" — compare as percentage strings.
|
||||
const headerPct = Number(headerWidth.replace("%", ""));
|
||||
const dx1Pct = Number(dx1Width.replace("%", ""));
|
||||
expect(headerPct).toBeGreaterThan(dx1Pct);
|
||||
|
||||
// History block is taller than the validation row.
|
||||
const historyHeight = Number(
|
||||
(history as HTMLElement).style.height.replace("px", "")
|
||||
);
|
||||
expect(historyHeight).toBeGreaterThan(validationHeight);
|
||||
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("no_props_required", () => {
|
||||
// The contract: "No props." Calling the component with no props
|
||||
// must not throw and must render something.
|
||||
expect(() => {
|
||||
const { unmount } = renderIntoContainer(<ClaimDrawerSkeleton />);
|
||||
unmount();
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user