mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-05 18:52:05 +00:00
Dispatchers see a one-line banner and a one-time toast while a Reactive/Emergency work order is past half of its SEV response window; the banner opens the board on exactly those work orders and dismisses for the session. Missed deadlines stay in the notification center until acknowledged, and Clear all skips them.
148 lines
5.3 KiB
TypeScript
148 lines
5.3 KiB
TypeScript
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<typeof atRisk>[]) {
|
|
return { generatedAt: "2026-09-18T11:12:00Z", sections: [], slaAtRisk };
|
|
}
|
|
|
|
function LocationProbe() {
|
|
const location = useLocation();
|
|
return <output aria-label="Current location">{`${location.pathname}${location.search}`}</output>;
|
|
}
|
|
|
|
function renderBanner() {
|
|
return renderWithProviders(
|
|
<NotificationSessionProvider>
|
|
<SlaAtRiskBanner />
|
|
<LocationProbe />
|
|
</NotificationSessionProvider>,
|
|
{ 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",
|
|
});
|
|
});
|
|
});
|