Files
next-tjwater-frontend/tests/browser/investigation-dev-tools.e2e.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

529 lines
37 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { expect, test } from "@playwright/test";
import { investigationFixture, mockInvestigationPresentation, selectInvestigation } from "./support/mock-investigation-presentation";
test("live investigation charts remain visible while Dev is open", async ({ page }) => {
const task = investigationFixture(1, "合成爆管展示核查");
const mock = await mockInvestigationPresentation(page, [task]);
await selectInvestigation(page, task.title);
await expect(page.getByLabel("调查实时展示", { exact: true })).toContainText("跟随所选调查");
await page.getByRole("button", { name: "切换地图 Dev Panel" }).click();
await mock.emit(task, { action: "open_chart", chart_id: "live-pressure", evidence_refs: ["dev:synthetic"],
chart: { title: "实时合成压力核查", chart_type: "line", x_data: ["08:00", "08:15"], series: [{ name: "压力水头(m)", data: [32, 28] }] } });
await expect(page.locator('[data-investigation-chart="live-pressure"]')).toBeVisible();
await expect(page.getByLabel("调查实时展示", { exact: true })).not.toContainText("图表当前不可见");
await page.getByRole("button", { name: "收起展示", exact: true }).click();
await mock.emit(task, { action: "open_chart", chart_id: "while-collapsed", evidence_refs: ["dev:synthetic"],
chart: { title: "收起期间的合成图表", chart_type: "line", x_data: ["08:00"], series: [{ name: "压力水头(m)", data: [32] }] } });
const activity = page.getByRole("list", { name: "动作识别记录" });
await expect(activity.locator("li")).toHaveCount(2);
await expect(activity.locator("li").last()).toContainText("已应用");
await page.getByRole("button", { name: "展开展示", exact: true }).click();
await expect(page.locator('[data-investigation-chart="while-collapsed"]')).toBeVisible();
expect(mock.mutations).toEqual([]);
});
test("Dev validates and previews JSONL locally, rejects invalid actions and clears on task switches", async ({ page }, testInfo) => {
test.setTimeout(60_000);
const task = investigationFixture(1, "合成爆管展示核查");
const other = investigationFixture(2, "另一项合成调查");
task.latest_report = { revision: 1, execution_generation: 1, source_session_id: "investigation-dev-test", published_at: task.created_at,
published_by: "fixture", report: { title: "合成调查报告", current_conclusion: "仅供展示核对", conclusion_boundary: "合成样例",
stage: "interim", outcome: "concluded", stop_reason: null, data_gaps: [], evidence_refs: ["dev:report"], recommendations: [], pending_items: [],
ui_blocks: [{ evidence_refs: ["dev:report"], block: {
version: "answer-ui@1", block_id: "saved-table", phase: "final", fallback_text: "报告候选列表", spec: {
root: "table", elements: { table: { type: "Table", props: {
title: "已保存的候选列表", columns: ["候选", "依据"], rows: [["DEMO-PIPE-03", "合成资料"]]
}, children: [] } }
}
} }] } };
const mock = await mockInvestigationPresentation(page, [task, other]);
await selectInvestigation(page, task.title);
await expect(page.getByLabel("调查实时展示", { exact: true })).toContainText("跟随所选调查");
await page.getByRole("button", { name: "切换地图 Dev Panel" }).click();
const dev = page.getByTestId("map-dev-panel");
const tools = dev.getByTestId("investigation-dev-tools");
await expect(tools).toContainText("报告图表 0 · 表格 1");
await page.getByRole("button", { name: "关闭通知", exact: true }).evaluateAll((buttons) => buttons.forEach((button) => (button as HTMLButtonElement).click()));
await expect(page.getByRole("button", { name: "关闭通知", exact: true })).toHaveCount(0);
await testInfo.attach("investigation-dev-default", { body: await page.screenshot({ path: testInfo.outputPath("dev-default.png") }), contentType: "image/png" });
await tools.getByRole("button", { name: "载入当前报告", exact: true }).click();
const input = tools.getByLabel("调查展示 JSONL", { exact: true });
await expect(input).toHaveValue(/ui_block/);
const reportInput = await input.inputValue();
await dev.getByRole("tab", { name: "地图调试", exact: true }).click();
await expect(dev.getByRole("button", { name: "定位并高亮", exact: true })).toBeVisible();
await expect(input).toBeHidden();
await dev.getByRole("tab", { name: "调查测试", exact: true }).click();
await expect(input).toHaveValue(reportInput);
await expect(dev.getByRole("button", { name: "重置地图调试效果", exact: true })).toBeHidden();
await expect(tools.getByRole("button", { name: "执行本地预览", exact: true })).toBeEnabled();
await tools.getByRole("button", { name: "校验 JSONL", exact: true }).click();
await tools.getByRole("button", { name: "执行本地预览", exact: true }).click();
await expect(page.getByLabel("本地预览展示", { exact: true }).getByRole("region", { name: "展示结果" })).toContainText("已保存的候选列表");
await tools.getByRole("button", { name: "载入合成样例", exact: true }).click();
await tools.getByRole("button", { name: "校验 JSONL", exact: true }).click();
await expect(tools).toContainText("已识别 3 条展示指令、1 个界面块、1 条调查数据");
await tools.getByRole("button", { name: "执行本地预览", exact: true }).click();
await expect(tools).toContainText("本地预览完成");
await expect(page.locator('[data-investigation-chart="dev-synthetic-pressure"]')).toBeVisible();
await expect(page.getByLabel("本地预览展示", { exact: true }).getByRole("region", { name: "展示结果" })).toContainText("合成候选对比");
await expect(tools.getByRole("list", { name: "动作识别记录" })).toContainText("本地测试");
const sourceSize = (source: string) => page.evaluate(async (source) => {
const map = window.__waterNetworkMap;
const data = await (map?.getSource(source) as { getData: () => Promise<{ features: unknown[] }> } | undefined)?.getData();
return data?.features.length ?? 0;
}, source);
await expect.poll(() => sourceSize("preview-presentation-highlights")).toBe(1);
await expect.poll(() => sourceSize("preview-presentation-overlay")).toBe(1);
const download = page.waitForEvent("download");
await tools.getByRole("button", { name: "下载测试 JSONL" }).click();
expect((await download).suggestedFilename()).toBe("investigation-preview-1.jsonl");
await testInfo.attach("investigation-dev-desktop", { body: await page.screenshot({ path: testInfo.outputPath("dev-desktop.png") }), contentType: "image/png" });
await page.setViewportSize({ width: 1024, height: 640 });
expect(await dev.evaluate((element) => element.scrollWidth - element.clientWidth)).toBeLessThanOrEqual(1);
await tools.getByRole("button", { name: "执行本地预览", exact: true }).click();
await expect(tools.getByRole("button", { name: "执行本地预览", exact: true })).toBeEnabled();
await page.locator('[data-investigation-chart="dev-synthetic-pressure"]').getByRole("img").scrollIntoViewIfNeeded();
const chartViewport = (await page.getByLabel("本地预览展示", { exact: true }).locator("[data-investigation-content]").boundingBox())!;
const plot = (await page.locator('[data-investigation-chart="dev-synthetic-pressure"]').getByRole("img", { name: "本地合成压力趋势", exact: true }).boundingBox())!;
expect(plot.y).toBeGreaterThanOrEqual(chartViewport.y - 1);
expect(plot.y + plot.height).toBeLessThanOrEqual(chartViewport.y + chartViewport.height + 1);
await testInfo.attach("investigation-dev-compact", { body: await page.screenshot({ path: testInfo.outputPath("dev-compact.png") }), contentType: "image/png" });
await page.setViewportSize({ width: 1440, height: 900 });
await input.fill('{"record_type":"presentation","command":{"action":"execute","command":"arbitrary-command"}}');
await tools.getByRole("button", { name: "校验 JSONL", exact: true }).click();
await expect(tools.getByRole("alert")).toContainText("第 1 行展示指令不合法");
await expect(tools.getByRole("button", { name: "执行本地预览", exact: true })).toBeEnabled();
await tools.getByRole("button", { name: "清除临时展示", exact: true }).click();
await expect.poll(() => sourceSize("preview-presentation-highlights")).toBe(0);
await expect.poll(() => sourceSize("preview-presentation-overlay")).toBe(0);
await expect(page.locator('[data-investigation-chart]')).toHaveCount(0);
await tools.getByRole("button", { name: "载入合成样例", exact: true }).click();
await tools.getByRole("button", { name: "校验 JSONL", exact: true }).click();
await tools.getByRole("button", { name: "执行本地预览", exact: true }).click();
await expect(tools).toContainText("本地预览完成");
await page.getByRole("button", { name: "切换地图 Dev Panel" }).click();
await page.getByRole("region", { name: "工况任务", exact: true }).getByText(other.title, { exact: true }).first().click();
await expect.poll(() => sourceSize("preview-presentation-highlights")).toBe(0);
await expect.poll(() => sourceSize("preview-presentation-overlay")).toBe(0);
await expect(page.locator('[data-investigation-chart]')).toHaveCount(0);
await page.getByRole("button", { name: "切换地图 Dev Panel" }).click();
await expect(tools).toContainText(other.title);
await expect(tools.getByRole("button", { name: "收起动作测试" })).toBeVisible();
await expect(tools.getByRole("textbox", { name: "调查展示 JSONL" })).toHaveValue("");
await page.setViewportSize({ width: 375, height: 720 });
await expect(page.getByRole("button", { name: "切换地图 Dev Panel" })).toBeHidden();
await expect(dev).toBeHidden();
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(375);
expect(mock.mutations).toEqual([]);
});
test("JSONL cases switch directly, reset validation and remove the previous local preview", async ({ page }, testInfo) => {
test.setTimeout(90_000);
const task = investigationFixture(1, "切换 JSONL 案例的合成调查");
const mock = await mockInvestigationPresentation(page, [task]);
await selectInvestigation(page, task.title);
await page.getByRole("button", { name: "切换地图 Dev Panel" }).click();
const tools = page.getByTestId("investigation-dev-tools");
const input = tools.getByLabel("调查展示 JSONL", { exact: true });
const cases = tools.getByRole("combobox", { name: "JSONL 测试案例", exact: true });
const execute = tools.getByRole("button", { name: "执行本地预览", exact: true });
const preview = page.getByLabel("本地预览展示", { exact: true }).getByRole("region", { name: "展示结果" });
async function choose(label: string) {
await cases.click();
await page.getByRole("option", { name: label, exact: true }).click();
await expect(execute).toBeEnabled();
await expect(input).not.toHaveValue("");
await expect(tools.getByRole("alert")).toHaveCount(0);
}
async function run() {
await tools.getByRole("button", { name: "校验 JSONL", exact: true }).click();
await execute.click();
await expect(tools.getByRole("status")).toContainText("本地预览完成");
}
const scene = (source: string) => page.evaluate(async (source) => {
const data = await (window.__waterNetworkMap?.getSource(source) as {
getData: () => Promise<{ features: Array<{ geometry: { type: string }; properties: Record<string, unknown> | null }> }>
} | undefined)?.getData();
return (data?.features ?? []).map((feature) => ({ type: feature.geometry.type, weight: feature.properties?.weight ?? null }));
}, source);
await choose("压力趋势对比");
await expect(tools.getByLabel("案例预期结果")).toContainText("两条合成压力曲线");
await page.getByRole("button", { name: "关闭通知", exact: true }).evaluateAll((buttons) => buttons.forEach((button) => (button as HTMLButtonElement).click()));
await testInfo.attach("jsonl-case-picker", { body: await page.screenshot({ path: testInfo.outputPath("jsonl-cases.png") }), contentType: "image/png" });
await run();
await expect(page.locator('[data-investigation-chart="dev-case-pressure-line"]')).toBeVisible();
// Editing resets validation, but the rendered scene is still removed on the next case switch.
await input.fill(`${await input.inputValue()}\n`);
await choose("候选管段对比");
await expect(page.locator("[data-investigation-chart]")).toHaveCount(0);
await expect(preview).toHaveCount(0);
await run();
await expect(preview).toContainText("合成候选管段对比");
await expect(tools.getByRole("region", { name: "JSONL 检查结果" })).not.toContainText("合成候选管段对比");
await tools.getByText("原始数据检查", { exact: true }).click();
await expect(tools.getByRole("button", { name: "查看第 2 条调查数据" })).toBeVisible();
await choose("隔离阀门清单");
await run();
await expect(preview).toContainText("合成隔离阀门清单");
await expect(preview).not.toContainText("合成候选管段对比");
await choose("地图定位与高亮");
await run();
await expect.poll(() => scene("preview-presentation-highlights")).toHaveLength(1);
await choose("合成影响范围");
await expect.poll(() => scene("preview-presentation-highlights")).toEqual([]);
await expect.poll(() => scene("preview-presentation-overlay")).toEqual([]);
await run();
await expect.poll(() => scene("preview-presentation-overlay")).toEqual([{ type: "Polygon", weight: null }]);
await choose("投诉热力分布");
await run();
await expect.poll(() => scene("preview-presentation-overlay")).toEqual([
{ type: "Point", weight: 1 }, { type: "Point", weight: 3 }, { type: "Point", weight: 2 }
]);
await choose("影响用户统计");
await run();
await expect(page.locator('[data-investigation-chart="dev-case-impact-bar"]')).toBeVisible();
await expect.poll(() => scene("preview-presentation-overlay")).toEqual([]);
await choose("纯 JSONL 数据");
await run();
await expect(preview).toContainText("DEMO-PIPE-03");
await expect(page.locator("[data-investigation-chart]")).toHaveCount(0);
await expect(tools.getByRole("list", { name: "动作识别记录" })).toHaveCount(1);
for (const [label, error] of [
["错误:JSON 语法", "第 2 行不是合法 JSON"],
["错误:不支持的动作", "第 1 行展示指令不合法"],
["错误:图表数据不齐", "第 1 行展示指令不合法"]
]) {
await choose(label);
await tools.getByRole("button", { name: "校验 JSONL", exact: true }).click();
await expect(tools.getByRole("alert")).toContainText(error);
await expect(execute).toBeEnabled();
await execute.click();
await expect(tools.getByRole("alert")).toContainText(error);
await expect(preview).toHaveCount(0);
await expect(tools.getByRole("list", { name: "动作识别记录" })).toHaveCount(1);
}
expect(mock.mutations).toEqual([]);
});
test("JSONL cases preview tables, charts and map overlays without an investigation or live following", async ({ page }, testInfo) => {
const mock = await mockInvestigationPresentation(page, []);
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.getByRole("button", { name: "切换地图 Dev Panel" }).click();
const tools = page.getByTestId("investigation-dev-tools");
await expect(tools.getByText("尚未选择调查", { exact: true })).toBeVisible();
await page.getByRole("checkbox", { name: "跟随所选调查", exact: true }).uncheck();
const cases = tools.getByRole("combobox", { name: "JSONL 测试案例", exact: true });
await expect(cases).toBeEnabled();
await cases.click();
await expect(page.getByRole("listbox")).toBeVisible();
await expect(page.getByRole("option")).toHaveCount(12);
await page.getByRole("button", { name: "关闭通知", exact: true }).evaluateAll((buttons) => buttons.forEach((button) => (button as HTMLButtonElement).click()));
await testInfo.attach("case-dropdown-without-task", {
body: await page.screenshot({ path: testInfo.outputPath("dropdown-without-task.png") }), contentType: "image/png"
});
await page.getByRole("option", { name: "候选管段对比", exact: true }).click();
await expect(cases).toContainText("候选管段对比");
await expect(tools.getByLabel("调查展示 JSONL", { exact: true })).toHaveValue(/DEMO-PIPE-01/);
const execute = tools.getByRole("button", { name: "执行本地预览", exact: true });
await expect(execute).toBeEnabled();
await execute.click();
await expect(page.getByLabel("本地预览展示", { exact: true }).getByRole("region", { name: "展示结果" })).toContainText("合成候选管段对比");
await cases.click();
await page.getByRole("option", { name: "影响用户统计", exact: true }).click();
await execute.click();
const display = page.getByLabel("本地预览展示", { exact: true });
await expect(display.locator('[data-investigation-chart="dev-case-impact-bar"]')).toBeVisible();
await expect(display).toContainText("仅当前浏览器");
await expect(execute).toBeEnabled();
await page.getByRole("checkbox", { name: "跟随所选调查", exact: true }).check();
await expect(display.locator('[data-investigation-chart="dev-case-impact-bar"]')).toBeVisible();
await page.getByRole("checkbox", { name: "跟随所选调查", exact: true }).uncheck();
await expect(display.locator('[data-investigation-chart="dev-case-impact-bar"]')).toBeVisible();
await testInfo.attach("preview-without-task", {
body: await page.screenshot({ path: testInfo.outputPath("preview-without-task.png") }), contentType: "image/png"
});
await cases.click();
await page.getByRole("option", { name: "合成影响范围", exact: true }).click();
await execute.click();
await expect(display.getByText("本地合成影响范围", { exact: true })).toBeVisible();
await expect.poll(() => page.evaluate(async () => {
const source = window.__waterNetworkMap?.getSource("preview-presentation-overlay") as
{ getData: () => Promise<{ features: Array<{ geometry: { type: string } }> }> } | undefined;
return (await source?.getData())?.features[0]?.geometry.type;
})).toBe("Polygon");
// A data-only edit does not strand the map preview without its controls.
await tools.getByLabel("调查展示 JSONL", { exact: true }).fill(JSON.stringify({
record_type: "candidate", origin: "synthetic", source_evidence_refs: [], element_id: "DEMO-DATA"
}));
await execute.click();
await expect(page.getByLabel("本地预览展示", { exact: true }).getByRole("region", { name: "展示结果" })).toContainText("DEMO-DATA");
await expect(display.getByText("本地合成影响范围", { exact: true })).toBeVisible();
await tools.getByRole("button", { name: "清除临时展示", exact: true }).click();
await expect(display).toContainText("当前没有展示结果");
await expect(display.getByRole("combobox", { name: "切换展示动作" })).toBeVisible();
await cases.click();
await page.getByRole("option", { name: "错误:图表数据不齐", exact: true }).click();
await execute.click();
await expect(tools.getByRole("alert")).toContainText("第 1 行展示指令不合法");
expect(mock.mutations).toEqual([]);
});
test("presentation panel keeps its header fixed while map controls, chart sources and compact layouts remain usable", async ({ page }, testInfo) => {
test.setTimeout(60_000);
const mock = await mockInvestigationPresentation(page, []);
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.getByRole("button", { name: "切换地图 Dev Panel" }).click();
const tools = page.getByTestId("investigation-dev-tools");
await tools.getByRole("button", { name: "载入合成样例", exact: true }).click();
await tools.getByRole("button", { name: "执行本地预览", exact: true }).click();
await expect(tools.getByRole("status")).toContainText("本地预览完成");
await page.getByRole("button", { name: "关闭通知", exact: true }).evaluateAll((buttons) => buttons.forEach((button) => (button as HTMLButtonElement).click()));
const panel = page.getByLabel("本地预览展示", { exact: true });
const header = panel.locator(":scope > header");
const content = panel.locator("[data-investigation-content]");
const chart = panel.locator('[data-investigation-chart="dev-synthetic-pressure"]');
const overlay = panel.getByRole("region", { name: "调查覆盖层" });
const headerY = (await header.boundingBox())!.y;
await overlay.getByRole("button", { name: "隐藏覆盖层", exact: true }).click();
await overlay.getByRole("button", { name: "显示覆盖层", exact: true }).click();
await overlay.getByRole("button", { name: "定位覆盖层", exact: true }).click();
await expect(panel.getByText("镜头跟随已暂停", { exact: true })).toBeVisible();
await panel.getByRole("button", { name: "恢复自动定位", exact: true }).click();
await chart.getByRole("button", { name: /查看依据/ }).click();
await expect(chart.getByText("合成测试数据,未保存为调查证据。", { exact: true })).toBeVisible();
expect(Math.abs((await header.boundingBox())!.y - headerY)).toBeLessThanOrEqual(1);
await expect(panel).toHaveCSS("background-color", "rgb(255, 255, 255)");
await expect(chart).toHaveCSS("background-color", "rgb(255, 255, 255)");
await expect(chart.locator(".agent-panel-nested")).toHaveCount(0);
for (const [name, width, height] of [["desktop", 1440, 900], ["compact", 1024, 640], ["mobile", 375, 812]] as const) {
await page.setViewportSize({ width, height });
await chart.getByRole("img", { name: "本地合成压力趋势", exact: true }).scrollIntoViewIfNeeded();
expect(await panel.evaluate((element) => element.scrollWidth - element.clientWidth)).toBeLessThanOrEqual(1);
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(width);
const plot = (await chart.getByRole("img", { name: "本地合成压力趋势", exact: true }).boundingBox())!;
const viewport = (await content.boundingBox())!;
expect(plot.y).toBeGreaterThanOrEqual(viewport.y - 1);
expect(plot.y + plot.height).toBeLessThanOrEqual(viewport.y + viewport.height + 1);
await testInfo.attach(`presentation-${name}`, {
body: await panel.screenshot({ path: testInfo.outputPath(`presentation-${name}.png`) }), contentType: "image/png"
});
}
await page.setViewportSize({ width: 1440, height: 900 });
await panel.getByRole("button", { name: "收起展示", exact: true }).click();
await expect(panel).toHaveAttribute("data-investigation-collapsed", "true");
await expect(chart).toHaveCount(0);
await panel.getByRole("button", { name: "展开展示", exact: true }).click();
await expect(chart).toBeVisible();
await chart.getByRole("button", { name: "关闭图表 本地合成压力趋势", exact: true }).click();
await expect(chart).toHaveCount(0);
await expect(overlay).toBeVisible();
expect(mock.mutations).toEqual([]);
});
for (const reducedMotion of ["no-preference", "reduce"] as const) {
test(`presentation actions remain interruptible with ${reducedMotion} motion`, async ({ page }) => {
test.setTimeout(60_000);
await page.emulateMedia({ reducedMotion });
const mock = await mockInvestigationPresentation(page, []);
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.getByRole("button", { name: "切换地图 Dev Panel" }).click();
const tools = page.getByTestId("investigation-dev-tools");
await tools.getByRole("button", { name: "载入合成样例", exact: true }).click();
await tools.getByRole("button", { name: "执行本地预览", exact: true }).click();
await expect(tools.getByRole("status")).toContainText("本地预览完成");
const panel = page.getByLabel("本地预览展示", { exact: true });
const chart = panel.locator('[data-investigation-chart="dev-synthetic-pressure"]');
const source = chart.getByRole("button", { name: /查看依据/ });
await source.click();
await expect(source).toHaveAttribute("aria-expanded", "true");
await source.click();
await expect(source).toHaveAttribute("aria-expanded", "false");
await source.focus();
await page.keyboard.press("Enter");
await expect(source).toHaveAttribute("aria-expanded", "true");
const overlay = panel.getByRole("region", { name: "调查覆盖层" });
await overlay.getByRole("button", { name: "隐藏覆盖层", exact: true }).click();
await overlay.getByRole("button", { name: "显示覆盖层", exact: true }).click();
await expect.poll(() => page.evaluate(() => window.__waterNetworkMap?.getLayoutProperty("preview-presentation-overlay-point", "visibility"))).toBe("visible");
await page.evaluate(() => {
const map = window.__waterNetworkMap!;
const center = map.getCenter();
map.jumpTo({ center: [center.lng + 0.002, center.lat + 0.001], zoom: 16 });
});
await overlay.getByRole("button", { name: "定位覆盖层", exact: true }).click();
await expect.poll(() => page.evaluate(() => window.__waterNetworkMap?.isMoving())).toBe(false);
await expect.poll(() => page.evaluate(() => window.__waterNetworkMap?.getZoom())).toBeCloseTo(18, 2);
await panel.getByRole("button", { name: "收起展示", exact: true }).click();
await expect(panel).toHaveAttribute("data-investigation-collapsed", "true");
await expect(chart).toHaveCount(0);
await panel.getByRole("button", { name: "展开展示", exact: true }).click();
await expect(chart).toBeVisible();
await chart.getByRole("button", { name: "关闭图表 本地合成压力趋势", exact: true }).click();
await expect(chart).toHaveCount(0);
await expect(overlay).toBeVisible();
expect(mock.mutations).toEqual([]);
});
}
test("presentation history keeps JSONL results and map snapshots after switching cases or clearing", async ({ page }, testInfo) => {
test.setTimeout(60_000);
const mock = await mockInvestigationPresentation(page, []);
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.getByRole("button", { name: "切换地图 Dev Panel" }).click();
const tools = page.getByTestId("investigation-dev-tools");
await tools.getByRole("button", { name: "载入合成样例", exact: true }).click();
await tools.getByRole("button", { name: "执行本地预览", exact: true }).click();
await expect(tools.getByRole("status")).toContainText("本地预览完成");
const panel = page.getByLabel("本地预览展示", { exact: true });
const history = panel.getByRole("combobox", { name: "切换展示动作" });
const overlayData = () => page.evaluate(async () => {
const source = window.__waterNetworkMap?.getSource("preview-presentation-overlay") as { getData: () => Promise<unknown> };
return source.getData();
});
const captured = await overlayData();
await tools.getByLabel("调查展示 JSONL", { exact: true }).fill([
"clear_highlights", "clear_overlay", "clear_scada_analysis"
].map((action) => JSON.stringify({ record_type: "presentation", command: { action, ...(action === "clear_overlay" ? { overlay_id: "dev-synthetic-area" } : {}) } })).join("\n"));
await tools.getByRole("button", { name: "执行本地预览", exact: true }).click();
await expect(tools.getByRole("status")).toContainText("本地预览完成:3 条展示指令");
await expect(history).toContainText("3 个动作");
await expect.poll(overlayData).toEqual({ type: "FeatureCollection", features: [] });
await tools.getByRole("combobox", { name: "JSONL 测试案例" }).click();
await page.getByRole("option", { name: "隔离阀门清单", exact: true }).click();
await tools.getByRole("button", { name: "执行本地预览", exact: true }).click();
await expect(panel.getByRole("region", { name: "展示结果" })).toContainText("合成隔离阀门清单");
await expect(tools.locator("[data-answer-ui]")).toHaveCount(0);
await expect(history).toContainText("4 个动作");
await history.click();
await page.getByRole("option", { name: /^\d+\. 打开图表 · 本地合成压力趋势 · 完成$/ }).click();
await expect(panel).toContainText("历史回看");
await expect(panel.getByRole("region", { name: "展示结果" })).toContainText("合成候选对比");
await expect(panel.locator('[data-investigation-chart="dev-synthetic-pressure"]')).toBeVisible();
await expect.poll(overlayData).toEqual(captured);
await expect(panel.getByRole("button", { name: "隐藏覆盖层" })).toBeDisabled();
await expect(panel.getByRole("button", { name: "关闭图表 本地合成压力趋势" })).toBeDisabled();
await page.getByRole("button", { name: "关闭通知", exact: true }).evaluateAll((buttons) => buttons.forEach((button) => (button as HTMLButtonElement).click()));
await testInfo.attach("history-map-and-chart", { body: await panel.screenshot({ path: testInfo.outputPath("history-map-and-chart.png") }), contentType: "image/png" });
await panel.getByRole("button", { name: "回到当前", exact: true }).click();
await expect(panel.getByRole("region", { name: "展示结果" })).toContainText("合成隔离阀门清单");
await expect(panel.locator("[data-investigation-chart]")).toHaveCount(0);
await expect.poll(overlayData).toEqual({ type: "FeatureCollection", features: [] });
await tools.getByRole("button", { name: "清除临时展示", exact: true }).click();
await expect(panel).toContainText("当前没有展示结果");
await expect(history).toContainText("4 个动作");
await panel.getByRole("button", { name: "上一个动作" }).click();
await expect(panel.getByRole("region", { name: "展示结果" })).toContainText("合成隔离阀门清单");
await panel.getByRole("button", { name: "下一个动作" }).click();
await expect(panel).toContainText("当前没有展示结果");
expect(mock.mutations).toEqual([]);
});
test("live actions continue recording without interrupting a historical view and can return to latest", async ({ page }) => {
test.setTimeout(60_000);
const task = investigationFixture(1, "历史回看中的实时调查");
const mock = await mockInvestigationPresentation(page, [task]);
await selectInvestigation(page, task.title);
const panel = page.getByLabel("调查实时展示", { exact: true });
await expect(panel.getByText("实时跟随", { exact: true })).toBeVisible();
const chart = (id: number) => ({ action: "open_chart", chart_id: "live-history", evidence_refs: ["dev:synthetic"], chart: { title: `压力快照 ${id}`, chart_type: "line", x_data: ["08:00"], series: [{ name: "压力", data: [id] }] } });
await mock.emit(task, chart(1));
await expect(panel.getByRole("group", { name: "压力快照 1", exact: true })).toBeVisible();
const switcher = panel.getByRole("combobox", { name: "切换展示动作" });
await expect(switcher).toContainText("压力快照 1");
await switcher.click();
await expect(page.getByRole("option")).toHaveCount(1);
await expect(page.getByRole("option", { name: /当前(最新).*压力快照 1/ })).toBeVisible();
await page.keyboard.press("Escape");
await expect(panel.getByRole("button", { name: "上一个动作" })).toBeDisabled();
await mock.emit(task, chart(2));
await expect(switcher).toContainText("压力快照 2");
await switcher.click();
await expect(page.getByRole("option")).toHaveCount(2);
await page.getByRole("option", { name: "1. 打开图表 · 压力快照 1 · 完成", exact: true }).click();
await expect(panel).toContainText("历史回看");
for (let id = 3; id <= 12; id++) await mock.emit(task, chart(id));
await expect(switcher).toContainText("1. 打开图表");
await switcher.click();
await expect(page.getByRole("option")).toHaveText([
/当前(最新).*压力快照 12/,
...Array.from({ length: 11 }, (_, index) => `${11 - index}. 打开图表 · 压力快照 ${11 - index} · 完成`)
]);
await expect(page.getByRole("option", { name: /当前(最新).*压力快照 12/ })).toBeVisible();
await expect(page.getByRole("option", { name: /^12\. 打开图表/ })).toHaveCount(0);
await page.keyboard.press("Escape");
await expect(panel.getByRole("group", { name: "压力快照 1", exact: true })).toBeVisible();
await expect(panel.getByRole("group", { name: "压力快照 12", exact: true })).toHaveCount(0);
await panel.getByRole("button", { name: "回到当前", exact: true }).click();
await expect(panel.getByRole("group", { name: "压力快照 12", exact: true })).toBeVisible();
await panel.getByRole("button", { name: "上一个动作" }).focus();
await page.keyboard.press("Enter");
await expect(switcher).toContainText("11. 打开图表");
await expect(panel.getByRole("group", { name: "压力快照 11", exact: true })).toBeVisible();
expect(mock.mutations).toEqual([]);
});
test("selecting an investigation restores saved actions, keeps live continuity and deduplicates task reselection", async ({ page }, testInfo) => {
test.setTimeout(60_000);
const task = investigationFixture(1, "已保存展示的调查");
const completed = investigationFixture(2, "已完成的历史调查");
completed.status = "completed";
completed.lifecycle_status = "completed";
const runId = completed.active_run!.run_id;
completed.active_run = null;
const command = (title: string) => ({ action: "open_chart", chart_id: "saved-pressure", evidence_refs: ["saved:reading"], chart: { title, chart_type: "line", x_data: ["08:00"], series: [{ name: "压力", data: [20] }] } });
const overlay = { action: "set_overlay", overlay_id: "saved-area", title: "保存的影响范围", mode: "geometry", evidence_id: "saved:area", data: { type: "FeatureCollection", features: [{ type: "Feature", properties: {}, geometry: { type: "Polygon", coordinates: [[[121.48,31.1],[121.49,31.1],[121.49,31.11],[121.48,31.1]]] } }] } };
const saved = [command("初次压力对比"), { action: "close_chart", chart_id: "saved-pressure" }, overlay, command("复核压力对比")].map((command, index) => ({ task_id: task.task_id, run_id: task.active_run!.run_id, event_seq: index + 1, execution_generation: 1, occurred_at: task.created_at, command }));
saved.push({ task_id: completed.task_id, run_id: runId, event_seq: 5, execution_generation: 1, occurred_at: completed.created_at, command: command("已完成调查的压力曲线") });
const mock = await mockInvestigationPresentation(page, [task, completed], saved);
await selectInvestigation(page, task.title);
const panel = page.getByLabel("调查实时展示", { exact: true });
const switcher = panel.getByRole("combobox", { name: "切换展示动作" });
await expect(switcher).toContainText("4 个动作");
await expect(panel.getByRole("group", { name: "复核压力对比", exact: true })).toBeVisible();
await expect(panel.getByRole("region", { name: "调查覆盖层" })).toContainText("保存的影响范围");
await switcher.click();
await expect(page.getByRole("option")).toHaveCount(4);
await page.getByRole("option", { name: "1. 打开图表 · 初次压力对比 · 完成", exact: true }).click();
await expect(panel.getByRole("group", { name: "初次压力对比", exact: true })).toBeVisible();
await expect(panel.getByRole("region", { name: "调查覆盖层" })).toHaveCount(0);
await mock.emit(task, command("当前实时压力"));
await expect(panel.getByRole("status")).toContainText("后续 4 个动作");
await panel.getByRole("button", { name: "回到当前", exact: true }).click();
await expect(switcher).toContainText("5 个动作");
await expect(panel.getByRole("group", { name: "当前实时压力", exact: true })).toBeVisible();
await page.getByRole("button", { name: "关闭通知", exact: true }).evaluateAll((buttons) => buttons.forEach((button) => (button as HTMLButtonElement).click()));
await testInfo.attach("saved-investigation-history", { body: await panel.screenshot({ path: testInfo.outputPath("saved-investigation-history.png") }), contentType: "image/png" });
// Turn off live following, then an already completed task still loads its saved presentation.
await page.getByRole("button", { name: "切换地图 Dev Panel" }).click();
await page.getByRole("checkbox", { name: "跟随所选调查", exact: true }).uncheck();
await expect(panel.getByText("已载入历史", { exact: true })).toBeVisible();
await page.getByRole("button", { name: "切换地图 Dev Panel" }).click();
const tasks = page.getByRole("region", { name: "工况任务", exact: true });
// Completed tasks can be selected through the all-status filter.
const completedTitle = tasks.getByText(completed.title, { exact: true }).first();
if (!await completedTitle.isVisible()) {
const all = tasks.getByRole("button", { name: /全部/ }).first();
if (await all.isVisible()) await all.click();
}
await completedTitle.click();
await expect(panel.getByRole("group", { name: "已完成调查的压力曲线", exact: true })).toBeVisible();
await expect(switcher).toContainText("1 个动作");
await tasks.getByText(task.title, { exact: true }).first().click();
await expect(switcher).toContainText("5 个动作");
await expect(panel.getByRole("group", { name: "当前实时压力", exact: true })).toBeVisible();
expect(mock.mutations).toEqual([]);
});