Files
headquarter/apps/web/src/components/toast-rules.test.ts
T
alex f13a63dc2f feat: container monitoring frontend UI (PR-2)
- Custom ToastContext + ToastProvider + ToastContainer (~170 lines, no deps)
- useEvents() SSE hook with exponential backoff reconnect
- EventProvider context for app-wide SSE stream sharing
- Event-to-toast bridge with severity mapping and deduplication
- Real-time status badge updates replacing 30s polling
- EventSource auth probe (401/429 detection via fetch)
- 14 frontend tests (useEvents + toast-rules)

Quality gates: vitest 14 passed, tsc clean, eslint clean
2026-05-29 10:25:00 +02:00

149 lines
3.9 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from "vitest";
import { handleEventToast, clearToastDedup } from "./toast-rules";
import type { InstanceEventPayload } from "../types/events";
const mockToastInfo = vi.fn();
const mockToastSuccess = vi.fn();
const mockToastWarning = vi.fn();
const mockToastError = vi.fn();
vi.mock("../state/toast", () => ({
toast: {
info: (...args: unknown[]) => mockToastInfo(...args),
success: (...args: unknown[]) => mockToastSuccess(...args),
warning: (...args: unknown[]) => mockToastWarning(...args),
error: (...args: unknown[]) => mockToastError(...args),
},
}));
describe("toast-rules", () => {
beforeEach(() => {
clearToastDedup();
mockToastInfo.mockClear();
mockToastSuccess.mockClear();
mockToastWarning.mockClear();
mockToastError.mockClear();
});
it("maps instance.started to info toast", () => {
const event: InstanceEventPayload = {
event: "instance.started",
instance_id: "inst-1",
status: "starting",
message: "Container starting...",
metadata: {},
timestamp: "2026-05-28T12:00:00Z",
correlation_id: "corr-1",
};
handleEventToast(event);
expect(mockToastInfo).toHaveBeenCalledWith("Container starting...", {
duration: 3000,
});
});
it("maps health_changed to running to success toast", () => {
const event: InstanceEventPayload = {
event: "instance.health_changed",
instance_id: "inst-1",
status: "running",
message: "Container is running",
metadata: { previous_status: "starting" },
timestamp: "2026-05-28T12:00:00Z",
correlation_id: "corr-1",
};
handleEventToast(event);
expect(mockToastSuccess).toHaveBeenCalledWith("Container running", {
duration: 3000,
});
});
it("maps health_changed to unhealthy to warning toast", () => {
const event: InstanceEventPayload = {
event: "instance.health_changed",
instance_id: "inst-1",
status: "unhealthy",
message: "Container is unhealthy",
metadata: { previous_status: "running" },
timestamp: "2026-05-28T12:00:00Z",
correlation_id: "corr-1",
};
handleEventToast(event);
expect(mockToastWarning).toHaveBeenCalledWith("Container unhealthy", {
duration: 5000,
});
});
it("maps instance.error to error toast with exit code", () => {
const event: InstanceEventPayload = {
event: "instance.error",
instance_id: "inst-1",
status: "error",
message: "Container crashed",
metadata: { exit_code: 137 },
timestamp: "2026-05-28T12:00:00Z",
correlation_id: "corr-1",
};
handleEventToast(event);
expect(mockToastError).toHaveBeenCalledWith(
"Container crashed (exit code: 137)",
{ duration: 10000 },
);
});
it("maps instance.error to error toast without exit code", () => {
const event: InstanceEventPayload = {
event: "instance.error",
instance_id: "inst-1",
status: "error",
message: "Build failed",
metadata: {},
timestamp: "2026-05-28T12:00:00Z",
correlation_id: "corr-1",
};
handleEventToast(event);
expect(mockToastError).toHaveBeenCalledWith("Build failed", {
duration: 10000,
});
});
it("deduplicates within one second", () => {
const event: InstanceEventPayload = {
event: "instance.started",
instance_id: "inst-1",
status: "starting",
message: "Container starting...",
metadata: {},
timestamp: "2026-05-28T12:00:00Z",
correlation_id: "corr-1",
};
handleEventToast(event);
handleEventToast(event);
expect(mockToastInfo).toHaveBeenCalledTimes(1);
});
it("allows duplicate after one second", () => {
vi.useFakeTimers();
const event: InstanceEventPayload = {
event: "instance.started",
instance_id: "inst-1",
status: "starting",
message: "Container starting...",
metadata: {},
timestamp: "2026-05-28T12:00:00Z",
correlation_id: "corr-1",
};
handleEventToast(event);
vi.advanceTimersByTime(1100);
handleEventToast(event);
expect(mockToastInfo).toHaveBeenCalledTimes(2);
vi.useRealTimers();
});
});