Files
next-tjwater-frontend/tests/browser/investigation-diagram-status.e2e.ts
T
jiang a617aa95e9 fix(workbench): stabilize investigation presentation rendering
Keep investigation graphs stable during updates, defer expensive chart rendering, and prevent stale results when switching tasks. Adopt reference annotations and consolidate frontend documentation.
2026-11-11 19:10:41 +08:00

75 lines
5.3 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { mockAgentApi } from "./support/mock-agent-api";
for (const width of [1440, 375, 320]) {
test(`investigation diagram displays saved reference annotations at ${width}px`, async ({ page }, testInfo) => {
await page.setViewportSize({ width, height: 900 });
await page.emulateMedia({ reducedMotion: "reduce" });
await mockAgentApi(page);
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.setContent('<html lang="zh-CN"><head><link rel="stylesheet" href="/src/styles.css"></head><body><main id="state-reader" class="workbench-shadcn-surface bg-background text-foreground" style="max-width:760px;margin:auto;padding:12px"></main></body></html>');
await page.evaluate(async () => {
const entry = await (await fetch("/src/main.tsx")).text();
const reactUrl = entry.match(/from "([^"\n]*\/react\.js[^"\n]*)"/)?.[1];
const clientUrl = entry.match(/from "([^"\n]*\/react-dom_client\.js[^"\n]*)"/)?.[1];
if (!reactUrl || !clientUrl) throw new Error("Vite React module paths unavailable");
const reactModule = await import(reactUrl);
const React = reactModule.default ?? reactModule;
const clientModule = await import(clientUrl);
const { createRoot } = clientModule.default ?? clientModule;
const { InvestigationDiagramView } = await import("/src/features/workbench/components/investigation-diagram-view.tsx");
const { investigationDiagramSchema } = await import("/src/features/workbench/api/investigation-report.ts");
const diagram = investigationDiagramSchema.parse({
title: "调查流程参考",
mermaid: "flowchart TD\nREF[核查方向] --> RESULT[监测结果]\nREF --> NEED[资料缺口]\nRESULT --> FAIL[首次尝试失败]\nRESULT --> OTHER[未采用分支]\nNEED --> OLD[旧执行中标记]:::tjRunning\nNEED --> UNSET[无标记步骤]",
node_refs: [
{ node_id: "REF", label: "核查方向", status: "reference" },
{ node_id: "RESULT", label: "监测结果", status: "recorded" },
{ node_id: "NEED", label: "资料缺口", status: "needs_input" },
{ node_id: "FAIL", label: "首次尝试失败", status: "failed", detail: "此前计算超时,保留原尝试。" },
{ node_id: "OTHER", label: "未采用分支", status: "not_taken" },
{ node_id: "OLD", label: "旧执行中标记", status: "running" },
{ node_id: "UNSET", label: "无标记步骤" }
]
});
const root = createRoot(document.getElementById("state-reader"));
const render = () => root.render(React.createElement(InvestigationDiagramView, {
diagram: { ...diagram, node_refs: [...diagram.node_refs] },
renderDetails: () => React.createElement("p", null, "图中标记来自最近整理,实际任务状态在任务记录中查看。")
}));
Object.assign(window, { updateReferenceAnnotation: () => { diagram.node_refs[0].status = "recorded"; render(); } });
render();
});
const diagram = page.getByRole("region", { name: "调查脉络", exact: true });
await expect(diagram.locator("svg g.node")).toHaveCount(7);
await expect(diagram.locator("svg g.node.tjReference")).toHaveCount(3);
for (const [className, fill] of [["tjRecorded", "rgb(237, 247, 244)"], ["tjNeedsInput", "rgb(255, 251, 234)"],
["tjFailed", "rgb(254, 242, 242)"], ["tjNotTaken", "rgb(246, 248, 252)"]]) {
await expect(diagram.locator(`svg g.node.${className}`)).toHaveCount(1);
await expect(diagram.locator(`svg g.node.${className}`).locator("rect,polygon").first()).toHaveCSS("fill", fill);
}
await expect(diagram.locator("svg g.node.tjRunning,svg g.node.tjRecordedRunning")).toHaveCount(0);
await expect(diagram).toContainText("不实时同步执行状态");
await expect(diagram.getByLabel("图示状态说明")).toContainText("已有结果");
await diagram.getByRole("button", { name: /^首次尝试失败 ·/ }).click();
await expect(diagram.getByLabel("节点详情 首次尝试失败")).toContainText("此前计算超时");
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(width);
await diagram.screenshot({ path: testInfo.outputPath(`reference-states-${width}.png`) });
await diagram.getByRole("button", { name: "放大查看", exact: true }).click();
const reader = page.getByRole("dialog", { name: "调查流程参考", exact: true });
await expect(reader.locator("svg g.node")).toHaveCount(7);
await reader.getByRole("button", { name: "Zoom in", exact: true }).click();
const application = reader.getByRole("application");
const transform = await application.evaluate((element) => {
element.setAttribute("data-reference-instance", "retained");
return (element as HTMLElement).style.transform;
});
await page.evaluate(() => (window as unknown as { updateReferenceAnnotation: () => void }).updateReferenceAnnotation());
await expect(reader.locator("svg g.node.tjRecorded")).toHaveCount(2);
await expect(reader.locator("svg g.node.tjFailed")).toHaveCount(1);
await expect(application).toHaveAttribute("data-reference-instance", "retained");
expect(await application.evaluate((element) => (element as HTMLElement).style.transform)).toBe(transform);
await expect(reader).not.toContainText("正在执行");
});
}