130 lines
4.4 KiB
TypeScript
130 lines
4.4 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import type { FeatureCollection } from "geojson";
|
|
import { presentationCommandSchema } from "@/features/agent";
|
|
import {
|
|
executePresentationCommand,
|
|
type PresentationPorts
|
|
} from "./presentation-command-executor";
|
|
import { parseInvestigationDevJsonl } from "../utils/investigation-dev-preview";
|
|
import { investigationPresentationEventSchema } from "../api/investigation-presentation";
|
|
|
|
const chart = {
|
|
title: "压力",
|
|
chart_type: "line",
|
|
x_data: ["10:00"],
|
|
series: [{ name: "D1", data: [0.4] }]
|
|
};
|
|
const data: FeatureCollection = {
|
|
type: "FeatureCollection",
|
|
features: [
|
|
{
|
|
type: "Feature",
|
|
properties: { id: "D1" },
|
|
geometry: { type: "Point", coordinates: [121, 31] }
|
|
}
|
|
]
|
|
};
|
|
const commands = [
|
|
{ action: "locate_features", layer: "scada", feature_ids: ["D1", "missing"] },
|
|
{ action: "zoom_to_map", center: [121, 31], zoom: 14 },
|
|
{ action: "highlight_features", layer: "scada", feature_ids: ["D1", "missing"], effect: "pulse" },
|
|
{ action: "clear_highlights" },
|
|
{ action: "open_chart", chart_id: "pressure", chart, evidence_refs: ["E1"] },
|
|
{ action: "close_chart", chart_id: "pressure" },
|
|
{
|
|
action: "set_overlay",
|
|
overlay_id: "points",
|
|
title: "证据",
|
|
mode: "geometry",
|
|
evidence_id: "E1",
|
|
data
|
|
},
|
|
{ action: "clear_overlay", overlay_id: "points" },
|
|
{ action: "render_scada_analysis", items: [{ device_id: "D1", level: "high" }] },
|
|
{ action: "clear_scada_analysis" }
|
|
];
|
|
function ports(): PresentationPorts {
|
|
return {
|
|
controller: { queryPresentationFeatures: vi.fn(async () => data) },
|
|
scene: {
|
|
highlight: vi.fn(),
|
|
clearHighlights: vi.fn(),
|
|
setOverlay: vi.fn(),
|
|
clearOverlay: vi.fn(),
|
|
renderScadaAnalysis: vi.fn(() => ({
|
|
rendered_ids: ["D1"],
|
|
missing_ids: [],
|
|
fitted: true as const,
|
|
level_counts: { high: 1, medium: 0, low: 0, unrated: 0 }
|
|
})),
|
|
clearScadaAnalysis: vi.fn(() => ({ cleared: true as const }))
|
|
},
|
|
cameraAllowed: () => false,
|
|
zoom: vi.fn(async () => {}),
|
|
openChart: vi.fn(async () => {}),
|
|
closeChart: vi.fn(async () => {}),
|
|
overlayChanged: vi.fn(),
|
|
highlightsChanged: vi.fn()
|
|
};
|
|
}
|
|
describe("shared presentation execution", () => {
|
|
it.each(commands)(
|
|
"accepts the same $action payload through chat, Dev and task events",
|
|
async (input) => {
|
|
const chat = presentationCommandSchema.parse(input);
|
|
const preview = parseInvestigationDevJsonl(
|
|
JSON.stringify({ record_type: "presentation", command: input })
|
|
).commands[0];
|
|
const task = investigationPresentationEventSchema.parse({
|
|
task_id: "71000000-0000-4000-8000-000000000001",
|
|
run_id: "71000000-0000-4000-8000-000000000002",
|
|
execution_generation: 1,
|
|
command: input
|
|
}).command;
|
|
const results = await Promise.all(
|
|
[chat, preview, task].map((command) =>
|
|
executePresentationCommand(command, new AbortController().signal, ports())
|
|
)
|
|
);
|
|
expect(results[1]).toEqual(results[0]);
|
|
expect(results[2]).toEqual(results[0]);
|
|
}
|
|
);
|
|
it("reports actual matching IDs and respects paused automatic camera following", async () => {
|
|
const target = ports();
|
|
expect(
|
|
await executePresentationCommand(
|
|
presentationCommandSchema.parse(commands[2]),
|
|
new AbortController().signal,
|
|
target
|
|
)
|
|
).toEqual({ highlighted_ids: ["D1"], missing_ids: ["missing"], fitted: false });
|
|
expect(target.scene.highlight).toHaveBeenCalledWith(data, true, false);
|
|
});
|
|
it("rejects empty targets and cancelled actions before any scene mutation", async () => {
|
|
const target = ports();
|
|
vi.mocked(target.controller.queryPresentationFeatures).mockResolvedValueOnce({
|
|
type: "FeatureCollection",
|
|
features: []
|
|
});
|
|
await expect(
|
|
executePresentationCommand(
|
|
presentationCommandSchema.parse(commands[0]),
|
|
new AbortController().signal,
|
|
target
|
|
)
|
|
).rejects.toThrow("FEATURE_NOT_FOUND");
|
|
const controller = new AbortController();
|
|
controller.abort();
|
|
await expect(
|
|
executePresentationCommand(
|
|
presentationCommandSchema.parse(commands[3]),
|
|
controller.signal,
|
|
target
|
|
)
|
|
).rejects.toThrow();
|
|
expect(target.scene.highlight).not.toHaveBeenCalled();
|
|
expect(target.scene.clearHighlights).not.toHaveBeenCalled();
|
|
});
|
|
});
|