feat(workbench): group live presentation steps into expandable categories
Group adjacent live display steps into category menus with merged snapshots, original-step submenus and scoped playback. The submenu exposes the latest raw step, so explicit selections must remain pinned. Locate failed and cancelled steps using the full group sequence, and prevent an exiting menu from dismissing a trigger-owned reopen. Regression tests reproduce these paths before the fixes. Validation: lint, typecheck, 1178 unit tests and 10 focused browser cases passed; update the presentation history guide.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
import { investigationFixture, mockInvestigationPresentation, selectInvestigation } from "./support/mock-investigation-presentation";
|
||||
import { investigationFixture, mockInvestigationPresentation, selectInvestigation, showInvestigationMap } from "./support/mock-investigation-presentation";
|
||||
|
||||
const chart = (index: number) => ({
|
||||
action: "open_chart", chart_id: "pressure", evidence_refs: ["synthetic:pressure"],
|
||||
@@ -260,6 +260,7 @@ test("playback restores charts without opening windows while live results remain
|
||||
run_id: task.active_run!.run_id, execution_generation: 1, occurred_at: task.created_at, command }));
|
||||
const mock = await mockInvestigationPresentation(page, [task], saved);
|
||||
await selectInvestigation(page, task.title);
|
||||
await showInvestigationMap(page);
|
||||
const panel = page.getByLabel("调查实时展示", { exact: true });
|
||||
await expect(panel.getByRole("button", { name: "播放历史", exact: true })).toBeVisible();
|
||||
await expect(page.locator("[data-floating-window]")).toHaveCount(0);
|
||||
@@ -276,7 +277,7 @@ test("playback restores charts without opening windows while live results remain
|
||||
const progress = panel.getByRole("slider", { name: "历史播放进度" });
|
||||
await progress.focus();
|
||||
await page.keyboard.press("ArrowRight");
|
||||
await expect(panel.getByRole("combobox", { name: "切换展示动作" })).toContainText("2.");
|
||||
await expect(panel.getByRole("button", { name: "切换展示动作" })).toHaveText("地图操作");
|
||||
await expect(page.locator("[data-floating-window]")).toHaveCount(0);
|
||||
await page.keyboard.press("ArrowRight");
|
||||
await expect(page.locator("[data-floating-window]")).toHaveCount(0);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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";
|
||||
import { investigationFixture, mockInvestigationPresentation, selectInvestigation, showInvestigationMap, showInvestigationTasks } from "./support/mock-investigation-presentation";
|
||||
|
||||
test("live investigation charts remain visible while Dev is open", async ({ page }) => {
|
||||
const task = investigationFixture(1, "合成爆管展示核查");
|
||||
@@ -766,36 +766,34 @@ test("live actions continue recording without interrupting a historical view and
|
||||
const task = investigationFixture(1, "历史回看中的实时调查");
|
||||
const mock = await mockInvestigationPresentation(page, [task]);
|
||||
await selectInvestigation(page, task.title);
|
||||
await showInvestigationMap(page);
|
||||
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");
|
||||
const switcher = panel.getByRole("button", { name: "切换展示动作" });
|
||||
await expect(switcher).toHaveText("当前(最新)");
|
||||
const manager = page.getByRole("dialog", { name: "图表管理", exact: true });
|
||||
await expect(manager).toBeVisible();
|
||||
await manager.getByRole("button", { name: "关闭窗口", exact: true }).click();
|
||||
await switcher.click();
|
||||
await expect(page.getByRole("option")).toHaveCount(1);
|
||||
await expect(page.getByRole("option", { name: /当前(最新).*压力快照 1/ })).toBeVisible();
|
||||
await expect(page.getByRole("menu", { name: "展示类别", exact: true }).locator("[data-history-group-row]")).toHaveText(["图表"]);
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(panel.getByRole("button", { name: "上一个动作" })).toBeDisabled();
|
||||
await expect(panel.getByRole("button", { name: "上一个动作" })).toBeEnabled();
|
||||
await mock.emit(task, chart(2));
|
||||
await expect(switcher).toContainText("压力快照 2");
|
||||
await expect(switcher).toHaveText("当前(最新)");
|
||||
await switcher.click();
|
||||
await expect(page.getByRole("option")).toHaveCount(2);
|
||||
await page.getByRole("option", { name: "1. 打开图表 · 压力快照 1 · 完成", exact: true }).click();
|
||||
await page.getByRole("menuitem", { name: "展开图表步骤", exact: true }).last().click();
|
||||
await page.getByRole("menuitem", { 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 expect(switcher).toContainText("1. 压力快照 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.getByRole("menuitem", { name: "展开图表步骤", exact: true }).last().click();
|
||||
const historyMenu = page.getByRole("menu", { name: "图表展示步骤", exact: true });
|
||||
await expect(historyMenu.getByRole("menuitem", { name: /^\d+\./ })).toHaveCount(12);
|
||||
await expect(historyMenu.getByRole("menuitem", { name: "12. 压力快照 12", exact: true })).toBeVisible();
|
||||
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);
|
||||
@@ -803,8 +801,8 @@ test("live actions continue recording without interrupting a historical view and
|
||||
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();
|
||||
await expect(switcher).toHaveText("图表");
|
||||
await expect(panel.getByRole("group", { name: "压力快照 12", exact: true })).toBeVisible();
|
||||
expect(mock.mutations).toEqual([]);
|
||||
});
|
||||
|
||||
@@ -822,20 +820,22 @@ test("selecting an investigation restores saved actions, keeps live continuity a
|
||||
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);
|
||||
await showInvestigationMap(page);
|
||||
const panel = page.getByLabel("调查实时展示", { exact: true });
|
||||
const switcher = panel.getByRole("combobox", { name: "切换展示动作" });
|
||||
await expect(switcher).toContainText("4 个动作");
|
||||
const switcher = panel.getByRole("button", { name: "切换展示动作" });
|
||||
await expect(panel.getByRole("slider", { name: "历史播放进度" })).toHaveAttribute("max", "2");
|
||||
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(page.getByRole("menu", { name: "展示类别", exact: true }).locator("[data-history-group-row]")).toHaveCount(3);
|
||||
await page.getByRole("menuitem", { name: "展开图表步骤", exact: true }).last().click();
|
||||
await page.getByRole("menuitem", { 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("slider", { name: "历史播放进度" })).toHaveAttribute("max", "2");
|
||||
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" });
|
||||
@@ -844,6 +844,7 @@ test("selecting an investigation restores saved actions, keeps live continuity a
|
||||
await page.getByRole("checkbox", { name: "跟随所选调查", exact: true }).uncheck();
|
||||
await expect(panel.getByText("已载入历史", { exact: true })).toBeVisible();
|
||||
await page.getByRole("button", { name: "切换地图 Dev Panel" }).click();
|
||||
await showInvestigationTasks(page);
|
||||
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();
|
||||
@@ -852,10 +853,13 @@ test("selecting an investigation restores saved actions, keeps live continuity a
|
||||
if (await all.isVisible()) await all.click();
|
||||
}
|
||||
await completedTitle.click();
|
||||
await showInvestigationMap(page);
|
||||
await expect(panel.getByRole("group", { name: "已完成调查的压力曲线", exact: true })).toBeVisible();
|
||||
await expect(switcher).toContainText("1 个动作");
|
||||
await expect(panel.getByRole("slider", { name: "历史播放进度" })).toHaveAttribute("max", "0");
|
||||
await showInvestigationTasks(page);
|
||||
await tasks.getByText(task.title, { exact: true }).first().click();
|
||||
await expect(switcher).toContainText("5 个动作");
|
||||
await showInvestigationMap(page);
|
||||
await expect(panel.getByRole("slider", { name: "历史播放进度" })).toHaveAttribute("max", "2");
|
||||
await expect(panel.getByRole("group", { name: "当前实时压力", exact: true })).toBeVisible();
|
||||
expect(mock.mutations).toEqual([]);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
import { expect, test, type Page, type TestInfo } from "@playwright/test";
|
||||
import { investigationFixture, mockInvestigationPresentation, selectInvestigation, showInvestigationMap } from "./support/mock-investigation-presentation";
|
||||
|
||||
const chart = (id: string, title: string) => ({ action: "open_chart", chart_id: id, evidence_refs: ["synthetic:reading"],
|
||||
chart: { title, chart_type: "line", x_data: ["08:00", "08:15"], series: [{ name: "压力(m)", data: [30, 28] }] } });
|
||||
|
||||
async function captureMenus(page: Page, testInfo: TestInfo, name: string) {
|
||||
for (const menu of await page.getByRole("menu").all()) {
|
||||
await menu.evaluate((node) => Promise.all(node.getAnimations().map((animation) => animation.finished.catch(() => undefined))));
|
||||
}
|
||||
const boxes = await Promise.all((await page.getByRole("menu").all()).map((menu) => menu.boundingBox()));
|
||||
const visible = boxes.filter((box) => box !== null);
|
||||
const x = Math.min(...visible.map((box) => box.x));
|
||||
const y = Math.min(...visible.map((box) => box.y));
|
||||
const right = Math.max(...visible.map((box) => box.x + box.width));
|
||||
const bottom = Math.max(...visible.map((box) => box.y + box.height));
|
||||
await testInfo.attach(name, { body: await page.screenshot({ path: testInfo.outputPath(`${name}.png`),
|
||||
clip: { x, y, width: right - x, height: bottom - y } }), contentType: "image/png" });
|
||||
}
|
||||
|
||||
test("pins the latest original step while new live charts continue arriving", async ({ page }) => {
|
||||
const task = investigationFixture(1, "最新步骤固定回看");
|
||||
const saved = [chart("P1", "压力 1"), chart("P2", "压力 2")].map((command, index) => ({
|
||||
task_id: task.task_id, event_seq: index + 1, run_id: task.active_run!.run_id,
|
||||
execution_generation: 1, occurred_at: task.created_at, command }));
|
||||
const mock = await mockInvestigationPresentation(page, [task], saved);
|
||||
await selectInvestigation(page, task.title, "keyboard");
|
||||
await showInvestigationMap(page);
|
||||
const panel = page.getByLabel("调查实时展示", { exact: true });
|
||||
await panel.getByRole("button", { name: "切换展示动作", exact: true }).click();
|
||||
await page.getByRole("menuitem", { name: "展开图表步骤", exact: true }).click();
|
||||
await page.getByRole("menuitem", { name: "2. 压力 2", exact: true }).click();
|
||||
await expect(panel.getByRole("button", { name: "回到当前", exact: true })).toBeVisible();
|
||||
await mock.emit(task, chart("P3", "压力 3"));
|
||||
await expect(panel.getByText("后续 1 个动作", { exact: true })).toBeVisible();
|
||||
await expect(panel.getByRole("group", { name: "压力 2", exact: true })).toBeVisible();
|
||||
await expect(panel.getByRole("group", { name: "压力 3", exact: true })).toHaveCount(0);
|
||||
await panel.getByRole("button", { name: "回到当前", exact: true }).click();
|
||||
await expect(panel.getByRole("group", { name: "压力 3", exact: true })).toBeVisible();
|
||||
expect(mock.mutations).toEqual([]);
|
||||
});
|
||||
|
||||
test("hover keeps an expanded category stable across its title, arrow and original steps", async ({ page }, testInfo) => {
|
||||
const task = investigationFixture(1, "历史菜单悬停核查");
|
||||
const overlay = { action: "set_overlay", overlay_id: "hover-area", title: "隔离影响范围", mode: "geometry", evidence_id: "synthetic: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 = [chart("P1", "测点 P1 压力趋势"), chart("P2", "测点 P2 压力趋势"), overlay].map((command, index) => ({
|
||||
task_id: task.task_id, event_seq: index + 1, run_id: task.active_run!.run_id,
|
||||
execution_generation: 1, occurred_at: task.created_at, command }));
|
||||
const mock = await mockInvestigationPresentation(page, [task], saved);
|
||||
await selectInvestigation(page, task.title, "keyboard");
|
||||
await showInvestigationMap(page);
|
||||
await page.getByLabel("调查实时展示", { exact: true }).getByRole("button", { name: "切换展示动作", exact: true }).click();
|
||||
const root = page.getByRole("menu", { name: "展示类别", exact: true });
|
||||
const arrow = root.getByRole("menuitem", { name: "展开图表步骤", exact: true });
|
||||
const title = root.getByRole("menuitem", { name: "图表", exact: true });
|
||||
const steps = page.getByRole("menu", { name: "图表展示步骤", exact: true });
|
||||
await arrow.hover();
|
||||
await expect(steps).toBeVisible();
|
||||
await page.evaluate(() => {
|
||||
const changes: string[] = [];
|
||||
const observer = new MutationObserver((records) => {
|
||||
for (const record of records) {
|
||||
if (record.type === "attributes" && record.target instanceof Element && record.target.getAttribute("aria-label") === "展开图表步骤")
|
||||
changes.push(record.target.getAttribute("data-state") ?? "");
|
||||
}
|
||||
});
|
||||
observer.observe(document.body, { subtree: true, attributes: true, attributeFilter: ["data-state"] });
|
||||
Object.assign(window, { historyHoverChanges: changes });
|
||||
});
|
||||
for (let repeat = 0; repeat < 3; repeat++) {
|
||||
await title.hover();
|
||||
const titleBox = await title.boundingBox();
|
||||
await page.mouse.move(titleBox!.x + 12, titleBox!.y + titleBox!.height / 2, { steps: 8 });
|
||||
await expect(steps).toBeVisible();
|
||||
await arrow.hover();
|
||||
await expect(steps).toBeVisible();
|
||||
await steps.getByRole("menuitem", { name: "1. 测点 P1 压力趋势", exact: true }).hover();
|
||||
await expect(steps).toBeVisible();
|
||||
await arrow.hover();
|
||||
}
|
||||
const changes = await page.evaluate(() => (window as unknown as { historyHoverChanges: string[] }).historyHoverChanges);
|
||||
await testInfo.attach("hover-state-changes", { body: JSON.stringify(changes), contentType: "application/json" });
|
||||
expect(changes).not.toContain("closed");
|
||||
await captureMenus(page, testInfo, "stable-history-hover");
|
||||
await root.getByRole("menuitem", { name: "展开覆盖层步骤", exact: true }).hover();
|
||||
await expect(page.getByRole("menu", { name: "覆盖层展示步骤", exact: true })).toBeVisible();
|
||||
await expect(steps).toHaveCount(0);
|
||||
await arrow.hover();
|
||||
await expect(steps).toBeVisible();
|
||||
await expect(page.getByRole("menu", { name: "覆盖层展示步骤", exact: true })).toHaveCount(0);
|
||||
await title.click();
|
||||
await expect(root).toHaveCount(0);
|
||||
const panel = page.getByLabel("调查实时展示", { exact: true });
|
||||
await expect(panel.getByRole("group", { name: "测点 P1 压力趋势", exact: true })).toBeVisible();
|
||||
await expect(panel.getByRole("group", { name: "测点 P2 压力趋势", exact: true })).toBeVisible();
|
||||
expect(mock.mutations).toEqual([]);
|
||||
});
|
||||
|
||||
test("adjacent historical rounds share one first-level group and keep their original steps below", async ({ page }, testInfo) => {
|
||||
const task = investigationFixture(1, "历史 SCADA 多轮调查");
|
||||
task.execution_generation = 14;
|
||||
task.active_run!.generation = 14;
|
||||
const saved = [1, 7, 11].flatMap((generation, index) => [1, 2].map((step) => ({
|
||||
task_id: task.task_id, run_id: task.active_run!.run_id, event_seq: index * 2 + step,
|
||||
execution_generation: generation, occurred_at: task.created_at,
|
||||
command: chart(`G${generation}-${step}`, `第 ${generation} 轮压力 ${step}`)
|
||||
})));
|
||||
const mock = await mockInvestigationPresentation(page, [task], saved);
|
||||
await selectInvestigation(page, task.title, "keyboard");
|
||||
await showInvestigationMap(page);
|
||||
const panel = page.getByLabel("调查实时展示", { exact: true });
|
||||
const trigger = panel.getByRole("button", { name: "切换展示动作", exact: true });
|
||||
await trigger.click();
|
||||
const root = page.getByRole("menu", { name: "展示类别", exact: true });
|
||||
await expect(root.locator("[data-history-group-row]")).toHaveText(["图表"]);
|
||||
await root.getByRole("menuitem", { name: "图表", exact: true }).focus();
|
||||
await page.keyboard.press("ArrowRight");
|
||||
const charts = page.getByRole("menu", { name: "图表展示步骤", exact: true });
|
||||
await expect(charts.getByRole("menuitem")).toHaveText([
|
||||
"6. 第 11 轮压力 2", "5. 第 11 轮压力 1", "4. 第 7 轮压力 2", "3. 第 7 轮压力 1", "2. 第 1 轮压力 2", "1. 第 1 轮压力 1"
|
||||
]);
|
||||
await expect(page.getByText("合并查看", { exact: true })).toHaveCount(0);
|
||||
await captureMenus(page, testInfo, "adjacent-round-submenu");
|
||||
await page.keyboard.press("ArrowLeft");
|
||||
await expect(root).toBeVisible();
|
||||
await root.getByRole("menuitem", { name: "图表", exact: true }).click();
|
||||
await expect(trigger).toHaveText("图表");
|
||||
await expect(panel.getByRole("group", { name: "第 7 轮压力 1", exact: true })).toBeVisible();
|
||||
await expect(panel.getByRole("group", { name: "第 1 轮压力 1", exact: true })).toBeVisible();
|
||||
await expect(panel.getByRole("group", { name: "第 11 轮压力 1", exact: true })).toBeVisible();
|
||||
expect(mock.mutations).toEqual([]);
|
||||
});
|
||||
|
||||
test("first-level titles display merged adjacent steps and arrows expose only their own steps on desktop and narrow screens", async ({ page }, testInfo) => {
|
||||
test.setTimeout(60_000);
|
||||
const task = investigationFixture(1, "SCADA 调查相邻步骤核查");
|
||||
const overlay = { action: "set_overlay", overlay_id: "test-area", title: "隔离影响范围", mode: "geometry", evidence_id: "synthetic: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 = [chart("P1", "测点 P1 压力趋势"), chart("P2", "测点 P2 压力趋势"), overlay,
|
||||
chart("P3", "测点 P3 压力趋势"), chart("P4", "测点 P4 压力趋势")].map((command, index) => ({
|
||||
task_id: task.task_id, event_seq: index + 1, run_id: task.active_run!.run_id,
|
||||
execution_generation: 1, occurred_at: task.created_at, command }));
|
||||
const mock = await mockInvestigationPresentation(page, [task], saved);
|
||||
await selectInvestigation(page, task.title, "keyboard");
|
||||
await showInvestigationMap(page);
|
||||
const panel = page.getByLabel("调查实时展示", { exact: true });
|
||||
await expect(panel.getByRole("group", { name: "测点 P4 压力趋势", exact: true })).toBeVisible();
|
||||
const closeWindow = page.getByRole("dialog").getByRole("button", { name: "关闭窗口", exact: true });
|
||||
while (await closeWindow.count()) await closeWindow.first().click();
|
||||
const trigger = panel.getByRole("button", { name: "切换展示动作", exact: true });
|
||||
await trigger.click();
|
||||
const root = page.getByRole("menu", { name: "展示类别", exact: true });
|
||||
await expect(root.locator("[data-history-group-row]")).toHaveText(["图表", "覆盖层", "图表"]);
|
||||
await expect(root).not.toContainText("合并查看");
|
||||
await expect(root).not.toContainText("第 1 轮");
|
||||
await captureMenus(page, testInfo, "adjacent-groups-desktop");
|
||||
await root.getByRole("menuitem", { name: "图表", exact: true }).first().click();
|
||||
await expect(trigger).toHaveText("图表");
|
||||
await expect(panel.getByText("历史回看", { exact: true })).toBeVisible();
|
||||
await expect(panel.getByRole("group", { name: "测点 P3 压力趋势", exact: true })).toBeVisible();
|
||||
await expect(panel.getByRole("group", { name: "测点 P4 压力趋势", exact: true })).toBeVisible();
|
||||
await expect(panel.getByRole("group", { name: "测点 P1 压力趋势", exact: true })).toHaveCount(0);
|
||||
await expect(panel.getByRole("group", { name: "测点 P2 压力趋势", exact: true })).toHaveCount(0);
|
||||
await expect(panel.getByRole("region", { name: "调查覆盖层" })).toHaveCount(0);
|
||||
await mock.emit(task, chart("P5", "测点 P5 压力趋势"));
|
||||
await expect(panel.getByText("后续 1 个动作", { exact: true })).toBeVisible();
|
||||
await expect(panel.getByRole("group", { name: "测点 P5 压力趋势", exact: true })).toHaveCount(0);
|
||||
await trigger.click();
|
||||
await root.getByRole("menuitem", { name: "展开图表步骤", exact: true }).first().click();
|
||||
const charts = page.getByRole("menu", { name: "图表展示步骤", exact: true });
|
||||
await expect(charts.getByRole("menuitem")).toHaveText(["6. 测点 P5 压力趋势", "5. 测点 P4 压力趋势", "4. 测点 P3 压力趋势"]);
|
||||
await captureMenus(page, testInfo, "adjacent-group-submenu-desktop");
|
||||
await page.keyboard.press("Escape");
|
||||
await page.setViewportSize({ width: 375, height: 720 });
|
||||
await showInvestigationMap(page);
|
||||
await page.getByRole("button", { name: "关闭通知", exact: true }).evaluateAll((buttons) => buttons.forEach((button) => (button as HTMLButtonElement).click()));
|
||||
await trigger.click();
|
||||
await expect(root.locator("[data-history-group-row]")).toHaveText(["图表", "覆盖层", "图表"]);
|
||||
await root.getByRole("menuitem", { name: "展开图表步骤", exact: true }).last().click();
|
||||
const compactCharts = page.getByRole("menu", { name: "图表展示步骤", exact: true });
|
||||
await expect(compactCharts.getByRole("menuitem")).toHaveText(["返回类别", "2. 测点 P2 压力趋势", "1. 测点 P1 压力趋势"]);
|
||||
const bounds = await compactCharts.boundingBox();
|
||||
expect(bounds!.x).toBeGreaterThanOrEqual(0);
|
||||
expect(bounds!.x + bounds!.width).toBeLessThanOrEqual(375);
|
||||
expect(await compactCharts.evaluate((node) => node.scrollWidth - node.clientWidth)).toBeLessThanOrEqual(1);
|
||||
await captureMenus(page, testInfo, "adjacent-submenu-narrow");
|
||||
await compactCharts.getByRole("menuitem", { name: "返回类别", exact: true }).click();
|
||||
await expect(root.locator("[data-history-group-row]")).toHaveText(["图表", "覆盖层", "图表"]);
|
||||
await root.getByRole("menuitem", { name: "图表", exact: true }).last().click();
|
||||
await expect(panel.getByRole("group", { name: "测点 P1 压力趋势", exact: true })).toBeVisible();
|
||||
await expect(panel.getByRole("group", { name: "测点 P2 压力趋势", exact: true })).toBeVisible();
|
||||
await expect(panel.getByRole("group", { name: "测点 P3 压力趋势", exact: true })).toHaveCount(0);
|
||||
await trigger.click();
|
||||
await expect(trigger).toHaveAttribute("aria-expanded", "true");
|
||||
await trigger.click();
|
||||
await expect(trigger).toHaveAttribute("aria-expanded", "false");
|
||||
await trigger.click();
|
||||
await expect(trigger).toHaveAttribute("aria-expanded", "true");
|
||||
await panel.getByRole("heading", { name: "调查实时展示", exact: true }).click();
|
||||
await expect(trigger).toHaveAttribute("aria-expanded", "false");
|
||||
await trigger.click();
|
||||
await expect(trigger).toHaveAttribute("aria-expanded", "true");
|
||||
await root.getByRole("menuitem", { name: "展开图表步骤", exact: true }).last().click();
|
||||
await compactCharts.getByRole("menuitem", { name: "1. 测点 P1 压力趋势", exact: true }).click();
|
||||
await expect(panel.getByRole("group", { name: "测点 P2 压力趋势", exact: true })).toHaveCount(0);
|
||||
await panel.getByRole("button", { name: "回到当前", exact: true }).click();
|
||||
await expect(panel.getByRole("group", { name: "测点 P5 压力趋势", exact: true })).toBeVisible();
|
||||
await trigger.click();
|
||||
await root.getByRole("menuitem", { name: "覆盖层", exact: true }).click();
|
||||
await expect(panel.getByRole("region", { name: "调查覆盖层" })).toContainText("隔离影响范围");
|
||||
await expect(panel.getByRole("group", { name: "测点 P1 压力趋势", exact: true })).toHaveCount(0);
|
||||
expect(mock.mutations).toEqual([]);
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
import { MAP_STYLE_TOKENS } from "../../src/features/workbench/map/map-colors";
|
||||
import { investigationFixture, mockInvestigationPresentation, selectInvestigation } from "./support/mock-investigation-presentation";
|
||||
import { investigationFixture, mockInvestigationPresentation, selectInvestigation, showInvestigationMap, showInvestigationTasks } from "./support/mock-investigation-presentation";
|
||||
|
||||
const chart = (index: number) => ({ action: "open_chart", chart_id: "pressure-history", evidence_refs: ["synthetic:pressure"], chart: {
|
||||
title: `压力对比 ${index}`, chart_type: "line", x_data: ["10:00", "10:15"], series: [{ name: "压力(m)", data: [30, 30 - index] }]
|
||||
@@ -9,12 +9,18 @@ const overlay = { action: "set_overlay", overlay_id: "warning-area", title: "合
|
||||
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]]] } }]
|
||||
} };
|
||||
|
||||
async function closeChartWindows(page: Page) {
|
||||
const closeWindow = page.getByRole("dialog").getByRole("button", { name: "关闭窗口", exact: true });
|
||||
while (await closeWindow.count()) await closeWindow.first().click();
|
||||
}
|
||||
|
||||
test("successful charts and warning geometry survive stream loss and failed reconnect", async ({ page }, testInfo) => {
|
||||
test.setTimeout(60_000);
|
||||
const task = investigationFixture(1, "断线保留展示核查");
|
||||
const other = investigationFixture(2, "切换后独立调查");
|
||||
const mock = await mockInvestigationPresentation(page, [task, other]);
|
||||
await selectInvestigation(page, task.title);
|
||||
await showInvestigationMap(page);
|
||||
const panel = page.getByLabel("调查实时展示", { exact: true });
|
||||
await expect(panel.getByText("实时跟随", { exact: true })).toBeVisible();
|
||||
await mock.emit(task, chart(1));
|
||||
@@ -47,44 +53,70 @@ test("successful charts and warning geometry survive stream loss and failed reco
|
||||
await connection(true);
|
||||
await expect(panel.getByText("实时跟随", { exact: true })).toBeVisible();
|
||||
expect(await geometry()).toEqual(saved);
|
||||
await closeChartWindows(page);
|
||||
await showInvestigationTasks(page);
|
||||
await page.getByRole("region", { name: "工况任务", exact: true }).getByText(other.title, { exact: true }).first().click();
|
||||
await showInvestigationMap(page);
|
||||
await expect(panel.getByRole("group", { name: "压力对比 1", exact: true })).toHaveCount(0);
|
||||
await expect.poll(geometry).toEqual({ type: "FeatureCollection", features: [] });
|
||||
expect(mock.mutations).toEqual([]);
|
||||
});
|
||||
|
||||
test("history playback pauses, seeks and finishes on a frozen snapshot while current receives new results", async ({ page }, testInfo) => {
|
||||
test("history playback plays adjacent groups, pauses and seeks merged snapshots while current receives new results", async ({ page }, testInfo) => {
|
||||
test.setTimeout(60_000);
|
||||
const task = investigationFixture(1, "只读历史播放核查");
|
||||
const saved = [1, 2, 3].map((index) => ({ task_id: task.task_id, event_seq: index, run_id: task.active_run!.run_id,
|
||||
execution_generation: 1, occurred_at: task.created_at, command: chart(index) }));
|
||||
const groupedChart = (index: number) => ({ ...chart(index), chart_id: `pressure-${index}` });
|
||||
const saved = [groupedChart(1), groupedChart(2), overlay, groupedChart(3), groupedChart(4)].map((command, index) => ({
|
||||
task_id: task.task_id, event_seq: index + 1, run_id: task.active_run!.run_id,
|
||||
execution_generation: 1, occurred_at: task.created_at, command }));
|
||||
const mock = await mockInvestigationPresentation(page, [task], saved);
|
||||
await selectInvestigation(page, task.title);
|
||||
await selectInvestigation(page, task.title, "keyboard");
|
||||
await showInvestigationMap(page);
|
||||
const panel = page.getByLabel("调查实时展示", { exact: true });
|
||||
await expect(panel.getByRole("group", { name: "压力对比 3", exact: true })).toBeVisible();
|
||||
await page.getByRole("dialog", { name: "压力对比 3", exact: true }).getByRole("button", { name: "关闭窗口", exact: true }).click();
|
||||
await expect(panel.getByRole("group", { name: "压力对比 4", exact: true })).toBeVisible();
|
||||
await closeChartWindows(page);
|
||||
await panel.getByRole("button", { name: "播放历史", exact: true }).click();
|
||||
await panel.getByRole("button", { name: "暂停历史播放", exact: true }).click();
|
||||
await expect(panel.getByRole("group", { name: "压力对比 1", exact: true })).toBeVisible();
|
||||
await page.getByRole("dialog", { name: "压力对比 1", exact: true }).getByRole("button", { name: "关闭窗口", exact: true }).click();
|
||||
await expect(panel.getByRole("group", { name: "压力对比 2", exact: true })).toBeVisible();
|
||||
await expect(panel.getByRole("group", { name: "压力对比 3", exact: true })).toHaveCount(0);
|
||||
const slider = panel.getByRole("slider", { name: "历史播放进度" });
|
||||
await expect(slider).toHaveAttribute("max", "2");
|
||||
await expect(slider).toHaveValue("0");
|
||||
await expect(panel.getByLabel("历史播放位置")).toHaveText("1/3");
|
||||
await closeChartWindows(page);
|
||||
await panel.getByRole("combobox", { name: "历史播放速度" }).click();
|
||||
await page.getByRole("option", { name: "2×", exact: true }).click();
|
||||
await panel.getByRole("button", { name: "播放历史", exact: true }).click();
|
||||
await mock.emit(task, chart(4));
|
||||
await mock.emit(task, groupedChart(5));
|
||||
await expect(panel.getByRole("group", { name: "压力对比 3", exact: true })).toBeVisible();
|
||||
await expect(panel.getByRole("group", { name: "压力对比 4", exact: true })).toBeVisible();
|
||||
await expect(panel.getByRole("button", { name: "播放历史", exact: true })).toBeVisible();
|
||||
await expect(panel.getByRole("group", { name: "压力对比 4", exact: true })).toHaveCount(0);
|
||||
await panel.getByRole("slider", { name: "历史播放进度" }).focus();
|
||||
await expect(panel.getByRole("group", { name: "压力对比 5", exact: true })).toHaveCount(0);
|
||||
await expect(panel.getByLabel("历史播放位置")).toHaveText("3/3");
|
||||
await slider.focus();
|
||||
await page.keyboard.press("ArrowLeft");
|
||||
await expect(panel.getByRole("region", { name: "调查覆盖层" })).toContainText("合成影响示意");
|
||||
await expect(panel.getByRole("group", { name: "压力对比 3", exact: true })).toHaveCount(0);
|
||||
await expect(panel.getByLabel("历史播放位置")).toHaveText("2/3");
|
||||
await page.keyboard.press("Home");
|
||||
await expect(panel.getByRole("group", { name: "压力对比 1", exact: true })).toBeVisible();
|
||||
await expect(panel.getByRole("group", { name: "压力对比 2", exact: true })).toBeVisible();
|
||||
await expect(panel.getByLabel("历史播放位置")).toHaveText("1/3");
|
||||
await closeChartWindows(page);
|
||||
await testInfo.attach("history-playback-desktop", { body: await panel.screenshot({ path: testInfo.outputPath("history-playback-desktop.png") }), contentType: "image/png" });
|
||||
await page.setViewportSize({ width: 375, height: 720 });
|
||||
await showInvestigationMap(page);
|
||||
await page.getByRole("button", { name: "关闭通知", exact: true }).evaluateAll((buttons) => buttons.forEach((button) => (button as HTMLButtonElement).click()));
|
||||
await expect(panel.getByRole("slider", { name: "历史播放进度" })).toBeVisible();
|
||||
expect(await panel.evaluate((node) => node.scrollWidth - node.clientWidth)).toBeLessThanOrEqual(1);
|
||||
await testInfo.attach("history-playback-narrow", { body: await panel.screenshot({ path: testInfo.outputPath("history-playback-narrow.png") }), contentType: "image/png" });
|
||||
await page.getByRole("dialog", { name: "压力对比 1", exact: true }).getByRole("button", { name: "关闭窗口", exact: true }).click();
|
||||
await closeChartWindows(page);
|
||||
await panel.getByRole("button", { name: "下一个动作", exact: true }).click();
|
||||
await expect(panel.getByLabel("历史播放位置")).toHaveText("2/3");
|
||||
await expect(panel.getByRole("region", { name: "调查覆盖层" })).toContainText("合成影响示意");
|
||||
await panel.getByRole("button", { name: "回到当前", exact: true }).click();
|
||||
await expect(panel.getByRole("group", { name: "压力对比 4", exact: true })).toBeVisible();
|
||||
await expect(panel.getByRole("group", { name: "压力对比 5", exact: true })).toBeVisible();
|
||||
expect(mock.mutations).toEqual([]);
|
||||
});
|
||||
|
||||
@@ -93,10 +125,11 @@ test("restores a persisted chart when the stream disconnects during display conf
|
||||
const task = investigationFixture(1, "图表确认阶段断线恢复");
|
||||
const mock = await mockInvestigationPresentation(page, [task]);
|
||||
await selectInvestigation(page, task.title);
|
||||
await showInvestigationMap(page);
|
||||
const panel = page.getByLabel("调查实时展示", { exact: true });
|
||||
await expect(panel.getByText("实时跟随", { exact: true })).toBeVisible();
|
||||
await mock.emit(task, chart(1));
|
||||
await expect(panel.getByRole("combobox", { name: "切换展示动作" })).toContainText("1. ");
|
||||
await expect(panel.getByRole("button", { name: "切换展示动作" })).toContainText("当前(最新)");
|
||||
await page.evaluate(() => {
|
||||
const observer = new MutationObserver(() => {
|
||||
const plot = document.querySelector('[data-presentation-scope="investigation"] [data-investigation-chart="pressure-history"]');
|
||||
@@ -116,7 +149,7 @@ test("restores a persisted chart when the stream disconnects during display conf
|
||||
});
|
||||
await expect(panel.getByText("实时跟随", { exact: true })).toBeVisible();
|
||||
await expect(panel.getByRole("group", { name: "压力对比 2", exact: true })).toBeVisible();
|
||||
await expect(panel.getByRole("combobox", { name: "切换展示动作" })).toContainText("2 个动作");
|
||||
await expect(panel.getByRole("slider", { name: "历史播放进度" })).toHaveAttribute("max", "0");
|
||||
await testInfo.attach("chart-recovered-after-interrupted-confirmation", {
|
||||
body: await panel.screenshot({ path: testInfo.outputPath("chart-recovered-after-interrupted-confirmation.png") }), contentType: "image/png",
|
||||
});
|
||||
|
||||
@@ -130,7 +130,7 @@ export async function mockInvestigationPresentation(page: Page, tasks: Operation
|
||||
};
|
||||
}
|
||||
|
||||
export async function selectInvestigation(page: Page, title: string) {
|
||||
export async function selectInvestigation(page: Page, title: string, input: "pointer" | "keyboard" = "pointer") {
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
await page.getByRole("button", { name: /^(?:打开)?工况任务$/ }).click();
|
||||
const panel = page.getByRole("region", { name: "工况任务", exact: true });
|
||||
@@ -142,7 +142,8 @@ export async function selectInvestigation(page: Page, title: string) {
|
||||
const collapse = panel.getByRole("button", { name: "收起工况任务", exact: true });
|
||||
if (await collapse.isVisible()) await collapse.click();
|
||||
}
|
||||
await task.click();
|
||||
if (input === "keyboard") await task.press("Enter");
|
||||
else await task.click();
|
||||
}
|
||||
|
||||
export async function showInvestigationMap(page: Page) {
|
||||
|
||||
Reference in New Issue
Block a user