Python内核代码移除,建模界面缩放bug修复
This commit is contained in:
1 parent
743663e3a6
commit
0dcb465d84
27 files changed
+354
-6092
No files matched your search
@@ -3319,7 +3319,7 @@ test("端口图标随画布缩小且物理端口始终保持标准圆形", async
|
||||
?.map(Number) ?? [];
|
||||
return values[0] ?? 1;
|
||||
});
|
||||
const readPortShapes = async () => {
|
||||
const readPortShapes = async (currentZoom: number) => {
|
||||
const shapes = await scalablePorts.evaluateAll((elements) =>
|
||||
elements.map((element) => {
|
||||
const handleBounds = element.getBoundingClientRect();
|
||||
@@ -3352,8 +3352,8 @@ test("端口图标随画布缩小且物理端口始终保持标准圆形", async
|
||||
}),
|
||||
);
|
||||
for (const shape of shapes) {
|
||||
expect(Math.abs(shape.handleWidth - 6)).toBeLessThan(0.05);
|
||||
expect(Math.abs(shape.handleHeight - 6)).toBeLessThan(0.05);
|
||||
expect(Math.abs(shape.handleWidth - 6 * currentZoom)).toBeLessThan(0.1);
|
||||
expect(Math.abs(shape.handleHeight - 6 * currentZoom)).toBeLessThan(0.1);
|
||||
expect(shape.glyphWidth).not.toBeNull();
|
||||
expect(shape.glyphHeight).not.toBeNull();
|
||||
expect(Math.abs(shape.glyphWidth! - shape.glyphHeight!)).toBeLessThan(0.01);
|
||||
@@ -3371,7 +3371,10 @@ test("端口图标随画布缩小且物理端口始终保持标准圆形", async
|
||||
return shapes;
|
||||
};
|
||||
const initialZoom = await readViewportZoom();
|
||||
const initialShapes = await readPortShapes();
|
||||
const initialShapes = await readPortShapes(initialZoom);
|
||||
const initialPortStyles = await scalablePorts.evaluateAll((elements) =>
|
||||
elements.map((element) => element.getAttribute("style")),
|
||||
);
|
||||
for (const shape of initialShapes) {
|
||||
expect(Math.abs(shape.glyphWidth! - 6 * initialZoom)).toBeLessThan(0.05);
|
||||
expect(Math.abs(shape.glyphHeight! - 6 * initialZoom)).toBeLessThan(0.05);
|
||||
@@ -3391,7 +3394,23 @@ test("端口图标随画布缩小且物理端口始终保持标准圆形", async
|
||||
.poll(async () => (await readViewportZoom()) / initialZoom)
|
||||
.toBeLessThan(0.7);
|
||||
const zoomedZoom = await readViewportZoom();
|
||||
const zoomedShapes = await readPortShapes();
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const hitScale = await page
|
||||
.locator(".flow-canvas")
|
||||
.evaluate((element) =>
|
||||
Number(
|
||||
element.style.getPropertyValue("--port-handle-hit-scale"),
|
||||
),
|
||||
);
|
||||
return Math.abs(hitScale - 1 / zoomedZoom);
|
||||
})
|
||||
.toBeLessThan(0.02);
|
||||
const zoomedShapes = await readPortShapes(zoomedZoom);
|
||||
const zoomedPortStyles = await scalablePorts.evaluateAll((elements) =>
|
||||
elements.map((element) => element.getAttribute("style")),
|
||||
);
|
||||
expect(zoomedPortStyles).toEqual(initialPortStyles);
|
||||
const zoomRatio = zoomedZoom / initialZoom;
|
||||
for (const [index, shape] of zoomedShapes.entries()) {
|
||||
expect(Math.abs(shape.glyphWidth! - 6 * zoomedZoom)).toBeLessThan(0.1);
|
||||
@@ -3400,6 +3419,25 @@ test("端口图标随画布缩小且物理端口始终保持标准圆形", async
|
||||
Math.abs(shape.glyphWidth! / initialShapes[index].glyphWidth! - zoomRatio),
|
||||
).toBeLessThan(0.02);
|
||||
}
|
||||
|
||||
// 可见端口随画布缩小,但透明命中区仍保持易于点击的屏幕尺寸。
|
||||
const firstZoomedPort = scalablePorts.first();
|
||||
const firstZoomedPortBounds = await firstZoomedPort.boundingBox();
|
||||
expect(firstZoomedPortBounds).not.toBeNull();
|
||||
const hitPortName = await page.evaluate(
|
||||
({ x, y }) =>
|
||||
document
|
||||
.elementFromPoint(x, y)
|
||||
?.closest(".port-handle")
|
||||
?.getAttribute("data-port-name") ?? null,
|
||||
{
|
||||
x: firstZoomedPortBounds!.x - 3,
|
||||
y: firstZoomedPortBounds!.y + firstZoomedPortBounds!.height / 2,
|
||||
},
|
||||
);
|
||||
expect(hitPortName).toBe(
|
||||
await firstZoomedPort.getAttribute("data-port-name"),
|
||||
);
|
||||
});
|
||||
|
||||
test("放大画面后接触吸附仍保留足够的画布判定半径", async ({ page }) => {
|
||||
|
||||
@@ -549,6 +549,82 @@ test("正交连接线可拖动内部线段,且无连接交叉点显示跨线
|
||||
expect(savedRoute?.length).toBeGreaterThan(1);
|
||||
});
|
||||
|
||||
test("缩小画布后连接线端点仍与端口图标边缘对齐", async ({ page }) => {
|
||||
await page.addInitScript((storedProject) => {
|
||||
window.localStorage.setItem(
|
||||
"system-simulation-flow:project:demo-system",
|
||||
JSON.stringify(storedProject),
|
||||
);
|
||||
}, wideProject);
|
||||
await page.goto("/");
|
||||
await page.getByRole("button", { name: "加载工程", exact: true }).click();
|
||||
|
||||
const viewport = page.locator(".flow-canvas .react-flow__viewport");
|
||||
const readViewportZoom = () =>
|
||||
viewport.evaluate((element) => {
|
||||
const values =
|
||||
getComputedStyle(element)
|
||||
.transform.match(/-?\d*\.?\d+(?:e[-+]?\d+)?/gi)
|
||||
?.map(Number) ?? [];
|
||||
return values[0] ?? 1;
|
||||
});
|
||||
const initialZoom = await readViewportZoom();
|
||||
const pane = page.locator(".flow-canvas .react-flow__pane");
|
||||
const paneBounds = await pane.boundingBox();
|
||||
expect(paneBounds).not.toBeNull();
|
||||
await page.mouse.move(
|
||||
paneBounds!.x + paneBounds!.width / 2,
|
||||
paneBounds!.y + paneBounds!.height / 2,
|
||||
);
|
||||
await page.keyboard.down("Control");
|
||||
await page.mouse.wheel(0, 720);
|
||||
await page.keyboard.up("Control");
|
||||
await expect
|
||||
.poll(async () => (await readViewportZoom()) / initialZoom)
|
||||
.toBeLessThan(0.7);
|
||||
const zoom = await readViewportZoom();
|
||||
|
||||
const alignment = await page.evaluate(() => {
|
||||
const path = document.querySelector<SVGPathElement>(
|
||||
'.flow-canvas .react-flow__edge[data-id="edge-1"] .react-flow__edge-path',
|
||||
);
|
||||
const source = document.querySelector<HTMLElement>(
|
||||
'.flow-canvas .react-flow__node[data-id="generic_sensor_1"] .port-handle[data-port-name="port_b"]',
|
||||
);
|
||||
const target = document.querySelector<HTMLElement>(
|
||||
'.flow-canvas .react-flow__node[data-id="generic_sensor_2"] .port-handle[data-port-name="port_a"]',
|
||||
);
|
||||
const matrix = path?.getScreenCTM();
|
||||
if (!path || !source || !target || !matrix) {
|
||||
return null;
|
||||
}
|
||||
const start = path.getPointAtLength(0).matrixTransform(matrix);
|
||||
const end = path.getPointAtLength(path.getTotalLength()).matrixTransform(matrix);
|
||||
const sourceBounds = source.getBoundingClientRect();
|
||||
const targetBounds = target.getBoundingClientRect();
|
||||
return {
|
||||
sourceGapX: Math.abs(start.x - sourceBounds.right),
|
||||
sourceGapY: Math.abs(
|
||||
start.y - (sourceBounds.top + sourceBounds.height / 2),
|
||||
),
|
||||
sourceWidth: sourceBounds.width,
|
||||
targetGapX: Math.abs(end.x - targetBounds.left),
|
||||
targetGapY: Math.abs(
|
||||
end.y - (targetBounds.top + targetBounds.height / 2),
|
||||
),
|
||||
targetWidth: targetBounds.width,
|
||||
};
|
||||
});
|
||||
|
||||
expect(alignment).not.toBeNull();
|
||||
expect(Math.abs(alignment!.sourceWidth - 6 * zoom)).toBeLessThan(0.1);
|
||||
expect(Math.abs(alignment!.targetWidth - 6 * zoom)).toBeLessThan(0.1);
|
||||
expect(alignment!.sourceGapX).toBeLessThan(0.75);
|
||||
expect(alignment!.sourceGapY).toBeLessThan(0.75);
|
||||
expect(alignment!.targetGapX).toBeLessThan(0.75);
|
||||
expect(alignment!.targetGapY).toBeLessThan(0.75);
|
||||
});
|
||||
|
||||
test("直线首次折弯后即使原手柄卸载,拖动仍连续且保持正交", async ({
|
||||
page,
|
||||
}) => {
|
||||
|
||||
@@ -257,18 +257,18 @@ test("普通滚轮纵向平移,Ctrl 滚轮缩放且滚动条和中键拖动保
|
||||
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(
|
||||
await expect.poll(async () => {
|
||||
const horizontalAfterZoom = await horizontalScrollbar.evaluate(
|
||||
(element) => element.scrollLeft,
|
||||
);
|
||||
const verticalAfterZoom = await verticalScrollbar.evaluate(
|
||||
(element) => element.scrollTop,
|
||||
);
|
||||
return Math.hypot(
|
||||
horizontalAfterZoom - horizontalBeforeZoom,
|
||||
verticalAfterZoom - verticalBeforeZoom,
|
||||
),
|
||||
).toBeGreaterThan(1);
|
||||
);
|
||||
}).toBeGreaterThan(1);
|
||||
|
||||
const beforeMiddleDrag = await readModelingViewport(page);
|
||||
await page.mouse.move(pointer.x, pointer.y);
|
||||
@@ -404,6 +404,67 @@ test("导入大型工程 JSON 后非接触连线端点与端口保持对齐", as
|
||||
|
||||
});
|
||||
|
||||
test("大型工程连续缩放时滚动条不逐帧重排且停稳后同步", async ({ page }) => {
|
||||
await prepareApp(page);
|
||||
await page.goto("/");
|
||||
await page.locator('input[type="file"]').setInputFiles(MQL_8_PROJECT_PATH);
|
||||
await expectAllNodesInsideCanvas(page, ".flow-canvas", mql8Project.nodes.length);
|
||||
await expect(page.getByRole("scrollbar", { name: "水平滚动画布" }))
|
||||
.toHaveAttribute("aria-valuemax", /[1-9]/);
|
||||
|
||||
const changes = await page.evaluate(async () => {
|
||||
const canvas = document.querySelector<HTMLElement>(".flow-canvas")!;
|
||||
const pane = canvas.querySelector<HTMLElement>(".react-flow__pane")!;
|
||||
const viewport = canvas.querySelector<HTMLElement>(".react-flow__viewport")!;
|
||||
const scrollbars = canvas.querySelector<HTMLElement>(".flow-viewport-scrollbars")!;
|
||||
const bounds = canvas.getBoundingClientRect();
|
||||
let viewportChanges = 0;
|
||||
let scrollbarChanges = 0;
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
for (const mutation of mutations) {
|
||||
if (mutation.target === viewport) viewportChanges++;
|
||||
if (scrollbars.contains(mutation.target)) scrollbarChanges++;
|
||||
}
|
||||
});
|
||||
observer.observe(viewport, { attributes: true, attributeFilter: ["style"] });
|
||||
observer.observe(scrollbars, {
|
||||
attributes: true, attributeFilter: ["style"], subtree: true,
|
||||
});
|
||||
// 连续输入覆盖整个手势,避免驱动侧等待导致其被拆成多个独立缩放。
|
||||
for (let index = 0; index < 12; index++) {
|
||||
pane.dispatchEvent(new WheelEvent("wheel", {
|
||||
bubbles: true, cancelable: true, ctrlKey: true, deltaY: -8,
|
||||
clientX: bounds.x + bounds.width / 2,
|
||||
clientY: bounds.y + bounds.height / 2,
|
||||
}));
|
||||
await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()));
|
||||
}
|
||||
observer.disconnect();
|
||||
return { viewportChanges, scrollbarChanges };
|
||||
});
|
||||
expect(changes.viewportChanges).toBeGreaterThan(5);
|
||||
expect(changes.scrollbarChanges).toBeLessThan(changes.viewportChanges / 2);
|
||||
|
||||
await expect.poll(async () => {
|
||||
const viewport = await readModelingViewport(page);
|
||||
const scale = await page.locator(".flow-canvas").evaluate((canvas) =>
|
||||
Number(canvas.style.getPropertyValue("--port-handle-hit-scale")),
|
||||
);
|
||||
return Math.abs(scale * viewport.zoom - 1);
|
||||
}).toBeLessThan(0.01);
|
||||
const alignment = await readVisibleEdgeEndpointAlignment(page, mql8Project.edges);
|
||||
expect(alignment.missingHandles).toEqual([]);
|
||||
expect(alignment.checkedEdgeCount).toBeGreaterThan(20);
|
||||
expect(alignment.maxDistance).toBeLessThanOrEqual(4);
|
||||
const zoom = (await readModelingViewport(page)).zoom;
|
||||
const horizontal = page.getByRole("scrollbar", { name: "水平滚动画布" });
|
||||
await horizontal.focus();
|
||||
const before = await readModelingViewport(page);
|
||||
await horizontal.press("ArrowRight");
|
||||
await expect.poll(async () => (await readModelingViewport(page)).x).toBeLessThan(before.x);
|
||||
expect((await readModelingViewport(page)).zoom).toBeCloseTo(zoom, 5);
|
||||
});
|
||||
|
||||
test("恢复自动保存工程后自动适配建模画布", async ({ page }) => {
|
||||
await prepareApp(page);
|
||||
await page.addInitScript((project) => {
|
||||
|
||||
Reference in new issue
Block a user