完善建模交互、组件图标与系统协议
This commit is contained in:
1 parent
456c29b3b6
commit
6572defaa4
66 files changed
+10067
-4163
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
import {
|
||||
expectAllNodesInsideCanvas,
|
||||
prepareApp,
|
||||
@@ -6,14 +6,39 @@ import {
|
||||
wideProject,
|
||||
} from "./fixtures";
|
||||
|
||||
async function readModelingViewport(page: Page) {
|
||||
return page
|
||||
.locator(".flow-canvas .react-flow__viewport")
|
||||
.evaluate((viewport) => {
|
||||
const values =
|
||||
getComputedStyle(viewport)
|
||||
.transform.match(/-?\d*\.?\d+(?:e[-+]?\d+)?/gi)
|
||||
?.map(Number) ?? [];
|
||||
return {
|
||||
x: values[4] ?? 0,
|
||||
y: values[5] ?? 0,
|
||||
zoom: values[0] ?? 1,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
test("拖入组件不自动适配画布,手动适配按钮仍然生效", async ({ page }) => {
|
||||
await prepareApp(page);
|
||||
await page.goto("/");
|
||||
|
||||
const viewport = page.locator(".flow-canvas .react-flow__viewport");
|
||||
const zoomOut = page.locator(".flow-canvas .react-flow__controls-zoomout");
|
||||
await zoomOut.click();
|
||||
await zoomOut.click();
|
||||
await expect(page.locator(".flow-canvas .react-flow__controls")).toHaveCount(0);
|
||||
await expect(page.locator(".flow-canvas .react-flow__attribution")).toHaveCount(0);
|
||||
const pane = page.locator(".flow-canvas .react-flow__pane");
|
||||
const paneBox = await pane.boundingBox();
|
||||
expect(paneBox).not.toBeNull();
|
||||
await page.mouse.move(
|
||||
paneBox!.x + paneBox!.width / 2,
|
||||
paneBox!.y + paneBox!.height / 2,
|
||||
);
|
||||
await page.keyboard.down("Control");
|
||||
await page.mouse.wheel(0, 240);
|
||||
await page.keyboard.up("Control");
|
||||
await page.waitForTimeout(250);
|
||||
const viewportBeforeDrop = await viewport.getAttribute("style");
|
||||
expect(viewportBeforeDrop).toBeTruthy();
|
||||
@@ -31,6 +56,133 @@ test("拖入组件不自动适配画布,手动适配按钮仍然生效", async
|
||||
.not.toBe(viewportBeforeDrop);
|
||||
});
|
||||
|
||||
test("普通滚轮纵向平移,Ctrl 滚轮缩放且滚动条和中键拖动保持同步", async ({
|
||||
page,
|
||||
}) => {
|
||||
await prepareApp(page);
|
||||
await page.goto("/");
|
||||
|
||||
const canvas = page.locator(".flow-canvas");
|
||||
const pane = canvas.locator(".react-flow__pane");
|
||||
const horizontalScrollbar = page.getByRole("scrollbar", {
|
||||
name: "水平滚动画布",
|
||||
});
|
||||
const verticalScrollbar = page.getByRole("scrollbar", {
|
||||
name: "垂直滚动画布",
|
||||
});
|
||||
await expect(horizontalScrollbar).toBeVisible();
|
||||
await expect(verticalScrollbar).toBeVisible();
|
||||
|
||||
const horizontalBox = await horizontalScrollbar.boundingBox();
|
||||
const verticalBox = await verticalScrollbar.boundingBox();
|
||||
expect(horizontalBox).not.toBeNull();
|
||||
expect(verticalBox).not.toBeNull();
|
||||
expect(horizontalBox!.height).toBeGreaterThanOrEqual(8);
|
||||
expect(horizontalBox!.height).toBeLessThanOrEqual(12);
|
||||
expect(verticalBox!.width).toBeGreaterThanOrEqual(8);
|
||||
expect(verticalBox!.width).toBeLessThanOrEqual(12);
|
||||
|
||||
const paneBox = await pane.boundingBox();
|
||||
expect(paneBox).not.toBeNull();
|
||||
const pointer = {
|
||||
x: paneBox!.x + paneBox!.width * 0.55,
|
||||
y: paneBox!.y + paneBox!.height * 0.45,
|
||||
};
|
||||
await page.mouse.move(pointer.x, pointer.y);
|
||||
|
||||
const beforeWheel = await readModelingViewport(page);
|
||||
const beforeVerticalScroll = await verticalScrollbar.evaluate(
|
||||
(element) => element.scrollTop,
|
||||
);
|
||||
await page.mouse.wheel(0, 180);
|
||||
await expect
|
||||
.poll(async () => (await readModelingViewport(page)).y)
|
||||
.not.toBeCloseTo(beforeWheel.y, 1);
|
||||
const afterWheel = await readModelingViewport(page);
|
||||
expect(afterWheel.x).toBeCloseTo(beforeWheel.x, 1);
|
||||
expect(afterWheel.zoom).toBeCloseTo(beforeWheel.zoom, 3);
|
||||
await expect
|
||||
.poll(() => verticalScrollbar.evaluate((element) => element.scrollTop))
|
||||
.not.toBeCloseTo(beforeVerticalScroll, 1);
|
||||
|
||||
const beforeZoom = await readModelingViewport(page);
|
||||
const horizontalBeforeZoom = await horizontalScrollbar.evaluate(
|
||||
(element) => element.scrollLeft,
|
||||
);
|
||||
const verticalBeforeZoom = await verticalScrollbar.evaluate(
|
||||
(element) => element.scrollTop,
|
||||
);
|
||||
await page.keyboard.down("Control");
|
||||
await page.mouse.wheel(0, -180);
|
||||
await page.keyboard.up("Control");
|
||||
await expect
|
||||
.poll(async () => (await readModelingViewport(page)).zoom)
|
||||
.not.toBeCloseTo(beforeZoom.zoom, 2);
|
||||
const horizontalAfterZoom = await horizontalScrollbar.evaluate(
|
||||
(element) => element.scrollLeft,
|
||||
);
|
||||
const verticalAfterZoom = await verticalScrollbar.evaluate(
|
||||
(element) => element.scrollTop,
|
||||
);
|
||||
expect(
|
||||
Math.hypot(
|
||||
horizontalAfterZoom - horizontalBeforeZoom,
|
||||
verticalAfterZoom - verticalBeforeZoom,
|
||||
),
|
||||
).toBeGreaterThan(1);
|
||||
|
||||
const beforeMiddleDrag = await readModelingViewport(page);
|
||||
await page.mouse.move(pointer.x, pointer.y);
|
||||
await page.mouse.down({ button: "middle" });
|
||||
await page.mouse.move(pointer.x + 70, pointer.y + 45, { steps: 4 });
|
||||
await page.mouse.up({ button: "middle" });
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const next = await readModelingViewport(page);
|
||||
return Math.hypot(next.x - beforeMiddleDrag.x, next.y - beforeMiddleDrag.y);
|
||||
})
|
||||
.toBeGreaterThan(20);
|
||||
const afterMiddleDrag = await readModelingViewport(page);
|
||||
expect(afterMiddleDrag.zoom).toBeCloseTo(beforeMiddleDrag.zoom, 3);
|
||||
await expect(canvas.locator(".react-flow__selection")).toHaveCount(0);
|
||||
|
||||
await verticalScrollbar.focus();
|
||||
const beforeKeyboardScroll = await verticalScrollbar.evaluate(
|
||||
(element) => element.scrollTop,
|
||||
);
|
||||
await page.keyboard.press("ArrowDown");
|
||||
await expect
|
||||
.poll(() => verticalScrollbar.evaluate((element) => element.scrollTop))
|
||||
.toBeGreaterThan(beforeKeyboardScroll);
|
||||
|
||||
const beforeScrollbarDrag = await readModelingViewport(page);
|
||||
await horizontalScrollbar.evaluate((element) => {
|
||||
element.scrollLeft += 120;
|
||||
element.dispatchEvent(new Event("scroll", { bubbles: true }));
|
||||
});
|
||||
await expect
|
||||
.poll(async () => (await readModelingViewport(page)).x)
|
||||
.not.toBeCloseTo(beforeScrollbarDrag.x, 1);
|
||||
const afterScrollbarDrag = await readModelingViewport(page);
|
||||
expect(afterScrollbarDrag.y).toBeCloseTo(beforeScrollbarDrag.y, 1);
|
||||
expect(afterScrollbarDrag.zoom).toBeCloseTo(beforeScrollbarDrag.zoom, 3);
|
||||
|
||||
const horizontalMaximum = await horizontalScrollbar.evaluate(
|
||||
(element) => element.scrollWidth - element.clientWidth,
|
||||
);
|
||||
const verticalMaximum = await verticalScrollbar.evaluate(
|
||||
(element) => element.scrollHeight - element.clientHeight,
|
||||
);
|
||||
await expect(horizontalScrollbar).toHaveAttribute(
|
||||
"aria-valuemax",
|
||||
String(Math.round(horizontalMaximum)),
|
||||
);
|
||||
await expect(verticalScrollbar).toHaveAttribute(
|
||||
"aria-valuemax",
|
||||
String(Math.round(verticalMaximum)),
|
||||
);
|
||||
});
|
||||
|
||||
test("加载本地工程后自动适配建模画布", async ({ page }) => {
|
||||
await prepareApp(page);
|
||||
await page.addInitScript((project) => {
|
||||
@@ -48,7 +200,10 @@ test("加载本地工程后自动适配建模画布", async ({ page }) => {
|
||||
name: "仿真控制台",
|
||||
exact: true,
|
||||
});
|
||||
await expect(consolePanel).not.toContainText("工程已加载");
|
||||
await expect(consolePanel.locator(".simulation-console-dock-summary")).toContainText(
|
||||
"工程已加载",
|
||||
);
|
||||
await expect(consolePanel.locator(".simulation-console-dock-log")).toHaveCount(0);
|
||||
await page.getByRole("button", { name: "展开仿真控制台" }).click();
|
||||
await expect(consolePanel).toContainText("工程已加载");
|
||||
});
|
||||
@@ -83,7 +238,10 @@ test("恢复自动保存工程后自动适配建模画布", async ({ page }) =>
|
||||
name: "仿真控制台",
|
||||
exact: true,
|
||||
});
|
||||
await expect(consolePanel).not.toContainText("已恢复上次自动保存的工程");
|
||||
await expect(consolePanel.locator(".simulation-console-dock-summary")).toContainText(
|
||||
"已恢复上次自动保存的工程",
|
||||
);
|
||||
await expect(consolePanel.locator(".simulation-console-dock-log")).toHaveCount(0);
|
||||
await page.getByRole("button", { name: "展开仿真控制台" }).click();
|
||||
await expect(consolePanel).toContainText("已恢复上次自动保存的工程");
|
||||
});
|
||||
@@ -113,6 +271,9 @@ test("切换到结果页时自动适配只读系统图", async ({ page }) => {
|
||||
);
|
||||
await page.getByRole("tab", { name: /^结果/ }).click();
|
||||
await expectAllNodesInsideCanvas(page, ".results-system-canvas");
|
||||
await expect(
|
||||
page.locator(".results-system-canvas .react-flow__attribution"),
|
||||
).toHaveCount(0);
|
||||
await expect(
|
||||
page.getByRole("complementary", { name: "仿真控制台", exact: true }),
|
||||
).toHaveCount(0);
|
||||
@@ -145,4 +306,51 @@ test("切换到结果页时自动适配只读系统图", async ({ page }) => {
|
||||
);
|
||||
await expect(idleResultHandles).toHaveCount(2);
|
||||
await expect(connectedResultHandles.first()).toHaveCSS("opacity", "0");
|
||||
await expect(
|
||||
idleResultHandles.locator('[data-port-glyph-shape="circle"]'),
|
||||
).toHaveCount(2);
|
||||
const resultViewportZoom = await page
|
||||
.locator(".results-system-canvas .react-flow__viewport")
|
||||
.evaluate(
|
||||
(element) =>
|
||||
new DOMMatrixReadOnly(getComputedStyle(element).transform).a,
|
||||
);
|
||||
const idleResultPortShapes = await idleResultHandles.evaluateAll((elements) =>
|
||||
elements.map((element) => {
|
||||
const bounds = element.getBoundingClientRect();
|
||||
const glyphBounds = element
|
||||
.querySelector<SVGSVGElement>(".port-handle-glyph")
|
||||
?.getBoundingClientRect();
|
||||
const circle = element.querySelector<SVGCircleElement>(
|
||||
'[data-port-glyph-shape="circle"]',
|
||||
);
|
||||
return {
|
||||
circle: circle
|
||||
? {
|
||||
cx: circle.cx.baseVal.value,
|
||||
cy: circle.cy.baseVal.value,
|
||||
r: circle.r.baseVal.value,
|
||||
}
|
||||
: null,
|
||||
glyphHeight: glyphBounds?.height ?? null,
|
||||
glyphWidth: glyphBounds?.width ?? null,
|
||||
height: bounds.height,
|
||||
width: bounds.width,
|
||||
};
|
||||
}),
|
||||
);
|
||||
for (const shape of idleResultPortShapes) {
|
||||
expect(Math.abs(shape.width - 6)).toBeLessThan(0.05);
|
||||
expect(Math.abs(shape.height - 6)).toBeLessThan(0.05);
|
||||
expect(shape.glyphWidth).not.toBeNull();
|
||||
expect(shape.glyphHeight).not.toBeNull();
|
||||
expect(
|
||||
Math.abs(shape.glyphWidth! - 6 * resultViewportZoom),
|
||||
).toBeLessThan(0.1);
|
||||
expect(
|
||||
Math.abs(shape.glyphHeight! - 6 * resultViewportZoom),
|
||||
).toBeLessThan(0.1);
|
||||
expect(Math.abs(shape.glyphWidth! - shape.glyphHeight!)).toBeLessThan(0.01);
|
||||
expect(shape.circle).toEqual({ cx: 3, cy: 3, r: 2.5 });
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user