108 lines
3.5 KiB
TypeScript
108 lines
3.5 KiB
TypeScript
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
|
|
});
|
|
});
|
|
});
|