Files
next-tjwater-frontend/src/features/workbench/map/presentation-command-executor.test.ts
T
jiang c0e4231b83
Generic Container CI/CD / test-build-publish (push) Successful in 1m52s
Frontend CI/CD / build-test-publish-and-deploy (push) Successful in 1m52s
feat: expand intelligent investigation workspace
2026-10-30 17:30:19 +08:00

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();
});
});