feat(workbench): dock task and chart workspaces
This commit is contained in:
@@ -1,6 +1,25 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { expect, test, type Locator, type Page } from "@playwright/test";
|
||||
import { mockInvestigationPresentation } from "./support/mock-investigation-presentation";
|
||||
|
||||
async function selectSecondObject(page: Page, surface: Locator) {
|
||||
const select = surface.getByRole("combobox", { name: "选择图表对象", exact: true });
|
||||
let label: string;
|
||||
if (await select.isVisible()) {
|
||||
await select.click();
|
||||
const option = page.getByRole("option").nth(1);
|
||||
label = (await option.innerText()).trim();
|
||||
await option.click();
|
||||
} else {
|
||||
const button = surface.getByRole("button", { name: /^切换对象 / }).nth(1);
|
||||
label = (await button.getAttribute("aria-label"))!.replace("切换对象 ", "");
|
||||
await button.click();
|
||||
}
|
||||
return async () => {
|
||||
if (await select.isVisible()) await expect(select).toContainText(label);
|
||||
else await expect(surface.getByRole("button", { name: `切换对象 ${label}`, exact: true })).toHaveAttribute("aria-pressed", "true");
|
||||
};
|
||||
}
|
||||
|
||||
test.use({ reducedMotion: "reduce", viewport: { width: 1600, height: 1000 } });
|
||||
for (const target of ["manager", "child"] as const) {
|
||||
test(`resizing ${target} reflows charts at their actual dimensions without stretching text`, async ({ page }, testInfo) => {
|
||||
@@ -13,6 +32,7 @@ for (const target of ["manager", "child"] as const) {
|
||||
await page.getByRole("option", { name: "复杂图表:多测点与长时序", exact: true }).click();
|
||||
await tools.getByRole("button", { name: "执行本地预览", exact: true }).click();
|
||||
await expect(tools.getByRole("status")).toContainText("本地预览完成", { timeout: 20_000 });
|
||||
await page.getByRole("button", { name: "切换地图 Dev Panel" }).click();
|
||||
const panel = page.getByLabel("本地预览展示", { exact: true });
|
||||
await panel.getByRole("button", { name: "打开图表管理", exact: true }).click();
|
||||
const manager = page.getByRole("dialog", { name: "图表管理", exact: true });
|
||||
@@ -27,7 +47,8 @@ for (const target of ["manager", "child"] as const) {
|
||||
size: getComputedStyle(node).fontSize, height: node.getBoundingClientRect().height
|
||||
}));
|
||||
const before = (await popup.boundingBox())!;
|
||||
const handle = (await popup.locator(".floating-window-resize").boundingBox())!;
|
||||
const resizeControl = target === "manager" ? page.getByRole("separator", { name: "调整地图与报告宽度" }) : popup.locator(".floating-window-resize");
|
||||
const handle = (await resizeControl.boundingBox())!;
|
||||
const session = await page.context().newCDPSession(page);
|
||||
await session.send("Emulation.setCPUThrottlingRate", { rate: 4 });
|
||||
await session.send("Performance.enable");
|
||||
@@ -41,9 +62,12 @@ for (const target of ["manager", "child"] as const) {
|
||||
});
|
||||
const first = await session.send("Performance.getMetrics");
|
||||
const started = Date.now();
|
||||
await page.mouse.move(handle.x + handle.width - 3, handle.y + handle.height - 3);
|
||||
const pointer = target === "manager"
|
||||
? { x: handle.x + handle.width / 2, y: handle.y + handle.height / 2, dx: -128, dy: 0 }
|
||||
: { x: handle.x + handle.width - 3, y: handle.y + handle.height - 3, dx: 128, dy: 64 };
|
||||
await page.mouse.move(pointer.x, pointer.y);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(handle.x + handle.width - 3 + 128, handle.y + handle.height - 3 + 64, { steps: 32 });
|
||||
await page.mouse.move(pointer.x + pointer.dx, pointer.y + pointer.dy, { steps: 32 });
|
||||
// While the pointer is still held, layout and the chart drawing must adapt together.
|
||||
await expect.poll(() => popup.locator(".echarts-for-react").first().evaluate((node) => Math.abs(node.clientWidth - Number(node.querySelector("svg")?.getAttribute("width"))))).toBeLessThan(1);
|
||||
await expect.poll(() => popup.locator(".echarts-for-react").first().evaluate((node) => Math.abs(node.clientHeight - Number(node.querySelector("svg")?.getAttribute("height"))))).toBeLessThan(1);
|
||||
@@ -65,7 +89,7 @@ for (const target of ["manager", "child"] as const) {
|
||||
console.info("Chart resize profile", JSON.stringify(summary));
|
||||
await testInfo.attach("resize-profile", { body: JSON.stringify(summary, null, 2), contentType: "application/json" });
|
||||
expect(Math.round(preview.width - before.width)).toBe(128);
|
||||
expect(Math.round(preview.height - before.height)).toBe(64);
|
||||
expect(Math.round(preview.height - before.height)).toBe(target === "manager" ? 0 : 64);
|
||||
expect(during).toBeGreaterThan(0);
|
||||
// Bound the expensive work on the same 4x CPU fixture used to reproduce the lag.
|
||||
expect(summary.elapsedMs).toBeLessThan(12_000);
|
||||
@@ -89,40 +113,46 @@ for (const target of ["manager", "child"] as const) {
|
||||
await testInfo.attach("resize-layout", { body: JSON.stringify(layout, null, 2), contentType: "application/json" });
|
||||
await session.send("Emulation.setCPUThrottlingRate", { rate: 1 });
|
||||
const chartSurface = target === "manager" ? manager.getByRole("group", { name: "合成全天压力对比", exact: true }) : popup;
|
||||
const selectedObject = chartSurface.getByRole("button", { name: /^切换对象 / }).nth(1);
|
||||
await selectedObject.click();
|
||||
const expectSelectedObject = await selectSecondObject(page, chartSurface);
|
||||
const hiddenSeries = chartSurface.getByLabel("显示的数据序列").getByRole("button").last();
|
||||
await hiddenSeries.click();
|
||||
await expect(selectedObject).toHaveAttribute("aria-pressed", "true");
|
||||
await expectSelectedObject();
|
||||
await expect(hiddenSeries).toHaveAttribute("aria-pressed", "false");
|
||||
const selectedInstance = await chartSurface.locator(".echarts-for-react").first().getAttribute("_echarts_instance_");
|
||||
const beforeCancel = (await popup.boundingBox())!;
|
||||
const cancelHandle = popup.locator(".floating-window-resize");
|
||||
const cancelBox = (await cancelHandle.boundingBox())!;
|
||||
await cancelHandle.evaluate((node) => node.addEventListener("pointerdown", (event) => {
|
||||
(node as HTMLElement).dataset.testPointerId = String((event as PointerEvent).pointerId);
|
||||
}, { once: true }));
|
||||
await page.mouse.move(cancelBox.x + cancelBox.width - 3, cancelBox.y + cancelBox.height - 3);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(cancelBox.x + cancelBox.width - 3 + 48, cancelBox.y + cancelBox.height - 3 + 32, { steps: 4 });
|
||||
await expect.poll(async () => Math.round((await popup.boundingBox())!.width - beforeCancel.width)).toBe(48);
|
||||
await cancelHandle.evaluate((node) => node.dispatchEvent(new PointerEvent("pointercancel", {
|
||||
pointerId: Number((node as HTMLElement).dataset.testPointerId)
|
||||
})));
|
||||
await page.mouse.up();
|
||||
await expect.poll(() => popup.boundingBox()).toEqual(beforeCancel);
|
||||
await expect(popup.locator("[data-floating-window-document]")).toHaveCSS("transform", "none");
|
||||
await expect(selectedObject).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(hiddenSeries).toHaveAttribute("aria-pressed", "false");
|
||||
const boundaryBox = (await cancelHandle.boundingBox())!;
|
||||
await page.mouse.move(boundaryBox.x + boundaryBox.width - 3, boundaryBox.y + boundaryBox.height - 3);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(1599, 999, { steps: 8 });
|
||||
await page.mouse.up();
|
||||
const final = (await popup.boundingBox())!;
|
||||
expect(Math.round(final.x + final.width)).toBe(1592);
|
||||
expect(Math.round(final.y + final.height)).toBe(992);
|
||||
await expect(selectedObject).toHaveAttribute("aria-pressed", "true");
|
||||
if (target === "child") {
|
||||
const beforeCancel = (await popup.boundingBox())!;
|
||||
const cancelHandle = popup.locator(".floating-window-resize");
|
||||
const cancelBox = (await cancelHandle.boundingBox())!;
|
||||
await cancelHandle.evaluate((node) => node.addEventListener("pointerdown", (event) => {
|
||||
(node as HTMLElement).dataset.testPointerId = String((event as PointerEvent).pointerId);
|
||||
}, { once: true }));
|
||||
await page.mouse.move(cancelBox.x + cancelBox.width - 3, cancelBox.y + cancelBox.height - 3);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(cancelBox.x + cancelBox.width - 3 + 48, cancelBox.y + cancelBox.height - 3 + 32, { steps: 4 });
|
||||
await expect.poll(async () => Math.round((await popup.boundingBox())!.width - beforeCancel.width)).toBe(48);
|
||||
await cancelHandle.evaluate((node) => node.dispatchEvent(new PointerEvent("pointercancel", {
|
||||
pointerId: Number((node as HTMLElement).dataset.testPointerId)
|
||||
})));
|
||||
await page.mouse.up();
|
||||
await expect.poll(() => popup.boundingBox()).toEqual(beforeCancel);
|
||||
await expect(popup.locator("[data-floating-window-document]")).toHaveCSS("transform", "none");
|
||||
await expectSelectedObject();
|
||||
await expect(hiddenSeries).toHaveAttribute("aria-pressed", "false");
|
||||
const boundaryBox = (await cancelHandle.boundingBox())!;
|
||||
await page.mouse.move(boundaryBox.x + boundaryBox.width - 3, boundaryBox.y + boundaryBox.height - 3);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(1599, 999, { steps: 8 });
|
||||
await page.mouse.up();
|
||||
const final = (await popup.boundingBox())!;
|
||||
expect(Math.round(final.x + final.width)).toBe(1592);
|
||||
expect(Math.round(final.y + final.height)).toBe(992);
|
||||
} else {
|
||||
await expect(popup.locator("[data-window-resize-handle]")).toHaveCount(0);
|
||||
await resizeControl.focus();
|
||||
await page.keyboard.press("ArrowRight");
|
||||
await page.keyboard.press("ArrowLeft");
|
||||
}
|
||||
await expectSelectedObject();
|
||||
await expect(hiddenSeries).toHaveAttribute("aria-pressed", "false");
|
||||
await expect(chartSurface.locator(".echarts-for-react").first()).toHaveAttribute("_echarts_instance_", selectedInstance!);
|
||||
await expect.poll(() => chartSurface.locator(".echarts-for-react").first().evaluate((node) => Math.abs(node.clientWidth - Number(node.querySelector("svg")?.getAttribute("width"))))).toBeLessThan(1);
|
||||
|
||||
Reference in New Issue
Block a user