Ver Fonte

fix: regression in indexing when adding elements to frame (#6904)

David Luzar há 1 ano atrás
pai
commit
de1ebad755
3 ficheiros alterados com 141 adições e 10 exclusões
  1. 128 0
      src/frame.test.tsx
  2. 2 10
      src/frame.ts
  3. 11 0
      src/tests/helpers/api.ts

+ 128 - 0
src/frame.test.tsx

@@ -0,0 +1,128 @@
+import {
+  convertToExcalidrawElements,
+  Excalidraw,
+} from "./packages/excalidraw/index";
+import { API } from "./tests/helpers/api";
+import { Pointer } from "./tests/helpers/ui";
+import { render } from "./tests/test-utils";
+
+const { h } = window;
+const mouse = new Pointer("mouse");
+
+describe("adding elements to frames", () => {
+  type ElementType = string;
+  const assertOrder = (
+    els: readonly { type: ElementType }[],
+    order: ElementType[],
+  ) => {
+    expect(els.map((el) => el.type)).toEqual(order);
+  };
+
+  const reorderElements = <T extends { type: ElementType }>(
+    els: readonly T[],
+    order: ElementType[],
+  ) => {
+    return order.reduce((acc: T[], el) => {
+      acc.push(els.find((e) => e.type === el)!);
+      return acc;
+    }, []);
+  };
+
+  describe("resizing frame over elements", () => {
+    const testElements = async (
+      containerType: "arrow" | "rectangle",
+      initialOrder: ElementType[],
+      expectedOrder: ElementType[],
+    ) => {
+      await render(<Excalidraw />);
+
+      const frame = API.createElement({ type: "frame", x: 0, y: 0 });
+
+      h.elements = reorderElements(
+        [
+          frame,
+          ...convertToExcalidrawElements([
+            {
+              type: containerType,
+              x: 100,
+              y: 100,
+              height: 10,
+              label: { text: "xx" },
+            },
+          ]),
+        ],
+        initialOrder,
+      );
+
+      assertOrder(h.elements, initialOrder);
+
+      expect(h.elements[1].frameId).toBe(null);
+      expect(h.elements[2].frameId).toBe(null);
+
+      const container = h.elements[1];
+
+      mouse.clickAt(0, 0);
+      mouse.downAt(frame.x + frame.width, frame.y + frame.height);
+      mouse.moveTo(
+        container.x + container.width + 100,
+        container.y + container.height + 100,
+      );
+      mouse.up();
+      assertOrder(h.elements, expectedOrder);
+
+      expect(h.elements[0].frameId).toBe(frame.id);
+      expect(h.elements[1].frameId).toBe(frame.id);
+    };
+
+    it("resizing over text containers / labelled arrows", async () => {
+      await testElements(
+        "rectangle",
+        ["frame", "rectangle", "text"],
+        ["rectangle", "text", "frame"],
+      );
+      await testElements(
+        "rectangle",
+        ["frame", "text", "rectangle"],
+        ["rectangle", "text", "frame"],
+      );
+      await testElements(
+        "rectangle",
+        ["rectangle", "text", "frame"],
+        ["rectangle", "text", "frame"],
+      );
+      await testElements(
+        "rectangle",
+        ["text", "rectangle", "frame"],
+        ["text", "rectangle", "frame"],
+      );
+
+      await testElements(
+        "arrow",
+        ["frame", "arrow", "text"],
+        ["arrow", "text", "frame"],
+      );
+      await testElements(
+        "arrow",
+        ["text", "arrow", "frame"],
+        ["text", "arrow", "frame"],
+      );
+
+      // FIXME failing in tests (it fails to add elements to frame for some
+      // reason) but works in browser. (╯°□°)╯︵ ┻━┻
+      //
+      // Looks like the `getElementsCompletelyInFrame()` doesn't work
+      // in these cases.
+      //
+      // await testElements(
+      //   "arrow",
+      //   ["arrow", "text", "frame"],
+      //   ["arrow", "text", "frame"],
+      // );
+      // await testElements(
+      //   "arrow",
+      //   ["frame", "text", "arrow"],
+      //   ["text", "arrow", "frame"],
+      // );
+    });
+  });
+});

+ 2 - 10
src/frame.ts

@@ -469,14 +469,6 @@ export const addElementsToFrame = (
   }
 
   let nextElements = allElements.slice();
-  // Optimisation since findIndex on "newElements" is slow
-  const nextElementsIndex = nextElements.reduce(
-    (acc: Record<string, number | undefined>, element, index) => {
-      acc[element.id] = index;
-      return acc;
-    },
-    {},
-  );
 
   const frameBoundary = findIndex(nextElements, (e) => e.frameId === frame.id);
   for (const element of omitGroupsContainingFrames(
@@ -492,8 +484,8 @@ export const addElementsToFrame = (
         false,
       );
 
-      const frameIndex = nextElementsIndex[frame.id] ?? -1;
-      const elementIndex = nextElementsIndex[element.id] ?? -1;
+      const frameIndex = findIndex(nextElements, (e) => e.id === frame.id);
+      const elementIndex = findIndex(nextElements, (e) => e.id === element.id);
 
       if (elementIndex < frameBoundary) {
         nextElements = [

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

@@ -17,6 +17,7 @@ import path from "path";
 import { getMimeType } from "../../data/blob";
 import {
   newEmbeddableElement,
+  newFrameElement,
   newFreeDrawElement,
   newImageElement,
 } from "../../element/newElement";
@@ -24,6 +25,7 @@ import { Point } from "../../types";
 import { getSelectedElements } from "../../scene/selection";
 import { isLinearElementType } from "../../element/typeChecks";
 import { Mutable } from "../../utility-types";
+import { assertNever } from "../../utils";
 
 const readFile = util.promisify(fs.readFile);
 
@@ -244,6 +246,15 @@ export class API {
           scale: rest.scale || [1, 1],
         });
         break;
+      case "frame":
+        element = newFrameElement({ ...base, width, height });
+        break;
+      default:
+        assertNever(
+          type,
+          `API.createElement: unimplemented element type ${type}}`,
+        );
+        break;
     }
     if (element.type === "arrow") {
       element.startBinding = rest.startBinding ?? null;