import { act, cleanup, renderHook } from "@testing-library/react"; import { afterEach, expect, it } from "vitest"; import { useScadaEventAtmosphere, type ScadaEventTask } from "./use-scada-event-atmosphere"; import type { WorkbenchAlert } from "../types"; const task: ScadaEventTask = { task_id: "task-1", project_id: "project-1", task_type: "event_investigation", workflow: "scada_anomaly", lifecycle_status: "open", title: "监测事件", created_at: "2026-07-16T00:00:00Z", source: { kind: "external_system", external_id: "scada:1", external_revision: 1, event_kind: "scada_anomaly", replay_context: { replay_id: "replay-1", knowledge_cutoff: "2026-07-16T00:00:00Z", batch_sequence: 1 } } }; const alert: WorkbenchAlert = { id: "incident-1", conditionId: "operational-task:task-1", title: "SCADA 指标异常", time: "08:00", description: "调查已启动", source: { kind: "scada_replay", replayId: "replay-1", ruleId: "scada_diagnosis", batchSequence: 1 } }; afterEach(() => { cleanup(); sessionStorage.clear(); }); it("retains an event after sensor-alert recovery, human waiting and execution failure, until event completion", () => { const { result, rerender } = renderHook(({ alerts, tasks }) => useScadaEventAtmosphere("project-1", alerts, tasks), { initialProps: { alerts: [alert], tasks: [task] } }); expect(result.current.atmosphere?.count).toBe(1); rerender({ alerts: [], tasks: [{ ...task, lifecycle_status: "waiting_human" }] }); expect(result.current.atmosphere?.alert?.conditionId).toBe(alert.conditionId); rerender({ alerts: [], tasks: [{ ...task, lifecycle_status: "failed" }] }); expect(result.current.atmosphere).not.toBeNull(); rerender({ alerts: [alert], tasks: [{ ...task, lifecycle_status: "completed" }] }); expect(result.current.atmosphere).toBeNull(); }); it("restores unfinished events on reload and remembers a manual dismissal for that event", () => { const first = renderHook(() => useScadaEventAtmosphere("project-1", null, [task])); expect(first.result.current.atmosphere).not.toBeNull(); act(() => first.result.current.dismissAtmosphere(first.result.current.atmosphere!.id)); expect(first.result.current.atmosphere).toBeNull(); first.unmount(); const next = renderHook(() => useScadaEventAtmosphere("project-1", [alert], [task])); expect(next.result.current.atmosphere).toBeNull(); }); it("keeps other events visible when one ends and shows a new event after the earlier group was dismissed", () => { const second = { ...task, task_id: "task-2" }; const { result, rerender } = renderHook(({ tasks }) => useScadaEventAtmosphere("project-1", [], tasks), { initialProps: { tasks: [task, second] } }); expect(result.current.atmosphere?.count).toBe(2); rerender({ tasks: [{ ...task, lifecycle_status: "cancelled" }, second] }); expect(result.current.atmosphere?.count).toBe(1); act(() => result.current.dismissAtmosphere(result.current.atmosphere!.id)); rerender({ tasks: [second, { ...task, task_id: "task-3" }] }); expect(result.current.atmosphere?.count).toBe(1); expect(result.current.atmosphere?.alert?.conditionId).toBe("operational-task:task-3"); }); it("isolates project state and lets Dev end only the local preview", () => { const { result, rerender } = renderHook(({ project, tasks }) => useScadaEventAtmosphere(project, [], tasks), { initialProps: { project: "project-1", tasks: [] as ScadaEventTask[] } }); act(() => result.current.previewAtmosphere()); expect(result.current.atmosphere?.preview).toBe(true); act(() => result.current.endAtmospherePreview()); expect(result.current.atmosphere).toBeNull(); rerender({ project: "project-1", tasks: [task] }); act(() => result.current.endAtmospherePreview()); expect(result.current.atmosphere?.preview).toBe(false); rerender({ project: "project-2", tasks: [task] }); expect(result.current.atmosphere).toBeNull(); });