/** * @file StatCard.test.tsx * @description Unit tests for the StatCard component, which is a reusable React component that displays a statistic with a label, value, icon, and optional trend information. The tests cover rendering of the label, value (both numeric and string), trend information, and the icon. The tests also verify that custom accent colors are applied correctly. The tests use React Testing Library and Vitest for assertions and mocking. * @author Nguyễn Ngọc Trí Vĩ */ import { describe, it, expect } from "vitest"; import { render, screen } from "@testing-library/react"; import { StatCard } from "../StatCard"; import { Activity } from "lucide-react"; describe("StatCard", () => { it("should render label", () => { render(); expect(screen.getByText("Total Sessions")).toBeInTheDocument(); }); it("should render numeric value", () => { render(); expect(screen.getByText("156")).toBeInTheDocument(); }); it("should render string value", () => { render(); expect(screen.getByText("-")).toBeInTheDocument(); }); it("should render trend when provided", () => { render(); expect(screen.getByText("3 active")).toBeInTheDocument(); }); it("should not render trend when not provided", () => { render(); expect(screen.queryByText("active")).not.toBeInTheDocument(); }); it("should render the icon", () => { const { container } = render(); // Lucide renders as SVG const svg = container.querySelector("svg"); expect(svg).toBeInTheDocument(); }); it("should apply custom accent color", () => { const { container } = render( ); const svg = container.querySelector("svg"); expect(svg?.className?.baseVal ?? svg?.getAttribute("class")).toContain("text-status-success"); }); it("should apply default accent color when not specified", () => { const { container } = render(); const svg = container.querySelector("svg"); expect(svg?.className?.baseVal ?? svg?.getAttribute("class")).toContain("text-accent"); }); it("should render a skeleton placeholder when loading and hide the real value", () => { const { container } = render(); // value text should NOT appear so users never see a flash of "-" or 0 expect(screen.queryByText("-")).not.toBeInTheDocument(); expect(screen.queryByText("0")).not.toBeInTheDocument(); // skeleton primitive renders an aria-busy node expect(container.querySelector('[aria-busy="true"]')).toBeInTheDocument(); }); it("should swap from skeleton to value when loading flips false", () => { const { rerender } = render(); expect(screen.queryByText("42")).not.toBeInTheDocument(); rerender(); expect(screen.getByText("42")).toBeInTheDocument(); }); });