102 lines
7.1 KiB
TypeScript
102 lines
7.1 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 recorded node states 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 diagram = {
|
|
title: "调查节点状态",
|
|
mermaid: 'flowchart TD\nP[待启动核查] --> R[核对监测]:::tjAction\nR --> C[核查完成]:::tjHuman\nR --> W[等待系统结果]\nR --> H[等待人工复核]\nC --> F:::tjHuman\nC --> S[未选用分支]\nW --> X[取消分支]\nH --> U:::tjAction\nstyle R fill:#FFF6E8,stroke:#C4A168;',
|
|
node_refs: [
|
|
{ node_id: "P", label: "待启动核查", status: "pending", evidence_refs: [], action_refs: [] },
|
|
{ node_id: "R", label: "核对监测", status: "running", evidence_refs: ["e1"], action_refs: ["50000000-0000-4000-8000-000000000001"] },
|
|
{ node_id: "C", label: "核查完成", status: "completed", evidence_refs: [], action_refs: [] },
|
|
{ node_id: "W", label: "等待系统结果", status: "waiting", evidence_refs: [], action_refs: [] },
|
|
{ node_id: "H", label: "等待人工复核", status: "waiting_human", evidence_refs: [], action_refs: [] },
|
|
{ node_id: "F", label: "查询失败", status: "failed", evidence_refs: [], action_refs: [] },
|
|
{ node_id: "S", label: "未选用分支", status: "skipped", evidence_refs: [], action_refs: [] },
|
|
{ node_id: "X", label: "取消分支", status: "cancelled", evidence_refs: [], action_refs: [] },
|
|
{ node_id: "U", label: "状态未记录", evidence_refs: ["e2"], action_refs: [] }
|
|
]
|
|
};
|
|
const root = createRoot(document.getElementById("state-reader"));
|
|
const render = (running: boolean) => root.render(React.createElement(InvestigationDiagramView, {
|
|
diagram: { ...diagram, node_refs: [...diagram.node_refs] }, running,
|
|
renderDetails: () => React.createElement("p", null, "状态来自节点快照,关联证据不代表步骤已经完成。")
|
|
}));
|
|
Object.assign(window, { setDiagramState: (running: boolean, status = "running") => {
|
|
diagram.node_refs[1].status = status;
|
|
render(running);
|
|
} });
|
|
render(true);
|
|
});
|
|
const diagram = page.getByRole("region", { name: "调查脉络", exact: true });
|
|
const node = (className: string) => diagram.locator(`svg g.node.${className}`);
|
|
const shape = (className: string) => node(className).locator("rect,polygon,circle,ellipse,path").first();
|
|
await expect(diagram.locator("svg g.node")).toHaveCount(9);
|
|
for (const [className, fill, stroke] of [
|
|
["tjPending", "rgb(246, 248, 252)", "rgb(165, 181, 204)"],
|
|
["tjRunning", "rgb(232, 240, 255)", "rgb(66, 99, 199)"],
|
|
["tjCompleted", "rgb(237, 247, 244)", "rgb(107, 158, 139)"],
|
|
["tjWaiting", "rgb(255, 251, 234)", "rgb(200, 172, 96)"],
|
|
["tjWaitingHuman", "rgb(255, 246, 232)", "rgb(196, 161, 104)"],
|
|
["tjFailed", "rgb(254, 242, 242)", "rgb(217, 108, 117)"],
|
|
["tjSkipped", "rgb(246, 248, 252)", "rgb(165, 181, 204)"],
|
|
["tjCancelled", "rgb(241, 245, 249)", "rgb(148, 163, 184)"],
|
|
["tjUnknown", "rgb(255, 255, 255)", "rgb(204, 214, 229)"]
|
|
]) {
|
|
await expect(node(className)).toHaveCount(1);
|
|
await expect(shape(className)).toHaveCSS("fill", fill);
|
|
await expect(shape(className)).toHaveCSS("stroke", stroke);
|
|
}
|
|
const legend = diagram.getByLabel("图示状态说明");
|
|
await expect(legend).toContainText("等待依赖 / 外部结果");
|
|
await expect(legend).toContainText("等待人工处理");
|
|
await expect(legend).toContainText("未标注状态");
|
|
expect(await shape("tjSkipped").evaluate((element) => getComputedStyle(element).strokeDasharray)).not.toBe("none");
|
|
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(`node-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.tjRunning > rect")).toHaveCSS("fill", "rgb(232, 240, 255)");
|
|
const bounds = (await reader.boundingBox())!;
|
|
expect(bounds.width).toBeLessThan(width);
|
|
expect(bounds.height).toBeLessThan(900);
|
|
await reader.getByRole("button", { name: "Zoom in", exact: true }).click();
|
|
const application = reader.getByRole("application");
|
|
const transform = await application.evaluate((element) => {
|
|
element.setAttribute("data-state-test-instance", "retained");
|
|
return (element as HTMLElement).style.transform;
|
|
});
|
|
await page.evaluate(() => (window as unknown as { setDiagramState: (running: boolean) => void }).setDiagramState(false));
|
|
const archived = reader.locator("svg g.node.tjRecordedRunning > rect");
|
|
await expect(archived).toHaveCSS("fill", "rgb(246, 248, 252)");
|
|
await expect(reader.getByLabel("图示状态说明")).toContainText("记录时执行中");
|
|
await expect(reader.locator("svg g.node.tjCompleted > rect")).toHaveCSS("fill", "rgb(237, 247, 244)");
|
|
await expect(reader.locator("svg g.node")).toHaveCount(9);
|
|
await expect(application).toHaveAttribute("data-state-test-instance", "retained");
|
|
expect(await application.evaluate((element) => (element as HTMLElement).style.transform)).toBe(transform);
|
|
await page.evaluate(() => (window as unknown as { setDiagramState: (running: boolean, status: string) => void }).setDiagramState(true, "completed"));
|
|
await expect(reader.locator("svg g.node.tjCompleted")).toHaveCount(2);
|
|
await expect(reader.locator("svg g.node.tjRunning")).toHaveCount(0);
|
|
await expect(reader.getByLabel("节点详情 核对监测")).toContainText("已完成");
|
|
});
|
|
}
|