Files
next-tjwater-frontend/tests/browser/investigation-dev-tools.e2e.ts
T

832 lines
59 KiB
TypeScript
Raw 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 { INVESTIGATION_DEV_NETWORK } from "../../src/features/workbench/utils/investigation-dev-network";
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] }] } });
await expect(page.getByRole("region", { name: "最近展示动作" })).toHaveCount(0);
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: [[INVESTIGATION_DEV_NETWORK.pipeIds[2], "合成资料"]]
}, 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("已识别 2 条展示指令、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.getByText("原始数据检查", { exact: true })).toHaveCount(0);
await expect(tools.getByRole("region", { name: "最近展示动作" })).toHaveCount(0);
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(0);
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", { name: "本地合成压力趋势", exact: true }).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.getByText("原始数据检查", { exact: true })).toHaveCount(0);
await choose("隔离阀门清单");
await run();
await expect(preview).toContainText("合成隔离阀门清单");
await expect(preview).not.toContainText("合成候选管段对比");
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(INVESTIGATION_DEV_NETWORK.pipeIds[2]);
await expect(page.locator("[data-investigation-chart]")).toHaveCount(0);
await expect(tools.getByRole("list", { name: "动作识别记录" })).toHaveCount(0);
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(0);
}
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(14);
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(new RegExp(INVESTIGATION_DEV_NETWORK.pipeIds[0]));
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 expect.poll(async () => Math.abs((await panel.boundingBox())!.height - Math.min(480, height * 0.55))).toBeLessThan(1);
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 panel.getByRole("button", { name: "收起展示", exact: true }).click();
await expect(panel).toHaveAttribute("data-investigation-collapsed", "true");
await expect(chart).toHaveCount(0);
expect((await panel.boundingBox())!.height).toBe(112);
await expect(panel.getByRole("combobox", { name: "切换展示动作" })).toBeVisible();
expect(await header.evaluate((element) => element.scrollHeight - element.clientHeight)).toBeLessThanOrEqual(1);
await testInfo.attach(`presentation-${name}-collapsed`, {
body: await panel.screenshot({ path: testInfo.outputPath(`presentation-${name}-collapsed.png`) }), contentType: "image/png"
});
await panel.getByRole("button", { name: "展开展示", exact: true }).click();
await expect(chart).toBeVisible();
}
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((await panel.boundingBox())!.height).toBe(480);
await tools.getByRole("combobox", { name: "JSONL 测试案例", exact: true }).click();
await page.getByRole("option", { name: "纯 JSONL 数据", exact: true }).click();
await tools.getByRole("button", { name: "执行本地预览", exact: true }).click();
await expect(tools.getByRole("status")).toContainText("本地预览完成");
expect((await panel.boundingBox())!.height).toBe(480);
await tools.getByRole("button", { name: "清除临时展示", exact: true }).click();
expect((await panel.boundingBox())!.height).toBe(480);
expect(mock.mutations).toEqual([]);
});
test("individual charts float, move and resize while the preview stays in place", async ({ page }, testInfo) => {
test.setTimeout(90_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();
const input = tools.getByLabel("调查展示 JSONL", { exact: true });
await input.fill(`${await input.inputValue()}\n${JSON.stringify({ record_type: "presentation", command: {
action: "open_chart", chart_id: "second-chart", evidence_refs: ["dev:synthetic"],
chart: { title: "合成流量趋势", chart_type: "line", x_data: ["08:00", "08:15"], series: [{ name: "流量", data: [10, 12] }] }
} })}`);
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 original = (await panel.boundingBox())!;
const chart = panel.locator('[data-investigation-chart="dev-synthetic-pressure"]');
const plot = chart.getByRole("img", { name: "本地合成压力趋势", exact: true });
await plot.evaluate((element) => element.setAttribute("data-preview-state", "preserved"));
const trigger = chart.getByRole("button", { name: "放大查看 本地合成压力趋势", exact: true });
await trigger.click();
const first = page.getByRole("dialog", { name: "本地合成压力趋势", exact: true });
await expect(first).toBeVisible();
await expect(plot).toHaveAttribute("data-preview-state", "preserved");
expect(await panel.boundingBox()).toEqual(original);
const firstPlot = first.getByRole("img", { name: "本地合成压力趋势", exact: true });
expect((await firstPlot.boundingBox())!.width).toBeGreaterThan((await plot.boundingBox())!.width);
const before = (await first.boundingBox())!;
const heading = first.locator(".floating-window-heading");
const bar = (await heading.boundingBox())!;
await page.mouse.move(bar.x + 90, bar.y + 20);
await page.mouse.down();
await page.mouse.move(bar.x + 190, bar.y + 90, { steps: 8 });
await page.mouse.up();
await expect.poll(async () => (await first.boundingBox())!.x - before.x).toBeGreaterThan(90);
await expect.poll(async () => (await first.boundingBox())!.y - before.y).toBeGreaterThan(60);
const beforeResize = (await first.boundingBox())!;
const handle = (await first.locator(".floating-window-resize").boundingBox())!;
await page.mouse.move(handle.x + 8, handle.y + 8);
await page.mouse.down();
await page.mouse.move(handle.x + 88, handle.y + 58, { steps: 8 });
const resizedPlot = firstPlot.locator("svg");
await expect.poll(async () => Math.abs(Number(await resizedPlot.getAttribute("width")) - (await firstPlot.boundingBox())!.width)).toBeLessThan(1);
await expect.poll(async () => Math.abs(Number(await resizedPlot.getAttribute("height")) - (await firstPlot.boundingBox())!.height)).toBeLessThan(1);
await page.mouse.up();
await expect.poll(async () => (await first.boundingBox())!.width - beforeResize.width).toBeGreaterThan(70);
await expect.poll(async () => (await first.boundingBox())!.height - beforeResize.height).toBeGreaterThan(40);
expect(await panel.boundingBox()).toEqual(original);
await testInfo.attach("chart-floating", { body: await page.screenshot({ path: testInfo.outputPath("chart-floating.png") }), contentType: "image/png" });
await panel.locator('[data-investigation-chart="second-chart"]').getByRole("button", { name: "放大查看 合成流量趋势", exact: true }).click();
const second = page.getByRole("dialog", { name: "合成流量趋势", exact: true });
await expect(second).toBeVisible();
await expect(first).toBeVisible();
await heading.focus();
expect(Number(await first.evaluate((element) => getComputedStyle(element).zIndex))).toBeGreaterThan(Number(await second.evaluate((element) => getComputedStyle(element).zIndex)));
const moved = (await first.boundingBox())!;
await page.keyboard.press("ArrowLeft");
expect((await first.boundingBox())!.x).toBe(moved.x - 16);
await page.keyboard.press("Shift+ArrowRight");
expect((await first.boundingBox())!.width).toBe(moved.width + 16);
await first.getByRole("button", { name: "最大化窗口", exact: true }).click();
await expect.poll(async () => (await first.boundingBox())!.width).toBe(1424);
await first.getByRole("button", { name: "还原窗口", exact: true }).click();
expect((await first.boundingBox())!.width).toBe(moved.width + 16);
await page.keyboard.press("Escape");
await expect(first).toHaveCount(0);
await expect(trigger).toBeFocused();
await expect(second).toBeVisible();
await second.getByRole("button", { name: "关闭窗口", exact: true }).click();
await expect(second).toHaveCount(0);
await trigger.click();
for (const [name, width, height] of [["compact", 1024, 640], ["mobile", 375, 812], ["small", 320, 640]] as const) {
await page.setViewportSize({ width, height });
await expect.poll(async () => (await first.boundingBox())!.x + (await first.boundingBox())!.width).toBeLessThanOrEqual(width);
const box = (await first.boundingBox())!;
expect(box.y + box.height).toBeLessThanOrEqual(height);
expect(box.x).toBeGreaterThanOrEqual(0);
expect(await first.evaluate((element) => element.scrollWidth - element.clientWidth)).toBeLessThanOrEqual(1);
await testInfo.attach(`chart-floating-${name}`, { body: await first.screenshot({ path: testInfo.outputPath(`chart-floating-${name}.png`) }), contentType: "image/png" });
}
await heading.focus();
await page.keyboard.press("Escape");
await expect(first).toHaveCount(0);
expect(mock.mutations).toEqual([]);
});
test("report chart blocks enlarge independently and keep range controls usable", async ({ page }) => {
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.getByLabel("调查展示 JSONL", { exact: true }).fill(JSON.stringify({ record_type: "ui_block", evidence_refs: [], block: {
version: "answer-ui@1", block_id: "floating-pressure", phase: "final", fallback_text: "合成压力数据", spec: {
root: "chart", elements: { chart: { type: "Chart", props: { title: "交互压力图", chart_type: "line", source: "合成样例", x_data: ["08:00", "08:15"], series: [{ name: "压力", data: [31, 29] }] }, children: [] } }
}
} }));
await tools.getByRole("button", { name: "执行本地预览", exact: true }).click();
await expect(tools.getByRole("status")).toContainText("本地预览完成");
const panel = page.getByLabel("本地预览展示", { exact: true });
await expect(panel).toHaveCSS("transform", "none");
const bounds = (await panel.boundingBox())!;
const trigger = panel.getByRole("button", { name: "放大查看 交互压力图", exact: true });
await trigger.click();
const floating = page.getByRole("dialog", { name: "交互压力图", exact: true });
await expect(floating).toBeVisible();
await floating.getByRole("button", { name: "筛选数据范围", exact: true }).click();
await floating.getByRole("combobox", { name: "区间起点", exact: true }).click();
await expect(page.getByRole("option", { name: "08:15", exact: true })).toBeVisible();
await page.keyboard.press("Escape");
await expect(page.getByRole("listbox")).toHaveCount(0);
await expect(floating).toBeVisible();
await floating.getByRole("combobox", { name: "区间起点", exact: true }).click();
await page.getByRole("option", { name: "08:15", exact: true }).click();
await floating.getByRole("tab", { name: "数据", exact: true }).click();
await expect(floating.getByRole("table")).toContainText("29");
await floating.getByRole("button", { name: "放大查看表格 交互压力图", exact: true }).click();
const dataWindow = page.getByRole("dialog", { name: "交互压力图 · 表格", exact: true });
await expect(dataWindow.getByRole("table")).toContainText("29");
await page.keyboard.press("Escape");
await expect(dataWindow).toHaveCount(0);
await expect(floating).toBeVisible();
const after = (await panel.boundingBox())!;
for (const key of ["x", "y", "width", "height"] as const) expect(Math.abs(after[key] - bounds[key])).toBeLessThan(1);
await floating.getByRole("button", { name: "关闭窗口", exact: true }).click();
await expect(trigger).toBeFocused();
expect(mock.mutations).toEqual([]);
});
test("complex tables retain search and sort in floating windows, including empty and raw results", 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");
const cases = tools.getByRole("combobox", { name: "JSONL 测试案例", exact: true });
async function choose(label: string) {
await cases.click();
await page.getByRole("option", { name: label, exact: true }).click();
await tools.getByRole("button", { name: "执行本地预览", exact: true }).click();
await expect(tools.getByRole("status")).toContainText("本地预览完成");
}
await choose("复杂表格:宽表、长文本与边界数据");
const panel = page.getByLabel("本地预览展示", { exact: true });
await expect(panel).toHaveCSS("transform", "none");
const before = (await panel.boundingBox())!;
const trigger = panel.getByRole("button", { name: "放大查看表格 合成候选核查宽表", exact: true });
await panel.getByRole("textbox", { name: "搜索合成候选核查宽表", exact: true }).fill("样例记录 042");
await trigger.click();
const wide = page.getByRole("dialog", { name: "合成候选核查宽表 · 表格", exact: true });
const search = wide.getByRole("textbox", { name: "搜索合成候选核查宽表", exact: true });
await expect(search).toHaveValue("样例记录 042");
await expect(wide.getByText("1 / 50 行", { exact: true })).toBeVisible();
await search.fill("");
await wide.getByRole("button", { name: "按序号排序", exact: true }).click();
await wide.getByRole("button", { name: "按序号排序", exact: true }).click();
await expect(wide.locator("tbody tr").first()).toContainText(INVESTIGATION_DEV_NETWORK.pipeIds[49]);
await search.fill("不存在的合成对象");
await expect(wide).toContainText("当前筛选没有匹配的数据");
await search.fill(INVESTIGATION_DEV_NETWORK.pipeIds[0]);
const after = (await panel.boundingBox())!;
for (const key of ["x", "y", "width", "height"] as const) expect(Math.abs(after[key] - before[key])).toBeLessThan(1);
await page.setViewportSize({ width: 375, height: 812 });
await expect.poll(async () => (await wide.boundingBox())!.width).toBeLessThanOrEqual(359);
expect(await wide.evaluate((element) => element.scrollWidth - element.clientWidth)).toBeLessThanOrEqual(1);
const region = wide.getByRole("region", { name: "合成候选核查宽表", exact: true });
expect(await region.evaluate((element) => element.scrollWidth)).toBeGreaterThan(await region.evaluate((element) => element.clientWidth));
await testInfo.attach("wide-table-mobile", { body: await wide.screenshot({ path: testInfo.outputPath("wide-table-mobile.png") }), contentType: "image/png" });
await wide.getByRole("button", { name: "关闭窗口", exact: true }).click();
await expect(trigger).toBeFocused();
await page.setViewportSize({ width: 1440, height: 900 });
await panel.getByRole("button", { name: "放大查看表格 合成空结果表", exact: true }).click();
const empty = page.getByRole("dialog", { name: "合成空结果表 · 表格", exact: true });
await expect(empty).toContainText("没有已加载的数据");
await empty.getByRole("button", { name: "关闭窗口", exact: true }).click();
await choose("纯 JSONL 数据");
await panel.getByRole("button", { name: "放大查看表格 调查数据摘要", exact: true }).click();
const raw = page.getByRole("dialog", { name: "调查数据摘要", exact: true });
await expect(raw.getByRole("table")).toContainText(INVESTIGATION_DEV_NETWORK.pipeIds[2]);
await expect(raw.locator("tbody tr")).toHaveCount(2);
await raw.getByRole("button", { name: "关闭窗口", exact: true }).click();
expect(mock.mutations).toEqual([]);
});
test("complex investigation cases show multi-chart and multi-table results without feature lookup actions", async ({ page }) => {
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("combobox", { name: "JSONL 测试案例", exact: true }).click();
await expect(page.getByRole("option", { name: "地图定位与高亮", exact: true })).toHaveCount(0);
await page.getByRole("option", { name: "复杂调查:多图多表联动", exact: true }).click();
await tools.getByRole("button", { name: "执行本地预览", exact: true }).click();
await expect(tools.getByRole("status")).toContainText("本地预览完成:2 条展示指令、4 个界面块");
const panel = page.getByLabel("本地预览展示", { exact: true });
await expect(panel.getByRole("button", { name: "放大查看 合成全天压力对比", exact: true })).toBeVisible();
await expect(panel.getByRole("button", { name: "放大查看 合成分区流量趋势", exact: true })).toHaveCount(1);
await expect(panel.getByRole("button", { name: "放大查看表格 合成候选方案核查", exact: true })).toHaveCount(1);
await expect(panel.getByRole("button", { name: "放大查看表格 合成阀门核查清单", exact: true })).toHaveCount(1);
await page.getByTestId("map-dev-panel").getByRole("tab", { name: "地图调试", exact: true }).click();
await expect(page.getByRole("button", { name: "仅高亮", exact: true })).toBeVisible();
await expect(page.getByRole("button", { name: "缩放到要素", exact: true })).toBeVisible();
await expect(page.getByRole("button", { name: "定位并高亮", exact: true })).toBeVisible();
expect(mock.mutations).toEqual([]);
});
test("expanded bars fill the window while preserving legends, type size and the chart instance", async ({ page }, testInfo) => {
test.setTimeout(60_000);
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("combobox", { name: "JSONL 测试案例", exact: true }).click();
await page.getByRole("option", { name: "复杂调查:多图多表联动", exact: true }).click();
await tools.getByRole("button", { name: "执行本地预览", exact: true }).click();
await expect(tools.getByRole("status")).toContainText("本地预览完成");
await page.getByLabel("本地预览展示", { exact: true }).getByRole("button", { name: "放大查看 合成候选方案影响对比", exact: true }).click();
const floating = page.getByRole("dialog", { name: "合成候选方案影响对比", exact: true });
const legend = floating.getByLabel("类别图例", { exact: true });
for (const label of ["合成方案 A:局部隔离", "合成方案 B:扩大核查", "合成方案 C:保持供水", "合成方案 D:分段验证", "合成方案 E:待补充资料"]) {
await expect(legend.getByText(label, { exact: true })).toBeVisible();
}
await expect(legend).toContainText("610");
const plot = floating.getByRole("img", { name: "合成候选方案影响对比", exact: true });
await plot.scrollIntoViewIfNeeded();
const svg = plot.locator("svg");
await expect.poll(async () => Number(await svg.getAttribute("width"))).toBe((await plot.boundingBox())!.width);
const drawing = (await plot.boundingBox())!;
const sidebar = (await floating.locator("aside").boundingBox())!;
const instance = await plot.locator(".echarts-for-react").getAttribute("_echarts_instance_");
const fontSize = await svg.locator("text").first().evaluate((element) => getComputedStyle(element).fontSize);
await floating.locator(".floating-window-heading").focus();
await page.keyboard.press("Shift+ArrowRight");
await page.keyboard.press("Shift+ArrowDown");
await expect.poll(async () => (await plot.boundingBox())!.width).toBe(drawing.width + 16);
await expect.poll(async () => (await plot.boundingBox())!.height).toBe(drawing.height + 16);
await floating.getByRole("button", { name: "最大化窗口", exact: true }).click();
await expect.poll(async () => (await plot.boundingBox())!.width).toBeGreaterThan(drawing.width + 400);
await expect.poll(async () => (await plot.boundingBox())!.height).toBeGreaterThan(drawing.height + 100);
await expect.poll(async () => Number(await svg.getAttribute("width"))).toBe((await plot.boundingBox())!.width);
await expect.poll(async () => Number(await svg.getAttribute("height"))).toBe((await plot.boundingBox())!.height);
expect((await floating.locator("aside").boundingBox())!.width).toBe(sidebar.width);
expect(await svg.locator("text").first().evaluate((element) => getComputedStyle(element).fontSize)).toBe(fontSize);
await expect(plot.locator(".echarts-for-react")).toHaveAttribute("_echarts_instance_", instance!);
await page.getByRole("button", { name: "关闭通知", exact: true }).evaluateAll((buttons) => buttons.forEach((button) => (button as HTMLButtonElement).click()));
await testInfo.attach("responsive-bar-maximized", { body: await floating.screenshot({ path: testInfo.outputPath("responsive-bar-maximized.png") }), contentType: "image/png" });
await floating.getByRole("button", { name: "还原窗口", exact: true }).click();
await expect.poll(async () => (await plot.boundingBox())!.width).toBe(drawing.width + 16);
await page.setViewportSize({ width: 375, height: 812 });
const viewport = floating.locator("[data-floating-window-content]");
await expect.poll(async () => (await floating.boundingBox())!.width).toBeLessThanOrEqual(359);
expect((await plot.boundingBox())!.width).toBeLessThan(drawing.width);
await viewport.evaluate((element) => { element.scrollLeft = element.scrollWidth; });
expect(await viewport.evaluate((element) => element.scrollLeft)).toBe(0);
await floating.getByRole("tab", { name: "数据", exact: true }).click();
await expect(floating.getByRole("table")).toContainText("合成方案 E:待补充资料");
});
test("full-day charts expose all eight series and retain legend selection while resizing", async ({ page }) => {
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("combobox", { name: "JSONL 测试案例", exact: true }).click();
await page.getByRole("option", { name: "复杂图表:多测点与长时序", exact: true }).click();
await tools.getByRole("button", { name: "执行本地预览", exact: true }).click();
await expect(tools.getByRole("status")).toContainText("本地预览完成");
await page.getByLabel("本地预览展示", { exact: true }).getByRole("button", { name: "放大查看 合成全天压力对比", exact: true }).click();
const floating = page.getByRole("dialog", { name: "合成全天压力对比", exact: true });
const series = floating.getByLabel("显示的数据序列").getByRole("button");
await expect(series).toHaveCount(8);
await series.last().click();
await expect(series.last()).toHaveAttribute("aria-pressed", "false");
const plot = floating.getByRole("img", { name: "合成全天压力对比", exact: true });
const drawing = (await plot.boundingBox())!;
await floating.locator(".floating-window-heading").focus();
await page.keyboard.press("Shift+ArrowDown");
await expect.poll(async () => (await plot.boundingBox())!.height).toBe(drawing.height + 16);
await expect(series.last()).toHaveAttribute("aria-pressed", "false");
await floating.getByRole("button", { name: "显示全部序列", exact: true }).click();
await expect(series.last()).toHaveAttribute("aria-pressed", "true");
await floating.getByRole("tab", { name: "数据", exact: true }).click();
await expect(floating.locator("tbody tr")).toHaveCount(96);
});
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([]);
});