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