From e90350b7d122c309f8109491e3ce494f06eee201 Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Fri, 28 Mar 2025 18:17:39 +0100 Subject: [PATCH] Fix tests --- packages/element/tests/binding.test.tsx | 119 ++++++++------- packages/element/tests/duplicate.test.tsx | 128 ++++++++++++++++- packages/element/tests/elbowArrow.test.tsx | 113 --------------- packages/element/tests/resize.test.tsx | 22 +-- .../excalidraw/actions/actionFlip.test.tsx | 15 +- .../tests/__snapshots__/history.test.tsx.snap | 130 ++++++++--------- .../tests/__snapshots__/move.test.tsx.snap | 136 ++++++++++++++++++ packages/excalidraw/tests/move.test.tsx | 13 +- packages/excalidraw/tests/rotate.test.tsx | 14 +- 9 files changed, 432 insertions(+), 258 deletions(-) diff --git a/packages/element/tests/binding.test.tsx b/packages/element/tests/binding.test.tsx index ff3c8d10ad..2b65859655 100644 --- a/packages/element/tests/binding.test.tsx +++ b/packages/element/tests/binding.test.tsx @@ -18,7 +18,9 @@ const mouse = new Pointer("mouse"); describe("element binding", () => { beforeEach(async () => { + localStorage.clear(); await render(); + mouse.reset(); }); it("should create valid binding if duplicate start/end points", async () => { @@ -89,46 +91,55 @@ describe("element binding", () => { }); }); - //@TODO fix the test with rotation - it.skip("rotation of arrow should rebind both ends", () => { - const rectLeft = UI.createElement("rectangle", { - x: 0, - width: 200, - height: 500, - }); - const rectRight = UI.createElement("rectangle", { - x: 400, - width: 200, - height: 500, - }); - const arrow = UI.createElement("arrow", { - x: 210, - y: 250, - width: 180, - height: 1, - }); - expect(arrow.startBinding?.elementId).toBe(rectLeft.id); - expect(arrow.endBinding?.elementId).toBe(rectRight.id); + // UX RATIONALE: We are not aware of any use-case where the user would want to + // have the arrow rebind after rotation but not when the arrow shaft is + // dragged so either the start or the end point is in the binding range of a + // bindable element. So to remain consistent, we only "rebind" if at the end + // of the rotation the original binding would remain the same (i.e. like we + // would've evaluated binding only at the end of the operation). + it( + "rotation of arrow should not rebind on both ends if rotated enough to" + + " not be in the binding range of the original elements", + () => { + const rectLeft = UI.createElement("rectangle", { + x: 0, + width: 200, + height: 500, + }); + const rectRight = UI.createElement("rectangle", { + x: 400, + width: 200, + height: 500, + }); + const arrow = UI.createElement("arrow", { + x: 210, + y: 250, + width: 180, + height: 1, + }); + expect(arrow.startBinding?.elementId).toBe(rectLeft.id); + expect(arrow.endBinding?.elementId).toBe(rectRight.id); - const rotation = getTransformHandles( - arrow, - h.state.zoom, - arrayToMap(h.elements), - "mouse", - ).rotation!; - const rotationHandleX = rotation[0] + rotation[2] / 2; - const rotationHandleY = rotation[1] + rotation[3] / 2; - mouse.down(rotationHandleX, rotationHandleY); - mouse.move(300, 400); - mouse.up(); - expect(arrow.angle).toBeGreaterThan(0.7 * Math.PI); - expect(arrow.angle).toBeLessThan(1.3 * Math.PI); - expect(arrow.startBinding?.elementId).toBe(rectRight.id); - expect(arrow.endBinding?.elementId).toBe(rectLeft.id); - }); + const rotation = getTransformHandles( + arrow, + h.state.zoom, + arrayToMap(h.elements), + "mouse", + ).rotation!; + const rotationHandleX = rotation[0] + rotation[2] / 2; + const rotationHandleY = rotation[1] + rotation[3] / 2; + mouse.down(rotationHandleX, rotationHandleY); + mouse.move(300, 400); + mouse.up(); + expect(arrow.angle).toBeGreaterThan(0.7 * Math.PI); + expect(arrow.angle).toBeLessThan(1.3 * Math.PI); + expect(arrow.startBinding).toBe(null); + expect(arrow.endBinding).toBe(null); + }, + ); // TODO fix & reenable once we rewrite tests to work with concurrency - it.skip( + it( "editing arrow and moving its head to bind it to element A, finalizing the" + "editing by clicking on element A should end up selecting A", async () => { @@ -142,7 +153,10 @@ describe("element binding", () => { mouse.up(0, 80); // Edit arrow with multi-point - mouse.doubleClick(); + Keyboard.withModifierKeys({ ctrl: true }, () => { + mouse.doubleClick(); + }); + // move arrow head mouse.down(); mouse.up(0, 10); @@ -152,11 +166,7 @@ describe("element binding", () => { // the issue, due to https://github.com/excalidraw/excalidraw/blob/46bff3daceb602accf60c40a84610797260fca94/src/components/App.tsx#L740 mouse.reset(); expect(h.state.editingLinearElement).not.toBe(null); - mouse.down(0, 0); - await new Promise((r) => setTimeout(r, 100)); - expect(h.state.editingLinearElement).toBe(null); - expect(API.getSelectedElement().type).toBe("rectangle"); - mouse.up(); + mouse.click(); expect(API.getSelectedElement().type).toBe("rectangle"); }, ); @@ -187,23 +197,24 @@ describe("element binding", () => { expect(arrow.endBinding?.elementId).toBe(rectangle.id); Keyboard.keyPress(KEYS.ARROW_LEFT); expect(arrow.endBinding?.elementId).toBe(rectangle.id); + expect(API.getSelectedElement().type).toBe("arrow"); // Sever connection - expect(API.getSelectedElement().type).toBe("arrow"); Keyboard.withModifierKeys({ shift: true }, () => { // We have to move a significant distance to get out of the binding zone - Keyboard.keyPress(KEYS.ARROW_LEFT); - Keyboard.keyPress(KEYS.ARROW_LEFT); - Keyboard.keyPress(KEYS.ARROW_LEFT); - Keyboard.keyPress(KEYS.ARROW_LEFT); - Keyboard.keyPress(KEYS.ARROW_LEFT); - Keyboard.keyPress(KEYS.ARROW_LEFT); - Keyboard.keyPress(KEYS.ARROW_LEFT); - Keyboard.keyPress(KEYS.ARROW_LEFT); - Keyboard.keyPress(KEYS.ARROW_LEFT); + Array.from({ length: 10 }).forEach(() => { + Keyboard.keyPress(KEYS.ARROW_LEFT); + }); }); expect(arrow.endBinding).toBe(null); - Keyboard.keyPress(KEYS.ARROW_RIGHT); + + Keyboard.withModifierKeys({ shift: true }, () => { + // We have to move a significant distance to return to the binding + Array.from({ length: 10 }).forEach(() => { + Keyboard.keyPress(KEYS.ARROW_RIGHT); + }); + }); + // We are back in the binding zone but we shouldn't rebind expect(arrow.endBinding).toBe(null); }); diff --git a/packages/element/tests/duplicate.test.tsx b/packages/element/tests/duplicate.test.tsx index 7492bcc586..15ce309028 100644 --- a/packages/element/tests/duplicate.test.tsx +++ b/packages/element/tests/duplicate.test.tsx @@ -10,11 +10,14 @@ import { import { Excalidraw } from "@excalidraw/excalidraw"; -import { actionDuplicateSelection } from "@excalidraw/excalidraw/actions"; +import { + actionDuplicateSelection, + actionSelectAll, +} from "@excalidraw/excalidraw/actions"; import { API } from "@excalidraw/excalidraw/tests/helpers/api"; -import { UI, Keyboard, Pointer } from "@excalidraw/excalidraw/tests/helpers/ui"; +import { Keyboard, Pointer, UI } from "@excalidraw/excalidraw/tests/helpers/ui"; import { act, @@ -28,7 +31,10 @@ import type { LocalPoint } from "@excalidraw/math"; import { mutateElement } from "../src/mutateElement"; import { duplicateElement, duplicateElements } from "../src/duplicate"; -import type { ExcalidrawLinearElement } from "../src/types"; +import type { + ExcalidrawArrowElement, + ExcalidrawLinearElement, +} from "../src/types"; const { h } = window; const mouse = new Pointer("mouse"); @@ -408,6 +414,122 @@ describe("duplicating multiple elements", () => { }); }); +describe("elbow arrow duplication", () => { + beforeEach(async () => { + await render(); + }); + + it("keeps arrow shape when the whole set of arrow and bindables are duplicated", async () => { + UI.createElement("rectangle", { + x: -150, + y: -150, + width: 100, + height: 100, + }); + UI.createElement("rectangle", { + x: 50, + y: 50, + width: 100, + height: 100, + }); + + UI.clickTool("arrow"); + UI.clickOnTestId("elbow-arrow"); + + mouse.reset(); + mouse.moveTo(-43, -99); + mouse.click(); + mouse.moveTo(43, 99); + mouse.click(); + + const arrow = h.scene.getSelectedElements( + h.state, + )[0] as ExcalidrawArrowElement; + const originalArrowId = arrow.id; + + expect(arrow.startBinding).not.toBe(null); + expect(arrow.endBinding).not.toBe(null); + + act(() => { + h.app.actionManager.executeAction(actionSelectAll); + }); + + act(() => { + h.app.actionManager.executeAction(actionDuplicateSelection); + }); + + expect(h.elements.length).toEqual(6); + + const duplicatedArrow = h.scene.getSelectedElements( + h.state, + )[2] as ExcalidrawArrowElement; + + expect(duplicatedArrow.id).not.toBe(originalArrowId); + expect(duplicatedArrow.type).toBe("arrow"); + expect(duplicatedArrow.elbowed).toBe(true); + expect(duplicatedArrow.points).toEqual([ + [0, 0], + [45, 0], + [45, 200], + [90, 200], + ]); + expect(arrow.startBinding).not.toBe(null); + expect(arrow.endBinding).not.toBe(null); + }); + + it("changes arrow shape to unbind variant if only the connected elbow arrow is duplicated", async () => { + UI.createElement("rectangle", { + x: -150, + y: -150, + width: 100, + height: 100, + }); + UI.createElement("rectangle", { + x: 50, + y: 50, + width: 100, + height: 100, + }); + + UI.clickTool("arrow"); + UI.clickOnTestId("elbow-arrow"); + + mouse.reset(); + mouse.moveTo(-43, -99); + mouse.click(); + mouse.moveTo(43, 99); + mouse.click(); + + const arrow = h.scene.getSelectedElements( + h.state, + )[0] as ExcalidrawArrowElement; + const originalArrowId = arrow.id; + + expect(arrow.startBinding).not.toBe(null); + expect(arrow.endBinding).not.toBe(null); + + act(() => { + h.app.actionManager.executeAction(actionDuplicateSelection); + }); + + expect(h.elements.length).toEqual(4); + + const duplicatedArrow = h.scene.getSelectedElements( + h.state, + )[0] as ExcalidrawArrowElement; + + expect(duplicatedArrow.id).not.toBe(originalArrowId); + expect(duplicatedArrow.type).toBe("arrow"); + expect(duplicatedArrow.elbowed).toBe(true); + expect(duplicatedArrow.points).toEqual([ + [0, 0], + [0, 100], + [90, 100], + [90, 200], + ]); + }); +}); + describe("duplication z-order", () => { beforeEach(async () => { await render(); diff --git a/packages/element/tests/elbowArrow.test.tsx b/packages/element/tests/elbowArrow.test.tsx index b8b5a8b85d..72dd7985f4 100644 --- a/packages/element/tests/elbowArrow.test.tsx +++ b/packages/element/tests/elbowArrow.test.tsx @@ -3,14 +3,11 @@ import { pointFrom } from "@excalidraw/math"; import { Excalidraw, mutateElement } from "@excalidraw/excalidraw"; import Scene from "@excalidraw/excalidraw/scene/Scene"; -import { actionSelectAll } from "@excalidraw/excalidraw/actions"; -import { actionDuplicateSelection } from "@excalidraw/excalidraw/actions/actionDuplicateSelection"; import { API } from "@excalidraw/excalidraw/tests/helpers/api"; import { Pointer, UI } from "@excalidraw/excalidraw/tests/helpers/ui"; import { - act, fireEvent, GlobalTestState, queryByTestId, @@ -301,114 +298,4 @@ describe("elbow arrow ui", () => { [103, 165], ]); }); - - it("keeps arrow shape when the whole set of arrow and bindables are duplicated", async () => { - UI.createElement("rectangle", { - x: -150, - y: -150, - width: 100, - height: 100, - }); - UI.createElement("rectangle", { - x: 50, - y: 50, - width: 100, - height: 100, - }); - - UI.clickTool("arrow"); - UI.clickOnTestId("elbow-arrow"); - - mouse.reset(); - mouse.moveTo(-43, -99); - mouse.click(); - mouse.moveTo(43, 99); - mouse.click(); - - const arrow = h.scene.getSelectedElements( - h.state, - )[0] as ExcalidrawArrowElement; - const originalArrowId = arrow.id; - - expect(arrow.startBinding).not.toBe(null); - expect(arrow.endBinding).not.toBe(null); - - act(() => { - h.app.actionManager.executeAction(actionSelectAll); - }); - - act(() => { - h.app.actionManager.executeAction(actionDuplicateSelection); - }); - - expect(h.elements.length).toEqual(6); - - const duplicatedArrow = h.scene.getSelectedElements( - h.state, - )[2] as ExcalidrawArrowElement; - - expect(duplicatedArrow.id).not.toBe(originalArrowId); - expect(duplicatedArrow.type).toBe("arrow"); - expect(duplicatedArrow.elbowed).toBe(true); - expect(duplicatedArrow.points).toEqual([ - [0, 0], - [45, 0], - [45, 200], - [90, 200], - ]); - expect(arrow.startBinding).not.toBe(null); - expect(arrow.endBinding).not.toBe(null); - }); - - it("changes arrow shape to unbind variant if only the connected elbow arrow is duplicated", async () => { - UI.createElement("rectangle", { - x: -150, - y: -150, - width: 100, - height: 100, - }); - UI.createElement("rectangle", { - x: 50, - y: 50, - width: 100, - height: 100, - }); - - UI.clickTool("arrow"); - UI.clickOnTestId("elbow-arrow"); - - mouse.reset(); - mouse.moveTo(-43, -99); - mouse.click(); - mouse.moveTo(43, 99); - mouse.click(); - - const arrow = h.scene.getSelectedElements( - h.state, - )[0] as ExcalidrawArrowElement; - const originalArrowId = arrow.id; - - expect(arrow.startBinding).not.toBe(null); - expect(arrow.endBinding).not.toBe(null); - - act(() => { - h.app.actionManager.executeAction(actionDuplicateSelection); - }); - - expect(h.elements.length).toEqual(4); - - const duplicatedArrow = h.scene.getSelectedElements( - h.state, - )[0] as ExcalidrawArrowElement; - - expect(duplicatedArrow.id).not.toBe(originalArrowId); - expect(duplicatedArrow.type).toBe("arrow"); - expect(duplicatedArrow.elbowed).toBe(true); - expect(duplicatedArrow.points).toEqual([ - [0, 0], - [0, 100], - [90, 100], - [90, 200], - ]); - }); }); diff --git a/packages/element/tests/resize.test.tsx b/packages/element/tests/resize.test.tsx index a0e244efb3..f2381223e3 100644 --- a/packages/element/tests/resize.test.tsx +++ b/packages/element/tests/resize.test.tsx @@ -15,6 +15,8 @@ import { unmountComponent, } from "@excalidraw/excalidraw/tests/test-utils"; +import { FIXED_BINDING_DISTANCE } from "@excalidraw/element/binding"; + import type { LocalPoint } from "@excalidraw/math"; import { isLinearElement } from "../src/typeChecks"; @@ -1004,14 +1006,14 @@ describe("multiple selection", () => { size: 100, }); const leftBoundArrow = UI.createElement("arrow", { - x: -110, + x: -100 - FIXED_BINDING_DISTANCE, y: 50, width: 100, height: 0, }); const rightBoundArrow = UI.createElement("arrow", { - x: 210, + x: 200 + FIXED_BINDING_DISTANCE, y: 50, width: -100, height: 0, @@ -1032,9 +1034,9 @@ describe("multiple selection", () => { shift: true, }); - expect(leftBoundArrow.x).toBeCloseTo(-110); + expect(leftBoundArrow.x).toBeCloseTo(-100 - FIXED_BINDING_DISTANCE); expect(leftBoundArrow.y).toBeCloseTo(50); - expect(leftBoundArrow.width).toBeCloseTo(146, 0); + expect(leftBoundArrow.width).toBeCloseTo(146 - FIXED_BINDING_DISTANCE, 0); expect(leftBoundArrow.height).toBeCloseTo(7, 0); expect(leftBoundArrow.angle).toEqual(0); expect(leftBoundArrow.startBinding).toBeNull(); @@ -1044,15 +1046,17 @@ describe("multiple selection", () => { ); expect(leftBoundArrow.endBinding?.focus).toBe(leftArrowBinding.focus); - expect(rightBoundArrow.x).toBeCloseTo(210); + expect(rightBoundArrow.x).toBeCloseTo(210 - FIXED_BINDING_DISTANCE); expect(rightBoundArrow.y).toBeCloseTo( (selectionHeight - 50) * (1 - scale) + 50, + 0, ); - expect(rightBoundArrow.width).toBeCloseTo(100 * scale); + //console.log(JSON.stringify(h.elements)); + expect(rightBoundArrow.width).toBeCloseTo(100 * scale + 1, 0); expect(rightBoundArrow.height).toBeCloseTo(0); expect(rightBoundArrow.angle).toEqual(0); expect(rightBoundArrow.startBinding).toBeNull(); - expect(rightBoundArrow.endBinding?.gap).toBeCloseTo(8.0952); + expect(rightBoundArrow.endBinding?.gap).toBeCloseTo(FIXED_BINDING_DISTANCE); expect(rightBoundArrow.endBinding?.elementId).toBe( rightArrowBinding.elementId, ); @@ -1339,8 +1343,8 @@ describe("multiple selection", () => { expect(boundArrow.x).toBeCloseTo(380 * scaleX); expect(boundArrow.y).toBeCloseTo(240 * scaleY); - expect(boundArrow.points[1][0]).toBeCloseTo(-60 * scaleX); - expect(boundArrow.points[1][1]).toBeCloseTo(-80 * scaleY); + expect(boundArrow.points[1][0]).toBeCloseTo(-60 * scaleX - 2, 0); + expect(boundArrow.points[1][1]).toBeCloseTo(-80 * scaleY + 2, 0); expect(arrowLabelPos.x + arrowLabel.width / 2).toBeCloseTo( boundArrow.x + boundArrow.points[1][0] / 2, diff --git a/packages/excalidraw/actions/actionFlip.test.tsx b/packages/excalidraw/actions/actionFlip.test.tsx index 23e4ffc123..c92a188bda 100644 --- a/packages/excalidraw/actions/actionFlip.test.tsx +++ b/packages/excalidraw/actions/actionFlip.test.tsx @@ -87,6 +87,16 @@ describe("flipping arrowheads", () => { await render(); }); + // UX RATIONALE: If we flip bound arrows by the center axes then there could + // be a case where the bindable objects are offset and the arrow would lay + // outside both bindable objects binding range, yet remain bound to then, + // resulting in a jump on movement. + // + // We are aware that 2+ point simple arrows behave incorrectly when flipped + // this way but it was decided that there is no known use case for this so + // left as it is. + // + // Demo: https://excalidraw.com/#json=isE-S8LqNlD1u-LsS8Ezz,iZZ09PPasp6OWbGtJwOUGQ it("flipping bound arrow should flip arrowheads only", () => { const rect = API.createElement({ type: "rectangle", @@ -123,6 +133,7 @@ describe("flipping arrowheads", () => { expect(API.getElement(arrow).endArrowhead).toBe("arrow"); }); + // UX RATIONALE: See above for the reasoning. it("flipping bound arrow should flip arrowheads only 2", () => { const rect = API.createElement({ type: "rectangle", @@ -164,7 +175,9 @@ describe("flipping arrowheads", () => { expect(API.getElement(arrow).endArrowhead).toBe("circle"); }); - it("flipping unbound arrow shouldn't flip arrowheads", () => { + // UX RATIONALE: Unbound arrows are not constrained by other elements and + // should behave like any other element when flipped for consisency. + it("flipping unbound arrow should mirror on horizontal or vertical axis", () => { const arrow = API.createElement({ type: "arrow", id: "arrow1", diff --git a/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap index 2e18882ef3..edc6b327ad 100644 --- a/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap @@ -198,7 +198,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": "99.58947", + "height": "99.23572", "id": "id172", "index": "a2", "isDeleted": false, @@ -212,8 +212,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl 0, ], [ - "99.58947", - "99.58947", + "96.42891", + "99.23572", ], ], "roughness": 1, @@ -228,8 +228,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "type": "arrow", "updated": 1, "version": 40, - "width": "99.58947", - "x": 0, + "width": "96.42891", + "x": "3.53553", "y": 0, } `; @@ -295,10 +295,10 @@ History { "deleted": { "endBinding": { "elementId": "id171", - "focus": "0.01099", + "focus": "0.01140", "gap": 5, }, - "height": "0.96335", + "height": "1.00000", "points": [ [ 0, @@ -306,22 +306,22 @@ History { ], [ "92.92893", - "-0.96335", + "-1.00000", ], ], "startBinding": { "elementId": "id170", - "focus": "0.03005", + "focus": "0.03119", "gap": 5, }, }, "inserted": { "endBinding": { "elementId": "id171", - "focus": "-0.02041", + "focus": "-0.02000", "gap": 5, }, - "height": "0.03665", + "height": 0, "points": [ [ 0, @@ -329,12 +329,12 @@ History { ], [ "92.92893", - "0.03665", + 0, ], ], "startBinding": { "elementId": "id170", - "focus": "0.01884", + "focus": "0.02000", "gap": 5, }, }, @@ -390,29 +390,27 @@ History { "focus": 0, "gap": 1, }, - "height": "99.58947", + "height": "99.23572", "points": [ [ 0, 0, ], [ - "99.58947", - "99.58947", + "96.42891", + "99.23572", ], ], "startBinding": null, - "width": "99.58947", - "x": 0, "y": 0, }, "inserted": { "endBinding": { "elementId": "id171", - "focus": "0.01099", + "focus": "0.01140", "gap": 5, }, - "height": "0.96335", + "height": "1.00000", "points": [ [ 0, @@ -420,17 +418,15 @@ History { ], [ "92.92893", - "-0.96335", + "-1.00000", ], ], "startBinding": { "elementId": "id170", - "focus": "0.03005", + "focus": "0.03119", "gap": 5, }, - "width": "92.92893", - "x": "3.53553", - "y": "0.96335", + "y": "1.00000", }, }, "id175" => Delta { @@ -570,7 +566,7 @@ History { 0, ], [ - "96.46447", + "92.92893", 0, ], ], @@ -584,8 +580,8 @@ History { "strokeStyle": "solid", "strokeWidth": 2, "type": "arrow", - "width": "96.46447", - "x": 0, + "width": "92.92893", + "x": "3.53553", "y": 0, }, "inserted": { @@ -808,7 +804,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl 0, ], [ - "96.46447", + "92.92893", 0, ], ], @@ -824,8 +820,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "type": "arrow", "updated": 1, "version": 30, - "width": "96.46447", - "x": 150, + "width": "0.00000", + "x": "146.46447", "y": 0, } `; @@ -925,13 +921,11 @@ History { 0, ], [ - "96.46447", + "92.92893", 0, ], ], "startBinding": null, - "width": "96.46447", - "x": 150, }, "inserted": { "endBinding": { @@ -954,8 +948,6 @@ History { "focus": 0, "gap": 5, }, - "width": "0.00000", - "x": "146.46447", }, }, }, @@ -1082,7 +1074,7 @@ History { 0, ], [ - "96.46447", + "92.92893", 0, ], ], @@ -1096,8 +1088,8 @@ History { "strokeStyle": "solid", "strokeWidth": 2, "type": "arrow", - "width": "96.46447", - "x": 0, + "width": "92.92893", + "x": "3.53553", "y": 0, }, "inserted": { @@ -2334,7 +2326,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "fillStyle": "solid", "frameId": null, "groupIds": [], - "height": "369.21589", + "height": "369.23631", "id": "id186", "index": "a2", "isDeleted": false, @@ -2348,8 +2340,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl 0, ], [ - "496.84035", - "-369.21589", + "496.83418", + "-369.23631", ], ], "roughness": 1, @@ -2368,9 +2360,9 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "type": "arrow", "updated": 1, "version": 10, - "width": "496.84035", - "x": "2.18463", - "y": "-38.80748", + "width": "496.83418", + "x": "2.19080", + "y": "-38.78706", } `; @@ -2507,7 +2499,7 @@ History { 0, ], [ - "96.46447", + "92.92893", 0, ], ], @@ -2525,8 +2517,8 @@ History { "strokeStyle": "solid", "strokeWidth": 2, "type": "arrow", - "width": "96.46447", - "x": 0, + "width": "92.92893", + "x": "3.53553", "y": 0, }, "inserted": { @@ -15250,7 +15242,7 @@ History { 0, ], [ - "96.46447", + "92.92893", 0, ], ], @@ -15263,7 +15255,7 @@ History { 0, ], [ - "96.46447", + "92.92893", 0, ], ], @@ -15558,7 +15550,7 @@ History { 0, ], [ - "96.46447", + "92.92893", 0, ], ], @@ -15576,8 +15568,8 @@ History { "strokeStyle": "solid", "strokeWidth": 2, "type": "arrow", - "width": "96.46447", - "x": 0, + "width": "92.92893", + "x": "3.53553", "y": 0, }, "inserted": { @@ -16178,7 +16170,7 @@ History { 0, ], [ - "96.46447", + "92.92893", 0, ], ], @@ -16196,8 +16188,8 @@ History { "strokeStyle": "solid", "strokeWidth": 2, "type": "arrow", - "width": "96.46447", - "x": 0, + "width": "92.92893", + "x": "3.53553", "y": 0, }, "inserted": { @@ -16798,7 +16790,7 @@ History { 0, ], [ - "96.46447", + "92.92893", 0, ], ], @@ -16816,8 +16808,8 @@ History { "strokeStyle": "solid", "strokeWidth": 2, "type": "arrow", - "width": "96.46447", - "x": 0, + "width": "92.92893", + "x": "3.53553", "y": 0, }, "inserted": { @@ -17201,7 +17193,7 @@ History { 0, ], [ - "96.46447", + "92.92893", 0, ], ], @@ -17218,7 +17210,7 @@ History { 0, ], [ - "96.46447", + "92.92893", 0, ], ], @@ -17486,7 +17478,7 @@ History { 0, ], [ - "96.46447", + "92.92893", 0, ], ], @@ -17504,8 +17496,8 @@ History { "strokeStyle": "solid", "strokeWidth": 2, "type": "arrow", - "width": "96.46447", - "x": 0, + "width": "92.92893", + "x": "3.53553", "y": 0, }, "inserted": { @@ -17929,7 +17921,7 @@ History { 0, ], [ - "96.46447", + "92.92893", 0, ], ], @@ -17947,7 +17939,7 @@ History { 0, ], [ - "96.46447", + "92.92893", 0, ], ], @@ -18215,7 +18207,7 @@ History { 0, ], [ - "96.46447", + "92.92893", 0, ], ], @@ -18233,8 +18225,8 @@ History { "strokeStyle": "solid", "strokeWidth": 2, "type": "arrow", - "width": "96.46447", - "x": 0, + "width": "92.92893", + "x": "3.53553", "y": 0, }, "inserted": { diff --git a/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap index b20fee5042..583fce4695 100644 --- a/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap @@ -101,3 +101,139 @@ exports[`move element > rectangle 5`] = ` "y": 40, } `; + +exports[`move element > rectangles with binding arrow 5`] = ` +{ + "angle": 0, + "backgroundColor": "transparent", + "boundElements": [ + { + "id": "id2", + "type": "arrow", + }, + ], + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "id": "id0", + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "seed": 1278240551, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "updated": 1, + "version": 4, + "versionNonce": 1723083209, + "width": 100, + "x": 0, + "y": 0, +} +`; + +exports[`move element > rectangles with binding arrow 6`] = ` +{ + "angle": 0, + "backgroundColor": "transparent", + "boundElements": [ + { + "id": "id2", + "type": "arrow", + }, + ], + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 300, + "id": "id1", + "index": "a1", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "seed": 1150084233, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "updated": 1, + "version": 7, + "versionNonce": 745419401, + "width": 300, + "x": 201, + "y": 2, +} +`; + +exports[`move element > rectangles with binding arrow 7`] = ` +{ + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "elbowed": false, + "endArrowhead": "arrow", + "endBinding": { + "elementId": "id1", + "focus": "-0.40764", + "gap": 5, + }, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": "82.18136", + "id": "id2", + "index": "a2", + "isDeleted": false, + "lastCommittedPoint": null, + "link": null, + "locked": false, + "opacity": 100, + "points": [ + [ + 0, + 0, + ], + [ + "93.92893", + "82.18136", + ], + ], + "roughness": 1, + "roundness": { + "type": 2, + }, + "seed": 1604849351, + "startArrowhead": null, + "startBinding": { + "elementId": "id0", + "focus": "-0.49801", + "gap": 5, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "arrow", + "updated": 1, + "version": 11, + "versionNonce": 1051383431, + "width": "93.92893", + "x": "103.53553", + "y": "50.01536", +} +`; diff --git a/packages/excalidraw/tests/move.test.tsx b/packages/excalidraw/tests/move.test.tsx index 77fc7e57db..d78a2ffb4f 100644 --- a/packages/excalidraw/tests/move.test.tsx +++ b/packages/excalidraw/tests/move.test.tsx @@ -109,8 +109,10 @@ describe("move element", () => { expect(h.state.selectedElementIds[rectB.id]).toBeTruthy(); expect([rectA.x, rectA.y]).toEqual([0, 0]); expect([rectB.x, rectB.y]).toEqual([200, 0]); - expect([arrow.x, arrow.y]).toEqual([110, 50]); - expect([arrow.width, arrow.height]).toEqual([80, 80]); + expect([Math.round(arrow.x), Math.round(arrow.y)]).toEqual([104, 50]); + expect([Math.round(arrow.width), Math.round(arrow.height)]).toEqual([ + 93, 81, + ]); renderInteractiveScene.mockClear(); renderStaticScene.mockClear(); @@ -128,8 +130,11 @@ describe("move element", () => { expect(h.state.selectedElementIds[rectB.id]).toBeTruthy(); expect([rectA.x, rectA.y]).toEqual([0, 0]); expect([rectB.x, rectB.y]).toEqual([201, 2]); - expect([[arrow.x, arrow.y]]).toCloselyEqualPoints([[107.07, 47.07]]); - expect([[arrow.width, arrow.height]]).toCloselyEqualPoints([[86.86, 87.3]]); + + expect([[arrow.x, arrow.y]]).toCloselyEqualPoints([[103.53, 50.01]]); + expect([[arrow.width, arrow.height]]).toCloselyEqualPoints([ + [93.9289, 82.1813], + ]); h.elements.forEach((element) => expect(element).toMatchSnapshot()); }); diff --git a/packages/excalidraw/tests/rotate.test.tsx b/packages/excalidraw/tests/rotate.test.tsx index fb0ff22e0c..f9aa843c97 100644 --- a/packages/excalidraw/tests/rotate.test.tsx +++ b/packages/excalidraw/tests/rotate.test.tsx @@ -3,6 +3,8 @@ import { expect } from "vitest"; import { reseed } from "@excalidraw/common"; +import "@excalidraw/utils/test-utils"; + import { Excalidraw } from "../index"; import { UI } from "./helpers/ui"; @@ -71,14 +73,16 @@ test("unselected bound arrows update when rotating their target elements", async expect(ellipseArrow.endBinding?.elementId).toEqual(ellipse.id); expect(ellipseArrow.x).toEqual(0); expect(ellipseArrow.y).toEqual(0); - expect(ellipseArrow.points[0]).toEqual([0, 0]); - expect(ellipseArrow.points[1][0]).toBeCloseTo(48.98, 1); - expect(ellipseArrow.points[1][1]).toBeCloseTo(125.79, 1); + + expect(ellipseArrow.points).toCloselyEqualPoints([ + [0, 0], + [90.1827, 98.5896], + ]); expect(textArrow.endBinding?.elementId).toEqual(text.id); expect(textArrow.x).toEqual(360); expect(textArrow.y).toEqual(300); expect(textArrow.points[0]).toEqual([0, 0]); - expect(textArrow.points[1][0]).toBeCloseTo(-94, 0); - expect(textArrow.points[1][1]).toBeCloseTo(-116.1, 0); + expect(textArrow.points[1][0]).toBeCloseTo(-95, 0); + expect(textArrow.points[1][1]).toBeCloseTo(-129.1, 0); });