// @vitest-environment happy-dom // SP21 Task 2.5: Dashboard "Recent activity" card routes clicks by event // kind. This test verifies the wiring end-to-end: a `claim_paid` event // in the activity slice becomes a clickable row that navigates to // `/claims?claim=`, while a `remit_received` event surfaces a // "coming in a later phase" toast (since the RemitDrawer lands in // Phase 4). // // `Dashboard` reads `claims`, `providers`, `activity` from the // `useAppStore` slice, so we override the slice directly via // `useAppStore.setState` instead of mocking `useActivity` — the // sample-data mode is exactly what the dashboard sees when no API is // configured, so the test stays faithful to production behavior. (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; import { afterEach, describe, expect, it, vi } from "vitest"; import { cleanup, fireEvent, render } from "@testing-library/react"; import { MemoryRouter } from "react-router-dom"; import { Dashboard } from "./Dashboard"; import { useAppStore } from "@/store"; import type { Activity } from "@/types"; // sonner renders toasts through a portal; jsdom has no layout so the // `position` style is a no-op. We just verify that `toast.info` is // called with the expected message — sonner itself has its own tests. vi.mock("sonner", () => ({ toast: { info: vi.fn(), success: vi.fn(), error: vi.fn(), }, })); // Capture navigation side effects so we can assert on the URL the // Dashboard would push. We use a `MemoryRouter` (initialEntries=["/"]) // and observe the rendered route via a tiny listener component that // reads the current `` entry — no real `window.history` // or `BrowserRouter` required. import { useLocation } from "react-router-dom"; function LocationProbe() { const loc = useLocation(); return (
); } afterEach(() => { cleanup(); vi.clearAllMocks(); // Reset the activity slice so tests don't bleed into each other. useAppStore.setState({ activity: [] }); }); describe("Dashboard · Recent activity event routing (SP21 Task 2.5)", () => { it("navigates to /claims?claim=ID when a claim_paid event is clicked", () => { const activity: Activity[] = [ { id: "ae-1", kind: "claim_paid", message: "Paid CLM-42 · Jordan Smith", timestamp: "2026-06-20T12:00:00Z", claimId: "CLM-42", remittanceId: null, }, ]; useAppStore.setState({ activity }); const { getByTestId, getByRole } = render( , ); // The row becomes a button-like
  • with role="button" and a // descriptive aria-label derived from the kind. const row = getByRole("button", { name: /View claim paid/ }); fireEvent.click(row); const probe = getByTestId("location"); expect(probe.getAttribute("data-pathname")).toBe("/claims"); expect(probe.getAttribute("data-search")).toBe("?claim=CLM-42"); }); it("navigates to /providers?provider=NPI when a provider_added event is clicked", () => { const activity: Activity[] = [ { id: "ae-2", kind: "provider_added", message: "Added Cedar Park Family Medicine", timestamp: "2026-06-20T12:00:00Z", npi: "1730187395", claimId: null, remittanceId: null, }, ]; useAppStore.setState({ activity }); const { getByTestId, getByRole } = render( , ); fireEvent.click(getByRole("button", { name: /View provider added/ })); const probe = getByTestId("location"); expect(probe.getAttribute("data-pathname")).toBe("/providers"); expect(probe.getAttribute("data-search")).toBe("?provider=1730187395"); }); it("fires the toast (no navigation) for remit_received — Phase 4 drawer", async () => { const { toast } = await import("sonner"); const activity: Activity[] = [ { id: "ae-3", kind: "remit_received", message: "Remit REM-7 received", timestamp: "2026-06-20T12:00:00Z", claimId: null, remittanceId: "REM-7", }, ]; useAppStore.setState({ activity }); const { getByTestId, getByRole } = render( , ); fireEvent.click(getByRole("button", { name: /View remit received/ })); expect(toast.info).toHaveBeenCalledTimes(1); const msg = (toast.info as ReturnType).mock.calls[0]?.[0] as string; expect(msg).toMatch(/remit received/i); expect(msg).toMatch(/coming in a later phase/i); // URL must not have changed — the toast path is non-navigating. const probe = getByTestId("location"); expect(probe.getAttribute("data-pathname")).toBe("/"); expect(probe.getAttribute("data-search")).toBe(""); }); });