import { act, screen, waitFor, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { useLocation } from "react-router"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { SlaAtRiskBanner } from "@/components/notifications/sla-at-risk-banner"; import { NotificationSessionProvider } from "@/domain/notifications/session/notification-session-provider"; import { queryKeys } from "@/infra/query-key/query-key"; import { renderWithProviders } from "@/test/test-utils"; const apiGet = vi.hoisted(() => vi.fn()); const toastWarn = vi.hoisted(() => vi.fn()); vi.mock("@/api/api", () => ({ apiGet })); vi.mock("react-toastify", () => ({ toast: { warn: toastWarn } })); const BANNER = "Work orders at risk of missing their response deadline"; function atRisk(id: number, number: string | null = String(1000 + id)) { return { id, number, severity: 1, startedAt: "2026-09-18T10:00:00Z", deadlineAt: "2026-09-18T12:00:00Z", percentElapsed: 60, }; } function feed(...slaAtRisk: ReturnType[]) { return { generatedAt: "2026-09-18T11:12:00Z", sections: [], slaAtRisk }; } function LocationProbe() { const location = useLocation(); return {`${location.pathname}${location.search}`}; } function renderBanner() { return renderWithProviders( , { withAuth: false }, ); } describe("SLA at-risk banner", () => { beforeEach(() => { apiGet.mockReset(); toastWarn.mockReset(); }); it("stays hidden while no work order is past half of its response window", async () => { apiGet.mockResolvedValue(feed()); renderBanner(); await waitFor(() => expect(apiGet).toHaveBeenCalledWith("notifications")); expect(screen.queryByRole("region", { name: BANNER })).not.toBeInTheDocument(); expect(toastWarn).not.toHaveBeenCalled(); }); it("names the single work order at risk", async () => { apiGet.mockResolvedValue(feed(atRisk(101))); renderBanner(); const banner = await screen.findByRole("region", { name: BANNER }); expect( within(banner).getByRole("button", { name: "WO #1101 is at risk of missing its response deadline", }), ).toBeInTheDocument(); }); it("collapses several work orders to one line and opens Work Orders on exactly those", async () => { apiGet.mockResolvedValue(feed(atRisk(101), atRisk(205), atRisk(318))); const user = userEvent.setup(); renderBanner(); const banner = await screen.findByRole("region", { name: BANNER }); const open = within(banner).getByRole("button", { name: "3 work orders are at risk of missing their response deadline", }); expect(open).toHaveAttribute("title", "View in Work Orders"); expect(within(banner).getAllByRole("button")).toHaveLength(2); await user.click(open); expect(screen.getByRole("status", { name: "Current location" })).toHaveTextContent( "/workorders?ids=101,205,318", ); }); it("dismisses for this session only and returns when a new work order becomes at risk", async () => { apiGet.mockResolvedValue(feed(atRisk(101), atRisk(205))); const user = userEvent.setup(); const { queryClient, unmount } = renderBanner(); const banner = await screen.findByRole("region", { name: BANNER }); await user.click(within(banner).getByRole("button", { name: "Dismiss SLA alert" })); expect(screen.queryByRole("region", { name: BANNER })).not.toBeInTheDocument(); apiGet.mockResolvedValue(feed(atRisk(101), atRisk(205), atRisk(318))); await act(() => queryClient.invalidateQueries({ queryKey: queryKeys.notifications.feed() })); const returned = await screen.findByRole("region", { name: BANNER }); expect( within(returned).getByRole("button", { name: "WO #1318 is at risk of missing its response deadline", }), ).toBeInTheDocument(); unmount(); renderBanner(); expect( await screen.findByRole("button", { name: "3 work orders are at risk of missing their response deadline", }), ).toBeInTheDocument(); }); it("toasts each work order once per session, however often the feed polls", async () => { apiGet.mockResolvedValue(feed(atRisk(101), atRisk(205, null))); const { queryClient } = renderBanner(); await screen.findByRole("region", { name: BANNER }); expect(toastWarn).toHaveBeenCalledTimes(2); expect(toastWarn).toHaveBeenCalledWith("WO #1101 is at 50% of its SLA response window", { toastId: "sla-at-risk-101", }); expect(toastWarn).toHaveBeenCalledWith("WO 205 is at 50% of its SLA response window", { toastId: "sla-at-risk-205", }); apiGet.mockResolvedValue(feed(atRisk(101), atRisk(205, null), atRisk(318))); await act(() => queryClient.invalidateQueries({ queryKey: queryKeys.notifications.feed() })); await screen.findByRole("button", { name: "3 work orders are at risk of missing their response deadline", }); await act(() => queryClient.invalidateQueries({ queryKey: queryKeys.notifications.feed() })); expect(toastWarn).toHaveBeenCalledTimes(3); expect(toastWarn).toHaveBeenLastCalledWith("WO #1318 is at 50% of its SLA response window", { toastId: "sla-at-risk-318", }); }); });