From 6ec259a0e71174651bae95d4628138bf6fd68742 Mon Sep 17 00:00:00 2001
From: kj_sh604
Date: Sun, 15 Mar 2026 16:19:35 -0400
Subject: refactor: packages/

---
 packages/excalidraw/tests/align.test.tsx | 579 +++++++++++++++++++++++++++++++
 1 file changed, 579 insertions(+)
 create mode 100644 packages/excalidraw/tests/align.test.tsx

(limited to 'packages/excalidraw/tests/align.test.tsx')

diff --git a/packages/excalidraw/tests/align.test.tsx b/packages/excalidraw/tests/align.test.tsx
new file mode 100644
index 0000000..47624e8
--- /dev/null
+++ b/packages/excalidraw/tests/align.test.tsx
@@ -0,0 +1,579 @@
+import React from "react";
+import { act, unmountComponent, render } from "./test-utils";
+import { Excalidraw } from "../index";
+import { defaultLang, setLanguage } from "../i18n";
+import { UI, Pointer, Keyboard } from "./helpers/ui";
+import { API } from "./helpers/api";
+import { KEYS } from "../keys";
+import {
+  actionAlignVerticallyCentered,
+  actionAlignHorizontallyCentered,
+  actionGroup,
+  actionAlignTop,
+  actionAlignBottom,
+  actionAlignLeft,
+  actionAlignRight,
+} from "../actions";
+
+const mouse = new Pointer("mouse");
+
+const createAndSelectTwoRectangles = () => {
+  UI.clickTool("rectangle");
+  mouse.down();
+  mouse.up(100, 100);
+
+  UI.clickTool("rectangle");
+  mouse.down(10, 10);
+  mouse.up(100, 100);
+
+  // Select the first element.
+  // The second rectangle is already reselected because it was the last element created
+  mouse.reset();
+  Keyboard.withModifierKeys({ shift: true }, () => {
+    mouse.click();
+  });
+};
+
+const createAndSelectTwoRectanglesWithDifferentSizes = () => {
+  UI.clickTool("rectangle");
+  mouse.down();
+  mouse.up(100, 100);
+
+  UI.clickTool("rectangle");
+  mouse.down(10, 10);
+  mouse.up(110, 110);
+
+  // Select the first element.
+  // The second rectangle is already reselected because it was the last element created
+  mouse.reset();
+  Keyboard.withModifierKeys({ shift: true }, () => {
+    mouse.click();
+  });
+};
+
+describe("aligning", () => {
+  beforeEach(async () => {
+    unmountComponent();
+    mouse.reset();
+
+    await act(() => {
+      return setLanguage(defaultLang);
+    });
+    await render(<Excalidraw handleKeyboardGlobally={true} />);
+  });
+
+  it("aligns two objects correctly to the top", () => {
+    createAndSelectTwoRectangles();
+
+    expect(API.getSelectedElements()[0].x).toEqual(0);
+    expect(API.getSelectedElements()[1].x).toEqual(110);
+
+    expect(API.getSelectedElements()[0].y).toEqual(0);
+    expect(API.getSelectedElements()[1].y).toEqual(110);
+
+    Keyboard.withModifierKeys({ ctrl: true, shift: true }, () => {
+      Keyboard.keyPress(KEYS.ARROW_UP);
+    });
+
+    // Check if x position did not change
+    expect(API.getSelectedElements()[0].x).toEqual(0);
+    expect(API.getSelectedElements()[1].x).toEqual(110);
+
+    expect(API.getSelectedElements()[0].y).toEqual(0);
+    expect(API.getSelectedElements()[1].y).toEqual(0);
+  });
+
+  it("aligns two objects correctly to the bottom", () => {
+    createAndSelectTwoRectangles();
+
+    expect(API.getSelectedElements()[0].x).toEqual(0);
+    expect(API.getSelectedElements()[1].x).toEqual(110);
+
+    expect(API.getSelectedElements()[0].y).toEqual(0);
+    expect(API.getSelectedElements()[1].y).toEqual(110);
+
+    Keyboard.withModifierKeys({ ctrl: true, shift: true }, () => {
+      Keyboard.keyPress(KEYS.ARROW_DOWN);
+    });
+
+    // Check if x position did not change
+    expect(API.getSelectedElements()[0].x).toEqual(0);
+    expect(API.getSelectedElements()[1].x).toEqual(110);
+
+    expect(API.getSelectedElements()[0].y).toEqual(110);
+    expect(API.getSelectedElements()[1].y).toEqual(110);
+  });
+
+  it("aligns two objects correctly to the left", () => {
+    createAndSelectTwoRectangles();
+
+    expect(API.getSelectedElements()[0].x).toEqual(0);
+    expect(API.getSelectedElements()[1].x).toEqual(110);
+
+    expect(API.getSelectedElements()[0].y).toEqual(0);
+    expect(API.getSelectedElements()[1].y).toEqual(110);
+
+    Keyboard.withModifierKeys({ ctrl: true, shift: true }, () => {
+      Keyboard.keyPress(KEYS.ARROW_LEFT);
+    });
+
+    expect(API.getSelectedElements()[0].x).toEqual(0);
+    expect(API.getSelectedElements()[1].x).toEqual(0);
+
+    // Check if y position did not change
+    expect(API.getSelectedElements()[0].y).toEqual(0);
+    expect(API.getSelectedElements()[1].y).toEqual(110);
+  });
+
+  it("aligns two objects correctly to the right", () => {
+    createAndSelectTwoRectangles();
+
+    expect(API.getSelectedElements()[0].x).toEqual(0);
+    expect(API.getSelectedElements()[1].x).toEqual(110);
+
+    expect(API.getSelectedElements()[0].y).toEqual(0);
+    expect(API.getSelectedElements()[1].y).toEqual(110);
+
+    Keyboard.withModifierKeys({ ctrl: true, shift: true }, () => {
+      Keyboard.keyPress(KEYS.ARROW_RIGHT);
+    });
+
+    expect(API.getSelectedElements()[0].x).toEqual(110);
+    expect(API.getSelectedElements()[1].x).toEqual(110);
+
+    // Check if y position did not change
+    expect(API.getSelectedElements()[0].y).toEqual(0);
+    expect(API.getSelectedElements()[1].y).toEqual(110);
+  });
+
+  it("centers two objects with different sizes correctly vertically", () => {
+    createAndSelectTwoRectanglesWithDifferentSizes();
+
+    expect(API.getSelectedElements()[0].x).toEqual(0);
+    expect(API.getSelectedElements()[1].x).toEqual(110);
+
+    expect(API.getSelectedElements()[0].y).toEqual(0);
+    expect(API.getSelectedElements()[1].y).toEqual(110);
+
+    API.executeAction(actionAlignVerticallyCentered);
+
+    // Check if x position did not change
+    expect(API.getSelectedElements()[0].x).toEqual(0);
+    expect(API.getSelectedElements()[1].x).toEqual(110);
+
+    expect(API.getSelectedElements()[0].y).toEqual(60);
+    expect(API.getSelectedElements()[1].y).toEqual(55);
+  });
+
+  it("centers two objects with different sizes correctly horizontally", () => {
+    createAndSelectTwoRectanglesWithDifferentSizes();
+
+    expect(API.getSelectedElements()[0].x).toEqual(0);
+    expect(API.getSelectedElements()[1].x).toEqual(110);
+
+    expect(API.getSelectedElements()[0].y).toEqual(0);
+    expect(API.getSelectedElements()[1].y).toEqual(110);
+
+    API.executeAction(actionAlignHorizontallyCentered);
+
+    expect(API.getSelectedElements()[0].x).toEqual(60);
+    expect(API.getSelectedElements()[1].x).toEqual(55);
+
+    // Check if y position did not change
+    expect(API.getSelectedElements()[0].y).toEqual(0);
+    expect(API.getSelectedElements()[1].y).toEqual(110);
+  });
+
+  const createAndSelectGroupAndRectangle = () => {
+    UI.clickTool("rectangle");
+    mouse.down();
+    mouse.up(100, 100);
+
+    UI.clickTool("rectangle");
+    mouse.down(0, 0);
+    mouse.up(100, 100);
+
+    // Select the first element.
+    // The second rectangle is already reselected because it was the last element created
+    mouse.reset();
+    Keyboard.withModifierKeys({ shift: true }, () => {
+      mouse.click();
+    });
+
+    API.executeAction(actionGroup);
+
+    mouse.reset();
+    UI.clickTool("rectangle");
+    mouse.down(200, 200);
+    mouse.up(100, 100);
+
+    // Add the created group to the current selection
+    mouse.restorePosition(0, 0);
+    Keyboard.withModifierKeys({ shift: true }, () => {
+      mouse.click();
+    });
+  };
+
+  it("aligns a group with another element correctly to the top", () => {
+    createAndSelectGroupAndRectangle();
+
+    expect(API.getSelectedElements()[0].y).toEqual(0);
+    expect(API.getSelectedElements()[1].y).toEqual(100);
+    expect(API.getSelectedElements()[2].y).toEqual(200);
+
+    API.executeAction(actionAlignTop);
+
+    expect(API.getSelectedElements()[0].y).toEqual(0);
+    expect(API.getSelectedElements()[1].y).toEqual(100);
+    expect(API.getSelectedElements()[2].y).toEqual(0);
+  });
+
+  it("aligns a group with another element correctly to the bottom", () => {
+    createAndSelectGroupAndRectangle();
+
+    expect(API.getSelectedElements()[0].y).toEqual(0);
+    expect(API.getSelectedElements()[1].y).toEqual(100);
+    expect(API.getSelectedElements()[2].y).toEqual(200);
+
+    API.executeAction(actionAlignBottom);
+
+    expect(API.getSelectedElements()[0].y).toEqual(100);
+    expect(API.getSelectedElements()[1].y).toEqual(200);
+    expect(API.getSelectedElements()[2].y).toEqual(200);
+  });
+
+  it("aligns a group with another element correctly to the left", () => {
+    createAndSelectGroupAndRectangle();
+
+    expect(API.getSelectedElements()[0].x).toEqual(0);
+    expect(API.getSelectedElements()[1].x).toEqual(100);
+    expect(API.getSelectedElements()[2].x).toEqual(200);
+
+    API.executeAction(actionAlignLeft);
+
+    expect(API.getSelectedElements()[0].x).toEqual(0);
+    expect(API.getSelectedElements()[1].x).toEqual(100);
+    expect(API.getSelectedElements()[2].x).toEqual(0);
+  });
+
+  it("aligns a group with another element correctly to the right", () => {
+    createAndSelectGroupAndRectangle();
+
+    expect(API.getSelectedElements()[0].x).toEqual(0);
+    expect(API.getSelectedElements()[1].x).toEqual(100);
+    expect(API.getSelectedElements()[2].x).toEqual(200);
+
+    API.executeAction(actionAlignRight);
+
+    expect(API.getSelectedElements()[0].x).toEqual(100);
+    expect(API.getSelectedElements()[1].x).toEqual(200);
+    expect(API.getSelectedElements()[2].x).toEqual(200);
+  });
+
+  it("centers a group with another element correctly vertically", () => {
+    createAndSelectGroupAndRectangle();
+
+    expect(API.getSelectedElements()[0].y).toEqual(0);
+    expect(API.getSelectedElements()[1].y).toEqual(100);
+    expect(API.getSelectedElements()[2].y).toEqual(200);
+
+    API.executeAction(actionAlignVerticallyCentered);
+
+    expect(API.getSelectedElements()[0].y).toEqual(50);
+    expect(API.getSelectedElements()[1].y).toEqual(150);
+    expect(API.getSelectedElements()[2].y).toEqual(100);
+  });
+
+  it("centers a group with another element correctly horizontally", () => {
+    createAndSelectGroupAndRectangle();
+
+    expect(API.getSelectedElements()[0].x).toEqual(0);
+    expect(API.getSelectedElements()[1].x).toEqual(100);
+    expect(API.getSelectedElements()[2].x).toEqual(200);
+
+    API.executeAction(actionAlignHorizontallyCentered);
+
+    expect(API.getSelectedElements()[0].x).toEqual(50);
+    expect(API.getSelectedElements()[1].x).toEqual(150);
+    expect(API.getSelectedElements()[2].x).toEqual(100);
+  });
+
+  const createAndSelectTwoGroups = () => {
+    UI.clickTool("rectangle");
+    mouse.down();
+    mouse.up(100, 100);
+
+    UI.clickTool("rectangle");
+    mouse.down(0, 0);
+    mouse.up(100, 100);
+
+    // Select the first element.
+    // The second rectangle is already selected because it was the last element created
+    mouse.reset();
+    Keyboard.withModifierKeys({ shift: true }, () => {
+      mouse.click();
+    });
+
+    API.executeAction(actionGroup);
+
+    mouse.reset();
+    UI.clickTool("rectangle");
+    mouse.down(200, 200);
+    mouse.up(100, 100);
+
+    UI.clickTool("rectangle");
+    mouse.down();
+    mouse.up(100, 100);
+
+    mouse.restorePosition(200, 200);
+    Keyboard.withModifierKeys({ shift: true }, () => {
+      mouse.click();
+    });
+
+    API.executeAction(actionGroup);
+
+    // Select the first group.
+    // The second group is already selected because it was the last group created
+    mouse.reset();
+    Keyboard.withModifierKeys({ shift: true }, () => {
+      mouse.click();
+    });
+  };
+
+  it("aligns two groups correctly to the top", () => {
+    createAndSelectTwoGroups();
+
+    expect(API.getSelectedElements()[0].y).toEqual(0);
+    expect(API.getSelectedElements()[1].y).toEqual(100);
+    expect(API.getSelectedElements()[2].y).toEqual(200);
+    expect(API.getSelectedElements()[3].y).toEqual(300);
+
+    API.executeAction(actionAlignTop);
+
+    expect(API.getSelectedElements()[0].y).toEqual(0);
+    expect(API.getSelectedElements()[1].y).toEqual(100);
+    expect(API.getSelectedElements()[2].y).toEqual(0);
+    expect(API.getSelectedElements()[3].y).toEqual(100);
+  });
+
+  it("aligns two groups correctly to the bottom", () => {
+    createAndSelectTwoGroups();
+
+    expect(API.getSelectedElements()[0].y).toEqual(0);
+    expect(API.getSelectedElements()[1].y).toEqual(100);
+    expect(API.getSelectedElements()[2].y).toEqual(200);
+    expect(API.getSelectedElements()[3].y).toEqual(300);
+
+    API.executeAction(actionAlignBottom);
+
+    expect(API.getSelectedElements()[0].y).toEqual(200);
+    expect(API.getSelectedElements()[1].y).toEqual(300);
+    expect(API.getSelectedElements()[2].y).toEqual(200);
+    expect(API.getSelectedElements()[3].y).toEqual(300);
+  });
+
+  it("aligns two groups correctly to the left", () => {
+    createAndSelectTwoGroups();
+
+    expect(API.getSelectedElements()[0].x).toEqual(0);
+    expect(API.getSelectedElements()[1].x).toEqual(100);
+    expect(API.getSelectedElements()[2].x).toEqual(200);
+    expect(API.getSelectedElements()[3].x).toEqual(300);
+
+    API.executeAction(actionAlignLeft);
+
+    expect(API.getSelectedElements()[0].x).toEqual(0);
+    expect(API.getSelectedElements()[1].x).toEqual(100);
+    expect(API.getSelectedElements()[2].x).toEqual(0);
+    expect(API.getSelectedElements()[3].x).toEqual(100);
+  });
+
+  it("aligns two groups correctly to the right", () => {
+    createAndSelectTwoGroups();
+
+    expect(API.getSelectedElements()[0].x).toEqual(0);
+    expect(API.getSelectedElements()[1].x).toEqual(100);
+    expect(API.getSelectedElements()[2].x).toEqual(200);
+    expect(API.getSelectedElements()[3].x).toEqual(300);
+
+    API.executeAction(actionAlignRight);
+
+    expect(API.getSelectedElements()[0].x).toEqual(200);
+    expect(API.getSelectedElements()[1].x).toEqual(300);
+    expect(API.getSelectedElements()[2].x).toEqual(200);
+    expect(API.getSelectedElements()[3].x).toEqual(300);
+  });
+
+  it("centers two groups correctly vertically", () => {
+    createAndSelectTwoGroups();
+
+    expect(API.getSelectedElements()[0].y).toEqual(0);
+    expect(API.getSelectedElements()[1].y).toEqual(100);
+    expect(API.getSelectedElements()[2].y).toEqual(200);
+    expect(API.getSelectedElements()[3].y).toEqual(300);
+
+    API.executeAction(actionAlignVerticallyCentered);
+
+    expect(API.getSelectedElements()[0].y).toEqual(100);
+    expect(API.getSelectedElements()[1].y).toEqual(200);
+    expect(API.getSelectedElements()[2].y).toEqual(100);
+    expect(API.getSelectedElements()[3].y).toEqual(200);
+  });
+
+  it("centers two groups correctly horizontally", () => {
+    createAndSelectTwoGroups();
+
+    expect(API.getSelectedElements()[0].x).toEqual(0);
+    expect(API.getSelectedElements()[1].x).toEqual(100);
+    expect(API.getSelectedElements()[2].x).toEqual(200);
+    expect(API.getSelectedElements()[3].x).toEqual(300);
+
+    API.executeAction(actionAlignHorizontallyCentered);
+
+    expect(API.getSelectedElements()[0].x).toEqual(100);
+    expect(API.getSelectedElements()[1].x).toEqual(200);
+    expect(API.getSelectedElements()[2].x).toEqual(100);
+    expect(API.getSelectedElements()[3].x).toEqual(200);
+  });
+
+  const createAndSelectNestedGroupAndRectangle = () => {
+    UI.clickTool("rectangle");
+    mouse.down();
+    mouse.up(100, 100);
+
+    UI.clickTool("rectangle");
+    mouse.down(0, 0);
+    mouse.up(100, 100);
+
+    // Select the first element.
+    // The second rectangle is already reselected because it was the last element created
+    mouse.reset();
+    Keyboard.withModifierKeys({ shift: true }, () => {
+      mouse.click();
+    });
+
+    // Create first group of rectangles
+    API.executeAction(actionGroup);
+
+    mouse.reset();
+    UI.clickTool("rectangle");
+    mouse.down(200, 200);
+    mouse.up(100, 100);
+
+    // Add group to current selection
+    mouse.restorePosition(0, 0);
+    Keyboard.withModifierKeys({ shift: true }, () => {
+      mouse.click();
+    });
+
+    // Create the nested group
+    API.executeAction(actionGroup);
+
+    mouse.reset();
+    UI.clickTool("rectangle");
+    mouse.down(300, 300);
+    mouse.up(100, 100);
+
+    // Select the nested group, the rectangle is already selected
+    mouse.reset();
+    Keyboard.withModifierKeys({ shift: true }, () => {
+      mouse.click();
+    });
+  };
+
+  it("aligns nested group and other element correctly to the top", () => {
+    createAndSelectNestedGroupAndRectangle();
+
+    expect(API.getSelectedElements()[0].y).toEqual(0);
+    expect(API.getSelectedElements()[1].y).toEqual(100);
+    expect(API.getSelectedElements()[2].y).toEqual(200);
+    expect(API.getSelectedElements()[3].y).toEqual(300);
+
+    API.executeAction(actionAlignTop);
+
+    expect(API.getSelectedElements()[0].y).toEqual(0);
+    expect(API.getSelectedElements()[1].y).toEqual(100);
+    expect(API.getSelectedElements()[2].y).toEqual(200);
+    expect(API.getSelectedElements()[3].y).toEqual(0);
+  });
+
+  it("aligns nested group and other element correctly to the bottom", () => {
+    createAndSelectNestedGroupAndRectangle();
+
+    expect(API.getSelectedElements()[0].y).toEqual(0);
+    expect(API.getSelectedElements()[1].y).toEqual(100);
+    expect(API.getSelectedElements()[2].y).toEqual(200);
+    expect(API.getSelectedElements()[3].y).toEqual(300);
+
+    API.executeAction(actionAlignBottom);
+
+    expect(API.getSelectedElements()[0].y).toEqual(100);
+    expect(API.getSelectedElements()[1].y).toEqual(200);
+    expect(API.getSelectedElements()[2].y).toEqual(300);
+    expect(API.getSelectedElements()[3].y).toEqual(300);
+  });
+
+  it("aligns nested group and other element correctly to the left", () => {
+    createAndSelectNestedGroupAndRectangle();
+
+    expect(API.getSelectedElements()[0].x).toEqual(0);
+    expect(API.getSelectedElements()[1].x).toEqual(100);
+    expect(API.getSelectedElements()[2].x).toEqual(200);
+    expect(API.getSelectedElements()[3].x).toEqual(300);
+
+    API.executeAction(actionAlignLeft);
+
+    expect(API.getSelectedElements()[0].x).toEqual(0);
+    expect(API.getSelectedElements()[1].x).toEqual(100);
+    expect(API.getSelectedElements()[2].x).toEqual(200);
+    expect(API.getSelectedElements()[3].x).toEqual(0);
+  });
+
+  it("aligns nested group and other element correctly to the right", () => {
+    createAndSelectNestedGroupAndRectangle();
+
+    expect(API.getSelectedElements()[0].x).toEqual(0);
+    expect(API.getSelectedElements()[1].x).toEqual(100);
+    expect(API.getSelectedElements()[2].x).toEqual(200);
+    expect(API.getSelectedElements()[3].x).toEqual(300);
+
+    API.executeAction(actionAlignRight);
+
+    expect(API.getSelectedElements()[0].x).toEqual(100);
+    expect(API.getSelectedElements()[1].x).toEqual(200);
+    expect(API.getSelectedElements()[2].x).toEqual(300);
+    expect(API.getSelectedElements()[3].x).toEqual(300);
+  });
+
+  it("centers nested group and other element correctly vertically", () => {
+    createAndSelectNestedGroupAndRectangle();
+
+    expect(API.getSelectedElements()[0].y).toEqual(0);
+    expect(API.getSelectedElements()[1].y).toEqual(100);
+    expect(API.getSelectedElements()[2].y).toEqual(200);
+    expect(API.getSelectedElements()[3].y).toEqual(300);
+
+    API.executeAction(actionAlignVerticallyCentered);
+
+    expect(API.getSelectedElements()[0].y).toEqual(50);
+    expect(API.getSelectedElements()[1].y).toEqual(150);
+    expect(API.getSelectedElements()[2].y).toEqual(250);
+    expect(API.getSelectedElements()[3].y).toEqual(150);
+  });
+
+  it("centers nested group and other element correctly horizontally", () => {
+    createAndSelectNestedGroupAndRectangle();
+
+    expect(API.getSelectedElements()[0].x).toEqual(0);
+    expect(API.getSelectedElements()[1].x).toEqual(100);
+    expect(API.getSelectedElements()[2].x).toEqual(200);
+    expect(API.getSelectedElements()[3].x).toEqual(300);
+
+    API.executeAction(actionAlignHorizontallyCentered);
+
+    expect(API.getSelectedElements()[0].x).toEqual(50);
+    expect(API.getSelectedElements()[1].x).toEqual(150);
+    expect(API.getSelectedElements()[2].x).toEqual(250);
+    expect(API.getSelectedElements()[3].x).toEqual(150);
+  });
+});
-- 
cgit v1.2.3

