mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
feat: updates to test
This commit is contained in:
parent
60ff21041c
commit
4c3516a5b9
3 changed files with 144 additions and 56 deletions
|
@ -1131,7 +1131,9 @@ export class ElementsChange implements Change<SceneElementsMap> {
|
|||
);
|
||||
|
||||
// Need ordered nextElements to avoid z-index binding issues
|
||||
ElementsChange.redrawBoundArrows(nextElements, changedElements);
|
||||
ElementsChange.redrawBoundArrows(nextElements, changedElements, {
|
||||
preservePoints: true,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`Couldn't mutate elements after applying elements change`,
|
||||
|
@ -1266,34 +1268,19 @@ export class ElementsChange implements Change<SceneElementsMap> {
|
|||
(directlyApplicablePartial as any).points &&
|
||||
!directlyApplicablePartial.isDeleted
|
||||
) {
|
||||
// Only update points if there's a binding change that would affect the arrow shape
|
||||
const oldStart = element.startBinding;
|
||||
const oldEnd = element.endBinding;
|
||||
const newStart = (directlyApplicablePartial as any).startBinding;
|
||||
const newEnd = (directlyApplicablePartial as any).endBinding;
|
||||
|
||||
const startBindingChanged =
|
||||
(directlyApplicablePartial as any).startBinding !== undefined &&
|
||||
JSON.stringify((directlyApplicablePartial as any).startBinding) !==
|
||||
JSON.stringify(element.startBinding);
|
||||
|
||||
JSON.stringify(oldStart || null) !== JSON.stringify(newStart || null);
|
||||
const endBindingChanged =
|
||||
(directlyApplicablePartial as any).endBinding !== undefined &&
|
||||
JSON.stringify((directlyApplicablePartial as any).endBinding) !==
|
||||
JSON.stringify(element.endBinding);
|
||||
JSON.stringify(oldEnd || null) !== JSON.stringify(newEnd || null);
|
||||
|
||||
// If binding relationship changed significantly, we need to update points
|
||||
if (startBindingChanged || endBindingChanged) {
|
||||
// Let the points be updated by the delta
|
||||
return newElementWith(
|
||||
element,
|
||||
directlyApplicablePartial as ElementUpdate<typeof element>,
|
||||
);
|
||||
if (!startBindingChanged && !endBindingChanged) {
|
||||
delete (directlyApplicablePartial as any).points;
|
||||
}
|
||||
|
||||
// Otherwise preserve the original points
|
||||
const partialWithoutPoints = { ...directlyApplicablePartial };
|
||||
delete (partialWithoutPoints as any).points;
|
||||
|
||||
return newElementWith(
|
||||
element,
|
||||
partialWithoutPoints as ElementUpdate<typeof element>,
|
||||
);
|
||||
}
|
||||
|
||||
return newElementWith(element, directlyApplicablePartial);
|
||||
|
@ -1525,14 +1512,13 @@ export class ElementsChange implements Change<SceneElementsMap> {
|
|||
private static redrawBoundArrows(
|
||||
elements: SceneElementsMap,
|
||||
changed: Map<string, OrderedExcalidrawElement>,
|
||||
options?: { preservePoints?: boolean },
|
||||
) {
|
||||
for (const element of changed.values()) {
|
||||
if (!element.isDeleted && isBindableElement(element)) {
|
||||
// Only preserve points during undo/redo when the binding relationship hasn't changed significantly
|
||||
// This helps maintain arrow shape while allowing necessary updates when bindings change
|
||||
updateBoundElements(element, elements, {
|
||||
changedElements: changed,
|
||||
preservePoints: true, // Preserve arrow points during undo/redo
|
||||
preservePoints: options?.preservePoints === true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -739,13 +739,13 @@ export const updateBoundElements = (
|
|||
simultaneouslyUpdated?: readonly ExcalidrawElement[];
|
||||
newSize?: { width: number; height: number };
|
||||
changedElements?: Map<string, OrderedExcalidrawElement>;
|
||||
preservePoints?: boolean; // Add this option to preserve arrow points during undo/redo
|
||||
preservePoints?: boolean; // [FIX] added
|
||||
},
|
||||
) => {
|
||||
const {
|
||||
newSize,
|
||||
simultaneouslyUpdated,
|
||||
preservePoints = false,
|
||||
preservePoints = false, // [FIX] default false
|
||||
} = options ?? {};
|
||||
const simultaneouslyUpdatedElementIds = getSimultaneouslyUpdatedElementIds(
|
||||
simultaneouslyUpdated,
|
||||
|
@ -760,12 +760,10 @@ export const updateBoundElements = (
|
|||
return;
|
||||
}
|
||||
|
||||
// In case the boundElements are stale
|
||||
if (!doesNeedUpdate(element, changedElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for intersections before updating bound elements incase connected elements overlap
|
||||
const startBindingElement = element.startBinding
|
||||
? elementsMap.get(element.startBinding.elementId)
|
||||
: null;
|
||||
|
@ -793,26 +791,14 @@ export const updateBoundElements = (
|
|||
),
|
||||
};
|
||||
|
||||
// `linearElement` is being moved/scaled already, just update the binding
|
||||
if (simultaneouslyUpdatedElementIds.has(element.id)) {
|
||||
mutateElement(element, bindings, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// If preservePoints is true, only update the bindings without changing the points
|
||||
// This is specifically for undo/redo operations to maintain arrow shape
|
||||
// [FIX] If preservePoints is true, skip adjusting arrow geometry.
|
||||
if (preservePoints && isArrowElement(element)) {
|
||||
// Only preserve points if the binding relationship hasn't changed
|
||||
const startBindingChanged =
|
||||
changedElement.id === element.startBinding?.elementId &&
|
||||
bindings.startBinding !== element.startBinding;
|
||||
|
||||
const endBindingChanged =
|
||||
changedElement.id === element.endBinding?.elementId &&
|
||||
bindings.endBinding !== element.endBinding;
|
||||
|
||||
// If binding relationship changed, we need to update points
|
||||
if (!startBindingChanged && !endBindingChanged) {
|
||||
// Only update the binding fields
|
||||
mutateElement(element, {
|
||||
...(changedElement.id === element.startBinding?.elementId
|
||||
? { startBinding: bindings.startBinding }
|
||||
|
@ -823,7 +809,6 @@ export const updateBoundElements = (
|
|||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const updates = bindableElementsVisitor(
|
||||
elementsMap,
|
||||
|
|
117
packages/excalidraw/tests/arrow-binding-movement.test.tsx
Normal file
117
packages/excalidraw/tests/arrow-binding-movement.test.tsx
Normal file
|
@ -0,0 +1,117 @@
|
|||
import React from "react";
|
||||
import { pointFrom } from "@excalidraw/math";
|
||||
|
||||
import { Excalidraw } from "../index";
|
||||
import { KEYS } from "../keys";
|
||||
|
||||
import { UI } from "./helpers/ui";
|
||||
import { Keyboard, Pointer } from "./helpers/ui";
|
||||
import { render } from "./test-utils";
|
||||
|
||||
import type { ExcalidrawElement } from "../element/types";
|
||||
|
||||
const mouse = new Pointer("mouse");
|
||||
|
||||
describe("arrow binding movement", () => {
|
||||
beforeEach(async () => {
|
||||
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
||||
});
|
||||
|
||||
it("should maintain arrow position when bound shape is moved", () => {
|
||||
// Create a rectangle
|
||||
const rect = UI.createElement("rectangle", {
|
||||
x: 100,
|
||||
y: 100,
|
||||
width: 100,
|
||||
height: 100,
|
||||
});
|
||||
|
||||
// Create an arrow starting from rectangle border
|
||||
const arrow = UI.createElement("arrow", {
|
||||
x: 150,
|
||||
y: 100,
|
||||
width: 100,
|
||||
height: 50,
|
||||
points: [pointFrom(0, 0), pointFrom(100, 50)],
|
||||
});
|
||||
|
||||
// Store original arrow position relative to rectangle
|
||||
const originalArrowStart = [...arrow.points[0]];
|
||||
const originalArrowEnd = [...arrow.points[1]];
|
||||
const originalRelativeEndX = originalArrowEnd[0] - (rect.x - arrow.x);
|
||||
const originalRelativeEndY = originalArrowEnd[1] - (rect.y - arrow.y);
|
||||
|
||||
// Move the rectangle
|
||||
UI.clickTool("selection");
|
||||
mouse.clickOn(rect);
|
||||
mouse.downAt(rect.x + 50, rect.y + 50);
|
||||
mouse.moveTo(rect.x + 100, rect.y + 50); // Move 50px to the right
|
||||
mouse.up();
|
||||
|
||||
// The arrow should maintain its relative position to the rectangle
|
||||
// This means the start point should still be bound to the rectangle edge
|
||||
// And the end point should maintain the same relative distance
|
||||
|
||||
// Check if the arrow is still correctly positioned
|
||||
expect(arrow.points[0]).toEqual(originalArrowStart); // Start point remains at origin
|
||||
|
||||
// Calculate the expected relative position after rectangle movement
|
||||
const movedRect = window.h.elements.find(
|
||||
(el: ExcalidrawElement) => el.id === rect.id,
|
||||
)!;
|
||||
const expectedRelativeEndX = originalRelativeEndX + (movedRect.x - rect.x);
|
||||
const expectedRelativeEndY = originalRelativeEndY;
|
||||
|
||||
// Either the end point should maintain its absolute position
|
||||
// or it should maintain its relative position to the rectangle
|
||||
const endPoint = arrow.points[1];
|
||||
const endPointMaintainsRelativePosition =
|
||||
Math.abs(endPoint[0] - expectedRelativeEndX) < 1 &&
|
||||
Math.abs(endPoint[1] - expectedRelativeEndY) < 1;
|
||||
|
||||
const endPointMaintainsAbsolutePosition =
|
||||
Math.abs(endPoint[0] - originalArrowEnd[0]) < 1 &&
|
||||
Math.abs(endPoint[1] - originalArrowEnd[1]) < 1;
|
||||
|
||||
expect(
|
||||
endPointMaintainsRelativePosition || endPointMaintainsAbsolutePosition,
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("should restore arrow shape after undo", () => {
|
||||
// Create a rectangle
|
||||
const rect = UI.createElement("rectangle", {
|
||||
x: 100,
|
||||
y: 100,
|
||||
width: 100,
|
||||
height: 100,
|
||||
});
|
||||
|
||||
// Create an arrow starting from rectangle border
|
||||
const arrow = UI.createElement("arrow", {
|
||||
x: 150,
|
||||
y: 100,
|
||||
width: 100,
|
||||
height: 50,
|
||||
points: [pointFrom(0, 0), pointFrom(100, 50)],
|
||||
});
|
||||
|
||||
// Store original arrow points
|
||||
const originalPoints = [...arrow.points];
|
||||
|
||||
// Move the rectangle
|
||||
UI.clickTool("selection");
|
||||
mouse.clickOn(rect);
|
||||
mouse.downAt(rect.x + 50, rect.y + 50);
|
||||
mouse.moveTo(rect.x + 100, rect.y + 50); // Move 50px to the right
|
||||
mouse.up();
|
||||
|
||||
// Perform undo
|
||||
Keyboard.withModifierKeys({ ctrl: true }, () => {
|
||||
Keyboard.keyPress(KEYS.Z);
|
||||
});
|
||||
|
||||
// Verify arrow points are exactly the same after undo
|
||||
expect(arrow.points).toEqual(originalPoints);
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue