mirror of
				https://github.com/excalidraw/excalidraw.git
				synced 2025-11-04 12:54:23 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			407 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			407 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import { API } from "@excalidraw/excalidraw/tests/helpers/api";
 | 
						|
 | 
						|
import { mutateElement } from "@excalidraw/element";
 | 
						|
 | 
						|
import { normalizeElementOrder } from "../src/sortElements";
 | 
						|
 | 
						|
import type { ExcalidrawElement } from "../src/types";
 | 
						|
 | 
						|
const { h } = window;
 | 
						|
const assertOrder = (
 | 
						|
  elements: readonly ExcalidrawElement[],
 | 
						|
  expectedOrder: string[],
 | 
						|
) => {
 | 
						|
  const actualOrder = elements.map((element) => element.id);
 | 
						|
  expect(actualOrder).toEqual(expectedOrder);
 | 
						|
};
 | 
						|
 | 
						|
describe("normalizeElementsOrder", () => {
 | 
						|
  it("sort bound-text elements", () => {
 | 
						|
    const container = API.createElement({
 | 
						|
      id: "container",
 | 
						|
      type: "rectangle",
 | 
						|
    });
 | 
						|
    const boundText = API.createElement({
 | 
						|
      id: "boundText",
 | 
						|
      type: "text",
 | 
						|
      containerId: container.id,
 | 
						|
    });
 | 
						|
    const otherElement = API.createElement({
 | 
						|
      id: "otherElement",
 | 
						|
      type: "rectangle",
 | 
						|
      boundElements: [],
 | 
						|
    });
 | 
						|
    const otherElement2 = API.createElement({
 | 
						|
      id: "otherElement2",
 | 
						|
      type: "rectangle",
 | 
						|
      boundElements: [],
 | 
						|
    });
 | 
						|
 | 
						|
    mutateElement(container, new Map(), {
 | 
						|
      boundElements: [{ type: "text", id: boundText.id }],
 | 
						|
    });
 | 
						|
 | 
						|
    assertOrder(normalizeElementOrder([container, boundText]), [
 | 
						|
      "container",
 | 
						|
      "boundText",
 | 
						|
    ]);
 | 
						|
    assertOrder(normalizeElementOrder([boundText, container]), [
 | 
						|
      "container",
 | 
						|
      "boundText",
 | 
						|
    ]);
 | 
						|
    assertOrder(
 | 
						|
      normalizeElementOrder([
 | 
						|
        boundText,
 | 
						|
        container,
 | 
						|
        otherElement,
 | 
						|
        otherElement2,
 | 
						|
      ]),
 | 
						|
      ["container", "boundText", "otherElement", "otherElement2"],
 | 
						|
    );
 | 
						|
    assertOrder(normalizeElementOrder([container, otherElement, boundText]), [
 | 
						|
      "container",
 | 
						|
      "boundText",
 | 
						|
      "otherElement",
 | 
						|
    ]);
 | 
						|
    assertOrder(
 | 
						|
      normalizeElementOrder([
 | 
						|
        container,
 | 
						|
        otherElement,
 | 
						|
        otherElement2,
 | 
						|
        boundText,
 | 
						|
      ]),
 | 
						|
      ["container", "boundText", "otherElement", "otherElement2"],
 | 
						|
    );
 | 
						|
 | 
						|
    assertOrder(
 | 
						|
      normalizeElementOrder([
 | 
						|
        boundText,
 | 
						|
        otherElement,
 | 
						|
        container,
 | 
						|
        otherElement2,
 | 
						|
      ]),
 | 
						|
      ["otherElement", "container", "boundText", "otherElement2"],
 | 
						|
    );
 | 
						|
 | 
						|
    // noop
 | 
						|
    assertOrder(
 | 
						|
      normalizeElementOrder([
 | 
						|
        otherElement,
 | 
						|
        container,
 | 
						|
        boundText,
 | 
						|
        otherElement2,
 | 
						|
      ]),
 | 
						|
      ["otherElement", "container", "boundText", "otherElement2"],
 | 
						|
    );
 | 
						|
 | 
						|
    // text has existing containerId, but container doesn't list is
 | 
						|
    // as a boundElement
 | 
						|
    assertOrder(
 | 
						|
      normalizeElementOrder([
 | 
						|
        API.createElement({
 | 
						|
          id: "boundText",
 | 
						|
          type: "text",
 | 
						|
          containerId: "container",
 | 
						|
        }),
 | 
						|
        API.createElement({
 | 
						|
          id: "container",
 | 
						|
          type: "rectangle",
 | 
						|
        }),
 | 
						|
      ]),
 | 
						|
      ["boundText", "container"],
 | 
						|
    );
 | 
						|
    assertOrder(
 | 
						|
      normalizeElementOrder([
 | 
						|
        API.createElement({
 | 
						|
          id: "boundText",
 | 
						|
          type: "text",
 | 
						|
          containerId: "container",
 | 
						|
        }),
 | 
						|
      ]),
 | 
						|
      ["boundText"],
 | 
						|
    );
 | 
						|
    assertOrder(
 | 
						|
      normalizeElementOrder([
 | 
						|
        API.createElement({
 | 
						|
          id: "container",
 | 
						|
          type: "rectangle",
 | 
						|
          boundElements: [],
 | 
						|
        }),
 | 
						|
      ]),
 | 
						|
      ["container"],
 | 
						|
    );
 | 
						|
    assertOrder(
 | 
						|
      normalizeElementOrder([
 | 
						|
        API.createElement({
 | 
						|
          id: "container",
 | 
						|
          type: "rectangle",
 | 
						|
          boundElements: [{ id: "x", type: "text" }],
 | 
						|
        }),
 | 
						|
      ]),
 | 
						|
      ["container"],
 | 
						|
    );
 | 
						|
    assertOrder(
 | 
						|
      normalizeElementOrder([
 | 
						|
        API.createElement({
 | 
						|
          id: "arrow",
 | 
						|
          type: "arrow",
 | 
						|
        }),
 | 
						|
        API.createElement({
 | 
						|
          id: "container",
 | 
						|
          type: "rectangle",
 | 
						|
          boundElements: [{ id: "arrow", type: "arrow" }],
 | 
						|
        }),
 | 
						|
      ]),
 | 
						|
      ["arrow", "container"],
 | 
						|
    );
 | 
						|
  });
 | 
						|
 | 
						|
  it("normalize group order", () => {
 | 
						|
    assertOrder(
 | 
						|
      normalizeElementOrder([
 | 
						|
        API.createElement({
 | 
						|
          id: "A_rect1",
 | 
						|
          type: "rectangle",
 | 
						|
          groupIds: ["A"],
 | 
						|
        }),
 | 
						|
        API.createElement({
 | 
						|
          id: "rect2",
 | 
						|
          type: "rectangle",
 | 
						|
        }),
 | 
						|
        API.createElement({
 | 
						|
          id: "rect3",
 | 
						|
          type: "rectangle",
 | 
						|
        }),
 | 
						|
        API.createElement({
 | 
						|
          id: "A_rect4",
 | 
						|
          type: "rectangle",
 | 
						|
          groupIds: ["A"],
 | 
						|
        }),
 | 
						|
        API.createElement({
 | 
						|
          id: "A_rect5",
 | 
						|
          type: "rectangle",
 | 
						|
          groupIds: ["A"],
 | 
						|
        }),
 | 
						|
        API.createElement({
 | 
						|
          id: "rect6",
 | 
						|
          type: "rectangle",
 | 
						|
        }),
 | 
						|
        API.createElement({
 | 
						|
          id: "A_rect7",
 | 
						|
          type: "rectangle",
 | 
						|
          groupIds: ["A"],
 | 
						|
        }),
 | 
						|
      ]),
 | 
						|
      ["A_rect1", "A_rect4", "A_rect5", "A_rect7", "rect2", "rect3", "rect6"],
 | 
						|
    );
 | 
						|
    assertOrder(
 | 
						|
      normalizeElementOrder([
 | 
						|
        API.createElement({
 | 
						|
          id: "A_rect1",
 | 
						|
          type: "rectangle",
 | 
						|
          groupIds: ["A"],
 | 
						|
        }),
 | 
						|
        API.createElement({
 | 
						|
          id: "rect2",
 | 
						|
          type: "rectangle",
 | 
						|
        }),
 | 
						|
        API.createElement({
 | 
						|
          id: "B_rect3",
 | 
						|
          type: "rectangle",
 | 
						|
          groupIds: ["B"],
 | 
						|
        }),
 | 
						|
        API.createElement({
 | 
						|
          id: "A_rect4",
 | 
						|
          type: "rectangle",
 | 
						|
          groupIds: ["A"],
 | 
						|
        }),
 | 
						|
        API.createElement({
 | 
						|
          id: "B_rect5",
 | 
						|
          type: "rectangle",
 | 
						|
          groupIds: ["B"],
 | 
						|
        }),
 | 
						|
        API.createElement({
 | 
						|
          id: "rect6",
 | 
						|
          type: "rectangle",
 | 
						|
        }),
 | 
						|
        API.createElement({
 | 
						|
          id: "A_rect7",
 | 
						|
          type: "rectangle",
 | 
						|
          groupIds: ["A"],
 | 
						|
        }),
 | 
						|
      ]),
 | 
						|
      ["A_rect1", "A_rect4", "A_rect7", "rect2", "B_rect3", "B_rect5", "rect6"],
 | 
						|
    );
 | 
						|
    // nested groups
 | 
						|
    assertOrder(
 | 
						|
      normalizeElementOrder([
 | 
						|
        API.createElement({
 | 
						|
          id: "A_rect1",
 | 
						|
          type: "rectangle",
 | 
						|
          groupIds: ["A"],
 | 
						|
        }),
 | 
						|
        API.createElement({
 | 
						|
          id: "BA_rect2",
 | 
						|
          type: "rectangle",
 | 
						|
          groupIds: ["B", "A"],
 | 
						|
        }),
 | 
						|
      ]),
 | 
						|
      ["A_rect1", "BA_rect2"],
 | 
						|
    );
 | 
						|
    assertOrder(
 | 
						|
      normalizeElementOrder([
 | 
						|
        API.createElement({
 | 
						|
          id: "BA_rect1",
 | 
						|
          type: "rectangle",
 | 
						|
          groupIds: ["B", "A"],
 | 
						|
        }),
 | 
						|
        API.createElement({
 | 
						|
          id: "A_rect2",
 | 
						|
          type: "rectangle",
 | 
						|
          groupIds: ["A"],
 | 
						|
        }),
 | 
						|
      ]),
 | 
						|
      ["BA_rect1", "A_rect2"],
 | 
						|
    );
 | 
						|
    assertOrder(
 | 
						|
      normalizeElementOrder([
 | 
						|
        API.createElement({
 | 
						|
          id: "BA_rect1",
 | 
						|
          type: "rectangle",
 | 
						|
          groupIds: ["B", "A"],
 | 
						|
        }),
 | 
						|
        API.createElement({
 | 
						|
          id: "A_rect2",
 | 
						|
          type: "rectangle",
 | 
						|
          groupIds: ["A"],
 | 
						|
        }),
 | 
						|
        API.createElement({
 | 
						|
          id: "CBA_rect3",
 | 
						|
          type: "rectangle",
 | 
						|
          groupIds: ["C", "B", "A"],
 | 
						|
        }),
 | 
						|
        API.createElement({
 | 
						|
          id: "rect4",
 | 
						|
          type: "rectangle",
 | 
						|
        }),
 | 
						|
        API.createElement({
 | 
						|
          id: "A_rect5",
 | 
						|
          type: "rectangle",
 | 
						|
          groupIds: ["A"],
 | 
						|
        }),
 | 
						|
        API.createElement({
 | 
						|
          id: "BA_rect5",
 | 
						|
          type: "rectangle",
 | 
						|
          groupIds: ["B", "A"],
 | 
						|
        }),
 | 
						|
        API.createElement({
 | 
						|
          id: "BA_rect6",
 | 
						|
          type: "rectangle",
 | 
						|
          groupIds: ["B", "A"],
 | 
						|
        }),
 | 
						|
        API.createElement({
 | 
						|
          id: "CBA_rect7",
 | 
						|
          type: "rectangle",
 | 
						|
          groupIds: ["C", "B", "A"],
 | 
						|
        }),
 | 
						|
        API.createElement({
 | 
						|
          id: "X_rect8",
 | 
						|
          type: "rectangle",
 | 
						|
          groupIds: ["X"],
 | 
						|
        }),
 | 
						|
        API.createElement({
 | 
						|
          id: "rect9",
 | 
						|
          type: "rectangle",
 | 
						|
        }),
 | 
						|
        API.createElement({
 | 
						|
          id: "YX_rect10",
 | 
						|
          type: "rectangle",
 | 
						|
          groupIds: ["Y", "X"],
 | 
						|
        }),
 | 
						|
        API.createElement({
 | 
						|
          id: "X_rect11",
 | 
						|
          type: "rectangle",
 | 
						|
          groupIds: ["X"],
 | 
						|
        }),
 | 
						|
      ]),
 | 
						|
      [
 | 
						|
        "BA_rect1",
 | 
						|
        "BA_rect5",
 | 
						|
        "BA_rect6",
 | 
						|
        "A_rect2",
 | 
						|
        "A_rect5",
 | 
						|
        "CBA_rect3",
 | 
						|
        "CBA_rect7",
 | 
						|
        "rect4",
 | 
						|
        "X_rect8",
 | 
						|
        "X_rect11",
 | 
						|
        "YX_rect10",
 | 
						|
        "rect9",
 | 
						|
      ],
 | 
						|
    );
 | 
						|
  });
 | 
						|
 | 
						|
  // TODO
 | 
						|
  it.skip("normalize boundElements array", () => {
 | 
						|
    const container = API.createElement({
 | 
						|
      id: "container",
 | 
						|
      type: "rectangle",
 | 
						|
      boundElements: [],
 | 
						|
    });
 | 
						|
    const boundText = API.createElement({
 | 
						|
      id: "boundText",
 | 
						|
      type: "text",
 | 
						|
      containerId: container.id,
 | 
						|
    });
 | 
						|
 | 
						|
    h.app.scene.mutateElement(container, {
 | 
						|
      boundElements: [
 | 
						|
        { type: "text", id: boundText.id },
 | 
						|
        { type: "text", id: "xxx" },
 | 
						|
      ],
 | 
						|
    });
 | 
						|
 | 
						|
    expect(normalizeElementOrder([container, boundText])).toEqual([
 | 
						|
      expect.objectContaining({
 | 
						|
        id: container.id,
 | 
						|
      }),
 | 
						|
      expect.objectContaining({ id: boundText.id }),
 | 
						|
    ]);
 | 
						|
  });
 | 
						|
 | 
						|
  // should take around <100ms for 10K iterations (@dwelle's PC 22-05-25)
 | 
						|
  it.skip("normalizeElementsOrder() perf", () => {
 | 
						|
    const makeElements = (iterations: number) => {
 | 
						|
      const elements: ExcalidrawElement[] = [];
 | 
						|
      while (iterations--) {
 | 
						|
        const container = API.createElement({
 | 
						|
          type: "rectangle",
 | 
						|
          boundElements: [],
 | 
						|
          groupIds: ["B", "A"],
 | 
						|
        });
 | 
						|
        const boundText = API.createElement({
 | 
						|
          type: "text",
 | 
						|
          containerId: container.id,
 | 
						|
          groupIds: ["A"],
 | 
						|
        });
 | 
						|
        const otherElement = API.createElement({
 | 
						|
          type: "rectangle",
 | 
						|
          boundElements: [],
 | 
						|
          groupIds: ["C", "A"],
 | 
						|
        });
 | 
						|
        h.app.scene.mutateElement(container, {
 | 
						|
          boundElements: [{ type: "text", id: boundText.id }],
 | 
						|
        });
 | 
						|
 | 
						|
        elements.push(boundText, otherElement, container);
 | 
						|
      }
 | 
						|
      return elements;
 | 
						|
    };
 | 
						|
 | 
						|
    const elements = makeElements(10000);
 | 
						|
    const t0 = Date.now();
 | 
						|
    normalizeElementOrder(elements);
 | 
						|
    console.info(`${Date.now() - t0}ms`);
 | 
						|
  });
 | 
						|
});
 |