mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
refactor: separate elements logic into a standalone package (#9285)
Some checks failed
Auto release excalidraw next / Auto-release-excalidraw-next (push) Failing after 2m36s
Build Docker image / build-docker (push) Failing after 6s
Cancel previous runs / cancel (push) Failing after 1s
Publish Docker / publish-docker (push) Failing after 31s
New Sentry production release / sentry (push) Failing after 2m3s
Some checks failed
Auto release excalidraw next / Auto-release-excalidraw-next (push) Failing after 2m36s
Build Docker image / build-docker (push) Failing after 6s
Cancel previous runs / cancel (push) Failing after 1s
Publish Docker / publish-docker (push) Failing after 31s
New Sentry production release / sentry (push) Failing after 2m3s
This commit is contained in:
parent
a18f059188
commit
432a46ef9e
372 changed files with 3466 additions and 2466 deletions
|
@ -1,8 +1,9 @@
|
|||
import React from "react";
|
||||
import { vi } from "vitest";
|
||||
|
||||
import { reseed } from "@excalidraw/common";
|
||||
|
||||
import { Excalidraw } from "../index";
|
||||
import { reseed } from "../random";
|
||||
import * as StaticScene from "../renderer/staticScene";
|
||||
import { render, queryByTestId, unmountComponent } from "../tests/test-utils";
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import React from "react";
|
||||
|
||||
import { CODES } from "@excalidraw/common";
|
||||
|
||||
import { copiedStyles } from "../actions/actionStyles";
|
||||
import { Excalidraw } from "../index";
|
||||
import { CODES } from "../keys";
|
||||
import { API } from "../tests/helpers/api";
|
||||
import { Keyboard, Pointer, UI } from "../tests/helpers/ui";
|
||||
import {
|
||||
|
|
|
@ -1,581 +0,0 @@
|
|||
import React from "react";
|
||||
|
||||
import {
|
||||
actionAlignVerticallyCentered,
|
||||
actionAlignHorizontallyCentered,
|
||||
actionGroup,
|
||||
actionAlignTop,
|
||||
actionAlignBottom,
|
||||
actionAlignLeft,
|
||||
actionAlignRight,
|
||||
} from "../actions";
|
||||
import { defaultLang, setLanguage } from "../i18n";
|
||||
import { Excalidraw } from "../index";
|
||||
import { KEYS } from "../keys";
|
||||
|
||||
import { API } from "./helpers/api";
|
||||
import { UI, Pointer, Keyboard } from "./helpers/ui";
|
||||
import { act, unmountComponent, render } from "./test-utils";
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
|
@ -1,15 +1,16 @@
|
|||
import React from "react";
|
||||
|
||||
import { EXPORT_DATA_TYPES, MIME_TYPES } from "@excalidraw/common";
|
||||
|
||||
import type { ExcalidrawTextElement } from "@excalidraw/element/types";
|
||||
|
||||
import { getDefaultAppState } from "../appState";
|
||||
import { EXPORT_DATA_TYPES, MIME_TYPES } from "../constants";
|
||||
import { Excalidraw } from "../index";
|
||||
|
||||
import { API } from "./helpers/api";
|
||||
import { Pointer, UI } from "./helpers/ui";
|
||||
import { fireEvent, queryByTestId, render, waitFor } from "./test-utils";
|
||||
|
||||
import type { ExcalidrawTextElement } from "../element/types";
|
||||
|
||||
const { h } = window;
|
||||
|
||||
describe("appState", () => {
|
||||
|
|
|
@ -1,483 +0,0 @@
|
|||
import { pointFrom } from "@excalidraw/math";
|
||||
import React from "react";
|
||||
|
||||
import { actionWrapTextInContainer } from "../actions/actionBoundText";
|
||||
import { getTransformHandles } from "../element/transformHandles";
|
||||
import { Excalidraw, isLinearElement } from "../index";
|
||||
import { KEYS } from "../keys";
|
||||
import { arrayToMap } from "../utils";
|
||||
|
||||
import { API } from "./helpers/api";
|
||||
import { UI, Pointer, Keyboard } from "./helpers/ui";
|
||||
import { fireEvent, render } from "./test-utils";
|
||||
|
||||
const { h } = window;
|
||||
|
||||
const mouse = new Pointer("mouse");
|
||||
|
||||
describe("element binding", () => {
|
||||
beforeEach(async () => {
|
||||
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
||||
});
|
||||
|
||||
it("should create valid binding if duplicate start/end points", async () => {
|
||||
const rect = API.createElement({
|
||||
type: "rectangle",
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 50,
|
||||
height: 50,
|
||||
});
|
||||
const arrow = API.createElement({
|
||||
type: "arrow",
|
||||
x: 100,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 1,
|
||||
points: [
|
||||
pointFrom(0, 0),
|
||||
pointFrom(0, 0),
|
||||
pointFrom(100, 0),
|
||||
pointFrom(100, 0),
|
||||
],
|
||||
});
|
||||
API.setElements([rect, arrow]);
|
||||
expect(arrow.startBinding).toBe(null);
|
||||
|
||||
// select arrow
|
||||
mouse.clickAt(150, 0);
|
||||
|
||||
// move arrow start to potential binding position
|
||||
mouse.downAt(100, 0);
|
||||
mouse.moveTo(55, 0);
|
||||
mouse.up(0, 0);
|
||||
|
||||
// Point selection is evaluated like the points are rendered,
|
||||
// from right to left. So clicking on the first point should move the joint,
|
||||
// not the start point.
|
||||
expect(arrow.startBinding).toBe(null);
|
||||
|
||||
// Now that the start point is free, move it into overlapping position
|
||||
mouse.downAt(100, 0);
|
||||
mouse.moveTo(55, 0);
|
||||
mouse.up(0, 0);
|
||||
|
||||
expect(API.getSelectedElements()).toEqual([arrow]);
|
||||
|
||||
expect(arrow.startBinding).toEqual({
|
||||
elementId: rect.id,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
});
|
||||
|
||||
// Move the end point to the overlapping binding position
|
||||
mouse.downAt(200, 0);
|
||||
mouse.moveTo(55, 0);
|
||||
mouse.up(0, 0);
|
||||
|
||||
// Both the start and the end points should be bound
|
||||
expect(arrow.startBinding).toEqual({
|
||||
elementId: rect.id,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
});
|
||||
expect(arrow.endBinding).toEqual({
|
||||
elementId: rect.id,
|
||||
focus: expect.toBeNonNaNNumber(),
|
||||
gap: expect.toBeNonNaNNumber(),
|
||||
});
|
||||
});
|
||||
|
||||
//@TODO fix the test with rotation
|
||||
it.skip("rotation of arrow should rebind both ends", () => {
|
||||
const rectLeft = UI.createElement("rectangle", {
|
||||
x: 0,
|
||||
width: 200,
|
||||
height: 500,
|
||||
});
|
||||
const rectRight = UI.createElement("rectangle", {
|
||||
x: 400,
|
||||
width: 200,
|
||||
height: 500,
|
||||
});
|
||||
const arrow = UI.createElement("arrow", {
|
||||
x: 210,
|
||||
y: 250,
|
||||
width: 180,
|
||||
height: 1,
|
||||
});
|
||||
expect(arrow.startBinding?.elementId).toBe(rectLeft.id);
|
||||
expect(arrow.endBinding?.elementId).toBe(rectRight.id);
|
||||
|
||||
const rotation = getTransformHandles(
|
||||
arrow,
|
||||
h.state.zoom,
|
||||
arrayToMap(h.elements),
|
||||
"mouse",
|
||||
).rotation!;
|
||||
const rotationHandleX = rotation[0] + rotation[2] / 2;
|
||||
const rotationHandleY = rotation[1] + rotation[3] / 2;
|
||||
mouse.down(rotationHandleX, rotationHandleY);
|
||||
mouse.move(300, 400);
|
||||
mouse.up();
|
||||
expect(arrow.angle).toBeGreaterThan(0.7 * Math.PI);
|
||||
expect(arrow.angle).toBeLessThan(1.3 * Math.PI);
|
||||
expect(arrow.startBinding?.elementId).toBe(rectRight.id);
|
||||
expect(arrow.endBinding?.elementId).toBe(rectLeft.id);
|
||||
});
|
||||
|
||||
// TODO fix & reenable once we rewrite tests to work with concurrency
|
||||
it.skip(
|
||||
"editing arrow and moving its head to bind it to element A, finalizing the" +
|
||||
"editing by clicking on element A should end up selecting A",
|
||||
async () => {
|
||||
UI.createElement("rectangle", {
|
||||
y: 0,
|
||||
size: 100,
|
||||
});
|
||||
// Create arrow bound to rectangle
|
||||
UI.clickTool("arrow");
|
||||
mouse.down(50, -100);
|
||||
mouse.up(0, 80);
|
||||
|
||||
// Edit arrow with multi-point
|
||||
mouse.doubleClick();
|
||||
// move arrow head
|
||||
mouse.down();
|
||||
mouse.up(0, 10);
|
||||
expect(API.getSelectedElement().type).toBe("arrow");
|
||||
|
||||
// NOTE this mouse down/up + await needs to be done in order to repro
|
||||
// the issue, due to https://github.com/excalidraw/excalidraw/blob/46bff3daceb602accf60c40a84610797260fca94/src/components/App.tsx#L740
|
||||
mouse.reset();
|
||||
expect(h.state.editingLinearElement).not.toBe(null);
|
||||
mouse.down(0, 0);
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
expect(h.state.editingLinearElement).toBe(null);
|
||||
expect(API.getSelectedElement().type).toBe("rectangle");
|
||||
mouse.up();
|
||||
expect(API.getSelectedElement().type).toBe("rectangle");
|
||||
},
|
||||
);
|
||||
|
||||
it("should unbind arrow when moving it with keyboard", () => {
|
||||
const rectangle = UI.createElement("rectangle", {
|
||||
x: 75,
|
||||
y: 0,
|
||||
size: 100,
|
||||
});
|
||||
|
||||
// Creates arrow 1px away from bidding with rectangle
|
||||
const arrow = UI.createElement("arrow", {
|
||||
x: 0,
|
||||
y: 0,
|
||||
size: 50,
|
||||
});
|
||||
|
||||
expect(arrow.endBinding).toBe(null);
|
||||
|
||||
mouse.downAt(50, 50);
|
||||
mouse.moveTo(51, 0);
|
||||
mouse.up(0, 0);
|
||||
|
||||
// Test sticky connection
|
||||
expect(API.getSelectedElement().type).toBe("arrow");
|
||||
Keyboard.keyPress(KEYS.ARROW_RIGHT);
|
||||
expect(arrow.endBinding?.elementId).toBe(rectangle.id);
|
||||
Keyboard.keyPress(KEYS.ARROW_LEFT);
|
||||
expect(arrow.endBinding?.elementId).toBe(rectangle.id);
|
||||
|
||||
// Sever connection
|
||||
expect(API.getSelectedElement().type).toBe("arrow");
|
||||
Keyboard.keyPress(KEYS.ARROW_LEFT);
|
||||
expect(arrow.endBinding).toBe(null);
|
||||
Keyboard.keyPress(KEYS.ARROW_RIGHT);
|
||||
expect(arrow.endBinding).toBe(null);
|
||||
});
|
||||
|
||||
it("should unbind on bound element deletion", () => {
|
||||
const rectangle = UI.createElement("rectangle", {
|
||||
x: 60,
|
||||
y: 0,
|
||||
size: 100,
|
||||
});
|
||||
|
||||
const arrow = UI.createElement("arrow", {
|
||||
x: 0,
|
||||
y: 0,
|
||||
size: 50,
|
||||
});
|
||||
|
||||
expect(arrow.endBinding?.elementId).toBe(rectangle.id);
|
||||
|
||||
mouse.select(rectangle);
|
||||
expect(API.getSelectedElement().type).toBe("rectangle");
|
||||
Keyboard.keyDown(KEYS.DELETE);
|
||||
expect(arrow.endBinding).toBe(null);
|
||||
});
|
||||
|
||||
it("should unbind on text element deletion by submitting empty text", async () => {
|
||||
const text = API.createElement({
|
||||
type: "text",
|
||||
text: "ola",
|
||||
x: 60,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 100,
|
||||
});
|
||||
|
||||
API.setElements([text]);
|
||||
|
||||
const arrow = UI.createElement("arrow", {
|
||||
x: 0,
|
||||
y: 0,
|
||||
size: 50,
|
||||
});
|
||||
|
||||
expect(arrow.endBinding?.elementId).toBe(text.id);
|
||||
|
||||
// edit text element and submit
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
UI.clickTool("text");
|
||||
|
||||
mouse.clickAt(text.x + 50, text.y + 50);
|
||||
|
||||
const editor = document.querySelector(
|
||||
".excalidraw-textEditorContainer > textarea",
|
||||
) as HTMLTextAreaElement;
|
||||
|
||||
expect(editor).not.toBe(null);
|
||||
|
||||
fireEvent.change(editor, { target: { value: "" } });
|
||||
fireEvent.keyDown(editor, { key: KEYS.ESCAPE });
|
||||
|
||||
expect(
|
||||
document.querySelector(".excalidraw-textEditorContainer > textarea"),
|
||||
).toBe(null);
|
||||
expect(arrow.endBinding).toBe(null);
|
||||
});
|
||||
|
||||
it("should keep binding on text update", async () => {
|
||||
const text = API.createElement({
|
||||
type: "text",
|
||||
text: "ola",
|
||||
x: 60,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 100,
|
||||
});
|
||||
|
||||
API.setElements([text]);
|
||||
|
||||
const arrow = UI.createElement("arrow", {
|
||||
x: 0,
|
||||
y: 0,
|
||||
size: 50,
|
||||
});
|
||||
|
||||
expect(arrow.endBinding?.elementId).toBe(text.id);
|
||||
|
||||
// delete text element by submitting empty text
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
UI.clickTool("text");
|
||||
|
||||
mouse.clickAt(text.x + 50, text.y + 50);
|
||||
const editor = document.querySelector(
|
||||
".excalidraw-textEditorContainer > textarea",
|
||||
) as HTMLTextAreaElement;
|
||||
|
||||
expect(editor).not.toBe(null);
|
||||
|
||||
fireEvent.change(editor, { target: { value: "asdasdasdasdas" } });
|
||||
fireEvent.keyDown(editor, { key: KEYS.ESCAPE });
|
||||
|
||||
expect(
|
||||
document.querySelector(".excalidraw-textEditorContainer > textarea"),
|
||||
).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: [pointFrom(0, 0), pointFrom(0, -87.45777932247563)],
|
||||
startBinding: {
|
||||
elementId: "rectangle1",
|
||||
focus: 0.2,
|
||||
gap: 7,
|
||||
fixedPoint: [0.5, 1],
|
||||
},
|
||||
endBinding: {
|
||||
elementId: "text1",
|
||||
focus: 0.2,
|
||||
gap: 7,
|
||||
fixedPoint: [1, 0.5],
|
||||
},
|
||||
});
|
||||
|
||||
const arrow2 = API.createElement({
|
||||
type: "arrow",
|
||||
id: "arrow2",
|
||||
points: [pointFrom(0, 0), pointFrom(0, -87.45777932247563)],
|
||||
startBinding: {
|
||||
elementId: "text1",
|
||||
focus: 0.2,
|
||||
gap: 7,
|
||||
fixedPoint: [0.5, 1],
|
||||
},
|
||||
endBinding: {
|
||||
elementId: "rectangle1",
|
||||
focus: 0.2,
|
||||
gap: 7,
|
||||
fixedPoint: [1, 0.5],
|
||||
},
|
||||
});
|
||||
|
||||
const text1 = API.createElement({
|
||||
type: "text",
|
||||
id: "text1",
|
||||
text: "ola",
|
||||
boundElements: [
|
||||
{ id: "arrow1", type: "arrow" },
|
||||
{ id: "arrow2", type: "arrow" },
|
||||
],
|
||||
});
|
||||
|
||||
API.setElements([rectangle1, arrow1, arrow2, text1]);
|
||||
|
||||
API.setSelectedElements([text1]);
|
||||
|
||||
expect(h.state.selectedElementIds[text1.id]).toBe(true);
|
||||
|
||||
API.executeAction(actionWrapTextInContainer);
|
||||
|
||||
// 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);
|
||||
});
|
||||
|
||||
// #6459
|
||||
it("should unbind arrow only from the latest element", () => {
|
||||
const rectLeft = UI.createElement("rectangle", {
|
||||
x: 0,
|
||||
width: 200,
|
||||
height: 500,
|
||||
});
|
||||
const rectRight = UI.createElement("rectangle", {
|
||||
x: 400,
|
||||
width: 200,
|
||||
height: 500,
|
||||
});
|
||||
const arrow = UI.createElement("arrow", {
|
||||
x: 210,
|
||||
y: 250,
|
||||
width: 180,
|
||||
height: 1,
|
||||
});
|
||||
expect(arrow.startBinding?.elementId).toBe(rectLeft.id);
|
||||
expect(arrow.endBinding?.elementId).toBe(rectRight.id);
|
||||
|
||||
// Drag arrow off of bound rectangle range
|
||||
const handles = getTransformHandles(
|
||||
arrow,
|
||||
h.state.zoom,
|
||||
arrayToMap(h.elements),
|
||||
"mouse",
|
||||
).se!;
|
||||
|
||||
Keyboard.keyDown(KEYS.CTRL_OR_CMD);
|
||||
const elX = handles[0] + handles[2] / 2;
|
||||
const elY = handles[1] + handles[3] / 2;
|
||||
mouse.downAt(elX, elY);
|
||||
mouse.moveTo(300, 400);
|
||||
mouse.up();
|
||||
|
||||
expect(arrow.startBinding).not.toBe(null);
|
||||
expect(arrow.endBinding).toBe(null);
|
||||
});
|
||||
|
||||
it("should not unbind when duplicating via selection group", () => {
|
||||
const rectLeft = UI.createElement("rectangle", {
|
||||
x: 0,
|
||||
width: 200,
|
||||
height: 500,
|
||||
});
|
||||
const rectRight = UI.createElement("rectangle", {
|
||||
x: 400,
|
||||
y: 200,
|
||||
width: 200,
|
||||
height: 500,
|
||||
});
|
||||
const arrow = UI.createElement("arrow", {
|
||||
x: 210,
|
||||
y: 250,
|
||||
width: 177,
|
||||
height: 1,
|
||||
});
|
||||
expect(arrow.startBinding?.elementId).toBe(rectLeft.id);
|
||||
expect(arrow.endBinding?.elementId).toBe(rectRight.id);
|
||||
|
||||
mouse.downAt(-100, -100);
|
||||
mouse.moveTo(650, 750);
|
||||
mouse.up(0, 0);
|
||||
|
||||
expect(API.getSelectedElements().length).toBe(3);
|
||||
|
||||
mouse.moveTo(5, 5);
|
||||
Keyboard.withModifierKeys({ alt: true }, () => {
|
||||
mouse.downAt(5, 5);
|
||||
mouse.moveTo(1000, 1000);
|
||||
mouse.up(0, 0);
|
||||
|
||||
expect(window.h.elements.length).toBe(6);
|
||||
window.h.elements.forEach((element) => {
|
||||
if (isLinearElement(element)) {
|
||||
expect(element.startBinding).not.toBe(null);
|
||||
expect(element.endBinding).not.toBe(null);
|
||||
} else {
|
||||
expect(element.boundElements).not.toBe(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,13 +1,15 @@
|
|||
import React from "react";
|
||||
import { vi } from "vitest";
|
||||
|
||||
import { getLineHeightInPx } from "@excalidraw/element/textMeasurements";
|
||||
|
||||
import { KEYS, arrayToMap, getLineHeight } from "@excalidraw/common";
|
||||
|
||||
import { getElementBounds } from "@excalidraw/element/bounds";
|
||||
|
||||
import { createPasteEvent, serializeAsClipboardJSON } from "../clipboard";
|
||||
import { getElementBounds } from "../element";
|
||||
import { getLineHeightInPx } from "../element/textMeasurements";
|
||||
import { getLineHeight } from "../fonts";
|
||||
|
||||
import { Excalidraw } from "../index";
|
||||
import { KEYS } from "../keys";
|
||||
import { arrayToMap } from "../utils";
|
||||
|
||||
import { API } from "./helpers/api";
|
||||
import { mockMermaidToExcalidraw } from "./helpers/mocks";
|
||||
|
@ -25,7 +27,7 @@ const { h } = window;
|
|||
|
||||
const mouse = new Pointer("mouse");
|
||||
|
||||
vi.mock("../keys.ts", async (importOriginal) => {
|
||||
vi.mock("@excalidraw/common", async (importOriginal) => {
|
||||
const module: any = await importOriginal();
|
||||
return {
|
||||
__esmodule: true,
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import React from "react";
|
||||
import { vi } from "vitest";
|
||||
|
||||
import { KEYS, reseed } from "@excalidraw/common";
|
||||
|
||||
import { setDateTimeForTests } from "@excalidraw/common";
|
||||
|
||||
import { copiedStyles } from "../actions/actionStyles";
|
||||
import { Excalidraw } from "../index";
|
||||
import { KEYS } from "../keys";
|
||||
import { reseed } from "../random";
|
||||
import * as StaticScene from "../renderer/staticScene";
|
||||
import { setDateTimeForTests } from "../utils";
|
||||
|
||||
import { API } from "./helpers/api";
|
||||
import { UI, Pointer, Keyboard } from "./helpers/ui";
|
||||
|
|
|
@ -1,17 +1,22 @@
|
|||
import React from "react";
|
||||
import { vi } from "vitest";
|
||||
|
||||
import { KEYS, cloneJSON } from "@excalidraw/common";
|
||||
|
||||
import { duplicateElement } from "@excalidraw/element/duplicate";
|
||||
|
||||
import type {
|
||||
ExcalidrawImageElement,
|
||||
ImageCrop,
|
||||
} from "@excalidraw/element/types";
|
||||
|
||||
import { Excalidraw, exportToCanvas, exportToSvg } from "..";
|
||||
import { actionFlipHorizontal, actionFlipVertical } from "../actions";
|
||||
import { duplicateElement } from "../element";
|
||||
import { KEYS } from "../keys";
|
||||
import { cloneJSON } from "../utils";
|
||||
|
||||
import { API } from "./helpers/api";
|
||||
import { Keyboard, Pointer, UI } from "./helpers/ui";
|
||||
import { act, GlobalTestState, render, unmountComponent } from "./test-utils";
|
||||
|
||||
import type { ExcalidrawImageElement, ImageCrop } from "../element/types";
|
||||
import type { NormalizedZoomValue } from "../types";
|
||||
|
||||
const { h } = window;
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
import { reconcileElements } from "../../data/reconcile";
|
||||
import { syncInvalidIndices } from "../../fractionalIndex";
|
||||
import { randomInteger } from "../../random";
|
||||
import { cloneJSON } from "../../utils";
|
||||
import { syncInvalidIndices } from "@excalidraw/element/fractionalIndex";
|
||||
|
||||
import { randomInteger, cloneJSON } from "@excalidraw/common";
|
||||
|
||||
import type { RemoteExcalidrawElement } from "../../data/reconcile";
|
||||
import type {
|
||||
ExcalidrawElement,
|
||||
OrderedExcalidrawElement,
|
||||
} from "../../element/types";
|
||||
} from "@excalidraw/element/types";
|
||||
|
||||
import { reconcileElements } from "../../data/reconcile";
|
||||
|
||||
import type { RemoteExcalidrawElement } from "../../data/reconcile";
|
||||
|
||||
import type { AppState } from "../../types";
|
||||
|
||||
type Id = string;
|
||||
|
|
|
@ -1,21 +1,24 @@
|
|||
import { pointFrom } from "@excalidraw/math";
|
||||
import { vi } from "vitest";
|
||||
|
||||
import { getDefaultAppState } from "../../appState";
|
||||
import { DEFAULT_SIDEBAR, FONT_FAMILY, ROUNDNESS } from "../../constants";
|
||||
import * as restore from "../../data/restore";
|
||||
import { newElementWith } from "../../element/mutateElement";
|
||||
import * as sizeHelpers from "../../element/sizeHelpers";
|
||||
import { API } from "../helpers/api";
|
||||
import { DEFAULT_SIDEBAR, FONT_FAMILY, ROUNDNESS } from "@excalidraw/common";
|
||||
|
||||
import { newElementWith } from "@excalidraw/element/mutateElement";
|
||||
import * as sizeHelpers from "@excalidraw/element/sizeHelpers";
|
||||
|
||||
import type { ImportedDataState } from "../../data/types";
|
||||
import type {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawFreeDrawElement,
|
||||
ExcalidrawLinearElement,
|
||||
ExcalidrawTextElement,
|
||||
} from "../../element/types";
|
||||
import type { NormalizedZoomValue } from "../../types";
|
||||
} from "@excalidraw/element/types";
|
||||
import type { NormalizedZoomValue } from "@excalidraw/excalidraw/types";
|
||||
|
||||
import { API } from "../helpers/api";
|
||||
import * as restore from "../../data/restore";
|
||||
import { getDefaultAppState } from "../../appState";
|
||||
|
||||
import type { ImportedDataState } from "../../data/types";
|
||||
|
||||
describe("restoreElements", () => {
|
||||
const mockSizeHelper = vi.spyOn(sizeHelpers, "isInvisiblySmallElement");
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import React from "react";
|
||||
import { vi } from "vitest";
|
||||
|
||||
import { KEYS, reseed } from "@excalidraw/common";
|
||||
|
||||
import type { ExcalidrawLinearElement } from "@excalidraw/element/types";
|
||||
|
||||
import { Excalidraw } from "../index";
|
||||
import { KEYS } from "../keys";
|
||||
import { reseed } from "../random";
|
||||
import * as InteractiveScene from "../renderer/interactiveScene";
|
||||
import * as StaticScene from "../renderer/staticScene";
|
||||
|
||||
|
@ -15,8 +17,6 @@ import {
|
|||
unmountComponent,
|
||||
} from "./test-utils";
|
||||
|
||||
import type { ExcalidrawLinearElement } from "../element/types";
|
||||
|
||||
unmountComponent();
|
||||
|
||||
const renderInteractiveScene = vi.spyOn(
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import React from "react";
|
||||
|
||||
import { mutateElement } from "@excalidraw/element/mutateElement";
|
||||
|
||||
import { KEYS } from "@excalidraw/common";
|
||||
|
||||
import { actionSelectAll } from "../actions";
|
||||
import { mutateElement } from "../element/mutateElement";
|
||||
import { t } from "../i18n";
|
||||
import { Excalidraw } from "../index";
|
||||
import { KEYS } from "../keys";
|
||||
|
||||
import { API } from "../tests/helpers/api";
|
||||
import { Keyboard, Pointer, UI } from "../tests/helpers/ui";
|
||||
import { render, unmountComponent } from "../tests/test-utils";
|
||||
|
|
|
@ -2,7 +2,8 @@ import { queryByText, queryByTestId } from "@testing-library/react";
|
|||
import React from "react";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { THEME } from "../constants";
|
||||
import { THEME } from "@excalidraw/common";
|
||||
|
||||
import { t } from "../i18n";
|
||||
import { Excalidraw, Footer, MainMenu } from "../index";
|
||||
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import React from "react";
|
||||
|
||||
import { SVG_NS } from "@excalidraw/common";
|
||||
|
||||
import type { FileId } from "@excalidraw/element/types";
|
||||
|
||||
import { getDefaultAppState } from "../appState";
|
||||
import { SVG_NS } from "../constants";
|
||||
import { getDataURL } from "../data/blob";
|
||||
import { encodePngMetadata } from "../data/image";
|
||||
import { serializeAsJSON } from "../data/json";
|
||||
|
@ -15,8 +18,6 @@ import {
|
|||
import { API } from "./helpers/api";
|
||||
import { render, waitFor } from "./test-utils";
|
||||
|
||||
import type { FileId } from "../element/types";
|
||||
|
||||
const { h } = window;
|
||||
|
||||
const testElements = [
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { VERSIONS } from "../../constants";
|
||||
import { VERSIONS } from "@excalidraw/common";
|
||||
|
||||
import {
|
||||
diamondFixture,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { DEFAULT_FONT_FAMILY } from "@excalidraw/common";
|
||||
|
||||
import type { Radians } from "@excalidraw/math";
|
||||
|
||||
import { DEFAULT_FONT_FAMILY } from "../../constants";
|
||||
|
||||
import type { ExcalidrawElement } from "../../element/types";
|
||||
import type { ExcalidrawElement } from "@excalidraw/element/types";
|
||||
|
||||
const elementBase: Omit<ExcalidrawElement, "type"> = {
|
||||
id: "vWrqOAfkind2qcm7LDAGZ",
|
||||
|
|
|
@ -1,18 +1,30 @@
|
|||
import { pointFrom, type Radians } from "@excalidraw/math";
|
||||
import React from "react";
|
||||
import { vi } from "vitest";
|
||||
|
||||
import { ROUNDNESS, KEYS, arrayToMap, cloneJSON } from "@excalidraw/common";
|
||||
|
||||
import { pointFrom, type Radians } from "@excalidraw/math";
|
||||
|
||||
import { getBoundTextElementPosition } from "@excalidraw/element/textElement";
|
||||
import { getElementAbsoluteCoords } from "@excalidraw/element/bounds";
|
||||
import { newLinearElement } from "@excalidraw/element/newElement";
|
||||
|
||||
import type { LocalPoint } from "@excalidraw/math";
|
||||
|
||||
import type {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawImageElement,
|
||||
ExcalidrawLinearElement,
|
||||
ExcalidrawTextElementWithContainer,
|
||||
FileId,
|
||||
} from "@excalidraw/element/types";
|
||||
|
||||
import { actionFlipHorizontal, actionFlipVertical } from "../actions";
|
||||
import { createPasteEvent } from "../clipboard";
|
||||
import { ROUNDNESS } from "../constants";
|
||||
import { getElementAbsoluteCoords } from "../element";
|
||||
import { newLinearElement } from "../element";
|
||||
import { getBoundTextElementPosition } from "../element/textElement";
|
||||
import { Excalidraw } from "../index";
|
||||
import { KEYS } from "../keys";
|
||||
import { arrayToMap, cloneJSON } from "../utils";
|
||||
|
||||
// Importing to spy on it and mock the implementation (mocking does not work with simple vi.mock for some reason)
|
||||
import * as blobModule from "../data/blob";
|
||||
|
||||
import { API } from "./helpers/api";
|
||||
import { UI, Pointer, Keyboard } from "./helpers/ui";
|
||||
|
@ -25,25 +37,17 @@ import {
|
|||
waitFor,
|
||||
} from "./test-utils";
|
||||
|
||||
import type {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawImageElement,
|
||||
ExcalidrawLinearElement,
|
||||
ExcalidrawTextElementWithContainer,
|
||||
FileId,
|
||||
} from "../element/types";
|
||||
import type { NormalizedZoomValue } from "../types";
|
||||
|
||||
const { h } = window;
|
||||
const mouse = new Pointer("mouse");
|
||||
|
||||
vi.mock("../data/blob", async (actual) => {
|
||||
const orig: Object = await actual();
|
||||
return {
|
||||
...orig,
|
||||
resizeImageFile: (imageFile: File) => imageFile,
|
||||
generateIdFromFile: () => "fileId" as FileId,
|
||||
};
|
||||
beforeEach(() => {
|
||||
const generateIdSpy = vi.spyOn(blobModule, "generateIdFromFile");
|
||||
const resizeFileSpy = vi.spyOn(blobModule, "resizeImageFile");
|
||||
|
||||
generateIdSpy.mockImplementation(() => Promise.resolve("fileId" as FileId));
|
||||
resizeFileSpy.mockImplementation((file: File) => Promise.resolve(file));
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
|
|
|
@ -1,811 +0,0 @@
|
|||
/* eslint-disable no-lone-blocks */
|
||||
import { generateKeyBetween } from "fractional-indexing";
|
||||
|
||||
import { InvalidFractionalIndexError } from "../errors";
|
||||
import {
|
||||
syncInvalidIndices,
|
||||
syncMovedIndices,
|
||||
validateFractionalIndices,
|
||||
} from "../fractionalIndex";
|
||||
import { arrayToMap } from "../utils";
|
||||
|
||||
import { deepCopyElement } from "../element/duplicate";
|
||||
|
||||
import { API } from "./helpers/api";
|
||||
|
||||
import type { ExcalidrawElement, FractionalIndex } from "../element/types";
|
||||
|
||||
describe("sync invalid indices with array order", () => {
|
||||
describe("should NOT sync empty array", () => {
|
||||
testMovedIndicesSync({
|
||||
elements: [],
|
||||
movedElements: [],
|
||||
expect: {
|
||||
unchangedElements: [],
|
||||
validInput: true,
|
||||
},
|
||||
});
|
||||
|
||||
testInvalidIndicesSync({
|
||||
elements: [],
|
||||
expect: {
|
||||
unchangedElements: [],
|
||||
validInput: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("should NOT sync when index is well defined", () => {
|
||||
testMovedIndicesSync({
|
||||
elements: [{ id: "A", index: "a1" }],
|
||||
movedElements: [],
|
||||
expect: {
|
||||
unchangedElements: ["A"],
|
||||
validInput: true,
|
||||
},
|
||||
});
|
||||
|
||||
testInvalidIndicesSync({
|
||||
elements: [{ id: "A", index: "a1" }],
|
||||
expect: {
|
||||
unchangedElements: ["A"],
|
||||
validInput: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("should NOT sync when indices are well defined", () => {
|
||||
testMovedIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a1" },
|
||||
{ id: "B", index: "a2" },
|
||||
{ id: "C", index: "a3" },
|
||||
],
|
||||
movedElements: [],
|
||||
expect: {
|
||||
unchangedElements: ["A", "B", "C"],
|
||||
validInput: true,
|
||||
},
|
||||
});
|
||||
|
||||
testInvalidIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a1" },
|
||||
{ id: "B", index: "a2" },
|
||||
{ id: "C", index: "a3" },
|
||||
],
|
||||
expect: {
|
||||
unchangedElements: ["A", "B", "C"],
|
||||
validInput: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("should sync when fractional index is not defined", () => {
|
||||
testMovedIndicesSync({
|
||||
elements: [{ id: "A" }],
|
||||
movedElements: ["A"],
|
||||
expect: {
|
||||
unchangedElements: [],
|
||||
},
|
||||
});
|
||||
|
||||
testInvalidIndicesSync({
|
||||
elements: [{ id: "A" }],
|
||||
expect: {
|
||||
unchangedElements: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("should sync when fractional indices are duplicated", () => {
|
||||
testInvalidIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a1" },
|
||||
{ id: "B", index: "a1" },
|
||||
],
|
||||
expect: {
|
||||
unchangedElements: ["A"],
|
||||
},
|
||||
});
|
||||
|
||||
testInvalidIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a1" },
|
||||
{ id: "B", index: "a1" },
|
||||
],
|
||||
expect: {
|
||||
unchangedElements: ["A"],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("should sync when a fractional index is out of order", () => {
|
||||
testMovedIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a2" },
|
||||
{ id: "B", index: "a1" },
|
||||
],
|
||||
movedElements: ["B"],
|
||||
expect: {
|
||||
unchangedElements: ["A"],
|
||||
},
|
||||
});
|
||||
|
||||
testMovedIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a2" },
|
||||
{ id: "B", index: "a1" },
|
||||
],
|
||||
movedElements: ["A"],
|
||||
expect: {
|
||||
unchangedElements: ["B"],
|
||||
},
|
||||
});
|
||||
|
||||
testInvalidIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a2" },
|
||||
{ id: "B", index: "a1" },
|
||||
],
|
||||
expect: {
|
||||
unchangedElements: ["A"],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("should sync when fractional indices are out of order", () => {
|
||||
testMovedIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a3" },
|
||||
{ id: "B", index: "a2" },
|
||||
{ id: "C", index: "a1" },
|
||||
],
|
||||
movedElements: ["B", "C"],
|
||||
expect: {
|
||||
unchangedElements: ["A"],
|
||||
},
|
||||
});
|
||||
|
||||
testInvalidIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a3" },
|
||||
{ id: "B", index: "a2" },
|
||||
{ id: "C", index: "a1" },
|
||||
],
|
||||
expect: {
|
||||
unchangedElements: ["A"],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("should sync when incorrect fractional index is in between correct ones ", () => {
|
||||
testMovedIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a1" },
|
||||
{ id: "B", index: "a0" },
|
||||
{ id: "C", index: "a2" },
|
||||
],
|
||||
movedElements: ["B"],
|
||||
expect: {
|
||||
unchangedElements: ["A", "C"],
|
||||
},
|
||||
});
|
||||
|
||||
testInvalidIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a1" },
|
||||
{ id: "B", index: "a0" },
|
||||
{ id: "C", index: "a2" },
|
||||
],
|
||||
expect: {
|
||||
unchangedElements: ["A", "C"],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("should sync when incorrect fractional index is on top and duplicated below", () => {
|
||||
testMovedIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a1" },
|
||||
{ id: "B", index: "a2" },
|
||||
{ id: "C", index: "a1" },
|
||||
],
|
||||
movedElements: ["C"],
|
||||
expect: {
|
||||
unchangedElements: ["A", "B"],
|
||||
},
|
||||
});
|
||||
|
||||
testInvalidIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a1" },
|
||||
{ id: "B", index: "a2" },
|
||||
{ id: "C", index: "a1" },
|
||||
],
|
||||
expect: {
|
||||
unchangedElements: ["A", "B"],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("should sync when given a mix of duplicate / invalid indices", () => {
|
||||
testMovedIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a0" },
|
||||
{ id: "B", index: "a2" },
|
||||
{ id: "C", index: "a1" },
|
||||
{ id: "D", index: "a1" },
|
||||
{ id: "E", index: "a2" },
|
||||
],
|
||||
movedElements: ["C", "D", "E"],
|
||||
expect: {
|
||||
unchangedElements: ["A", "B"],
|
||||
},
|
||||
});
|
||||
|
||||
testInvalidIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a0" },
|
||||
{ id: "B", index: "a2" },
|
||||
{ id: "C", index: "a1" },
|
||||
{ id: "D", index: "a1" },
|
||||
{ id: "E", index: "a2" },
|
||||
],
|
||||
expect: {
|
||||
unchangedElements: ["A", "B"],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("should sync when given a mix of undefined / invalid indices", () => {
|
||||
testMovedIndicesSync({
|
||||
elements: [
|
||||
{ id: "A" },
|
||||
{ id: "B" },
|
||||
{ id: "C", index: "a0" },
|
||||
{ id: "D", index: "a2" },
|
||||
{ id: "E" },
|
||||
{ id: "F", index: "a3" },
|
||||
{ id: "G" },
|
||||
{ id: "H", index: "a1" },
|
||||
{ id: "I", index: "a2" },
|
||||
{ id: "J" },
|
||||
],
|
||||
movedElements: ["A", "B", "E", "G", "H", "I", "J"],
|
||||
expect: {
|
||||
unchangedElements: ["C", "D", "F"],
|
||||
},
|
||||
});
|
||||
|
||||
testInvalidIndicesSync({
|
||||
elements: [
|
||||
{ id: "A" },
|
||||
{ id: "B" },
|
||||
{ id: "C", index: "a0" },
|
||||
{ id: "D", index: "a2" },
|
||||
{ id: "E" },
|
||||
{ id: "F", index: "a3" },
|
||||
{ id: "G" },
|
||||
{ id: "H", index: "a1" },
|
||||
{ id: "I", index: "a2" },
|
||||
{ id: "J" },
|
||||
],
|
||||
expect: {
|
||||
unchangedElements: ["C", "D", "F"],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("should sync all moved elements regardless of their validity", () => {
|
||||
testMovedIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a2" },
|
||||
{ id: "B", index: "a4" },
|
||||
],
|
||||
movedElements: ["A"],
|
||||
expect: {
|
||||
validInput: true,
|
||||
unchangedElements: ["B"],
|
||||
},
|
||||
});
|
||||
|
||||
testMovedIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a2" },
|
||||
{ id: "B", index: "a4" },
|
||||
],
|
||||
movedElements: ["B"],
|
||||
expect: {
|
||||
validInput: true,
|
||||
unchangedElements: ["A"],
|
||||
},
|
||||
});
|
||||
|
||||
testMovedIndicesSync({
|
||||
elements: [
|
||||
{ id: "C", index: "a2" },
|
||||
{ id: "D", index: "a3" },
|
||||
{ id: "A", index: "a0" },
|
||||
{ id: "B", index: "a1" },
|
||||
],
|
||||
movedElements: ["C", "D"],
|
||||
expect: {
|
||||
unchangedElements: ["A", "B"],
|
||||
},
|
||||
});
|
||||
|
||||
testMovedIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a1" },
|
||||
{ id: "B", index: "a2" },
|
||||
{ id: "D", index: "a4" },
|
||||
{ id: "C", index: "a3" },
|
||||
{ id: "F", index: "a6" },
|
||||
{ id: "E", index: "a5" },
|
||||
{ id: "H", index: "a8" },
|
||||
{ id: "G", index: "a7" },
|
||||
{ id: "I", index: "a9" },
|
||||
],
|
||||
movedElements: ["D", "F", "H"],
|
||||
expect: {
|
||||
unchangedElements: ["A", "B", "C", "E", "G", "I"],
|
||||
},
|
||||
});
|
||||
|
||||
{
|
||||
testMovedIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a1" },
|
||||
{ id: "B", index: "a0" },
|
||||
{ id: "C", index: "a2" },
|
||||
],
|
||||
movedElements: ["B", "C"],
|
||||
expect: {
|
||||
unchangedElements: ["A"],
|
||||
},
|
||||
});
|
||||
|
||||
testMovedIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a1" },
|
||||
{ id: "B", index: "a0" },
|
||||
{ id: "C", index: "a2" },
|
||||
],
|
||||
movedElements: ["A", "B"],
|
||||
expect: {
|
||||
unchangedElements: ["C"],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
testMovedIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a0" },
|
||||
{ id: "B", index: "a2" },
|
||||
{ id: "C", index: "a1" },
|
||||
{ id: "D", index: "a1" },
|
||||
{ id: "E", index: "a2" },
|
||||
],
|
||||
movedElements: ["B", "D", "E"],
|
||||
expect: {
|
||||
unchangedElements: ["A", "C"],
|
||||
},
|
||||
});
|
||||
|
||||
testMovedIndicesSync({
|
||||
elements: [
|
||||
{ id: "A" },
|
||||
{ id: "B" },
|
||||
{ id: "C", index: "a0" },
|
||||
{ id: "D", index: "a2" },
|
||||
{ id: "E" },
|
||||
{ id: "F", index: "a3" },
|
||||
{ id: "G" },
|
||||
{ id: "H", index: "a1" },
|
||||
{ id: "I", index: "a2" },
|
||||
{ id: "J" },
|
||||
],
|
||||
movedElements: ["A", "B", "D", "E", "F", "G", "J"],
|
||||
expect: {
|
||||
unchangedElements: ["C", "H", "I"],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("should generate fractions for explicitly moved elements", () => {
|
||||
describe("should generate a fraction between 'A' and 'C'", () => {
|
||||
testMovedIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a1" },
|
||||
// doing actual fractions, without jitter 'a1' becomes 'a1V'
|
||||
// as V is taken as the charset's middle-right value
|
||||
{ id: "B", index: "a1" },
|
||||
{ id: "C", index: "a2" },
|
||||
],
|
||||
movedElements: ["B"],
|
||||
expect: {
|
||||
unchangedElements: ["A", "C"],
|
||||
},
|
||||
});
|
||||
|
||||
testInvalidIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a1" },
|
||||
{ id: "B", index: "a1" },
|
||||
{ id: "C", index: "a2" },
|
||||
],
|
||||
expect: {
|
||||
// as above, B will become fractional
|
||||
unchangedElements: ["A", "C"],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("should generate fractions given duplicated indices", () => {
|
||||
testMovedIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a01" },
|
||||
{ id: "B", index: "a01" },
|
||||
{ id: "C", index: "a01" },
|
||||
{ id: "D", index: "a01" },
|
||||
{ id: "E", index: "a02" },
|
||||
{ id: "F", index: "a02" },
|
||||
{ id: "G", index: "a02" },
|
||||
],
|
||||
movedElements: ["B", "C", "D", "E", "F"],
|
||||
expect: {
|
||||
unchangedElements: ["A", "G"],
|
||||
},
|
||||
});
|
||||
|
||||
testMovedIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a01" },
|
||||
{ id: "B", index: "a01" },
|
||||
{ id: "C", index: "a01" },
|
||||
{ id: "D", index: "a01" },
|
||||
{ id: "E", index: "a02" },
|
||||
{ id: "F", index: "a02" },
|
||||
{ id: "G", index: "a02" },
|
||||
],
|
||||
movedElements: ["A", "C", "D", "E", "G"],
|
||||
expect: {
|
||||
unchangedElements: ["B", "F"],
|
||||
},
|
||||
});
|
||||
|
||||
testMovedIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a01" },
|
||||
{ id: "B", index: "a01" },
|
||||
{ id: "C", index: "a01" },
|
||||
{ id: "D", index: "a01" },
|
||||
{ id: "E", index: "a02" },
|
||||
{ id: "F", index: "a02" },
|
||||
{ id: "G", index: "a02" },
|
||||
],
|
||||
movedElements: ["B", "C", "D", "F", "G"],
|
||||
expect: {
|
||||
unchangedElements: ["A", "E"],
|
||||
},
|
||||
});
|
||||
|
||||
testInvalidIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a01" },
|
||||
{ id: "B", index: "a01" },
|
||||
{ id: "C", index: "a01" },
|
||||
{ id: "D", index: "a01" },
|
||||
{ id: "E", index: "a02" },
|
||||
{ id: "F", index: "a02" },
|
||||
{ id: "G", index: "a02" },
|
||||
],
|
||||
expect: {
|
||||
// notice fallback considers first item (E) as a valid one
|
||||
unchangedElements: ["A", "E"],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("should be able to sync 20K invalid indices", () => {
|
||||
const length = 20_000;
|
||||
|
||||
describe("should sync all empty indices", () => {
|
||||
const elements = Array.from({ length }).map((_, index) => ({
|
||||
id: `A_${index}`,
|
||||
}));
|
||||
|
||||
testMovedIndicesSync({
|
||||
// elements without fractional index
|
||||
elements,
|
||||
movedElements: Array.from({ length }).map((_, index) => `A_${index}`),
|
||||
expect: {
|
||||
unchangedElements: [],
|
||||
},
|
||||
});
|
||||
|
||||
testInvalidIndicesSync({
|
||||
// elements without fractional index
|
||||
elements,
|
||||
expect: {
|
||||
unchangedElements: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("should sync all but last index given a growing array of indices", () => {
|
||||
let lastIndex: string | null = null;
|
||||
|
||||
const elements = Array.from({ length }).map((_, index) => {
|
||||
// going up from 'a0'
|
||||
lastIndex = generateKeyBetween(lastIndex, null);
|
||||
|
||||
return {
|
||||
id: `A_${index}`,
|
||||
// assigning the last generated index, so sync can go down from there
|
||||
// without jitter lastIndex is 'c4BZ' for 20000th element
|
||||
index: index === length - 1 ? lastIndex : undefined,
|
||||
};
|
||||
});
|
||||
const movedElements = Array.from({ length }).map(
|
||||
(_, index) => `A_${index}`,
|
||||
);
|
||||
// remove last element
|
||||
movedElements.pop();
|
||||
|
||||
testMovedIndicesSync({
|
||||
elements,
|
||||
movedElements,
|
||||
expect: {
|
||||
unchangedElements: [`A_${length - 1}`],
|
||||
},
|
||||
});
|
||||
|
||||
testInvalidIndicesSync({
|
||||
elements,
|
||||
expect: {
|
||||
unchangedElements: [`A_${length - 1}`],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("should sync all but first index given a declining array of indices", () => {
|
||||
let lastIndex: string | null = null;
|
||||
|
||||
const elements = Array.from({ length }).map((_, index) => {
|
||||
// going down from 'a0'
|
||||
lastIndex = generateKeyBetween(null, lastIndex);
|
||||
|
||||
return {
|
||||
id: `A_${index}`,
|
||||
// without jitter lastIndex is 'XvoR' for 20000th element
|
||||
index: lastIndex,
|
||||
};
|
||||
});
|
||||
const movedElements = Array.from({ length }).map(
|
||||
(_, index) => `A_${index}`,
|
||||
);
|
||||
// remove first element
|
||||
movedElements.shift();
|
||||
|
||||
testMovedIndicesSync({
|
||||
elements,
|
||||
movedElements,
|
||||
expect: {
|
||||
unchangedElements: [`A_0`],
|
||||
},
|
||||
});
|
||||
|
||||
testInvalidIndicesSync({
|
||||
elements,
|
||||
expect: {
|
||||
unchangedElements: [`A_0`],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("should automatically fallback to fixing all invalid indices", () => {
|
||||
describe("should fallback to syncing duplicated indices when moved elements are empty", () => {
|
||||
testMovedIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a1" },
|
||||
{ id: "B", index: "a1" },
|
||||
{ id: "C", index: "a1" },
|
||||
],
|
||||
// the validation will throw as nothing was synced
|
||||
// therefore it will lead to triggering the fallback and fixing all invalid indices
|
||||
movedElements: [],
|
||||
expect: {
|
||||
unchangedElements: ["A"],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("should fallback to syncing undefined / invalid indices when moved elements are empty", () => {
|
||||
testMovedIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a1" },
|
||||
{ id: "B" },
|
||||
{ id: "C", index: "a0" },
|
||||
],
|
||||
// since elements are invalid, this will fail the validation
|
||||
// leading to fallback fixing "B" and "C"
|
||||
movedElements: [],
|
||||
expect: {
|
||||
unchangedElements: ["A"],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("should fallback to syncing unordered indices when moved element is invalid", () => {
|
||||
testMovedIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a1" },
|
||||
{ id: "B", index: "a2" },
|
||||
{ id: "C", index: "a1" },
|
||||
],
|
||||
movedElements: ["A"],
|
||||
expect: {
|
||||
unchangedElements: ["A", "B"],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("should fallback when trying to generate an index in between unordered elements", () => {
|
||||
testMovedIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a2" },
|
||||
{ id: "B" },
|
||||
{ id: "C", index: "a1" },
|
||||
],
|
||||
// 'B' is invalid, but so is 'C', which was not marked as moved
|
||||
// therefore it will try to generate a key between 'a2' and 'a1'
|
||||
// which it cannot do, thus will throw during generation and automatically fallback
|
||||
movedElements: ["B"],
|
||||
expect: {
|
||||
unchangedElements: ["A"],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("should fallback when trying to generate an index in between duplicate indices", () => {
|
||||
testMovedIndicesSync({
|
||||
elements: [
|
||||
{ id: "A", index: "a01" },
|
||||
{ id: "B" },
|
||||
{ id: "C" },
|
||||
{ id: "D", index: "a01" },
|
||||
{ id: "E", index: "a01" },
|
||||
{ id: "F", index: "a01" },
|
||||
{ id: "G" },
|
||||
{ id: "I", index: "a03" },
|
||||
{ id: "H" },
|
||||
],
|
||||
// missed "E" therefore upper bound for 'B' is a01, while lower bound is 'a02'
|
||||
// therefore, similarly to above, it will fail during key generation and lead to fallback
|
||||
movedElements: ["B", "C", "D", "F", "G", "H"],
|
||||
expect: {
|
||||
unchangedElements: ["A", "I"],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function testMovedIndicesSync(args: {
|
||||
elements: { id: string; index?: string }[];
|
||||
movedElements: string[];
|
||||
expect: {
|
||||
unchangedElements: string[];
|
||||
validInput?: true;
|
||||
};
|
||||
}) {
|
||||
const [elements, movedElements] = prepareArguments(
|
||||
args.elements,
|
||||
args.movedElements,
|
||||
);
|
||||
const expectUnchangedElements = arrayToMap(
|
||||
args.expect.unchangedElements.map((x) => ({ id: x })),
|
||||
);
|
||||
|
||||
test(
|
||||
"should sync invalid indices of moved elements or fallback",
|
||||
elements,
|
||||
movedElements,
|
||||
expectUnchangedElements,
|
||||
args.expect.validInput,
|
||||
);
|
||||
}
|
||||
|
||||
function testInvalidIndicesSync(args: {
|
||||
elements: { id: string; index?: string }[];
|
||||
expect: {
|
||||
unchangedElements: string[];
|
||||
validInput?: true;
|
||||
};
|
||||
}) {
|
||||
const [elements] = prepareArguments(args.elements);
|
||||
const expectUnchangedElements = arrayToMap(
|
||||
args.expect.unchangedElements.map((x) => ({ id: x })),
|
||||
);
|
||||
|
||||
test(
|
||||
"should sync invalid indices of all elements",
|
||||
elements,
|
||||
undefined,
|
||||
expectUnchangedElements,
|
||||
args.expect.validInput,
|
||||
);
|
||||
}
|
||||
|
||||
function prepareArguments(
|
||||
elementsLike: { id: string; index?: string }[],
|
||||
movedElementsIds?: string[],
|
||||
): [ExcalidrawElement[], Map<string, ExcalidrawElement> | undefined] {
|
||||
const elements = elementsLike.map((x) =>
|
||||
API.createElement({ id: x.id, index: x.index as FractionalIndex }),
|
||||
);
|
||||
const movedMap = arrayToMap(movedElementsIds || []);
|
||||
const movedElements = movedElementsIds
|
||||
? arrayToMap(elements.filter((x) => movedMap.has(x.id)))
|
||||
: undefined;
|
||||
|
||||
return [elements, movedElements];
|
||||
}
|
||||
|
||||
function test(
|
||||
name: string,
|
||||
elements: ExcalidrawElement[],
|
||||
movedElements: Map<string, ExcalidrawElement> | undefined,
|
||||
expectUnchangedElements: Map<string, { id: string }>,
|
||||
expectValidInput?: boolean,
|
||||
) {
|
||||
it(name, () => {
|
||||
// ensure the input is invalid (unless the flag is on)
|
||||
if (!expectValidInput) {
|
||||
expect(() =>
|
||||
validateFractionalIndices(elements, {
|
||||
shouldThrow: true,
|
||||
includeBoundTextValidation: true,
|
||||
ignoreLogs: true,
|
||||
}),
|
||||
).toThrowError(InvalidFractionalIndexError);
|
||||
}
|
||||
|
||||
// clone due to mutation
|
||||
const clonedElements = elements.map((x) => deepCopyElement(x));
|
||||
|
||||
// act
|
||||
const syncedElements = movedElements
|
||||
? syncMovedIndices(clonedElements, movedElements)
|
||||
: syncInvalidIndices(clonedElements);
|
||||
|
||||
expect(syncedElements.length).toBe(elements.length);
|
||||
expect(() =>
|
||||
validateFractionalIndices(syncedElements, {
|
||||
shouldThrow: true,
|
||||
includeBoundTextValidation: true,
|
||||
ignoreLogs: true,
|
||||
}),
|
||||
).not.toThrowError(InvalidFractionalIndexError);
|
||||
|
||||
syncedElements.forEach((synced, index) => {
|
||||
const element = elements[index];
|
||||
// ensure the order hasn't changed
|
||||
expect(synced.id).toBe(element.id);
|
||||
|
||||
if (expectUnchangedElements.has(synced.id)) {
|
||||
// ensure we didn't mutate where we didn't want to mutate
|
||||
expect(synced.index).toBe(elements[index].index);
|
||||
expect(synced.version).toBe(elements[index].version);
|
||||
} else {
|
||||
expect(synced.index).not.toBe(elements[index].index);
|
||||
// ensure we mutated just once, even with fallback triggered
|
||||
expect(synced.version).toBe(elements[index].version + 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
|
@ -4,29 +4,26 @@ import util from "util";
|
|||
|
||||
import { pointFrom, type LocalPoint, type Radians } from "@excalidraw/math";
|
||||
|
||||
import { getDefaultAppState } from "../../appState";
|
||||
import { createTestHook } from "../../components/App";
|
||||
import { DEFAULT_VERTICAL_ALIGN, ROUNDNESS } from "../../constants";
|
||||
import { getMimeType } from "../../data/blob";
|
||||
import { newElement, newTextElement, newLinearElement } from "../../element";
|
||||
import { mutateElement } from "../../element/mutateElement";
|
||||
import { DEFAULT_VERTICAL_ALIGN, ROUNDNESS, assertNever } from "@excalidraw/common";
|
||||
|
||||
import { mutateElement } from "@excalidraw/element/mutateElement";
|
||||
import {
|
||||
newArrowElement,
|
||||
newElement,
|
||||
newEmbeddableElement,
|
||||
newFrameElement,
|
||||
newFreeDrawElement,
|
||||
newIframeElement,
|
||||
newImageElement,
|
||||
newLinearElement,
|
||||
newMagicFrameElement,
|
||||
} from "../../element/newElement";
|
||||
import { isLinearElementType } from "../../element/typeChecks";
|
||||
import { selectGroupsForSelectedElements } from "../../groups";
|
||||
import { getSelectedElements } from "../../scene/selection";
|
||||
import { assertNever } from "../../utils";
|
||||
import { GlobalTestState, createEvent, fireEvent, act } from "../test-utils";
|
||||
newTextElement,
|
||||
} from "@excalidraw/element/newElement";
|
||||
|
||||
import { isLinearElementType } from "@excalidraw/element/typeChecks";
|
||||
import { getSelectedElements } from "@excalidraw/element/selection";
|
||||
import { selectGroupsForSelectedElements } from "@excalidraw/element/groups";
|
||||
|
||||
import type { Action } from "../../actions/types";
|
||||
import type App from "../../components/App";
|
||||
import type {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawGenericElement,
|
||||
|
@ -41,9 +38,19 @@ import type {
|
|||
ExcalidrawElbowArrowElement,
|
||||
ExcalidrawArrowElement,
|
||||
FixedSegment,
|
||||
} from "../../element/types";
|
||||
} from "@excalidraw/element/types";
|
||||
|
||||
import type { Mutable } from "@excalidraw/common/utility-types";
|
||||
|
||||
import { getMimeType } from "../../data/blob";
|
||||
import { createTestHook } from "../../components/App";
|
||||
import { getDefaultAppState } from "../../appState";
|
||||
import { GlobalTestState, createEvent, fireEvent, act } from "../test-utils";
|
||||
|
||||
import type { Action } from "../../actions/types";
|
||||
import type App from "../../components/App";
|
||||
import type { AppState } from "../../types";
|
||||
import type { Mutable } from "../../utility-types";
|
||||
|
||||
|
||||
const readFile = util.promisify(fs.readFile);
|
||||
// so that window.h is available when App.tsx is not imported as well.
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { pointFrom, pointRotateRads } from "@excalidraw/math";
|
||||
|
||||
import type { GlobalPoint, LocalPoint, Radians } from "@excalidraw/math";
|
||||
|
||||
import { createTestHook } from "../../components/App";
|
||||
import { getCommonBounds, getElementPointsCoords } from "../../element/bounds";
|
||||
import { cropElement } from "../../element/cropElement";
|
||||
import { mutateElement } from "../../element/mutateElement";
|
||||
import {
|
||||
getCommonBounds,
|
||||
getElementPointsCoords,
|
||||
} from "@excalidraw/element/bounds";
|
||||
import { cropElement } from "@excalidraw/element/cropElement";
|
||||
import { mutateElement } from "@excalidraw/element/mutateElement";
|
||||
import {
|
||||
getTransformHandles,
|
||||
getTransformHandlesFromCoords,
|
||||
|
@ -13,21 +13,18 @@ import {
|
|||
OMIT_SIDES_FOR_MULTIPLE_ELEMENTS,
|
||||
type TransformHandle,
|
||||
type TransformHandleDirection,
|
||||
} from "../../element/transformHandles";
|
||||
} from "@excalidraw/element/transformHandles";
|
||||
import {
|
||||
isLinearElement,
|
||||
isFreeDrawElement,
|
||||
isTextElement,
|
||||
isFrameLikeElement,
|
||||
} from "../../element/typeChecks";
|
||||
import { KEYS } from "../../keys";
|
||||
import { arrayToMap } from "../../utils";
|
||||
import { getTextEditor } from "../queries/dom";
|
||||
import { act, fireEvent, GlobalTestState, screen } from "../test-utils";
|
||||
} from "@excalidraw/element/typeChecks";
|
||||
import { KEYS, arrayToMap } from "@excalidraw/common";
|
||||
|
||||
import { API } from "./api";
|
||||
import type { GlobalPoint, LocalPoint, Radians } from "@excalidraw/math";
|
||||
|
||||
import type { TransformHandleType } from "../../element/transformHandles";
|
||||
import type { TransformHandleType } from "@excalidraw/element/transformHandles";
|
||||
import type {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawLinearElement,
|
||||
|
@ -39,7 +36,14 @@ import type {
|
|||
ExcalidrawTextContainer,
|
||||
ExcalidrawTextElementWithContainer,
|
||||
ExcalidrawImageElement,
|
||||
} from "../../element/types";
|
||||
} from "@excalidraw/element/types";
|
||||
|
||||
import { createTestHook } from "../../components/App";
|
||||
import { getTextEditor } from "../queries/dom";
|
||||
import { act, fireEvent, GlobalTestState, screen } from "../test-utils";
|
||||
|
||||
import { API } from "./api";
|
||||
|
||||
import type { ToolType } from "../../types";
|
||||
|
||||
// so that window.h is available when App.tsx is not imported as well.
|
||||
|
|
|
@ -8,10 +8,35 @@ import {
|
|||
import { vi } from "vitest";
|
||||
import { pointFrom } from "@excalidraw/math";
|
||||
|
||||
import { newElementWith } from "@excalidraw/element/mutateElement";
|
||||
|
||||
import {
|
||||
EXPORT_DATA_TYPES,
|
||||
MIME_TYPES,
|
||||
ORIG_ID,
|
||||
KEYS,
|
||||
arrayToMap,
|
||||
COLOR_PALETTE,
|
||||
DEFAULT_ELEMENT_BACKGROUND_COLOR_INDEX,
|
||||
DEFAULT_ELEMENT_STROKE_COLOR_INDEX,
|
||||
} from "@excalidraw/common";
|
||||
|
||||
import "@excalidraw/utils/test-utils";
|
||||
|
||||
import type { LocalPoint, Radians } from "@excalidraw/math";
|
||||
|
||||
import type {
|
||||
ExcalidrawElbowArrowElement,
|
||||
ExcalidrawFrameElement,
|
||||
ExcalidrawGenericElement,
|
||||
ExcalidrawLinearElement,
|
||||
ExcalidrawTextElement,
|
||||
FixedPointBinding,
|
||||
FractionalIndex,
|
||||
SceneElementsMap,
|
||||
} from "@excalidraw/element/types";
|
||||
|
||||
import "../global.d.ts";
|
||||
import "../../utils/test-utils";
|
||||
|
||||
import {
|
||||
actionSendBackward,
|
||||
|
@ -23,17 +48,8 @@ import { actionToggleViewMode } from "../actions/actionToggleViewMode";
|
|||
import { getDefaultAppState } from "../appState";
|
||||
import { HistoryEntry } from "../history";
|
||||
import { Excalidraw } from "../index";
|
||||
import { KEYS } from "../keys";
|
||||
import * as StaticScene from "../renderer/staticScene";
|
||||
import { EXPORT_DATA_TYPES, MIME_TYPES, ORIG_ID } from "../constants";
|
||||
import { Snapshot, CaptureUpdateAction } from "../store";
|
||||
import { arrayToMap } from "../utils";
|
||||
import {
|
||||
COLOR_PALETTE,
|
||||
DEFAULT_ELEMENT_BACKGROUND_COLOR_INDEX,
|
||||
DEFAULT_ELEMENT_STROKE_COLOR_INDEX,
|
||||
} from "../colors";
|
||||
import { newElementWith } from "../element/mutateElement";
|
||||
import { AppStateChange, ElementsChange } from "../change";
|
||||
|
||||
import { API } from "./helpers/api";
|
||||
|
@ -47,16 +63,6 @@ import {
|
|||
getCloneByOrigId,
|
||||
} from "./test-utils";
|
||||
|
||||
import type {
|
||||
ExcalidrawElbowArrowElement,
|
||||
ExcalidrawFrameElement,
|
||||
ExcalidrawGenericElement,
|
||||
ExcalidrawLinearElement,
|
||||
ExcalidrawTextElement,
|
||||
FixedPointBinding,
|
||||
FractionalIndex,
|
||||
SceneElementsMap,
|
||||
} from "../element/types";
|
||||
import type { AppState } from "../types";
|
||||
|
||||
const { h } = window;
|
||||
|
|
|
@ -2,18 +2,21 @@ import { act, queryByTestId } from "@testing-library/react";
|
|||
import React from "react";
|
||||
import { vi } from "vitest";
|
||||
|
||||
import { MIME_TYPES, ORIG_ID } from "../constants";
|
||||
import { MIME_TYPES, ORIG_ID } from "@excalidraw/common";
|
||||
|
||||
import { getCommonBoundingBox } from "@excalidraw/element/bounds";
|
||||
|
||||
import type { ExcalidrawGenericElement } from "@excalidraw/element/types";
|
||||
|
||||
import { parseLibraryJSON } from "../data/blob";
|
||||
import { serializeLibraryAsJSON } from "../data/json";
|
||||
import { distributeLibraryItemsOnSquareGrid } from "../data/library";
|
||||
import { getCommonBoundingBox } from "../element/bounds";
|
||||
import { Excalidraw } from "../index";
|
||||
|
||||
import { API } from "./helpers/api";
|
||||
import { UI } from "./helpers/ui";
|
||||
import { fireEvent, getCloneByOrigId, render, waitFor } from "./test-utils";
|
||||
|
||||
import type { ExcalidrawGenericElement } from "../element/types";
|
||||
import type { LibraryItem, LibraryItems } from "../types";
|
||||
|
||||
const { h } = window;
|
||||
|
|
|
@ -3,23 +3,35 @@ import { act, queryByTestId, queryByText } from "@testing-library/react";
|
|||
import React from "react";
|
||||
import { vi } from "vitest";
|
||||
|
||||
import type { GlobalPoint } from "@excalidraw/math";
|
||||
import {
|
||||
ROUNDNESS,
|
||||
VERTICAL_ALIGN,
|
||||
KEYS,
|
||||
reseed,
|
||||
arrayToMap,
|
||||
} from "@excalidraw/common";
|
||||
|
||||
import { ROUNDNESS, VERTICAL_ALIGN } from "../constants";
|
||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||
import { LinearElementEditor } from "@excalidraw/element/linearElementEditor";
|
||||
import {
|
||||
getBoundTextElementPosition,
|
||||
getBoundTextMaxWidth,
|
||||
} from "../element/textElement";
|
||||
import * as textElementUtils from "../element/textElement";
|
||||
import { wrapText } from "../element/textWrapping";
|
||||
} from "@excalidraw/element/textElement";
|
||||
import * as textElementUtils from "@excalidraw/element/textElement";
|
||||
import { wrapText } from "@excalidraw/element/textWrapping";
|
||||
|
||||
import type { GlobalPoint } from "@excalidraw/math";
|
||||
|
||||
import type {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawLinearElement,
|
||||
ExcalidrawTextElementWithContainer,
|
||||
FontString,
|
||||
} from "@excalidraw/element/types";
|
||||
|
||||
import { Excalidraw, mutateElement } from "../index";
|
||||
import { KEYS } from "../keys";
|
||||
import { reseed } from "../random";
|
||||
import * as InteractiveCanvas from "../renderer/interactiveScene";
|
||||
import * as StaticScene from "../renderer/staticScene";
|
||||
import { API } from "../tests/helpers/api";
|
||||
import { arrayToMap } from "../utils";
|
||||
|
||||
import { Keyboard, Pointer, UI } from "./helpers/ui";
|
||||
import {
|
||||
|
@ -30,13 +42,6 @@ import {
|
|||
unmountComponent,
|
||||
} from "./test-utils";
|
||||
|
||||
import type {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawLinearElement,
|
||||
ExcalidrawTextElementWithContainer,
|
||||
FontString,
|
||||
} from "../element/types";
|
||||
|
||||
const renderInteractiveScene = vi.spyOn(
|
||||
InteractiveCanvas,
|
||||
"renderInteractiveScene",
|
||||
|
|
|
@ -1,23 +1,26 @@
|
|||
import React from "react";
|
||||
import { vi } from "vitest";
|
||||
|
||||
import "../../utils/test-utils";
|
||||
import { bindOrUnbindLinearElement } from "../element/binding";
|
||||
import { Excalidraw } from "../index";
|
||||
import { KEYS } from "../keys";
|
||||
import { reseed } from "../random";
|
||||
import * as InteractiveCanvas from "../renderer/interactiveScene";
|
||||
import * as StaticScene from "../renderer/staticScene";
|
||||
import { bindOrUnbindLinearElement } from "@excalidraw/element/binding";
|
||||
|
||||
import { UI, Pointer, Keyboard } from "./helpers/ui";
|
||||
import { render, fireEvent, act, unmountComponent } from "./test-utils";
|
||||
import { KEYS, reseed } from "@excalidraw/common";
|
||||
|
||||
import "@excalidraw/utils/test-utils";
|
||||
|
||||
import type {
|
||||
ExcalidrawLinearElement,
|
||||
NonDeleted,
|
||||
ExcalidrawRectangleElement,
|
||||
} from "../element/types";
|
||||
import type Scene from "../scene/Scene";
|
||||
} from "@excalidraw/element/types";
|
||||
|
||||
import type Scene from "@excalidraw/excalidraw/scene/Scene";
|
||||
|
||||
import { Excalidraw } from "../index";
|
||||
import * as InteractiveCanvas from "../renderer/interactiveScene";
|
||||
import * as StaticScene from "../renderer/staticScene";
|
||||
|
||||
import { UI, Pointer, Keyboard } from "./helpers/ui";
|
||||
import { render, fireEvent, act, unmountComponent } from "./test-utils";
|
||||
|
||||
unmountComponent();
|
||||
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import React from "react";
|
||||
import { vi } from "vitest";
|
||||
|
||||
import { KEYS, reseed } from "@excalidraw/common";
|
||||
|
||||
import type { ExcalidrawLinearElement } from "@excalidraw/element/types";
|
||||
|
||||
import { Excalidraw } from "../index";
|
||||
import { KEYS } from "../keys";
|
||||
import { reseed } from "../random";
|
||||
|
||||
import * as InteractiveCanvas from "../renderer/interactiveScene";
|
||||
import * as StaticScene from "../renderer/staticScene";
|
||||
|
||||
|
@ -15,8 +18,6 @@ import {
|
|||
unmountComponent,
|
||||
} from "./test-utils";
|
||||
|
||||
import type { ExcalidrawLinearElement } from "../element/types";
|
||||
|
||||
unmountComponent();
|
||||
|
||||
const renderInteractiveScene = vi.spyOn(
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import React from "react";
|
||||
import { vi } from "vitest";
|
||||
|
||||
import { resolvablePromise } from "@excalidraw/common";
|
||||
|
||||
import { Excalidraw, CaptureUpdateAction } from "../../index";
|
||||
import { resolvablePromise } from "../../utils";
|
||||
import { API } from "../helpers/api";
|
||||
import { Pointer } from "../helpers/ui";
|
||||
import { render } from "../test-utils";
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { queries, buildQueries } from "@testing-library/react";
|
||||
|
||||
import { TOOL_TYPE } from "../../constants";
|
||||
import { TOOL_TYPE } from "@excalidraw/common";
|
||||
|
||||
import type { ToolType } from "../../types";
|
||||
import type { ToolType } from "@excalidraw/excalidraw/types";
|
||||
|
||||
const _getAllByToolName = (container: HTMLElement, tool: ToolType | "lock") => {
|
||||
const toolTitle = tool === "lock" ? "lock" : TOOL_TYPE[tool];
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import React from "react";
|
||||
import { vi } from "vitest";
|
||||
|
||||
import { FONT_FAMILY } from "../constants";
|
||||
import { FONT_FAMILY, CODES, KEYS, reseed } from "@excalidraw/common";
|
||||
|
||||
import { setDateTimeForTests } from "@excalidraw/common";
|
||||
|
||||
import type { ExcalidrawElement } from "@excalidraw/element/types";
|
||||
|
||||
import { Excalidraw } from "../index";
|
||||
import { CODES, KEYS } from "../keys";
|
||||
import { reseed } from "../random";
|
||||
import * as StaticScene from "../renderer/staticScene";
|
||||
import { setDateTimeForTests } from "../utils";
|
||||
|
||||
import { API } from "./helpers/api";
|
||||
import { Keyboard, Pointer, UI } from "./helpers/ui";
|
||||
|
@ -19,8 +21,6 @@ import {
|
|||
unmountComponent,
|
||||
} from "./test-utils";
|
||||
|
||||
import type { ExcalidrawElement } from "../element/types";
|
||||
|
||||
const { h } = window;
|
||||
|
||||
const renderStaticScene = vi.spyOn(StaticScene, "renderStaticScene");
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,8 +1,9 @@
|
|||
import React from "react";
|
||||
import { expect } from "vitest";
|
||||
|
||||
import { reseed } from "@excalidraw/common";
|
||||
|
||||
import { Excalidraw } from "../index";
|
||||
import { reseed } from "../random";
|
||||
|
||||
import { UI } from "./helpers/ui";
|
||||
import { render, unmountComponent } from "./test-utils";
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
import { exportToCanvas, exportToSvg } from "@excalidraw/utils";
|
||||
|
||||
import { FONT_FAMILY, FRAME_STYLE } from "../../constants";
|
||||
import { FONT_FAMILY, FRAME_STYLE } from "@excalidraw/common";
|
||||
|
||||
import type {
|
||||
ExcalidrawTextElement,
|
||||
FractionalIndex,
|
||||
NonDeletedExcalidrawElement,
|
||||
} from "@excalidraw/element/types";
|
||||
|
||||
import { prepareElementsForExport } from "../../data";
|
||||
import * as exportUtils from "../../scene/export";
|
||||
import {
|
||||
|
@ -11,12 +18,6 @@ import {
|
|||
} from "../fixtures/elementFixture";
|
||||
import { API } from "../helpers/api";
|
||||
|
||||
import type {
|
||||
ExcalidrawTextElement,
|
||||
FractionalIndex,
|
||||
NonDeletedExcalidrawElement,
|
||||
} from "../../element/types";
|
||||
|
||||
describe("exportToSvg", () => {
|
||||
const ELEMENT_HEIGHT = 100;
|
||||
const ELEMENT_WIDTH = 100;
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import React from "react";
|
||||
|
||||
import { KEYS } from "@excalidraw/common";
|
||||
|
||||
import { Excalidraw } from "../index";
|
||||
import { KEYS } from "../keys";
|
||||
|
||||
import { API } from "./helpers/api";
|
||||
import { Keyboard } from "./helpers/ui";
|
||||
|
|
|
@ -1,16 +1,21 @@
|
|||
import React from "react";
|
||||
|
||||
import { CANVAS_SEARCH_TAB, CLASSES, DEFAULT_SIDEBAR } from "../constants";
|
||||
import {
|
||||
CANVAS_SEARCH_TAB,
|
||||
CLASSES,
|
||||
DEFAULT_SIDEBAR,
|
||||
KEYS,
|
||||
} from "@excalidraw/common";
|
||||
|
||||
import type { ExcalidrawTextElement } from "@excalidraw/element/types";
|
||||
|
||||
import { Excalidraw } from "../index";
|
||||
import { KEYS } from "../keys";
|
||||
|
||||
import { API } from "./helpers/api";
|
||||
import { Keyboard } from "./helpers/ui";
|
||||
import { updateTextEditor } from "./queries/dom";
|
||||
import { act, render, waitFor } from "./test-utils";
|
||||
|
||||
import type { ExcalidrawTextElement } from "../element/types";
|
||||
|
||||
const { h } = window;
|
||||
|
||||
const querySearchInput = async () => {
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import React from "react";
|
||||
import { vi } from "vitest";
|
||||
|
||||
import { KEYS, reseed } from "@excalidraw/common";
|
||||
|
||||
import { SHAPES } from "../components/shapes";
|
||||
|
||||
import { Excalidraw } from "../index";
|
||||
import { KEYS } from "../keys";
|
||||
import { reseed } from "../random";
|
||||
import * as InteractiveCanvas from "../renderer/interactiveScene";
|
||||
import * as StaticScene from "../renderer/staticScene";
|
||||
import { SHAPES } from "../shapes";
|
||||
|
||||
import { API } from "./helpers/api";
|
||||
import { Keyboard, Pointer, UI } from "./helpers/ui";
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import React from "react";
|
||||
|
||||
import { KEYS } from "@excalidraw/common";
|
||||
|
||||
import { Excalidraw } from "../index";
|
||||
import { KEYS } from "../keys";
|
||||
|
||||
import { API } from "./helpers/api";
|
||||
import { Keyboard } from "./helpers/ui";
|
||||
|
|
|
@ -9,10 +9,15 @@ import {
|
|||
} from "@testing-library/react";
|
||||
import ansi from "ansicolor";
|
||||
|
||||
import { ORIG_ID, arrayToMap } from "@excalidraw/common";
|
||||
|
||||
import { getSelectedElements } from "@excalidraw/element/selection";
|
||||
|
||||
import type { ExcalidrawElement } from "@excalidraw/element/types";
|
||||
|
||||
import type { AllPossibleKeys } from "@excalidraw/common/utility-types";
|
||||
|
||||
import { STORAGE_KEYS } from "../../../excalidraw-app/app_constants";
|
||||
import { ORIG_ID } from "../constants";
|
||||
import { getSelectedElements } from "../scene/selection";
|
||||
import { arrayToMap } from "../utils";
|
||||
|
||||
import { UI } from "./helpers/ui";
|
||||
import * as toolQueries from "./queries/toolQueries";
|
||||
|
@ -20,8 +25,6 @@ import * as toolQueries from "./queries/toolQueries";
|
|||
import type { RenderResult, RenderOptions } from "@testing-library/react";
|
||||
|
||||
import type { ImportedDataState } from "../data/types";
|
||||
import type { ExcalidrawElement } from "../element/types";
|
||||
import type { AllPossibleKeys } from "../utility-types";
|
||||
|
||||
export { cleanup as unmountComponent };
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import React from "react";
|
||||
|
||||
import { resolvablePromise } from "@excalidraw/common";
|
||||
|
||||
import { Excalidraw } from "../index";
|
||||
import { resolvablePromise } from "../utils";
|
||||
|
||||
import { Pointer } from "./helpers/ui";
|
||||
import { act, render } from "./test-utils";
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { isTransparent } from "../utils";
|
||||
import { isTransparent } from "@excalidraw/common";
|
||||
|
||||
describe("Test isTransparent", () => {
|
||||
it("should return true when color is rgb transparent", () => {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import React from "react";
|
||||
|
||||
import { CURSOR_TYPE } from "../constants";
|
||||
import { CURSOR_TYPE, KEYS } from "@excalidraw/common";
|
||||
|
||||
import { Excalidraw } from "../index";
|
||||
import { KEYS } from "../keys";
|
||||
|
||||
import { API } from "./helpers/api";
|
||||
import { Keyboard, Pointer, UI } from "./helpers/ui";
|
||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue