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);
});