feat(charts): manage investigation charts in a scrolling workspace
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
import { expect, test, type Locator, type Page } from "@playwright/test";
|
||||
import { basename, resolve } from "node:path";
|
||||
import { investigationFixture, mockInvestigationPresentation, selectInvestigation, type SavedPresentationEvent } from "./support/mock-investigation-presentation";
|
||||
|
||||
const pressureIds = ["170486", "175618", "159673", "135160", "174754", "123781", "142493"];
|
||||
const flowIds = ["541061", "542017", "486033"];
|
||||
const times = Array.from({ length: 13 }, (_, index) => `${String(5 + Math.floor(index / 4)).padStart(2, "0")}:${String(index % 4 * 15).padStart(2, "0")}`);
|
||||
const pressureTitle = "压力当前曲线与前七天同期对比(m)";
|
||||
const flowTitle = "流量当前曲线与前七天同期对比(m³/h)";
|
||||
|
||||
// These synthetic fixtures preserve the naming shape found in read-only persisted
|
||||
// SCADA task charts. Values, timestamps and references are self-contained test data.
|
||||
function comparison(id: string, flow = false, dateOnlyHistory = false) {
|
||||
const metric = flow ? "流量" : "压力";
|
||||
const unit = flow ? "m³/h" : "m";
|
||||
const offset = (flow ? flowIds : pressureIds).indexOf(id);
|
||||
const base = (flow ? 100 : 32) + offset;
|
||||
return { action: "open_chart", chart_id: `legacy-${flow ? "flow" : "pressure"}-${id}`, evidence_refs: [`synthetic:legacy-shape:${id}`],
|
||||
chart: { title: dateOnlyHistory ? `测点 ${id} ${metric}三小时与历史对照(${unit})` : `${metric}测点 ${id} 当前曲线与前七天同期对比(${unit})`,
|
||||
chart_type: dateOnlyHistory ? "line" : "time_comparison", x_axis_name: "时间", y_axis_name: `${metric}(${unit})`, x_data: times,
|
||||
series: [{ name: `${id} 当日`, type: "line", data: times.map((_, index) => base - index / 4),
|
||||
sample_times: times.map((time) => `2026-07-16T${time}:00+08:00`) },
|
||||
...Array.from({ length: 7 }, (_, day) => ({ name: `${dateOnlyHistory ? "" : `${id} `}07-${String(9 + day).padStart(2, "0")}`, type: "scatter",
|
||||
data: times.map((_, index) => index < 10 ? null : base + day / 2),
|
||||
sample_times: times.map((time, index) => index < 10 ? null : `2026-07-${String(9 + day).padStart(2, "0")}T${time}:00+08:00`) }))]
|
||||
} };
|
||||
}
|
||||
|
||||
const trend = (flow: boolean) => ({ action: "open_chart", chart_id: `trend-${flow ? "flow" : "pressure"}`, evidence_refs: ["synthetic:legacy-trend"],
|
||||
chart: { title: `${flow ? "流量" : "压力"}测点当前三小时总览`, chart_type: "line", x_data: times,
|
||||
series: [{ name: "合成当前趋势", data: times.map((_, index) => (flow ? 100 : 32) - index / 4) }] } });
|
||||
|
||||
function savedTask(index: number, title: string, commands: unknown[]) {
|
||||
const task = investigationFixture(index, title);
|
||||
const run = task.active_run!;
|
||||
task.status = "waiting_human";
|
||||
task.lifecycle_status = "waiting_human";
|
||||
task.latest_run = { ...run, status: "succeeded", finished_at: task.updated_at };
|
||||
task.active_run = null;
|
||||
const events: SavedPresentationEvent[] = commands.map((command, sequence) => ({ task_id: task.task_id,
|
||||
run_id: run.run_id, event_seq: sequence + 1, execution_generation: 1, occurred_at: task.created_at, command }));
|
||||
return { task, events };
|
||||
}
|
||||
|
||||
const managed = (manager: Locator, title: string) => manager.locator(`[data-managed-chart][data-chart-title="${title}"]`);
|
||||
const plot = (article: Locator) => article.locator('[data-chart-view] div[role="img"]:not(.echarts-for-react)');
|
||||
|
||||
async function locate(page: Page, manager: Locator, title: string) {
|
||||
await manager.getByRole("combobox", { name: "快速定位图表", exact: true }).click();
|
||||
await page.getByRole("option").filter({ hasText: title }).click();
|
||||
await expect(managed(manager, title)).toBeVisible();
|
||||
}
|
||||
|
||||
async function expectBottomLegend(article: Locator) {
|
||||
const drawing = (await plot(article).boundingBox())!;
|
||||
const legend = (await article.getByLabel("图表图例", { exact: true }).boundingBox())!;
|
||||
const bounds = (await article.boundingBox())!;
|
||||
expect(legend.y).toBeGreaterThanOrEqual(drawing.y + drawing.height - 1);
|
||||
expect(legend.y + legend.height).toBeLessThanOrEqual(bounds.y + bounds.height + 1);
|
||||
expect(drawing.height).toBeGreaterThanOrEqual(100);
|
||||
}
|
||||
|
||||
test.use({ reducedMotion: "reduce", hasTouch: true, screenshot: "only-on-failure" });
|
||||
test.setTimeout(90_000);
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.route("**/@fs/**/noto-sans-sc-*.woff2", (route) => route.fulfill({
|
||||
path: resolve("node_modules/@fontsource-variable/noto-sans-sc/files", basename(new URL(route.request().url()).pathname)), contentType: "font/woff2"
|
||||
}));
|
||||
});
|
||||
|
||||
test("saved waiting-human SCADA charts restore silently into two trends and two object groups", async ({ page }, testInfo) => {
|
||||
const { task, events } = savedTask(21, "合成历史 SCADA 旧标题恢复", [trend(false), trend(true),
|
||||
...pressureIds.map((id) => comparison(id)), ...flowIds.map((id) => comparison(id, true))]);
|
||||
const mock = await mockInvestigationPresentation(page, [task], events);
|
||||
await selectInvestigation(page, task.title);
|
||||
const panel = page.getByLabel("调查实时展示", { exact: true });
|
||||
await expect(panel.locator("[data-investigation-chart]")).toHaveCount(12);
|
||||
await expect(page.locator("[data-floating-window]")).toHaveCount(0);
|
||||
await panel.getByRole("button", { name: "打开图表管理", exact: true }).click();
|
||||
const manager = page.getByRole("dialog", { name: "图表管理", exact: true });
|
||||
await expect(manager.locator("[data-managed-chart]")).toHaveCount(4);
|
||||
await manager.getByRole("button", { name: "关闭窗口", exact: true }).click();
|
||||
await page.reload({ waitUntil: "domcontentloaded" });
|
||||
await selectInvestigation(page, task.title);
|
||||
await expect(panel.locator("[data-investigation-chart]")).toHaveCount(12);
|
||||
await expect(page.locator("[data-floating-window]")).toHaveCount(0);
|
||||
await panel.getByRole("button", { name: "打开图表管理", exact: true }).click();
|
||||
await expect(manager.locator("[data-managed-chart]")).toHaveCount(4);
|
||||
await expect(manager).toContainText("4 张图表");
|
||||
const pressure = managed(manager, pressureTitle);
|
||||
const flow = managed(manager, flowTitle);
|
||||
await expect(pressure).toBeVisible();
|
||||
await expect(flow).toBeVisible();
|
||||
await expect(managed(manager, "压力测点当前三小时总览")).toBeVisible();
|
||||
await expect(managed(manager, "流量测点当前三小时总览")).toBeVisible();
|
||||
const pressureObjects = pressure.locator('aside[aria-label="选择图表对象"]');
|
||||
const flowObjects = flow.locator('aside[aria-label="选择图表对象"]');
|
||||
await expect(pressureObjects.getByRole("button")).toHaveCount(7);
|
||||
await expect(flowObjects.getByRole("button")).toHaveCount(3);
|
||||
await pressureObjects.getByRole("button", { name: "切换对象 175618", exact: true }).click();
|
||||
await expect(plot(pressure)).toHaveAttribute("aria-label", "压力测点 175618 当前曲线与前七天同期对比(m)");
|
||||
const legend = pressure.getByLabel("图表图例", { exact: true });
|
||||
await expect(legend.getByRole("button")).toHaveCount(8);
|
||||
await expect(legend.getByRole("button", { name: "175618 当日", exact: true })).toBeVisible();
|
||||
await expectBottomLegend(pressure);
|
||||
await flowObjects.getByRole("button", { name: "切换对象 486033", exact: true }).click();
|
||||
await expect(plot(flow)).toHaveAttribute("aria-label", "流量测点 486033 当前曲线与前七天同期对比(m³/h)");
|
||||
await expectBottomLegend(flow);
|
||||
await pressure.getByRole("tab", { name: "数据", exact: true }).click();
|
||||
const table = pressure.getByRole("table");
|
||||
await expect(table.getByRole("row")).toHaveCount(14);
|
||||
await expect(table.getByRole("row").last()).toContainText("2026-07-16 08:00:00+08:00");
|
||||
await expect(table.getByRole("row").last()).toContainText("2026-07-09 08:00:00+08:00");
|
||||
await expect(table.getByRole("row").last()).toContainText("30 (2026-07-16");
|
||||
await pressure.getByRole("tab", { name: "复合", exact: true }).click();
|
||||
await pressure.getByRole("button", { name: `放大查看 ${pressureTitle}`, exact: true }).click();
|
||||
const child = page.getByRole("dialog", { name: pressureTitle, exact: true });
|
||||
await expect(child).toBeVisible();
|
||||
await expect(manager).toBeVisible();
|
||||
await expect(page.locator("[data-floating-window]")).toHaveCount(2);
|
||||
await child.getByRole("button", { name: "切换对象 142493", exact: true }).click();
|
||||
await expect(child.getByRole("img", { name: "压力测点 142493 当前曲线与前七天同期对比(m)", exact: true })).toBeVisible();
|
||||
await child.getByRole("button", { name: "关闭窗口", exact: true }).click();
|
||||
await expect(page.locator("[data-floating-window]")).toHaveCount(1);
|
||||
await page.evaluate(() => document.fonts.ready);
|
||||
await testInfo.attach("legacy-scada-groups-desktop", { body: await manager.screenshot({ path: testInfo.outputPath("legacy-scada-groups-desktop.png") }), contentType: "image/png" });
|
||||
for (const width of [375, 320]) {
|
||||
await page.setViewportSize({ width, height: 812 });
|
||||
await expect(manager.locator("[data-chart-layout]")).toHaveAttribute("data-chart-layout", "one");
|
||||
await locate(page, manager, pressureTitle);
|
||||
const id = width === 375 ? "175618" : "159673";
|
||||
await pressure.getByRole("combobox", { name: "选择图表对象", exact: true }).tap();
|
||||
await page.getByRole("option", { name: id, exact: true }).tap();
|
||||
await expect(plot(pressure)).toHaveAttribute("aria-label", `压力测点 ${id} 当前曲线与前七天同期对比(m)`);
|
||||
await expectBottomLegend(pressure);
|
||||
expect(await manager.evaluate((node) => node.scrollWidth - node.clientWidth)).toBeLessThanOrEqual(1);
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(width);
|
||||
await testInfo.attach(`legacy-scada-groups-${width}`, { body: await manager.screenshot({ path: testInfo.outputPath(`legacy-scada-groups-${width}.png`) }), contentType: "image/png" });
|
||||
await locate(page, manager, flowTitle);
|
||||
await flow.getByRole("combobox", { name: "选择图表对象", exact: true }).tap();
|
||||
await page.getByRole("option", { name: "541061", exact: true }).tap();
|
||||
await expect(plot(flow)).toHaveAttribute("aria-label", "流量测点 541061 当前曲线与前七天同期对比(m³/h)");
|
||||
expect(await manager.evaluate((node) => node.scrollWidth - node.clientWidth)).toBeLessThanOrEqual(1);
|
||||
}
|
||||
expect(mock.mutations).toEqual([]);
|
||||
});
|
||||
|
||||
test("saved generic point titles group date-only historical scatter labels without changing sampling times", async ({ page }, testInfo) => {
|
||||
const { task, events } = savedTask(22, "合成早期测点图恢复", [comparison("170486", false, true), comparison("175618", false, true)]);
|
||||
const mock = await mockInvestigationPresentation(page, [task], events);
|
||||
await selectInvestigation(page, task.title);
|
||||
const panel = page.getByLabel("调查实时展示", { exact: true });
|
||||
await expect(panel.locator("[data-investigation-chart]")).toHaveCount(2);
|
||||
await expect(page.locator("[data-floating-window]")).toHaveCount(0);
|
||||
await panel.getByRole("button", { name: "打开图表管理", exact: true }).click();
|
||||
const manager = page.getByRole("dialog", { name: "图表管理", exact: true });
|
||||
await expect(manager.locator("[data-managed-chart]")).toHaveCount(1);
|
||||
const group = managed(manager, "压力三小时与历史对照(m)");
|
||||
await expect(group.locator('aside[aria-label="选择图表对象"]').getByRole("button")).toHaveCount(2);
|
||||
await group.getByRole("button", { name: "切换对象 175618", exact: true }).click();
|
||||
await expect(plot(group)).toHaveAttribute("aria-label", "测点 175618 压力三小时与历史对照(m)");
|
||||
const legend = group.getByLabel("图表图例", { exact: true });
|
||||
await expect(legend.getByRole("button")).toHaveCount(8);
|
||||
await expect(legend.getByRole("button", { name: "07-09", exact: true })).toBeVisible();
|
||||
await expectBottomLegend(group);
|
||||
await group.getByRole("tab", { name: "数据", exact: true }).click();
|
||||
const row = group.getByRole("table").getByRole("row").filter({ hasText: /^07:30/ });
|
||||
await expect(row).toContainText("2026-07-16 07:30:00+08:00");
|
||||
await expect(row).toContainText("2026-07-09 07:30:00+08:00");
|
||||
await expect(row).toContainText("33 (2026-07-09");
|
||||
await page.evaluate(() => document.fonts.ready);
|
||||
await testInfo.attach("legacy-date-only-history", { body: await manager.screenshot({ path: testInfo.outputPath("legacy-date-only-history.png") }), contentType: "image/png" });
|
||||
expect(mock.mutations).toEqual([]);
|
||||
});
|
||||
Reference in New Issue
Block a user