Browse Source

fix: containerizing text incorrectly updates arrow bindings (#6369)

Co-authored-by: dwelle <luzar.david@gmail.com>
Samyat Gautam 2 years ago
parent
commit
0726911fa6
3 changed files with 122 additions and 7 deletions
  1. 15 7
      src/actions/actionBoundText.tsx
  2. 100 0
      src/tests/binding.test.tsx
  3. 7 0
      src/tests/helpers/api.ts

+ 15 - 7
src/actions/actionBoundText.tsx

@@ -239,15 +239,23 @@ export const actionCreateContainerFromText = register({
           linearElementIds.includes(ele.id),
         ) as ExcalidrawLinearElement[];
         linearElements.forEach((ele) => {
-          let startBinding = null;
-          let endBinding = null;
-          if (ele.startBinding) {
-            startBinding = { ...ele.startBinding, elementId: container.id };
+          let startBinding = ele.startBinding;
+          let endBinding = ele.endBinding;
+
+          if (startBinding?.elementId === textElement.id) {
+            startBinding = {
+              ...startBinding,
+              elementId: container.id,
+            };
+          }
+
+          if (endBinding?.elementId === textElement.id) {
+            endBinding = { ...endBinding, elementId: container.id };
           }
-          if (ele.endBinding) {
-            endBinding = { ...ele.endBinding, elementId: container.id };
+
+          if (startBinding || endBinding) {
+            mutateElement(ele, { startBinding, endBinding });
           }
-          mutateElement(ele, { startBinding, endBinding });
         });
       }
 

+ 100 - 0
src/tests/binding.test.tsx

@@ -4,6 +4,7 @@ import { UI, Pointer, Keyboard } from "./helpers/ui";
 import { getTransformHandles } from "../element/transformHandles";
 import { API } from "./helpers/api";
 import { KEYS } from "../keys";
+import { actionCreateContainerFromText } from "../actions/actionBoundText";
 
 const { h } = window;
 
@@ -209,4 +210,103 @@ describe("element binding", () => {
     ).toBe(null);
     expect(arrow.endBinding?.elementId).toBe(text.id);
   });
+
+  it("should update binding when text containerized", async () => {
+    const rectangle1 = API.createElement({
+      type: "rectangle",
+      id: "rectangle1",
+      width: 100,
+      height: 100,
+      boundElements: [
+        { id: "arrow1", type: "arrow" },
+        { id: "arrow2", type: "arrow" },
+      ],
+    });
+
+    const arrow1 = API.createElement({
+      type: "arrow",
+      id: "arrow1",
+      points: [
+        [0, 0],
+        [0, -87.45777932247563],
+      ],
+      startBinding: {
+        elementId: "rectangle1",
+        focus: 0.2,
+        gap: 7,
+      },
+      endBinding: {
+        elementId: "text1",
+        focus: 0.2,
+        gap: 7,
+      },
+    });
+
+    const arrow2 = API.createElement({
+      type: "arrow",
+      id: "arrow2",
+      points: [
+        [0, 0],
+        [0, -87.45777932247563],
+      ],
+      startBinding: {
+        elementId: "text1",
+        focus: 0.2,
+        gap: 7,
+      },
+      endBinding: {
+        elementId: "rectangle1",
+        focus: 0.2,
+        gap: 7,
+      },
+    });
+
+    const text1 = API.createElement({
+      type: "text",
+      id: "text1",
+      text: "ola",
+      boundElements: [
+        { id: "arrow1", type: "arrow" },
+        { id: "arrow2", type: "arrow" },
+      ],
+    });
+
+    h.elements = [rectangle1, arrow1, arrow2, text1];
+
+    API.setSelectedElements([text1]);
+
+    expect(h.state.selectedElementIds[text1.id]).toBe(true);
+
+    h.app.actionManager.executeAction(actionCreateContainerFromText);
+
+    // new text container will be placed before the text element
+    const container = h.elements.at(-2)!;
+
+    expect(container.type).toBe("rectangle");
+    expect(container.id).not.toBe(rectangle1.id);
+
+    expect(container).toEqual(
+      expect.objectContaining({
+        boundElements: expect.arrayContaining([
+          {
+            type: "text",
+            id: text1.id,
+          },
+          {
+            type: "arrow",
+            id: arrow1.id,
+          },
+          {
+            type: "arrow",
+            id: arrow2.id,
+          },
+        ]),
+      }),
+    );
+
+    expect(arrow1.startBinding?.elementId).toBe(rectangle1.id);
+    expect(arrow1.endBinding?.elementId).toBe(container.id);
+    expect(arrow2.startBinding?.elementId).toBe(container.id);
+    expect(arrow2.endBinding?.elementId).toBe(rectangle1.id);
+  });
 });

+ 7 - 0
src/tests/helpers/api.ts

@@ -111,6 +111,9 @@ export class API {
     fileId?: T extends "image" ? string : never;
     scale?: T extends "image" ? ExcalidrawImageElement["scale"] : never;
     status?: T extends "image" ? ExcalidrawImageElement["status"] : never;
+    startBinding?: T extends "arrow"
+      ? ExcalidrawLinearElement["startBinding"]
+      : never;
     endBinding?: T extends "arrow"
       ? ExcalidrawLinearElement["endBinding"]
       : never;
@@ -221,6 +224,10 @@ export class API {
         });
         break;
     }
+    if (element.type === "arrow") {
+      element.startBinding = rest.startBinding ?? null;
+      element.endBinding = rest.endBinding ?? null;
+    }
     if (id) {
       element.id = id;
     }