832 lines
59 KiB
TypeScript
832 lines
59 KiB
TypeScript
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([]);
|
||
});
|