优化组件选择交互与视图适配时机
This commit is contained in:
1 parent
11093613c5
commit
cf249d6c0d
7 files changed
+473
-18
No files matched your search
@@ -428,6 +428,8 @@ test("AMESim canvas nodes use icon anchors and highlight only compatible free po
|
||||
"data-connection-state",
|
||||
"incompatible",
|
||||
);
|
||||
await expect(sameNodePort).toHaveCSS("pointer-events", "none");
|
||||
await expect(signalInput).toHaveCSS("pointer-events", "none");
|
||||
|
||||
await page.locator(".flow-canvas .react-flow__pane").click({
|
||||
position: { x: 20, y: 20 },
|
||||
@@ -455,9 +457,23 @@ test("AMESim canvas nodes use icon anchors and highlight only compatible free po
|
||||
await expect(compatiblePhysicalPort).toHaveAttribute("data-connected", "true");
|
||||
await expect(firstPhysicalOutput).toHaveCSS("opacity", "0");
|
||||
await expect(compatiblePhysicalPort).toHaveCSS("opacity", "0");
|
||||
await expect(firstPhysicalOutput).toHaveCSS("pointer-events", "none");
|
||||
await expect(compatiblePhysicalPort).toHaveCSS("pointer-events", "none");
|
||||
|
||||
const physicalEdge = page.locator(".flow-canvas .react-flow__edge").first();
|
||||
await physicalEdge.locator(".react-flow__edge-interaction").click({ force: true });
|
||||
const edgeClickPoint = await physicalEdge
|
||||
.locator(".react-flow__edge-interaction")
|
||||
.evaluate((path: SVGPathElement) => {
|
||||
const point = path.getPointAtLength(path.getTotalLength() / 2);
|
||||
const matrix = path.getScreenCTM();
|
||||
if (!matrix) {
|
||||
throw new Error("Unable to resolve the edge screen position");
|
||||
}
|
||||
const screenPoint = new DOMPoint(point.x, point.y).matrixTransform(matrix);
|
||||
return { x: screenPoint.x, y: screenPoint.y };
|
||||
});
|
||||
await page.mouse.click(edgeClickPoint.x, edgeClickPoint.y);
|
||||
await expect(physicalEdge).toHaveClass(/selected|editor-edge-selected/);
|
||||
await page.getByRole("button", { name: "删除 (Delete)", exact: true }).click();
|
||||
await expect(page.locator(".flow-canvas .react-flow__edge")).toHaveCount(0);
|
||||
await expect(firstPhysicalOutput).toHaveAttribute("data-connected", "false");
|
||||
@@ -567,6 +583,19 @@ test("兼容接口接近时吸附为无连线接触连接,拖开后自动断
|
||||
'.port-handle[data-port-name="port_1"]',
|
||||
);
|
||||
|
||||
await dragNodePortNearTarget(
|
||||
page,
|
||||
secondOrifice,
|
||||
secondPort,
|
||||
firstPort,
|
||||
16,
|
||||
);
|
||||
await expect(firstPort).toHaveAttribute("data-connection-state", "idle");
|
||||
await expect(secondPort).toHaveAttribute("data-connection-state", "idle");
|
||||
expect(await distanceBetween(firstPort, secondPort)).toBeGreaterThan(14);
|
||||
await page.mouse.up();
|
||||
await expect(page.locator(".flow-canvas .react-flow__edge")).toHaveCount(0);
|
||||
|
||||
await dragNodePortNearTarget(
|
||||
page,
|
||||
secondOrifice,
|
||||
@@ -801,3 +830,58 @@ test("旧工程保存的通用图标会按当前目录定义升级", async ({ pa
|
||||
).toBeVisible();
|
||||
await expect(restoredNode.locator('[data-component-symbol="pipe"]')).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("专用图标的选择框和鼠标命中范围贴合实际图形包络", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
|
||||
const helium = await dragSymbolToCanvas(page, "amesim_helium_medium", {
|
||||
x: 430,
|
||||
y: 280,
|
||||
});
|
||||
const simNode = helium.locator(".sim-node");
|
||||
const envelope = helium.locator('[data-symbol-envelope="true"]');
|
||||
const circle = helium.locator(".component-symbol-svg circle");
|
||||
|
||||
await expect(helium).toHaveClass(/component-icon-node/);
|
||||
await expect(helium).toHaveCSS("pointer-events", "none");
|
||||
await expect(simNode).toHaveCSS("pointer-events", "none");
|
||||
await expect(envelope).toHaveCSS("outline-style", "dashed");
|
||||
await expect(envelope).toHaveCSS("pointer-events", "auto");
|
||||
await expect(simNode).toHaveCSS("outline-style", "none");
|
||||
|
||||
const [nodeBounds, envelopeBounds, circleBounds, paneBounds] =
|
||||
await Promise.all([
|
||||
helium.boundingBox(),
|
||||
envelope.boundingBox(),
|
||||
circle.boundingBox(),
|
||||
page.locator(".flow-canvas .react-flow__pane").boundingBox(),
|
||||
]);
|
||||
expect(nodeBounds).not.toBeNull();
|
||||
expect(envelopeBounds).not.toBeNull();
|
||||
expect(circleBounds).not.toBeNull();
|
||||
expect(paneBounds).not.toBeNull();
|
||||
expect(envelopeBounds!.width).toBeLessThan(nodeBounds!.width * 0.7);
|
||||
expect(
|
||||
Math.abs(envelopeBounds!.width - envelopeBounds!.height),
|
||||
).toBeLessThan(3);
|
||||
expect(envelopeBounds!.width - circleBounds!.width).toBeGreaterThanOrEqual(0);
|
||||
expect(envelopeBounds!.width - circleBounds!.width).toBeLessThanOrEqual(4);
|
||||
|
||||
await page.mouse.click(paneBounds!.x + 8, paneBounds!.y + 8);
|
||||
await expect(helium).not.toHaveClass(/selected/);
|
||||
|
||||
const oldTransparentMargin = {
|
||||
x: nodeBounds!.x + 5,
|
||||
y: envelopeBounds!.y + envelopeBounds!.height / 2,
|
||||
};
|
||||
expect(oldTransparentMargin.x).toBeLessThan(envelopeBounds!.x - 8);
|
||||
await page.mouse.click(oldTransparentMargin.x, oldTransparentMargin.y);
|
||||
await expect(helium).not.toHaveClass(/selected/);
|
||||
|
||||
await page.mouse.click(
|
||||
envelopeBounds!.x + envelopeBounds!.width / 2,
|
||||
envelopeBounds!.y + envelopeBounds!.height / 2,
|
||||
);
|
||||
await expect(helium).toHaveClass(/selected/);
|
||||
await expect(envelope).toHaveCSS("outline-style", "dashed");
|
||||
});
|
||||
@@ -6,6 +6,31 @@ import {
|
||||
wideProject,
|
||||
} from "./fixtures";
|
||||
|
||||
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 page.waitForTimeout(250);
|
||||
const viewportBeforeDrop = await viewport.getAttribute("style");
|
||||
expect(viewportBeforeDrop).toBeTruthy();
|
||||
|
||||
await page
|
||||
.getByRole("button", { name: "通用测试元件", exact: true })
|
||||
.dragTo(page.locator(".flow-canvas .react-flow__pane"));
|
||||
await expect(page.locator(".flow-canvas .react-flow__node")).toHaveCount(1);
|
||||
await page.waitForTimeout(250);
|
||||
await expect(viewport).toHaveAttribute("style", viewportBeforeDrop!);
|
||||
|
||||
await page.getByRole("button", { name: "适配视图", exact: true }).click();
|
||||
await expect
|
||||
.poll(() => viewport.getAttribute("style"))
|
||||
.not.toBe(viewportBeforeDrop);
|
||||
});
|
||||
|
||||
test("加载本地工程后自动适配建模画布", async ({ page }) => {
|
||||
await prepareApp(page);
|
||||
await page.addInitScript((project) => {
|
||||
@@ -28,6 +53,19 @@ test("加载本地工程后自动适配建模画布", async ({ page }) => {
|
||||
await expect(consolePanel).toContainText("工程已加载");
|
||||
});
|
||||
|
||||
test("导入工程 JSON 后自动适配建模画布", async ({ page }) => {
|
||||
await prepareApp(page);
|
||||
await page.goto("/");
|
||||
|
||||
await page.locator('input[type="file"]').setInputFiles({
|
||||
name: "wide-project.json",
|
||||
mimeType: "application/json",
|
||||
buffer: Buffer.from(JSON.stringify(wideProject)),
|
||||
});
|
||||
|
||||
await expectAllNodesInsideCanvas(page, ".flow-canvas");
|
||||
});
|
||||
|
||||
test("恢复自动保存工程后自动适配建模画布", async ({ page }) => {
|
||||
await prepareApp(page);
|
||||
await page.addInitScript((project) => {
|
||||
@@ -80,6 +118,7 @@ test("切换到结果页时自动适配只读系统图", async ({ page }) => {
|
||||
).toHaveCount(0);
|
||||
|
||||
await page.getByRole("tab", { name: "建模" }).click();
|
||||
await expectAllNodesInsideCanvas(page, ".flow-canvas");
|
||||
await expect(freeModelingHandle).toHaveAttribute(
|
||||
"data-connection-state",
|
||||
"idle",
|
||||
|
||||
@@ -44,3 +44,115 @@ test("未知图形元件使用带端口和名称的默认节点,并将动作
|
||||
await expect(page.locator(".validation-section")).toHaveCount(0);
|
||||
await expect(page.locator(".properties")).not.toContainText("仿真结果");
|
||||
});
|
||||
|
||||
test("拖入组件后仅选择新组件,变换操作不会继续作用于旧选择", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/");
|
||||
const paletteItem = page.getByRole("button", { name: /通用测试元件/ });
|
||||
const pane = page.locator(".flow-canvas .react-flow__pane");
|
||||
|
||||
await paletteItem.dragTo(pane, { targetPosition: { x: 220, y: 220 } });
|
||||
const firstNode = page.locator(
|
||||
'.flow-canvas .react-flow__node[data-id="generic_sensor_1"]',
|
||||
);
|
||||
await expect(firstNode).toHaveClass(/selected/);
|
||||
await expect(firstNode.locator(".sim-node")).toHaveCSS(
|
||||
"outline-style",
|
||||
"dashed",
|
||||
);
|
||||
|
||||
await paletteItem.dragTo(pane, { targetPosition: { x: 480, y: 340 } });
|
||||
const secondNode = page.locator(
|
||||
'.flow-canvas .react-flow__node[data-id="generic_sensor_2"]',
|
||||
);
|
||||
await expect(firstNode).not.toHaveClass(/selected/);
|
||||
await expect(secondNode).toHaveClass(/selected/);
|
||||
await expect(firstNode.locator(".sim-node")).toHaveCSS("outline-style", "none");
|
||||
await expect(secondNode.locator(".sim-node")).toHaveCSS(
|
||||
"outline-style",
|
||||
"dashed",
|
||||
);
|
||||
|
||||
await page
|
||||
.getByRole("button", { name: "顺时针旋转 90° (Ctrl+R)", exact: true })
|
||||
.click();
|
||||
await expect(firstNode.locator(".sim-node")).not.toHaveClass(
|
||||
/orientation-vertical/,
|
||||
);
|
||||
await expect(secondNode.locator(".sim-node")).toHaveClass(
|
||||
/orientation-vertical/,
|
||||
);
|
||||
});
|
||||
|
||||
test("框选结束后隐藏整体包围框并保留每个组件的虚线选中框", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/");
|
||||
const paletteItem = page.getByRole("button", { name: /通用测试元件/ });
|
||||
const pane = page.locator(".flow-canvas .react-flow__pane");
|
||||
|
||||
await paletteItem.dragTo(pane, { targetPosition: { x: 220, y: 220 } });
|
||||
await paletteItem.dragTo(pane, { targetPosition: { x: 480, y: 340 } });
|
||||
const firstNode = page.locator(
|
||||
'.flow-canvas .react-flow__node[data-id="generic_sensor_1"]',
|
||||
);
|
||||
const secondNode = page.locator(
|
||||
'.flow-canvas .react-flow__node[data-id="generic_sensor_2"]',
|
||||
);
|
||||
const [paneBounds, firstBounds, secondBounds] = await Promise.all([
|
||||
pane.boundingBox(),
|
||||
firstNode.boundingBox(),
|
||||
secondNode.boundingBox(),
|
||||
]);
|
||||
expect(paneBounds).not.toBeNull();
|
||||
expect(firstBounds).not.toBeNull();
|
||||
expect(secondBounds).not.toBeNull();
|
||||
|
||||
const startX = Math.max(
|
||||
paneBounds!.x + 4,
|
||||
Math.min(firstBounds!.x, secondBounds!.x) - 14,
|
||||
);
|
||||
const startY = Math.max(
|
||||
paneBounds!.y + 4,
|
||||
Math.min(firstBounds!.y, secondBounds!.y) - 14,
|
||||
);
|
||||
const endX = Math.min(
|
||||
paneBounds!.x + paneBounds!.width - 4,
|
||||
Math.max(
|
||||
firstBounds!.x + firstBounds!.width,
|
||||
secondBounds!.x + secondBounds!.width,
|
||||
) + 14,
|
||||
);
|
||||
const endY = Math.min(
|
||||
paneBounds!.y + paneBounds!.height - 4,
|
||||
Math.max(
|
||||
firstBounds!.y + firstBounds!.height,
|
||||
secondBounds!.y + secondBounds!.height,
|
||||
) + 14,
|
||||
);
|
||||
|
||||
await page.mouse.move(startX, startY);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(endX, endY, { steps: 12 });
|
||||
await page.mouse.up();
|
||||
|
||||
await expect(firstNode).toHaveClass(/selected/);
|
||||
await expect(secondNode).toHaveClass(/selected/);
|
||||
await expect(firstNode.locator(".sim-node")).toHaveCSS(
|
||||
"outline-style",
|
||||
"dashed",
|
||||
);
|
||||
await expect(secondNode.locator(".sim-node")).toHaveCSS(
|
||||
"outline-style",
|
||||
"dashed",
|
||||
);
|
||||
await expect(page.locator(".flow-canvas .react-flow__selection")).toHaveCount(0);
|
||||
|
||||
const aggregateSelection = page.locator(
|
||||
".flow-canvas .react-flow__nodesselection-rect",
|
||||
);
|
||||
await expect(aggregateSelection).toHaveCount(1);
|
||||
await expect(aggregateSelection).toHaveCSS("border-top-width", "0px");
|
||||
await expect(aggregateSelection).toHaveCSS("pointer-events", "none");
|
||||
});
|
||||
Reference in new issue
Block a user