import { describe, expect, it } from "vitest"; import type { ScheduledConditionItem } from "../types"; import { canCenterTimelineBesideMapScale, clusterOperationalEvents, createOperationalEvents, DEFAULT_OPERATIONAL_EVENT_VISIBILITY, filterOperationalEvents, OPERATIONAL_TIMELINE_LAYOUT, resolveTimelineScaleLayout } from "./operational-timeline-model"; const workOrder: ScheduledConditionItem = { id: "work-1", kind: "work_order", code: "WO-1", scheduledAt: "2026-08-19T10:00:00.000Z", title: "阀门调整", summary: "调整阀门", status: "running", riskLevel: "attention", updatedAt: 1, durationMinutes: 40, source: "调度方案", location: "边界阀门 BV-07", dispatcher: "调度", assignee: "班组", priority: "urgent", replyWindowMinutes: 20, stages: [], replyRequirements: [] }; describe("operational timeline model", () => { it("does not reserve workspace layout space for the floating timeline", () => { expect(OPERATIONAL_TIMELINE_LAYOUT.overlayReservedHeight).toBe(0); }); it("creates linked plan and actual events for active work orders", () => { const events = createOperationalEvents([workOrder]); expect(events.map((event) => event.lane)).toEqual(["plan", "actual"]); expect(events[0].correlationId).toBe(events[1].correlationId); }); it("filters event lanes from the interactive legend", () => { const events = createOperationalEvents([workOrder]); const filtered = filterOperationalEvents(events, { ...DEFAULT_OPERATIONAL_EVENT_VISIBILITY, actual: false }); expect(filtered.map((event) => event.lane)).toEqual(["plan"]); }); it("only promotes abnormal condition runs into exception events", () => { const normal = { id: "condition-normal", kind: "condition", taskId: "scada-diagnosis", sessionId: "s-1", scheduledAt: "2026-08-19T10:00:00.000Z", title: "诊断", summary: "正常", status: "completed", riskLevel: "normal", updatedAt: 1 } satisfies ScheduledConditionItem; const abnormal = { ...normal, id: "condition-error", status: "error", riskLevel: "critical" } satisfies ScheduledConditionItem; expect(createOperationalEvents([normal])).toHaveLength(0); expect(createOperationalEvents([abnormal])[0].lane).toBe("exception"); }); it("aggregates nearby events into ten minute lane buckets", () => { const events = createOperationalEvents([workOrder]); const nearbyPlan = { ...events[0], id: "plan-nearby", cursorTime: "2026-08-19T10:05:00.000Z" }; const clusters = clusterOperationalEvents([...events, nearbyPlan], 10); expect(clusters).toHaveLength(2); expect(clusters.find((cluster) => cluster.lane === "plan")?.events).toHaveLength(2); }); it("keeps a centered timeline beside scale info only when both side margins are wide enough", () => { expect(canCenterTimelineBesideMapScale(2560, "expanded", true)).toBe(true); expect(canCenterTimelineBesideMapScale(2048, "expanded", true)).toBe(false); expect(canCenterTimelineBesideMapScale(1380, "expanded", false)).toBe(true); }); it("keeps scale info in the map corner and moves the timeline when horizontal space is tight", () => { expect(resolveTimelineScaleLayout(2560, "expanded", true)).toEqual({ scaleBottom: 0, timelineBottom: 24 }); expect(resolveTimelineScaleLayout(2048, "expanded", true)).toEqual({ scaleBottom: 0, timelineBottom: 56 }); }); });