aboutsummaryrefslogtreecommitdiffstats
path: root/packages/excalidraw/element/sortElements.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/excalidraw/element/sortElements.test.ts')
-rw-r--r--packages/excalidraw/element/sortElements.test.ts402
1 files changed, 402 insertions, 0 deletions
diff --git a/packages/excalidraw/element/sortElements.test.ts b/packages/excalidraw/element/sortElements.test.ts
new file mode 100644
index 0000000..a7b78e8
--- /dev/null
+++ b/packages/excalidraw/element/sortElements.test.ts
@@ -0,0 +1,402 @@
+import { API } from "../tests/helpers/api";
+import { mutateElement } from "./mutateElement";
+import { normalizeElementOrder } from "./sortElements";
+import type { ExcalidrawElement } from "./types";
+
+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, {
+ 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,
+ });
+
+ 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"],
+ });
+ 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`);
+ });
+});