feat(workbench): dock task and chart workspaces
Generic Container CI/CD / test-build-publish (push) Successful in 1m13s
Frontend CI/CD / build-test-publish-and-deploy (push) Successful in 1m14s

This commit is contained in:
2026-11-12 11:42:25 +08:00
parent 33f0b90a7d
commit 7a304a91a8
27 changed files with 766 additions and 201 deletions
@@ -0,0 +1,118 @@
import { expect, test } from "@playwright/test";
import { investigationFixture, mockInvestigationPresentation, selectInvestigation, showInvestigationMap, showInvestigationTasks } from "./support/mock-investigation-presentation";
import { responsesFixture } from "./support/responses-fixture";
test.use({ reducedMotion: "reduce" });
const chart = (id: string) => ({ action: "open_chart", chart_id: id, evidence_refs: ["synthetic:pressure"],
chart: { title: `停靠监测 ${id}`, chart_type: "line", x_data: ["07:45", "08:00"], series: [{ name: "压力(m)", data: [32, 28] }] } });
test("chart manager occupies the right workspace and preserves charts when switching to tasks", async ({ page }, testInfo) => {
await page.setViewportSize({ width: 1920, height: 1000 });
const task = investigationFixture(1, "右侧图表工作区验收");
const mock = await mockInvestigationPresentation(page, [task]);
await selectInvestigation(page, task.title);
await page.getByRole("button", { name: /展开 Agent 助手面板/ }).click();
const presentation = page.getByLabel("调查实时展示", { exact: true });
await expect(presentation).toContainText("实时跟随");
await mock.emit(task, chart("P1"));
const manager = page.getByRole("dialog", { name: "图表管理", exact: true });
await expect(manager).toBeVisible();
await expect(manager).toHaveAttribute("data-window-presentation", "docked");
await expect(page.getByRole("button", { name: /窗口管理,当前/ })).toHaveCount(0);
await expect(manager.getByRole("button", { name: /最大化窗口|还原窗口/ })).toHaveCount(0);
await expect(manager.locator("[data-window-resize-handle]")).toHaveCount(0);
await expect(page.getByRole("region", { name: "工况任务", exact: true })).toBeHidden();
const box = (await manager.boundingBox())!;
const map = (await page.locator("#workspace-map").boundingBox())!;
expect(box.y).toBe(56);
expect(box.x).toBeGreaterThanOrEqual(map.x + map.width);
expect(Math.round(box.x + box.width)).toBe(1920);
await expect(manager.getByRole("img", { name: "停靠监测 P1", exact: true })).toBeVisible();
const separator = page.getByRole("separator", { name: "调整地图与报告宽度" });
await separator.focus();
await page.keyboard.press("End");
await expect.poll(async () => Math.round((await manager.boundingBox())!.width)).toBe(600);
await page.screenshot({ path: testInfo.outputPath("chart-workspace-minimum.png") });
const instance = await manager.locator(".echarts-for-react").getAttribute("_echarts_instance_");
await page.getByRole("button", { name: "工况任务", exact: true }).click();
await expect(manager).toBeHidden();
await expect(page.getByRole("region", { name: "工况任务", exact: true })).toBeVisible();
await showInvestigationMap(page);
await presentation.getByRole("button", { name: "打开图表管理", exact: true }).press("Enter");
await expect(manager).toBeVisible();
await expect(manager.getByLabel("图表管理标题栏", { exact: true })).toBeFocused();
await expect(manager.locator(".echarts-for-react")).toHaveAttribute("_echarts_instance_", instance!);
for (const width of [1200, 1024, 390, 320]) {
await page.setViewportSize({ width, height: 800 });
await expect(page.locator("#main-workspace")).toHaveAttribute("data-layout-mode", "focus");
const bounds = (await manager.boundingBox())!;
expect(bounds.x + bounds.width).toBeLessThanOrEqual(width);
expect(await manager.evaluate((node) => node.scrollWidth - node.clientWidth)).toBeLessThanOrEqual(1);
await page.screenshot({ path: testInfo.outputPath(`chart-workspace-${width}.png`) });
}
await page.setViewportSize({ width: 1920, height: 1000 });
await manager.getByRole("button", { name: "关闭窗口", exact: true }).click();
await expect(manager).toHaveCount(0);
await expect(page.locator("#workspace-map")).toBeVisible();
await showInvestigationTasks(page);
await expect(page.getByRole("region", { name: "工况任务", exact: true })).toBeVisible();
expect(mock.mutations).toEqual([]);
});
test("multiple chart sources share the visible dock without resetting their charts", async ({ page }) => {
await page.setViewportSize({ width: 1920, height: 1000 });
const task = investigationFixture(1, "多来源停靠验收");
const mock = await mockInvestigationPresentation(page, [task]);
await selectInvestigation(page, task.title);
await mock.emit(task, chart("P1"));
const first = page.getByRole("dialog", { name: "图表管理", exact: true }).filter({ hasText: task.title });
await expect(first).toBeVisible();
await expect(first.locator(".echarts-for-react svg")).toBeVisible();
const instance = await first.locator(".echarts-for-react").getAttribute("_echarts_instance_");
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: "presentation", command: chart("preview") }));
await expect(tools.getByRole("combobox", { name: "JSONL 测试案例", exact: true })).toBeEnabled();
await tools.getByRole("button", { name: "执行本地预览", exact: true }).click();
await expect(tools.getByRole("status")).toContainText("本地预览完成");
await page.getByRole("button", { name: "切换地图 Dev Panel" }).click();
await page.getByLabel("本地预览展示", { exact: true }).getByRole("button", { name: "打开图表管理", exact: true }).press("Enter");
const second = page.getByRole("dialog", { name: "图表管理", exact: true }).filter({ hasText: "本地预览展示" });
await expect(second).toBeVisible();
expect((await second.boundingBox())!.y).toBe(56);
await expect(first).toBeHidden();
await page.getByLabel("调查实时展示", { exact: true }).getByRole("button", { name: "打开图表管理", exact: true }).press("Enter");
await expect(first).toBeVisible();
expect((await first.boundingBox())!.y).toBe(56);
await expect(second).toBeHidden();
await expect(first.locator(".echarts-for-react")).toHaveAttribute("_echarts_instance_", instance!);
await expect(page.getByRole("button", { name: /窗口管理,当前/ })).toHaveCount(0);
});
test("Agent map location reveals the map while the chart workspace is focused", async ({ page }) => {
await page.setViewportSize({ width: 1200, height: 900 });
const task = investigationFixture(1, "图表专注地图定位验收");
const mock = await mockInvestigationPresentation(page, [task]);
const block = { version: "answer-ui@1", block_id: "dock-location", fallback_text: "合成地图定位", spec: {
root: "location", elements: { location: { type: "MapLocation", props: { label: "定位合成监测点", layer: "junctions", feature_ids: ["P1"], source: "浏览器验收" }, children: [] } }
} };
await page.route("**/api/v1/responses", (route) => route.request().method() === "OPTIONS" ? route.fallback() : route.fulfill({
headers: { "Access-Control-Allow-Origin": "*", "Content-Type": "text/event-stream" }, body: responsesFixture([
{ type: "start", messageId: "dock-location-answer" }, { type: "data-answer_ui", id: block.block_id, data: block },
{ type: "data-final_answer", data: { content: "合成地图定位", ui_block_ids: [block.block_id] } }, { type: "finish", finishReason: "stop" }
])
}));
await selectInvestigation(page, task.title);
await mock.emit(task, chart("P1"));
await expect(page.getByRole("dialog", { name: "图表管理", exact: true })).toBeVisible();
await page.getByRole("button", { name: /展开 Agent 助手面板/ }).click();
const agent = page.getByRole("complementary", { name: "Agent 命令面板" });
await agent.locator(".agent-panel-prompt-textarea").fill("定位合成监测点");
await agent.getByRole("button", { name: "发送 Agent 指令", exact: true }).click();
await expect(page.locator("#workspace-map")).toBeHidden();
await agent.getByRole("button", { name: "定位合成监测点", exact: true }).click();
await expect(page.locator("#workspace-map")).toBeVisible();
await expect(page.locator("#main-workspace")).toHaveAttribute("data-layout-mode", "map_only");
await expect(page.getByRole("button", { name: "返回图表管理", exact: true })).toBeVisible();
});