chore: bump @testing-library/react 12.1.5 -> 16.0.0 (#8322)

This commit is contained in:
David Luzar
2024-08-06 15:17:42 +02:00
committed by GitHub
parent 3cf14c73a3
commit f19ce30dfe
52 changed files with 1035 additions and 978 deletions
+1
View File
@@ -1,3 +1,4 @@
import React from "react";
import ReactDOM from "react-dom";
import * as StaticScene from "../renderer/staticScene";
import { reseed } from "../random";
@@ -1,4 +1,5 @@
import { act, render, waitFor } from "./test-utils";
import React from "react";
import { render, waitFor } from "./test-utils";
import { Excalidraw } from "../index";
import { expect } from "vitest";
import { getTextEditor, updateTextEditor } from "./queries/dom";
@@ -103,19 +104,9 @@ describe("Test <MermaidToExcalidraw/>", () => {
expect(dialog.querySelector('[data-testid="mermaid-error"]')).toBeNull();
expect(editor.textContent).toMatchInlineSnapshot(`
"flowchart TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[Car]"
`);
expect(editor.textContent).toMatchSnapshot();
await act(async () => {
updateTextEditor(editor, "flowchart TD1");
await new Promise((cb) => setTimeout(cb, 0));
});
updateTextEditor(editor, "flowchart TD1");
editor = await getTextEditor(selector, false);
expect(editor.textContent).toBe("flowchart TD1");
@@ -1,10 +1,19 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Test <MermaidToExcalidraw/> > should open mermaid popup when active tool is mermaid 1`] = `
"<div class="Modal Dialog ttd-dialog" role="dialog" aria-modal="true" aria-labelledby="dialog-title" data-prevent-outside-click="true"><div class="Modal__background"></div><div class="Modal__content" style="--max-width: 1200px;" tabindex="0"><div class="Island"><div class="Dialog__content"><div dir="ltr" data-orientation="horizontal" class="ttd-dialog-tabs-root"><p class="dialog-mermaid-title">Mermaid to Excalidraw</p><div data-state="active" data-orientation="horizontal" role="tabpanel" aria-labelledby="radix-:r0:-trigger-mermaid" id="radix-:r0:-content-mermaid" tabindex="0" class="ttd-dialog-content" style="animation-duration: 0s;"><div class="ttd-dialog-desc">Currently only <a href="https://mermaid.js.org/syntax/flowchart.html">Flowchart</a>,<a href="https://mermaid.js.org/syntax/sequenceDiagram.html"> Sequence, </a> and <a href="https://mermaid.js.org/syntax/classDiagram.html">Class </a>Diagrams are supported. The other types will be rendered as image in Excalidraw.</div><div class="ttd-dialog-panels"><div class="ttd-dialog-panel"><div class="ttd-dialog-panel__header"><label>Mermaid Syntax</label></div><textarea class="ttd-dialog-input" placeholder="Write Mermaid diagram defintion here...">flowchart TD
"<div class="Modal Dialog ttd-dialog" role="dialog" aria-modal="true" aria-labelledby="dialog-title" data-prevent-outside-click="true"><div class="Modal__background"></div><div class="Modal__content" style="--max-width: 1200px;" tabindex="0"><div class="Island"><div class="Dialog__content"><div dir="ltr" data-orientation="horizontal" class="ttd-dialog-tabs-root"><p class="dialog-mermaid-title">Mermaid to Excalidraw</p><div data-state="active" data-orientation="horizontal" role="tabpanel" aria-labelledby="radix-:r0:-trigger-mermaid" id="radix-:r0:-content-mermaid" tabindex="0" class="ttd-dialog-content" style=""><div class="ttd-dialog-desc">Currently only <a href="https://mermaid.js.org/syntax/flowchart.html">Flowchart</a>,<a href="https://mermaid.js.org/syntax/sequenceDiagram.html"> Sequence, </a> and <a href="https://mermaid.js.org/syntax/classDiagram.html">Class </a>Diagrams are supported. The other types will be rendered as image in Excalidraw.</div><div class="ttd-dialog-panels"><div class="ttd-dialog-panel"><div class="ttd-dialog-panel__header"><label>Mermaid Syntax</label></div><textarea class="ttd-dialog-input" placeholder="Write Mermaid diagram defintion here...">flowchart TD
A[Christmas] --&gt;|Get money| B(Go shopping)
B --&gt; C{Let me think}
C --&gt;|One| D[Laptop]
C --&gt;|Two| E[iPhone]
C --&gt;|Three| F[Car]</textarea><div class="ttd-dialog-panel-button-container invisible" style="display: flex; align-items: center;"><button type="button" class="excalidraw-button ttd-dialog-panel-button"><div class=""></div></button></div></div><div class="ttd-dialog-panel"><div class="ttd-dialog-panel__header"><label>Preview</label></div><div class="ttd-dialog-output-wrapper"><div style="opacity: 1;" class="ttd-dialog-output-canvas-container"><canvas width="89" height="158" dir="ltr"></canvas></div></div><div class="ttd-dialog-panel-button-container" style="display: flex; align-items: center;"><button type="button" class="excalidraw-button ttd-dialog-panel-button"><div class="">Insert<span><svg aria-hidden="true" focusable="false" role="img" viewBox="0 0 20 20" class="" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><g stroke-width="1.25"><path d="M4.16602 10H15.8327"></path><path d="M12.5 13.3333L15.8333 10"></path><path d="M12.5 6.66666L15.8333 9.99999"></path></g></svg></span></div></button><div class="ttd-dialog-submit-shortcut"><div class="ttd-dialog-submit-shortcut__key">Ctrl</div><div class="ttd-dialog-submit-shortcut__key">Enter</div></div></div></div></div></div></div></div></div></div></div>"
`;
exports[`Test <MermaidToExcalidraw/> > should show error in preview when mermaid library throws error 1`] = `
"flowchart TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[Car]"
`;
@@ -1,5 +1,17 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Test Linear Elements > Test bound text element > should bind text to arrow when clicked on arrow and enter pressed 1`] = `
"Online whiteboard
collaboration made
easy"
`;
exports[`Test Linear Elements > Test bound text element > should bind text to arrow when double clicked 1`] = `
"Online whiteboard
collaboration made
easy"
`;
exports[`Test Linear Elements > Test bound text element > should match styles for text editor 1`] = `
<textarea
class="excalidraw-wysiwyg"
@@ -10,3 +22,36 @@ exports[`Test Linear Elements > Test bound text element > should match styles fo
wrap="off"
/>
`;
exports[`Test Linear Elements > Test bound text element > should resize and position the bound text and bounding box correctly when 3 pointer arrow element resized 2`] = `
"Online whiteboard
collaboration made
easy"
`;
exports[`Test Linear Elements > Test bound text element > should resize and position the bound text and bounding box correctly when 3 pointer arrow element resized 6`] = `
"Online whiteboard
collaboration made easy"
`;
exports[`Test Linear Elements > Test bound text element > should resize and position the bound text correctly when 2 pointer linear element resized 2`] = `
"Online whiteboard
collaboration made
easy"
`;
exports[`Test Linear Elements > Test bound text element > should resize and position the bound text correctly when 2 pointer linear element resized 5`] = `
"Online whiteboard
collaboration made easy"
`;
exports[`Test Linear Elements > Test bound text element > should wrap the bound text when arrow bound container moves 1`] = `
"Online whiteboard
collaboration made easy"
`;
exports[`Test Linear Elements > Test bound text element > should wrap the bound text when arrow bound container moves 2`] = `
"Online whiteboard
collaboration made
easy"
`;
@@ -1,3 +1,4 @@
import React from "react";
import { Excalidraw } from "../index";
import { CODES } from "../keys";
import { API } from "../tests/helpers/api";
+30 -29
View File
@@ -1,5 +1,6 @@
import React from "react";
import ReactDOM from "react-dom";
import { render } from "./test-utils";
import { act, render } from "./test-utils";
import { Excalidraw } from "../index";
import { defaultLang, setLanguage } from "../i18n";
import { UI, Pointer, Keyboard } from "./helpers/ui";
@@ -15,8 +16,6 @@ import {
actionAlignRight,
} from "../actions";
const { h } = window;
const mouse = new Pointer("mouse");
const createAndSelectTwoRectangles = () => {
@@ -59,7 +58,9 @@ describe("aligning", () => {
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
mouse.reset();
await setLanguage(defaultLang);
await act(() => {
return setLanguage(defaultLang);
});
await render(<Excalidraw handleKeyboardGlobally={true} />);
});
@@ -156,7 +157,7 @@ describe("aligning", () => {
expect(API.getSelectedElements()[0].y).toEqual(0);
expect(API.getSelectedElements()[1].y).toEqual(110);
h.app.actionManager.executeAction(actionAlignVerticallyCentered);
API.executeAction(actionAlignVerticallyCentered);
// Check if x position did not change
expect(API.getSelectedElements()[0].x).toEqual(0);
@@ -175,7 +176,7 @@ describe("aligning", () => {
expect(API.getSelectedElements()[0].y).toEqual(0);
expect(API.getSelectedElements()[1].y).toEqual(110);
h.app.actionManager.executeAction(actionAlignHorizontallyCentered);
API.executeAction(actionAlignHorizontallyCentered);
expect(API.getSelectedElements()[0].x).toEqual(60);
expect(API.getSelectedElements()[1].x).toEqual(55);
@@ -201,7 +202,7 @@ describe("aligning", () => {
mouse.click();
});
h.app.actionManager.executeAction(actionGroup);
API.executeAction(actionGroup);
mouse.reset();
UI.clickTool("rectangle");
@@ -222,7 +223,7 @@ describe("aligning", () => {
expect(API.getSelectedElements()[1].y).toEqual(100);
expect(API.getSelectedElements()[2].y).toEqual(200);
h.app.actionManager.executeAction(actionAlignTop);
API.executeAction(actionAlignTop);
expect(API.getSelectedElements()[0].y).toEqual(0);
expect(API.getSelectedElements()[1].y).toEqual(100);
@@ -236,7 +237,7 @@ describe("aligning", () => {
expect(API.getSelectedElements()[1].y).toEqual(100);
expect(API.getSelectedElements()[2].y).toEqual(200);
h.app.actionManager.executeAction(actionAlignBottom);
API.executeAction(actionAlignBottom);
expect(API.getSelectedElements()[0].y).toEqual(100);
expect(API.getSelectedElements()[1].y).toEqual(200);
@@ -250,7 +251,7 @@ describe("aligning", () => {
expect(API.getSelectedElements()[1].x).toEqual(100);
expect(API.getSelectedElements()[2].x).toEqual(200);
h.app.actionManager.executeAction(actionAlignLeft);
API.executeAction(actionAlignLeft);
expect(API.getSelectedElements()[0].x).toEqual(0);
expect(API.getSelectedElements()[1].x).toEqual(100);
@@ -264,7 +265,7 @@ describe("aligning", () => {
expect(API.getSelectedElements()[1].x).toEqual(100);
expect(API.getSelectedElements()[2].x).toEqual(200);
h.app.actionManager.executeAction(actionAlignRight);
API.executeAction(actionAlignRight);
expect(API.getSelectedElements()[0].x).toEqual(100);
expect(API.getSelectedElements()[1].x).toEqual(200);
@@ -278,7 +279,7 @@ describe("aligning", () => {
expect(API.getSelectedElements()[1].y).toEqual(100);
expect(API.getSelectedElements()[2].y).toEqual(200);
h.app.actionManager.executeAction(actionAlignVerticallyCentered);
API.executeAction(actionAlignVerticallyCentered);
expect(API.getSelectedElements()[0].y).toEqual(50);
expect(API.getSelectedElements()[1].y).toEqual(150);
@@ -292,7 +293,7 @@ describe("aligning", () => {
expect(API.getSelectedElements()[1].x).toEqual(100);
expect(API.getSelectedElements()[2].x).toEqual(200);
h.app.actionManager.executeAction(actionAlignHorizontallyCentered);
API.executeAction(actionAlignHorizontallyCentered);
expect(API.getSelectedElements()[0].x).toEqual(50);
expect(API.getSelectedElements()[1].x).toEqual(150);
@@ -315,7 +316,7 @@ describe("aligning", () => {
mouse.click();
});
h.app.actionManager.executeAction(actionGroup);
API.executeAction(actionGroup);
mouse.reset();
UI.clickTool("rectangle");
@@ -331,7 +332,7 @@ describe("aligning", () => {
mouse.click();
});
h.app.actionManager.executeAction(actionGroup);
API.executeAction(actionGroup);
// Select the first group.
// The second group is already selected because it was the last group created
@@ -349,7 +350,7 @@ describe("aligning", () => {
expect(API.getSelectedElements()[2].y).toEqual(200);
expect(API.getSelectedElements()[3].y).toEqual(300);
h.app.actionManager.executeAction(actionAlignTop);
API.executeAction(actionAlignTop);
expect(API.getSelectedElements()[0].y).toEqual(0);
expect(API.getSelectedElements()[1].y).toEqual(100);
@@ -365,7 +366,7 @@ describe("aligning", () => {
expect(API.getSelectedElements()[2].y).toEqual(200);
expect(API.getSelectedElements()[3].y).toEqual(300);
h.app.actionManager.executeAction(actionAlignBottom);
API.executeAction(actionAlignBottom);
expect(API.getSelectedElements()[0].y).toEqual(200);
expect(API.getSelectedElements()[1].y).toEqual(300);
@@ -381,7 +382,7 @@ describe("aligning", () => {
expect(API.getSelectedElements()[2].x).toEqual(200);
expect(API.getSelectedElements()[3].x).toEqual(300);
h.app.actionManager.executeAction(actionAlignLeft);
API.executeAction(actionAlignLeft);
expect(API.getSelectedElements()[0].x).toEqual(0);
expect(API.getSelectedElements()[1].x).toEqual(100);
@@ -397,7 +398,7 @@ describe("aligning", () => {
expect(API.getSelectedElements()[2].x).toEqual(200);
expect(API.getSelectedElements()[3].x).toEqual(300);
h.app.actionManager.executeAction(actionAlignRight);
API.executeAction(actionAlignRight);
expect(API.getSelectedElements()[0].x).toEqual(200);
expect(API.getSelectedElements()[1].x).toEqual(300);
@@ -413,7 +414,7 @@ describe("aligning", () => {
expect(API.getSelectedElements()[2].y).toEqual(200);
expect(API.getSelectedElements()[3].y).toEqual(300);
h.app.actionManager.executeAction(actionAlignVerticallyCentered);
API.executeAction(actionAlignVerticallyCentered);
expect(API.getSelectedElements()[0].y).toEqual(100);
expect(API.getSelectedElements()[1].y).toEqual(200);
@@ -429,7 +430,7 @@ describe("aligning", () => {
expect(API.getSelectedElements()[2].x).toEqual(200);
expect(API.getSelectedElements()[3].x).toEqual(300);
h.app.actionManager.executeAction(actionAlignHorizontallyCentered);
API.executeAction(actionAlignHorizontallyCentered);
expect(API.getSelectedElements()[0].x).toEqual(100);
expect(API.getSelectedElements()[1].x).toEqual(200);
@@ -454,7 +455,7 @@ describe("aligning", () => {
});
// Create first group of rectangles
h.app.actionManager.executeAction(actionGroup);
API.executeAction(actionGroup);
mouse.reset();
UI.clickTool("rectangle");
@@ -468,7 +469,7 @@ describe("aligning", () => {
});
// Create the nested group
h.app.actionManager.executeAction(actionGroup);
API.executeAction(actionGroup);
mouse.reset();
UI.clickTool("rectangle");
@@ -490,7 +491,7 @@ describe("aligning", () => {
expect(API.getSelectedElements()[2].y).toEqual(200);
expect(API.getSelectedElements()[3].y).toEqual(300);
h.app.actionManager.executeAction(actionAlignTop);
API.executeAction(actionAlignTop);
expect(API.getSelectedElements()[0].y).toEqual(0);
expect(API.getSelectedElements()[1].y).toEqual(100);
@@ -506,7 +507,7 @@ describe("aligning", () => {
expect(API.getSelectedElements()[2].y).toEqual(200);
expect(API.getSelectedElements()[3].y).toEqual(300);
h.app.actionManager.executeAction(actionAlignBottom);
API.executeAction(actionAlignBottom);
expect(API.getSelectedElements()[0].y).toEqual(100);
expect(API.getSelectedElements()[1].y).toEqual(200);
@@ -522,7 +523,7 @@ describe("aligning", () => {
expect(API.getSelectedElements()[2].x).toEqual(200);
expect(API.getSelectedElements()[3].x).toEqual(300);
h.app.actionManager.executeAction(actionAlignLeft);
API.executeAction(actionAlignLeft);
expect(API.getSelectedElements()[0].x).toEqual(0);
expect(API.getSelectedElements()[1].x).toEqual(100);
@@ -538,7 +539,7 @@ describe("aligning", () => {
expect(API.getSelectedElements()[2].x).toEqual(200);
expect(API.getSelectedElements()[3].x).toEqual(300);
h.app.actionManager.executeAction(actionAlignRight);
API.executeAction(actionAlignRight);
expect(API.getSelectedElements()[0].x).toEqual(100);
expect(API.getSelectedElements()[1].x).toEqual(200);
@@ -554,7 +555,7 @@ describe("aligning", () => {
expect(API.getSelectedElements()[2].y).toEqual(200);
expect(API.getSelectedElements()[3].y).toEqual(300);
h.app.actionManager.executeAction(actionAlignVerticallyCentered);
API.executeAction(actionAlignVerticallyCentered);
expect(API.getSelectedElements()[0].y).toEqual(50);
expect(API.getSelectedElements()[1].y).toEqual(150);
@@ -570,7 +571,7 @@ describe("aligning", () => {
expect(API.getSelectedElements()[2].x).toEqual(200);
expect(API.getSelectedElements()[3].x).toEqual(300);
h.app.actionManager.executeAction(actionAlignHorizontallyCentered);
API.executeAction(actionAlignHorizontallyCentered);
expect(API.getSelectedElements()[0].x).toEqual(50);
expect(API.getSelectedElements()[1].x).toEqual(150);
+4 -4
View File
@@ -1,5 +1,5 @@
import { queryByTestId, render, waitFor } from "./test-utils";
import React from "react";
import { fireEvent, queryByTestId, render, waitFor } from "./test-utils";
import { Excalidraw } from "../index";
import { API } from "./helpers/api";
import { getDefaultAppState } from "../appState";
@@ -31,7 +31,7 @@ describe("appState", () => {
expect(h.state.viewBackgroundColor).toBe("#F00");
});
API.drop(
await API.drop(
new Blob(
[
JSON.stringify({
@@ -69,7 +69,7 @@ describe("appState", () => {
UI.clickTool("text");
expect(h.state.currentItemFontSize).toBe(30);
queryByTestId(container, "fontSize-small")!.click();
fireEvent.click(queryByTestId(container, "fontSize-small")!);
expect(h.state.currentItemFontSize).toBe(16);
const mouse = new Pointer("mouse");
+6 -5
View File
@@ -1,3 +1,4 @@
import React from "react";
import { fireEvent, render } from "./test-utils";
import { Excalidraw, isLinearElement } from "../index";
import { UI, Pointer, Keyboard } from "./helpers/ui";
@@ -37,7 +38,7 @@ describe("element binding", () => {
[100, 0],
],
});
h.elements = [rect, arrow];
API.setElements([rect, arrow]);
expect(arrow.startBinding).toBe(null);
// select arrow
@@ -225,7 +226,7 @@ describe("element binding", () => {
height: 100,
});
h.elements = [text];
API.setElements([text]);
const arrow = UI.createElement("arrow", {
x: 0,
@@ -267,7 +268,7 @@ describe("element binding", () => {
height: 100,
});
h.elements = [text];
API.setElements([text]);
const arrow = UI.createElement("arrow", {
x: 0,
@@ -362,13 +363,13 @@ describe("element binding", () => {
],
});
h.elements = [rectangle1, arrow1, arrow2, text1];
API.setElements([rectangle1, arrow1, arrow2, text1]);
API.setSelectedElements([text1]);
expect(h.state.selectedElementIds[text1.id]).toBe(true);
h.app.actionManager.executeAction(actionWrapTextInContainer);
API.executeAction(actionWrapTextInContainer);
// new text container will be placed before the text element
const container = h.elements.at(-2)!;
+5 -4
View File
@@ -1,3 +1,4 @@
import React from "react";
import { vi } from "vitest";
import ReactDOM from "react-dom";
import { render, waitFor, GlobalTestState } from "./test-utils";
@@ -279,7 +280,7 @@ describe("pasting & frames", () => {
});
const rect = API.createElement({ type: "rectangle" });
h.elements = [frame];
API.setElements([frame]);
const clipboardJSON = await serializeAsClipboardJSON({
elements: [rect],
@@ -318,7 +319,7 @@ describe("pasting & frames", () => {
y: 100,
});
h.elements = [frame];
API.setElements([frame]);
const clipboardJSON = await serializeAsClipboardJSON({
elements: [rect, rect2],
@@ -361,7 +362,7 @@ describe("pasting & frames", () => {
groupIds: ["g1"],
});
h.elements = [frame];
API.setElements([frame]);
const clipboardJSON = await serializeAsClipboardJSON({
elements: [rect, rect2],
@@ -412,7 +413,7 @@ describe("pasting & frames", () => {
frameId: frame2.id,
});
h.elements = [frame];
API.setElements([frame]);
const clipboardJSON = await serializeAsClipboardJSON({
elements: [rect, rect2, frame2],
@@ -1,3 +1,4 @@
import React from "react";
import ReactDOM from "react-dom";
import {
render,
@@ -159,7 +160,7 @@ describe("contextMenu element", () => {
width: 200,
backgroundColor: "red",
});
h.elements = [rect1, rect2];
API.setElements([rect1, rect2]);
API.setSelectedElements([rect1]);
// lower z-index
@@ -607,7 +608,7 @@ describe("contextMenu element", () => {
fillStyle: "solid",
groupIds: ["g1"],
});
h.elements = [rectangle1, rectangle2];
API.setElements([rectangle1, rectangle2]);
mouse.rightClickAt(50, 50);
expect(API.getSelectedElements().length).toBe(2);
@@ -1,3 +1,4 @@
import React from "react";
import ReactDOM from "react-dom";
import { Excalidraw } from "../index";
import * as StaticScene from "../renderer/staticScene";
@@ -1,3 +1,4 @@
import React from "react";
import ReactDOM from "react-dom";
import { Excalidraw } from "../index";
import { render } from "../tests/test-utils";
@@ -16,7 +17,7 @@ const h = window.h;
describe("element locking", () => {
beforeEach(async () => {
await render(<Excalidraw handleKeyboardGlobally={true} />);
h.elements = [];
API.setElements([]);
});
it("click-selecting a locked element is disabled", () => {
@@ -28,7 +29,7 @@ describe("element locking", () => {
locked: true,
});
h.elements = [lockedRectangle];
API.setElements([lockedRectangle]);
mouse.clickAt(50, 50);
expect(API.getSelectedElements().length).toBe(0);
@@ -45,7 +46,7 @@ describe("element locking", () => {
y: 100,
});
h.elements = [lockedRectangle];
API.setElements([lockedRectangle]);
mouse.downAt(50, 50);
mouse.moveTo(250, 250);
@@ -62,7 +63,7 @@ describe("element locking", () => {
locked: true,
});
h.elements = [lockedRectangle];
API.setElements([lockedRectangle]);
mouse.downAt(50, 50);
mouse.moveTo(100, 100);
@@ -85,7 +86,7 @@ describe("element locking", () => {
locked: true,
});
h.elements = [rectangle, lockedRectangle];
API.setElements([rectangle, lockedRectangle]);
mouse.downAt(50, 50);
mouse.moveTo(100, 100);
@@ -97,11 +98,11 @@ describe("element locking", () => {
});
it("selectAll shouldn't select locked elements", () => {
h.elements = [
API.setElements([
API.createElement({ type: "rectangle" }),
API.createElement({ type: "rectangle", locked: true }),
];
h.app.actionManager.executeAction(actionSelectAll);
]);
API.executeAction(actionSelectAll);
expect(API.getSelectedElements().length).toBe(1);
});
@@ -120,7 +121,7 @@ describe("element locking", () => {
locked: true,
});
h.elements = [rectangle, lockedRectangle];
API.setElements([rectangle, lockedRectangle]);
expect(API.getSelectedElements().length).toBe(0);
mouse.clickAt(50, 50);
expect(API.getSelectedElements().length).toBe(1);
@@ -142,7 +143,7 @@ describe("element locking", () => {
locked: true,
});
h.elements = [rectangle, lockedRectangle];
API.setElements([rectangle, lockedRectangle]);
expect(API.getSelectedElements().length).toBe(0);
mouse.rightClickAt(50, 50);
expect(API.getSelectedElements().length).toBe(1);
@@ -172,7 +173,7 @@ describe("element locking", () => {
locked: true,
});
h.elements = [rectangle, lockedRectangle];
API.setElements([rectangle, lockedRectangle]);
API.setSelectedElements([rectangle]);
expect(API.getSelectedElements().length).toBe(1);
expect(API.getSelectedElement().id).toBe(rectangle.id);
@@ -203,7 +204,7 @@ describe("element locking", () => {
y: 200,
});
h.elements = [rectangle, lockedRectangle];
API.setElements([rectangle, lockedRectangle]);
mouse.clickAt(250, 250);
expect(API.getSelectedElements().length).toBe(0);
@@ -228,7 +229,7 @@ describe("element locking", () => {
containerId: container.id,
locked: true,
});
h.elements = [container, text];
API.setElements([container, text]);
API.setSelectedElements([container]);
Keyboard.keyPress(KEYS.ENTER);
expect(h.state.editingElement?.id).not.toBe(text.id);
@@ -245,7 +246,7 @@ describe("element locking", () => {
height: 100,
locked: true,
});
h.elements = [text];
API.setElements([text]);
UI.clickTool("text");
mouse.clickAt(text.x + 50, text.y + 50);
const editor = document.querySelector(
@@ -267,7 +268,7 @@ describe("element locking", () => {
height: 100,
locked: true,
});
h.elements = [text];
API.setElements([text]);
UI.clickTool("selection");
mouse.doubleClickAt(text.x + 50, text.y + 50);
const editor = document.querySelector(
@@ -298,7 +299,7 @@ describe("element locking", () => {
boundElements: [{ id: text.id, type: "text" }],
});
h.elements = [container, text];
API.setElements([container, text]);
UI.clickTool("selection");
mouse.clickAt(container.x + 10, container.y + 10);
@@ -338,7 +339,7 @@ describe("element locking", () => {
mutateElement(container, {
boundElements: [{ id: text.id, type: "text" }],
});
h.elements = [container, text];
API.setElements([container, text]);
UI.clickTool("selection");
mouse.doubleClickAt(container.width / 2, container.height / 2);
@@ -372,7 +373,7 @@ describe("element locking", () => {
mutateElement(container, {
boundElements: [{ id: text.id, type: "text" }],
});
h.elements = [container, text];
API.setElements([container, text]);
UI.clickTool("text");
mouse.clickAt(container.width / 2, container.height / 2);
@@ -1,3 +1,4 @@
import React from "react";
import { fireEvent, GlobalTestState, toggleMenu, render } from "./test-utils";
import { Excalidraw, Footer, MainMenu } from "../index";
import { queryByText, queryByTestId } from "@testing-library/react";
+6 -5
View File
@@ -1,3 +1,4 @@
import React from "react";
import { render, waitFor } from "./test-utils";
import { Excalidraw } from "../index";
import { API } from "./helpers/api";
@@ -51,7 +52,7 @@ describe("export", () => {
blob: pngBlob,
metadata: serializeAsJSON(testElements, h.state, {}, "local"),
});
API.drop(pngBlobEmbedded);
await API.drop(pngBlobEmbedded);
await waitFor(() => {
expect(h.elements).toEqual([
@@ -71,7 +72,7 @@ describe("export", () => {
});
it("import embedded png (legacy v1)", async () => {
API.drop(await API.loadFile("./fixtures/test_embedded_v1.png"));
await API.drop(await API.loadFile("./fixtures/test_embedded_v1.png"));
await waitFor(() => {
expect(h.elements).toEqual([
expect.objectContaining({ type: "text", text: "test" }),
@@ -80,7 +81,7 @@ describe("export", () => {
});
it("import embedded png (v2)", async () => {
API.drop(await API.loadFile("./fixtures/smiley_embedded_v2.png"));
await API.drop(await API.loadFile("./fixtures/smiley_embedded_v2.png"));
await waitFor(() => {
expect(h.elements).toEqual([
expect.objectContaining({ type: "text", text: "😀" }),
@@ -89,7 +90,7 @@ describe("export", () => {
});
it("import embedded svg (legacy v1)", async () => {
API.drop(await API.loadFile("./fixtures/test_embedded_v1.svg"));
await API.drop(await API.loadFile("./fixtures/test_embedded_v1.svg"));
await waitFor(() => {
expect(h.elements).toEqual([
expect.objectContaining({ type: "text", text: "test" }),
@@ -98,7 +99,7 @@ describe("export", () => {
});
it("import embedded svg (v2)", async () => {
API.drop(await API.loadFile("./fixtures/smiley_embedded_v2.svg"));
await API.drop(await API.loadFile("./fixtures/smiley_embedded_v2.svg"));
await waitFor(() => {
expect(h.elements).toEqual([
expect.objectContaining({ type: "text", text: "😀" }),
+29 -15
View File
@@ -1,4 +1,5 @@
import { render } from "./test-utils";
import React from "react";
import { act, render } from "./test-utils";
import { API } from "./helpers/api";
import { Excalidraw } from "../index";
@@ -6,6 +7,17 @@ import { vi } from "vitest";
const { h } = window;
const waitForNextAnimationFrame = () => {
return act(
() =>
new Promise((resolve) => {
requestAnimationFrame(() => {
requestAnimationFrame(resolve);
});
}),
);
};
describe("fitToContent", () => {
it("should zoom to fit the selected element", async () => {
await render(<Excalidraw />);
@@ -22,7 +34,9 @@ describe("fitToContent", () => {
expect(h.state.zoom.value).toBe(1);
h.app.scrollToContent(rectElement, { fitToContent: true });
act(() => {
h.app.scrollToContent(rectElement, { fitToContent: true });
});
// element is 10x taller than the viewport size,
// zoom should be at least 1/10
@@ -51,8 +65,10 @@ describe("fitToContent", () => {
expect(h.state.zoom.value).toBe(1);
h.app.scrollToContent([topLeft, bottomRight], {
fitToContent: true,
act(() => {
h.app.scrollToContent([topLeft, bottomRight], {
fitToContent: true,
});
});
// elements take 100x100, which is 10x bigger than the viewport size,
@@ -77,7 +93,9 @@ describe("fitToContent", () => {
expect(h.state.scrollX).toBe(0);
expect(h.state.scrollY).toBe(0);
h.app.scrollToContent(rectElement);
act(() => {
h.app.scrollToContent(rectElement);
});
// zoom level should stay the same
expect(h.state.zoom.value).toBe(1);
@@ -88,14 +106,6 @@ describe("fitToContent", () => {
});
});
const waitForNextAnimationFrame = () => {
return new Promise((resolve) => {
requestAnimationFrame(() => {
requestAnimationFrame(resolve);
});
});
};
describe("fitToContent animated", () => {
beforeEach(() => {
vi.spyOn(window, "requestAnimationFrame");
@@ -118,7 +128,9 @@ describe("fitToContent animated", () => {
y: -100,
});
h.app.scrollToContent(rectElement, { animate: true });
act(() => {
h.app.scrollToContent(rectElement, { animate: true });
});
expect(window.requestAnimationFrame).toHaveBeenCalled();
@@ -157,7 +169,9 @@ describe("fitToContent animated", () => {
expect(h.state.scrollX).toBe(0);
expect(h.state.scrollY).toBe(0);
h.app.scrollToContent(rectElement, { animate: true, fitToContent: true });
act(() => {
h.app.scrollToContent(rectElement, { animate: true, fitToContent: true });
});
expect(window.requestAnimationFrame).toHaveBeenCalled();
+100 -87
View File
@@ -1,3 +1,4 @@
import React from "react";
import ReactDOM from "react-dom";
import {
fireEvent,
@@ -19,7 +20,6 @@ import type {
} from "../element/types";
import { newLinearElement } from "../element";
import { Excalidraw } from "../index";
import { mutateElement } from "../element/mutateElement";
import type { NormalizedZoomValue } from "../types";
import { ROUNDNESS } from "../constants";
import { vi } from "vitest";
@@ -54,7 +54,7 @@ beforeEach(async () => {
elementFromPoint: () => GlobalTestState.canvas,
});
await render(<Excalidraw autoFocus={true} handleKeyboardGlobally={true} />);
h.setState({
API.setAppState({
zoom: {
value: 1 as NormalizedZoomValue,
},
@@ -204,14 +204,14 @@ const checkElementsBoundingBox = async (
const checkHorizontalFlip = async (toleranceInPx: number = 0.00001) => {
const originalElement = cloneJSON(h.elements[0]);
h.app.actionManager.executeAction(actionFlipHorizontal);
API.executeAction(actionFlipHorizontal);
const newElement = h.elements[0];
await checkElementsBoundingBox(originalElement, newElement, toleranceInPx);
};
const checkTwoPointsLineHorizontalFlip = async () => {
const originalElement = cloneJSON(h.elements[0]) as ExcalidrawLinearElement;
h.app.actionManager.executeAction(actionFlipHorizontal);
API.executeAction(actionFlipHorizontal);
const newElement = h.elements[0] as ExcalidrawLinearElement;
await waitFor(() => {
expect(originalElement.points[0][0]).toBeCloseTo(
@@ -235,7 +235,7 @@ const checkTwoPointsLineHorizontalFlip = async () => {
const checkTwoPointsLineVerticalFlip = async () => {
const originalElement = cloneJSON(h.elements[0]) as ExcalidrawLinearElement;
h.app.actionManager.executeAction(actionFlipVertical);
API.executeAction(actionFlipVertical);
const newElement = h.elements[0] as ExcalidrawLinearElement;
await waitFor(() => {
expect(originalElement.points[0][0]).toBeCloseTo(
@@ -262,7 +262,7 @@ const checkRotatedHorizontalFlip = async (
toleranceInPx: number = 0.00001,
) => {
const originalElement = cloneJSON(h.elements[0]);
h.app.actionManager.executeAction(actionFlipHorizontal);
API.executeAction(actionFlipHorizontal);
const newElement = h.elements[0];
await waitFor(() => {
expect(newElement.angle).toBeCloseTo(expectedAngle);
@@ -275,7 +275,7 @@ const checkRotatedVerticalFlip = async (
toleranceInPx: number = 0.00001,
) => {
const originalElement = cloneJSON(h.elements[0]);
h.app.actionManager.executeAction(actionFlipVertical);
API.executeAction(actionFlipVertical);
const newElement = h.elements[0];
await waitFor(() => {
expect(newElement.angle).toBeCloseTo(expectedAngle);
@@ -286,7 +286,7 @@ const checkRotatedVerticalFlip = async (
const checkVerticalFlip = async (toleranceInPx: number = 0.00001) => {
const originalElement = cloneJSON(h.elements[0]);
h.app.actionManager.executeAction(actionFlipVertical);
API.executeAction(actionFlipVertical);
const newElement = h.elements[0];
await checkElementsBoundingBox(originalElement, newElement, toleranceInPx);
@@ -295,8 +295,8 @@ const checkVerticalFlip = async (toleranceInPx: number = 0.00001) => {
const checkVerticalHorizontalFlip = async (toleranceInPx: number = 0.00001) => {
const originalElement = cloneJSON(h.elements[0]);
h.app.actionManager.executeAction(actionFlipHorizontal);
h.app.actionManager.executeAction(actionFlipVertical);
API.executeAction(actionFlipHorizontal);
API.executeAction(actionFlipVertical);
const newElement = h.elements[0];
await checkElementsBoundingBox(originalElement, newElement, toleranceInPx);
@@ -309,7 +309,6 @@ const MULTIPOINT_LINEAR_ELEMENT_FLIP_TOLERANCE_IN_PIXELS = 20;
describe("rectangle", () => {
it("flips an unrotated rectangle horizontally correctly", async () => {
createAndSelectOneRectangle();
await checkHorizontalFlip();
});
@@ -408,8 +407,8 @@ describe("ellipse", () => {
describe("arrow", () => {
it("flips an unrotated arrow horizontally with line inside min/max points bounds", async () => {
const arrow = createLinearElementWithCurveInsideMinMaxPoints("arrow");
h.elements = [arrow];
h.app.setState({ selectedElementIds: { [arrow.id]: true } });
API.setElements([arrow]);
API.setAppState({ selectedElementIds: { [arrow.id]: true } });
await checkHorizontalFlip(
MULTIPOINT_LINEAR_ELEMENT_FLIP_TOLERANCE_IN_PIXELS,
);
@@ -417,8 +416,8 @@ describe("arrow", () => {
it("flips an unrotated arrow vertically with line inside min/max points bounds", async () => {
const arrow = createLinearElementWithCurveInsideMinMaxPoints("arrow");
h.elements = [arrow];
h.app.setState({ selectedElementIds: { [arrow.id]: true } });
API.setElements([arrow]);
API.setAppState({ selectedElementIds: { [arrow.id]: true } });
await checkVerticalFlip(50);
});
@@ -427,12 +426,14 @@ describe("arrow", () => {
const originalAngle = Math.PI / 4;
const expectedAngle = (7 * Math.PI) / 4;
const line = createLinearElementWithCurveInsideMinMaxPoints("arrow");
h.elements = [line];
h.state.selectedElementIds = {
...h.state.selectedElementIds,
[line.id]: true,
};
mutateElement(line, {
API.setElements([line]);
API.setAppState({
selectedElementIds: {
...h.state.selectedElementIds,
[line.id]: true,
},
});
API.updateElement(line, {
angle: originalAngle,
});
@@ -446,12 +447,14 @@ describe("arrow", () => {
const originalAngle = Math.PI / 4;
const expectedAngle = (7 * Math.PI) / 4;
const line = createLinearElementWithCurveInsideMinMaxPoints("arrow");
h.elements = [line];
h.state.selectedElementIds = {
...h.state.selectedElementIds,
[line.id]: true,
};
mutateElement(line, {
API.setElements([line]);
API.setAppState({
selectedElementIds: {
...h.state.selectedElementIds,
[line.id]: true,
},
});
API.updateElement(line, {
angle: originalAngle,
});
@@ -464,8 +467,8 @@ describe("arrow", () => {
//TODO: elements with curve outside minMax points have a wrong bounding box!!!
it.skip("flips an unrotated arrow horizontally with line outside min/max points bounds", async () => {
const arrow = createLinearElementsWithCurveOutsideMinMaxPoints("arrow");
h.elements = [arrow];
h.app.setState({ selectedElementIds: { [arrow.id]: true } });
API.setElements([arrow]);
API.setAppState({ selectedElementIds: { [arrow.id]: true } });
await checkHorizontalFlip(
MULTIPOINT_LINEAR_ELEMENT_FLIP_TOLERANCE_IN_PIXELS,
@@ -477,9 +480,9 @@ describe("arrow", () => {
const originalAngle = Math.PI / 4;
const expectedAngle = (7 * Math.PI) / 4;
const line = createLinearElementsWithCurveOutsideMinMaxPoints("arrow");
mutateElement(line, { angle: originalAngle });
h.elements = [line];
h.app.setState({ selectedElementIds: { [line.id]: true } });
API.updateElement(line, { angle: originalAngle });
API.setElements([line]);
API.setAppState({ selectedElementIds: { [line.id]: true } });
await checkRotatedVerticalFlip(
expectedAngle,
@@ -490,8 +493,8 @@ describe("arrow", () => {
//TODO: elements with curve outside minMax points have a wrong bounding box!!!
it.skip("flips an unrotated arrow vertically with line outside min/max points bounds", async () => {
const arrow = createLinearElementsWithCurveOutsideMinMaxPoints("arrow");
h.elements = [arrow];
h.app.setState({ selectedElementIds: { [arrow.id]: true } });
API.setElements([arrow]);
API.setAppState({ selectedElementIds: { [arrow.id]: true } });
await checkVerticalFlip(MULTIPOINT_LINEAR_ELEMENT_FLIP_TOLERANCE_IN_PIXELS);
});
@@ -501,9 +504,9 @@ describe("arrow", () => {
const originalAngle = Math.PI / 4;
const expectedAngle = (7 * Math.PI) / 4;
const line = createLinearElementsWithCurveOutsideMinMaxPoints("arrow");
mutateElement(line, { angle: originalAngle });
h.elements = [line];
h.app.setState({ selectedElementIds: { [line.id]: true } });
API.updateElement(line, { angle: originalAngle });
API.setElements([line]);
API.setAppState({ selectedElementIds: { [line.id]: true } });
await checkRotatedVerticalFlip(
expectedAngle,
@@ -538,8 +541,8 @@ describe("arrow", () => {
describe("line", () => {
it("flips an unrotated line horizontally with line inside min/max points bounds", async () => {
const line = createLinearElementWithCurveInsideMinMaxPoints("line");
h.elements = [line];
h.app.setState({ selectedElementIds: { [line.id]: true } });
API.setElements([line]);
API.setAppState({ selectedElementIds: { [line.id]: true } });
await checkHorizontalFlip(
MULTIPOINT_LINEAR_ELEMENT_FLIP_TOLERANCE_IN_PIXELS,
@@ -548,8 +551,8 @@ describe("line", () => {
it("flips an unrotated line vertically with line inside min/max points bounds", async () => {
const line = createLinearElementWithCurveInsideMinMaxPoints("line");
h.elements = [line];
h.app.setState({ selectedElementIds: { [line.id]: true } });
API.setElements([line]);
API.setAppState({ selectedElementIds: { [line.id]: true } });
await checkVerticalFlip(MULTIPOINT_LINEAR_ELEMENT_FLIP_TOLERANCE_IN_PIXELS);
});
@@ -563,8 +566,8 @@ describe("line", () => {
//TODO: elements with curve outside minMax points have a wrong bounding box
it.skip("flips an unrotated line horizontally with line outside min/max points bounds", async () => {
const line = createLinearElementsWithCurveOutsideMinMaxPoints("line");
h.elements = [line];
h.app.setState({ selectedElementIds: { [line.id]: true } });
API.setElements([line]);
API.setAppState({ selectedElementIds: { [line.id]: true } });
await checkHorizontalFlip(
MULTIPOINT_LINEAR_ELEMENT_FLIP_TOLERANCE_IN_PIXELS,
@@ -574,8 +577,8 @@ describe("line", () => {
//TODO: elements with curve outside minMax points have a wrong bounding box
it.skip("flips an unrotated line vertically with line outside min/max points bounds", async () => {
const line = createLinearElementsWithCurveOutsideMinMaxPoints("line");
h.elements = [line];
h.app.setState({ selectedElementIds: { [line.id]: true } });
API.setElements([line]);
API.setAppState({ selectedElementIds: { [line.id]: true } });
await checkVerticalFlip(MULTIPOINT_LINEAR_ELEMENT_FLIP_TOLERANCE_IN_PIXELS);
});
@@ -585,9 +588,9 @@ describe("line", () => {
const originalAngle = Math.PI / 4;
const expectedAngle = (7 * Math.PI) / 4;
const line = createLinearElementsWithCurveOutsideMinMaxPoints("line");
mutateElement(line, { angle: originalAngle });
h.elements = [line];
h.app.setState({ selectedElementIds: { [line.id]: true } });
API.updateElement(line, { angle: originalAngle });
API.setElements([line]);
API.setAppState({ selectedElementIds: { [line.id]: true } });
await checkRotatedHorizontalFlip(
expectedAngle,
@@ -600,9 +603,9 @@ describe("line", () => {
const originalAngle = Math.PI / 4;
const expectedAngle = (7 * Math.PI) / 4;
const line = createLinearElementsWithCurveOutsideMinMaxPoints("line");
mutateElement(line, { angle: originalAngle });
h.elements = [line];
h.app.setState({ selectedElementIds: { [line.id]: true } });
API.updateElement(line, { angle: originalAngle });
API.setElements([line]);
API.setAppState({ selectedElementIds: { [line.id]: true } });
await checkRotatedVerticalFlip(
expectedAngle,
@@ -619,12 +622,14 @@ describe("line", () => {
const originalAngle = Math.PI / 4;
const expectedAngle = (7 * Math.PI) / 4;
const line = createLinearElementWithCurveInsideMinMaxPoints("line");
h.elements = [line];
h.state.selectedElementIds = {
...h.state.selectedElementIds,
[line.id]: true,
};
mutateElement(line, {
API.setElements([line]);
API.setAppState({
selectedElementIds: {
...h.state.selectedElementIds,
[line.id]: true,
},
});
API.updateElement(line, {
angle: originalAngle,
});
@@ -638,12 +643,14 @@ describe("line", () => {
const originalAngle = Math.PI / 4;
const expectedAngle = (7 * Math.PI) / 4;
const line = createLinearElementWithCurveInsideMinMaxPoints("line");
h.elements = [line];
h.state.selectedElementIds = {
...h.state.selectedElementIds,
[line.id]: true,
};
mutateElement(line, {
API.setElements([line]);
API.setAppState({
selectedElementIds: {
...h.state.selectedElementIds,
[line.id]: true,
},
});
API.updateElement(line, {
angle: originalAngle,
});
@@ -669,20 +676,24 @@ describe("freedraw", () => {
it("flips an unrotated drawing horizontally correctly", async () => {
const draw = createAndReturnOneDraw();
// select draw, since not done automatically
h.state.selectedElementIds = {
...h.state.selectedElementIds,
[draw.id]: true,
};
API.setAppState({
selectedElementIds: {
...h.state.selectedElementIds,
[draw.id]: true,
},
});
await checkHorizontalFlip();
});
it("flips an unrotated drawing vertically correctly", async () => {
const draw = createAndReturnOneDraw();
// select draw, since not done automatically
h.state.selectedElementIds = {
...h.state.selectedElementIds,
[draw.id]: true,
};
API.setAppState({
selectedElementIds: {
...h.state.selectedElementIds,
[draw.id]: true,
},
});
await checkVerticalFlip();
});
@@ -692,10 +703,12 @@ describe("freedraw", () => {
const draw = createAndReturnOneDraw(originalAngle);
// select draw, since not done automatically
h.state.selectedElementIds = {
...h.state.selectedElementIds,
[draw.id]: true,
};
API.setAppState({
selectedElementIds: {
...h.state.selectedElementIds,
[draw.id]: true,
},
});
await checkRotatedHorizontalFlip(expectedAngle);
});
@@ -706,10 +719,12 @@ describe("freedraw", () => {
const draw = createAndReturnOneDraw(originalAngle);
// select draw, since not done automatically
h.state.selectedElementIds = {
...h.state.selectedElementIds,
[draw.id]: true,
};
API.setAppState({
selectedElementIds: {
...h.state.selectedElementIds,
[draw.id]: true,
},
});
await checkRotatedVerticalFlip(expectedAngle);
});
@@ -767,7 +782,7 @@ describe("image", () => {
expect(API.getSelectedElements()[0].type).toEqual("image");
expect(h.app.files.fileId).toBeDefined();
});
mutateElement(h.elements[0], {
API.updateElement(h.elements[0], {
angle: originalAngle,
});
await checkRotatedHorizontalFlip(expectedAngle);
@@ -786,7 +801,7 @@ describe("image", () => {
expect(API.getSelectedElements()[0].type).toEqual("image");
expect(h.app.files.fileId).toBeDefined();
});
mutateElement(h.elements[0], {
API.updateElement(h.elements[0], {
angle: originalAngle,
});
@@ -827,8 +842,7 @@ describe("mutliple elements", () => {
".excalidraw-textEditorContainer > textarea",
)!;
fireEvent.input(editor, { target: { value: "arrow" } });
await new Promise((resolve) => setTimeout(resolve, 0));
Keyboard.keyPress(KEYS.ESCAPE);
Keyboard.exitTextEditor(editor);
const rectangle = UI.createElement("rectangle", {
x: 0,
@@ -842,12 +856,11 @@ describe("mutliple elements", () => {
".excalidraw-textEditorContainer > textarea",
)!;
fireEvent.input(editor, { target: { value: "rect\ntext" } });
await new Promise((resolve) => setTimeout(resolve, 0));
Keyboard.keyPress(KEYS.ESCAPE);
Keyboard.exitTextEditor(editor);
mouse.select([arrow, rectangle]);
h.app.actionManager.executeAction(actionFlipHorizontal);
h.app.actionManager.executeAction(actionFlipVertical);
API.executeAction(actionFlipHorizontal);
API.executeAction(actionFlipVertical);
const arrowText = h.elements[1] as ExcalidrawTextElementWithContainer;
const arrowTextPos = getBoundTextElementPosition(
+51 -10
View File
@@ -13,7 +13,7 @@ import type {
import { newElement, newTextElement, newLinearElement } from "../../element";
import { DEFAULT_VERTICAL_ALIGN, ROUNDNESS } from "../../constants";
import { getDefaultAppState } from "../../appState";
import { GlobalTestState, createEvent, fireEvent } from "../test-utils";
import { GlobalTestState, createEvent, fireEvent, act } from "../test-utils";
import fs from "fs";
import util from "util";
import path from "path";
@@ -27,12 +27,15 @@ import {
newImageElement,
newMagicFrameElement,
} from "../../element/newElement";
import type { Point } from "../../types";
import type { AppState, Point } from "../../types";
import { getSelectedElements } from "../../scene/selection";
import { isLinearElementType } from "../../element/typeChecks";
import type { Mutable } from "../../utility-types";
import { assertNever } from "../../utils";
import type App from "../../components/App";
import { createTestHook } from "../../components/App";
import type { Action } from "../../actions/types";
import { mutateElement } from "../../element/mutateElement";
const readFile = util.promisify(fs.readFile);
// so that window.h is available when App.tsx is not imported as well.
@@ -41,12 +44,42 @@ createTestHook();
const { h } = window;
export class API {
static updateScene: InstanceType<typeof App>["updateScene"] = (...args) => {
act(() => {
h.app.updateScene(...args);
});
};
static setAppState: React.Component<any, AppState>["setState"] = (
state,
cb,
) => {
act(() => {
h.setState(state, cb);
});
};
static setElements = (elements: readonly ExcalidrawElement[]) => {
act(() => {
h.elements = elements;
});
};
static setSelectedElements = (elements: ExcalidrawElement[]) => {
h.setState({
selectedElementIds: elements.reduce((acc, element) => {
acc[element.id] = true;
return acc;
}, {} as Record<ExcalidrawElement["id"], true>),
act(() => {
h.setState({
selectedElementIds: elements.reduce((acc, element) => {
acc[element.id] = true;
return acc;
}, {} as Record<ExcalidrawElement["id"], true>),
});
});
};
static updateElement = (
...[element, updates]: Parameters<typeof mutateElement>
) => {
act(() => {
mutateElement(element, updates);
});
};
@@ -85,8 +118,10 @@ export class API {
};
static clearSelection = () => {
// @ts-ignore
h.app.clearSelection(null);
act(() => {
// @ts-ignore
h.app.clearSelection(null);
});
expect(API.getSelectedElements().length).toBe(0);
};
@@ -361,6 +396,12 @@ export class API {
},
},
});
fireEvent(GlobalTestState.interactiveCanvas, fileDropEvent);
await fireEvent(GlobalTestState.interactiveCanvas, fileDropEvent);
};
static executeAction = (action: Action) => {
act(() => {
h.app.actionManager.executeAction(action);
});
};
}
+29 -12
View File
@@ -20,7 +20,7 @@ import {
type TransformHandleDirection,
} from "../../element/transformHandles";
import { KEYS } from "../../keys";
import { fireEvent, GlobalTestState, screen } from "../test-utils";
import { act, fireEvent, GlobalTestState, screen } from "../test-utils";
import { mutateElement } from "../../element/mutateElement";
import { API } from "./api";
import {
@@ -125,6 +125,10 @@ export class Keyboard {
Keyboard.keyPress("z");
});
};
static exitTextEditor = (textarea: HTMLTextAreaElement) => {
fireEvent.keyDown(textarea, { key: KEYS.ESCAPE });
};
}
const getElementPointForSelection = (element: ExcalidrawElement): Point => {
@@ -299,14 +303,16 @@ const transform = (
keyboardModifiers: KeyboardModifiers = {},
) => {
const elements = Array.isArray(element) ? element : [element];
h.setState({
selectedElementIds: elements.reduce(
(acc, e) => ({
...acc,
[e.id]: true,
}),
{},
),
act(() => {
h.setState({
selectedElementIds: elements.reduce(
(acc, e) => ({
...acc,
[e.id]: true,
}),
{},
),
});
});
let handleCoords: TransformHandle | undefined;
if (elements.length === 1) {
@@ -487,7 +493,9 @@ export class UI {
const origElement = h.elements[h.elements.length - 1] as any;
if (angle !== 0) {
mutateElement(origElement, { angle });
act(() => {
mutateElement(origElement, { angle });
});
}
return proxy(origElement);
@@ -511,8 +519,9 @@ export class UI {
}
fireEvent.input(editor, { target: { value: text } });
await new Promise((resolve) => setTimeout(resolve, 0));
editor.blur();
act(() => {
editor.blur();
});
return isTextElement(element)
? element
@@ -523,6 +532,14 @@ export class UI {
);
}
static updateInput = (input: HTMLInputElement, value: string | number) => {
act(() => {
input.focus();
fireEvent.change(input, { target: { value: String(value) } });
input.blur();
});
};
static resize(
element: ExcalidrawElement | ExcalidrawElement[],
handle: TransformHandleDirection,
+111 -137
View File
@@ -1,5 +1,5 @@
import "../global.d.ts";
import React from "react";
import "../global.d.ts";
import * as StaticScene from "../renderer/staticScene";
import {
GlobalTestState,
@@ -16,8 +16,8 @@ import { fireEvent, queryByTestId, waitFor } from "@testing-library/react";
import { createUndoAction, createRedoAction } from "../actions/actionHistory";
import { actionToggleViewMode } from "../actions/actionToggleViewMode";
import { EXPORT_DATA_TYPES, MIME_TYPES } from "../constants";
import type { AppState, ExcalidrawImperativeAPI } from "../types";
import { arrayToMap, resolvablePromise } from "../utils";
import type { AppState } from "../types";
import { arrayToMap } from "../utils";
import {
COLOR_PALETTE,
DEFAULT_ELEMENT_BACKGROUND_COLOR_INDEX,
@@ -95,7 +95,7 @@ describe("history", () => {
await render(<Excalidraw handleKeyboardGlobally={true} />);
const rect = API.createElement({ type: "rectangle" });
h.elements = [rect];
API.setElements([rect]);
const corrupedEntry = HistoryEntry.create(
AppStateChange.empty(),
@@ -158,7 +158,7 @@ describe("history", () => {
const rect1 = API.createElement({ type: "rectangle", groupIds: ["A"] });
const rect2 = API.createElement({ type: "rectangle", groupIds: ["A"] });
h.elements = [rect1, rect2];
API.setElements([rect1, rect2]);
mouse.select(rect1);
assertSelectedElements([rect1, rect2]);
expect(h.state.selectedGroupIds).toEqual({ A: true });
@@ -173,19 +173,12 @@ describe("history", () => {
});
it("should not end up with history entry when there are no elements changes", async () => {
const excalidrawAPIPromise = resolvablePromise<ExcalidrawImperativeAPI>();
await render(
<Excalidraw
excalidrawAPI={(api) => excalidrawAPIPromise.resolve(api as any)}
handleKeyboardGlobally={true}
/>,
);
const excalidrawAPI = await excalidrawAPIPromise;
await render(<Excalidraw handleKeyboardGlobally={true} />);
const rect1 = API.createElement({ type: "rectangle" });
const rect2 = API.createElement({ type: "rectangle" });
excalidrawAPI.updateScene({
API.updateScene({
elements: [rect1, rect2],
storeAction: StoreAction.CAPTURE,
});
@@ -197,7 +190,7 @@ describe("history", () => {
expect.objectContaining({ id: rect2.id, isDeleted: false }),
]);
excalidrawAPI.updateScene({
API.updateScene({
elements: [rect1, rect2],
storeAction: StoreAction.CAPTURE, // even though the flag is on, same elements are passed, nothing to commit
});
@@ -447,7 +440,7 @@ describe("history", () => {
const undoAction = createUndoAction(h.history, h.store);
const redoAction = createRedoAction(h.history, h.store);
// noop
act(() => h.app.actionManager.executeAction(undoAction));
API.executeAction(undoAction);
expect(h.elements).toEqual([
expect.objectContaining({ id: "A", isDeleted: false }),
]);
@@ -456,21 +449,21 @@ describe("history", () => {
expect.objectContaining({ id: "A" }),
expect.objectContaining({ id: rectangle.id }),
]);
act(() => h.app.actionManager.executeAction(undoAction));
API.executeAction(undoAction);
expect(h.elements).toEqual([
expect.objectContaining({ id: "A", isDeleted: false }),
expect.objectContaining({ id: rectangle.id, isDeleted: true }),
]);
// noop
act(() => h.app.actionManager.executeAction(undoAction));
API.executeAction(undoAction);
expect(h.elements).toEqual([
expect.objectContaining({ id: "A", isDeleted: false }),
expect.objectContaining({ id: rectangle.id, isDeleted: true }),
]);
expect(API.getUndoStack().length).toBe(0);
act(() => h.app.actionManager.executeAction(redoAction));
API.executeAction(redoAction);
expect(h.elements).toEqual([
expect.objectContaining({ id: "A", isDeleted: false }),
expect.objectContaining({ id: rectangle.id, isDeleted: false }),
@@ -495,7 +488,7 @@ describe("history", () => {
expect(h.elements).toEqual([expect.objectContaining({ id: "A" })]),
);
API.drop(
await API.drop(
new Blob(
[
JSON.stringify({
@@ -523,7 +516,7 @@ describe("history", () => {
const undoAction = createUndoAction(h.history, h.store);
const redoAction = createRedoAction(h.history, h.store);
act(() => h.app.actionManager.executeAction(undoAction));
API.executeAction(undoAction);
expect(API.getSnapshot()).toEqual([
expect.objectContaining({ id: "A", isDeleted: false }),
@@ -535,7 +528,7 @@ describe("history", () => {
]);
expect(h.state.viewBackgroundColor).toBe("#FFF");
act(() => h.app.actionManager.executeAction(redoAction));
API.executeAction(redoAction);
expect(h.state.viewBackgroundColor).toBe("#000");
expect(API.getSnapshot()).toEqual([
expect.objectContaining({ id: "A", isDeleted: true }),
@@ -548,10 +541,8 @@ describe("history", () => {
});
it("should support appstate name or viewBackgroundColor change", async () => {
const excalidrawAPIPromise = resolvablePromise<ExcalidrawImperativeAPI>();
await render(
<Excalidraw
excalidrawAPI={(api) => excalidrawAPIPromise.resolve(api as any)}
handleKeyboardGlobally={true}
initialData={{
appState: {
@@ -561,9 +552,11 @@ describe("history", () => {
}}
/>,
);
const excalidrawAPI = await excalidrawAPIPromise;
excalidrawAPI.updateScene({
expect(h.state.isLoading).toBe(false);
expect(h.state.name).toBe("Old name");
API.updateScene({
appState: {
name: "New name",
},
@@ -574,7 +567,7 @@ describe("history", () => {
expect(API.getRedoStack().length).toBe(0);
expect(h.state.name).toBe("New name");
excalidrawAPI.updateScene({
API.updateScene({
appState: {
viewBackgroundColor: "#000",
},
@@ -586,7 +579,7 @@ describe("history", () => {
expect(h.state.viewBackgroundColor).toBe("#000");
// just to double check that same change is not recorded
excalidrawAPI.updateScene({
API.updateScene({
appState: {
name: "New name",
viewBackgroundColor: "#000",
@@ -1060,7 +1053,7 @@ describe("history", () => {
x: 100,
});
h.elements = [rect1, rect2];
API.setElements([rect1, rect2]);
mouse.select(rect1);
assertSelectedElements([rect1, rect2]);
expect(API.getUndoStack().length).toBe(1);
@@ -1203,7 +1196,7 @@ describe("history", () => {
const rect2 = UI.createElement("rectangle", { x: 20, y: 20 });
const rect3 = UI.createElement("rectangle", { x: 40, y: 40 });
act(() => h.app.actionManager.executeAction(actionSendBackward));
API.executeAction(actionSendBackward);
expect(API.getUndoStack().length).toBe(4);
expect(API.getRedoStack().length).toBe(0);
@@ -1234,7 +1227,7 @@ describe("history", () => {
expect(API.getRedoStack().length).toBe(0);
assertSelectedElements([rect1, rect3]);
act(() => h.app.actionManager.executeAction(actionBringForward));
API.executeAction(actionBringForward);
expect(API.getUndoStack().length).toBe(7);
expect(API.getRedoStack().length).toBe(0);
@@ -1262,8 +1255,6 @@ describe("history", () => {
});
describe("should support bidirectional bindings", async () => {
let excalidrawAPI: ExcalidrawImperativeAPI;
let rect1: ExcalidrawGenericElement;
let rect2: ExcalidrawGenericElement;
let text: ExcalidrawTextElement;
@@ -1292,22 +1283,13 @@ describe("history", () => {
} as const;
beforeEach(async () => {
const excalidrawAPIPromise =
resolvablePromise<ExcalidrawImperativeAPI>();
await render(
<Excalidraw
excalidrawAPI={(api) => excalidrawAPIPromise.resolve(api as any)}
handleKeyboardGlobally={true}
/>,
);
excalidrawAPI = await excalidrawAPIPromise;
await render(<Excalidraw handleKeyboardGlobally={true} />);
rect1 = API.createElement({ ...rect1Props });
text = API.createElement({ ...textProps });
rect2 = API.createElement({ ...rect2Props });
excalidrawAPI.updateScene({
API.updateScene({
elements: [rect1, text, rect2],
storeAction: StoreAction.CAPTURE,
});
@@ -1758,14 +1740,14 @@ describe("history", () => {
expect(undoButton).not.toBeDisabled();
expect(redoButton).toBeDisabled();
act(() => h.app.actionManager.executeAction(undoAction));
API.executeAction(undoAction);
expect(h.history.isUndoStackEmpty).toBeTruthy();
expect(h.history.isRedoStackEmpty).toBeFalsy();
expect(undoButton).toBeDisabled();
expect(redoButton).not.toBeDisabled();
act(() => h.app.actionManager.executeAction(redoAction));
API.executeAction(redoAction);
expect(h.history.isUndoStackEmpty).toBeFalsy();
expect(h.history.isRedoStackEmpty).toBeTruthy();
@@ -1807,13 +1789,13 @@ describe("history", () => {
expect(queryByTestId(container, "button-undo")).not.toBeDisabled();
expect(queryByTestId(container, "button-redo")).toBeDisabled();
act(() => h.app.actionManager.executeAction(actionToggleViewMode));
API.executeAction(actionToggleViewMode);
expect(h.state.viewModeEnabled).toBe(true);
expect(queryByTestId(container, "button-undo")).toBeNull();
expect(queryByTestId(container, "button-redo")).toBeNull();
act(() => h.app.actionManager.executeAction(actionToggleViewMode));
API.executeAction(actionToggleViewMode);
expect(h.state.viewModeEnabled).toBe(false);
await waitFor(() => {
@@ -1824,20 +1806,20 @@ describe("history", () => {
// testing redo button
// -----------------------------------------------------------------------
act(() => h.app.actionManager.executeAction(undoAction));
API.executeAction(undoAction);
expect(h.history.isUndoStackEmpty).toBeTruthy();
expect(h.history.isRedoStackEmpty).toBeFalsy();
expect(queryByTestId(container, "button-undo")).toBeDisabled();
expect(queryByTestId(container, "button-redo")).not.toBeDisabled();
act(() => h.app.actionManager.executeAction(actionToggleViewMode));
API.executeAction(actionToggleViewMode);
expect(h.state.viewModeEnabled).toBe(true);
expect(queryByTestId(container, "button-undo")).toBeNull();
expect(queryByTestId(container, "button-redo")).toBeNull();
act(() => h.app.actionManager.executeAction(actionToggleViewMode));
API.executeAction(actionToggleViewMode);
expect(h.state.viewModeEnabled).toBe(false);
expect(h.history.isUndoStackEmpty).toBeTruthy();
@@ -1848,8 +1830,6 @@ describe("history", () => {
});
describe("multiplayer undo/redo", () => {
let excalidrawAPI: ExcalidrawImperativeAPI;
// Util to check that we end up in the same state after series of undo / redo
function runTwice(callback: () => void) {
for (let i = 0; i < 2; i++) {
@@ -1858,15 +1838,9 @@ describe("history", () => {
}
beforeEach(async () => {
const excalidrawAPIPromise = resolvablePromise<ExcalidrawImperativeAPI>();
await render(
<Excalidraw
excalidrawAPI={(api) => excalidrawAPIPromise.resolve(api as any)}
handleKeyboardGlobally={true}
isCollaborating={true}
/>,
<Excalidraw handleKeyboardGlobally={true} isCollaborating={true} />,
);
excalidrawAPI = await excalidrawAPIPromise;
});
it("should not override remote changes on different elements", async () => {
@@ -1881,7 +1855,7 @@ describe("history", () => {
]);
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
...h.elements,
API.createElement({
@@ -1921,7 +1895,7 @@ describe("history", () => {
expect(API.getUndoStack().length).toBe(2);
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[0], {
strokeColor: yellow,
@@ -1969,7 +1943,7 @@ describe("history", () => {
]);
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[0], {
backgroundColor: yellow,
@@ -1985,7 +1959,7 @@ describe("history", () => {
]);
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[0], {
backgroundColor: violet,
@@ -2034,13 +2008,13 @@ describe("history", () => {
elbowed: true,
});
excalidrawAPI.updateScene({
API.updateScene({
elements: [rect, diamond],
storeAction: StoreAction.CAPTURE,
});
// Connect the arrow
excalidrawAPI.updateScene({
API.updateScene({
elements: [
{
...rect,
@@ -2090,7 +2064,7 @@ describe("history", () => {
Keyboard.undo();
excalidrawAPI.updateScene({
API.updateScene({
elements: h.elements.map((el) =>
el.id === "KPrBI4g_v9qUB1XxYLgSz"
? {
@@ -2122,13 +2096,13 @@ describe("history", () => {
const rect2 = API.createElement({ type: "rectangle" });
// Initialize scene
excalidrawAPI.updateScene({
API.updateScene({
elements: [rect1, rect2],
storeAction: StoreAction.UPDATE,
});
// Simulate local update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[0], { groupIds: ["A"] }),
newElementWith(h.elements[1], { groupIds: ["A"] }),
@@ -2140,7 +2114,7 @@ describe("history", () => {
const rect4 = API.createElement({ type: "rectangle", groupIds: ["B"] });
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[0], { groupIds: ["A", "B"] }),
newElementWith(h.elements[1], { groupIds: ["A", "B"] }),
@@ -2181,7 +2155,7 @@ describe("history", () => {
Keyboard.keyPress(KEYS.ENTER);
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[0] as ExcalidrawLinearElement, {
points: [
@@ -2281,7 +2255,7 @@ describe("history", () => {
]);
// Simulate remote update & restore
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[0], {
backgroundColor: yellow,
@@ -2358,7 +2332,7 @@ describe("history", () => {
]);
// Simulate remote update & deletion
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[0], {
backgroundColor: yellow,
@@ -2417,7 +2391,7 @@ describe("history", () => {
expect(API.getUndoStack().length).toBe(5);
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
h.elements[0],
newElementWith(h.elements[1], {
@@ -2482,7 +2456,7 @@ describe("history", () => {
const rect2 = API.createElement({ type: "rectangle", x: 20, y: 20 });
const rect3 = API.createElement({ type: "rectangle", x: 30, y: 30 });
h.elements = [rect1, rect2, rect3];
API.setElements([rect1, rect2, rect3]);
mouse.select(rect1);
mouse.select([rect2, rect3]);
@@ -2493,7 +2467,7 @@ describe("history", () => {
]);
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
h.elements[0],
newElementWith(h.elements[1], {
@@ -2532,7 +2506,7 @@ describe("history", () => {
]);
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
h.elements[0],
newElementWith(h.elements[1], {
@@ -2586,7 +2560,7 @@ describe("history", () => {
});
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [rect1, rect2],
storeAction: StoreAction.UPDATE,
});
@@ -2596,7 +2570,7 @@ describe("history", () => {
});
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [h.elements[0], h.elements[1], rect3, rect4],
storeAction: StoreAction.UPDATE,
});
@@ -2610,7 +2584,7 @@ describe("history", () => {
expect(h.state.selectedGroupIds).toEqual({ A: true, B: true });
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[0], {
isDeleted: true,
@@ -2635,7 +2609,7 @@ describe("history", () => {
Keyboard.undo();
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[0], {
isDeleted: false,
@@ -2653,7 +2627,7 @@ describe("history", () => {
expect(h.state.selectedGroupIds).toEqual({ A: true });
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [h.elements[0], h.elements[1], rect3, rect4],
storeAction: StoreAction.UPDATE,
});
@@ -2676,7 +2650,7 @@ describe("history", () => {
x: 100,
});
h.elements = [rect1, rect2];
API.setElements([rect1, rect2]);
mouse.select(rect1);
// inside the editing group
@@ -2692,7 +2666,7 @@ describe("history", () => {
expect(h.state.editingGroupId).toBeNull();
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[0], {
isDeleted: true,
@@ -2715,7 +2689,7 @@ describe("history", () => {
expect(h.state.editingGroupId).toBeNull();
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[0], {
isDeleted: false,
@@ -2759,7 +2733,7 @@ describe("history", () => {
expect(h.state.selectedLinearElement).toBeNull();
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[0], {
isDeleted: true,
@@ -2786,11 +2760,11 @@ describe("history", () => {
const rect2 = API.createElement({ type: "rectangle", x: 20, y: 20 }); // b "a1"
const rect3 = API.createElement({ type: "rectangle", x: 30, y: 30 }); // c "a2"
h.elements = [rect1, rect2, rect3];
API.setElements([rect1, rect2, rect3]);
mouse.select(rect2);
act(() => h.app.actionManager.executeAction(actionSendToBack));
API.executeAction(actionSendToBack);
expect(API.getUndoStack().length).toBe(2);
expect(API.getRedoStack().length).toBe(0);
@@ -2802,7 +2776,7 @@ describe("history", () => {
]);
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[2], { index: "Zy" as FractionalIndex }),
h.elements[0],
@@ -2841,7 +2815,7 @@ describe("history", () => {
]);
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[2], { index: "Zx" as FractionalIndex }),
h.elements[0],
@@ -2876,11 +2850,11 @@ describe("history", () => {
const rect2 = API.createElement({ type: "rectangle", x: 20, y: 20 });
const rect3 = API.createElement({ type: "rectangle", x: 30, y: 30 });
h.elements = [rect1, rect2, rect3];
API.setElements([rect1, rect2, rect3]);
mouse.select(rect2);
act(() => h.app.actionManager.executeAction(actionSendToBack));
API.executeAction(actionSendToBack);
expect(API.getUndoStack().length).toBe(2);
expect(API.getRedoStack().length).toBe(0);
@@ -2892,7 +2866,7 @@ describe("history", () => {
]);
// Simulate remote update (fixes all invalid z-indices)
excalidrawAPI.updateScene({
API.updateScene({
elements: [
h.elements[2], // rect3
h.elements[0], // rect2
@@ -2922,7 +2896,7 @@ describe("history", () => {
]);
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
h.elements[1], // rect2
h.elements[0], // rect3
@@ -2956,7 +2930,7 @@ describe("history", () => {
const rect = API.createElement({ ...rectProps });
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [...h.elements, rect],
storeAction: StoreAction.UPDATE,
});
@@ -3008,7 +2982,7 @@ describe("history", () => {
const rect3 = API.createElement({ ...rect3Props });
// // Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [...h.elements, rect3],
storeAction: StoreAction.UPDATE,
});
@@ -3098,7 +3072,7 @@ describe("history", () => {
const rect3 = API.createElement({ ...rect3Props });
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [...h.elements, rect3],
storeAction: StoreAction.UPDATE,
});
@@ -3275,13 +3249,13 @@ describe("history", () => {
it("should rebind bindings when both are updated through the history and there no conflicting updates in the meantime", async () => {
// Initialize the scene
excalidrawAPI.updateScene({
API.updateScene({
elements: [container, text],
storeAction: StoreAction.UPDATE,
});
// Simulate local update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[0], {
boundElements: [{ id: text.id, type: "text" }],
@@ -3310,7 +3284,7 @@ describe("history", () => {
]);
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[0], {
// no conflicting updates
@@ -3362,13 +3336,13 @@ describe("history", () => {
// TODO: #7348 we do rebind now, when we have bi-directional binding in history, to eliminate potential data-integrity issues, but we should consider not rebinding in the future
it("should rebind bindings when both are updated through the history and the container got bound to a different text in the meantime", async () => {
// Initialize the scene
excalidrawAPI.updateScene({
API.updateScene({
elements: [container, text],
storeAction: StoreAction.UPDATE,
});
// Simulate local update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[0], {
boundElements: [{ id: text.id, type: "text" }],
@@ -3403,7 +3377,7 @@ describe("history", () => {
});
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[0], {
boundElements: [{ id: remoteText.id, type: "text" }],
@@ -3465,13 +3439,13 @@ describe("history", () => {
// TODO: #7348 we do rebind now, when we have bi-directional binding in history, to eliminate potential data-integrity issues, but we should consider not rebinding in the future
it("should rebind bindings when both are updated through the history and the text got bound to a different container in the meantime", async () => {
// Initialize the scene
excalidrawAPI.updateScene({
API.updateScene({
elements: [container, text],
storeAction: StoreAction.UPDATE,
});
// Simulate local update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[0], {
boundElements: [{ id: text.id, type: "text" }],
@@ -3507,7 +3481,7 @@ describe("history", () => {
});
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
h.elements[0],
newElementWith(remoteContainer, {
@@ -3573,13 +3547,13 @@ describe("history", () => {
it("should rebind remotely added bound text when it's container is added through the history", async () => {
// Simulate local update
excalidrawAPI.updateScene({
API.updateScene({
elements: [container],
storeAction: StoreAction.CAPTURE,
});
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[0], {
boundElements: [{ id: text.id, type: "text" }],
@@ -3634,13 +3608,13 @@ describe("history", () => {
it("should rebind remotely added container when it's bound text is added through the history", async () => {
// Simulate local update
excalidrawAPI.updateScene({
API.updateScene({
elements: [text],
storeAction: StoreAction.CAPTURE,
});
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(container, {
boundElements: [{ id: text.id, type: "text" }],
@@ -3694,13 +3668,13 @@ describe("history", () => {
it("should preserve latest remotely added binding and unbind previous one when the container is added through the history", async () => {
// Simulate local update
excalidrawAPI.updateScene({
API.updateScene({
elements: [container],
storeAction: StoreAction.CAPTURE,
});
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[0], {
boundElements: [{ id: text.id, type: "text" }],
@@ -3736,7 +3710,7 @@ describe("history", () => {
});
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[0], {
boundElements: [{ id: remoteText.id, type: "text" }],
@@ -3801,13 +3775,13 @@ describe("history", () => {
it("should preserve latest remotely added binding and unbind previous one when the text is added through history", async () => {
// Simulate local update
excalidrawAPI.updateScene({
API.updateScene({
elements: [text],
storeAction: StoreAction.CAPTURE,
});
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(container, {
boundElements: [{ id: text.id, type: "text" }],
@@ -3843,7 +3817,7 @@ describe("history", () => {
});
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[0], {
boundElements: [{ id: remoteText.id, type: "text" }],
@@ -3907,13 +3881,13 @@ describe("history", () => {
it("should unbind remotely deleted bound text from container when the container is added through the history", async () => {
// Simulate local update
excalidrawAPI.updateScene({
API.updateScene({
elements: [container],
storeAction: StoreAction.CAPTURE,
});
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[0], {
boundElements: [{ id: text.id, type: "text" }],
@@ -3964,13 +3938,13 @@ describe("history", () => {
it("should unbind remotely deleted container from bound text when the text is added through the history", async () => {
// Simulate local update
excalidrawAPI.updateScene({
API.updateScene({
elements: [text],
storeAction: StoreAction.CAPTURE,
});
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(container, {
boundElements: [{ id: text.id, type: "text" }],
@@ -4021,13 +3995,13 @@ describe("history", () => {
it("should redraw remotely added bound text when it's container is updated through the history", async () => {
// Initialize the scene
excalidrawAPI.updateScene({
API.updateScene({
elements: [container],
storeAction: StoreAction.UPDATE,
});
// Simulate local update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[0], {
x: 200,
@@ -4041,7 +4015,7 @@ describe("history", () => {
Keyboard.undo();
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[0], {
boundElements: [{ id: text.id, type: "text" }],
@@ -4139,13 +4113,13 @@ describe("history", () => {
// TODO: #7348 this leads to empty undo/redo and could be confusing - instead we might consider redrawing container based on the text dimensions
it("should redraw bound text to match container dimensions when the bound text is updated through the history", async () => {
// Initialize the scene
excalidrawAPI.updateScene({
API.updateScene({
elements: [text],
storeAction: StoreAction.UPDATE,
});
// Simulate local update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[0], {
x: 205,
@@ -4159,7 +4133,7 @@ describe("history", () => {
Keyboard.undo();
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(container, {
boundElements: [{ id: text.id, type: "text" }],
@@ -4257,7 +4231,7 @@ describe("history", () => {
rect2 = API.createElement({ ...rect2Props });
// Simulate local update
excalidrawAPI.updateScene({
API.updateScene({
elements: [rect1, rect2],
storeAction: StoreAction.CAPTURE,
});
@@ -4333,7 +4307,7 @@ describe("history", () => {
]);
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[0], {
// no conflicting updates
@@ -4478,7 +4452,7 @@ describe("history", () => {
});
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
h.elements[0],
newElementWith(h.elements[1], { boundElements: [] }),
@@ -4589,7 +4563,7 @@ describe("history", () => {
});
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
arrow,
newElementWith(h.elements[0], {
@@ -4674,13 +4648,13 @@ describe("history", () => {
});
// Simulate local update
excalidrawAPI.updateScene({
API.updateScene({
elements: [arrow],
storeAction: StoreAction.CAPTURE,
});
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[0] as ExcalidrawLinearElement, {
startBinding: {
@@ -4833,7 +4807,7 @@ describe("history", () => {
);
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
h.elements[0],
newElementWith(h.elements[1], { x: 500, y: -500 }),
@@ -4909,19 +4883,19 @@ describe("history", () => {
it("should not rebind frame child with frame when frame was remotely deleted and frame child is added back through the history ", async () => {
// Initialize the scene
excalidrawAPI.updateScene({
API.updateScene({
elements: [frame],
storeAction: StoreAction.UPDATE,
});
// Simulate local update
excalidrawAPI.updateScene({
API.updateScene({
elements: [rect, h.elements[0]],
storeAction: StoreAction.CAPTURE,
});
// Simulate local update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
newElementWith(h.elements[0], {
frameId: frame.id,
@@ -4965,7 +4939,7 @@ describe("history", () => {
Keyboard.undo();
// Simulate remote update
excalidrawAPI.updateScene({
API.updateScene({
elements: [
h.elements[0],
newElementWith(h.elements[1], {
+6 -3
View File
@@ -1,6 +1,7 @@
import React from "react";
import { vi } from "vitest";
import { fireEvent, render, waitFor } from "./test-utils";
import { queryByTestId } from "@testing-library/react";
import { act, queryByTestId } from "@testing-library/react";
import { Excalidraw } from "../index";
import { API } from "./helpers/api";
@@ -43,7 +44,9 @@ vi.mock("../data/filesystem.ts", async (importOriginal) => {
describe("library", () => {
beforeEach(async () => {
await render(<Excalidraw />);
h.app.library.resetLibrary();
await act(() => {
return h.app.library.resetLibrary();
});
});
it("import library via drag&drop", async () => {
@@ -208,7 +211,7 @@ describe("library menu", () => {
"dropdown-menu-button",
)!,
);
queryByTestId(container, "lib-dropdown--load")!.click();
fireEvent.click(queryByTestId(container, "lib-dropdown--load")!);
const libraryItems = parseLibraryJSON(await libraryJSONPromise);
@@ -1,3 +1,4 @@
import React from "react";
import ReactDOM from "react-dom";
import type {
ExcalidrawElement,
@@ -17,7 +18,7 @@ import { API } from "../tests/helpers/api";
import type { Point } from "../types";
import { KEYS } from "../keys";
import { LinearElementEditor } from "../element/linearElementEditor";
import { queryByTestId, queryByText } from "@testing-library/react";
import { act, queryByTestId, queryByText } from "@testing-library/react";
import {
getBoundTextElementPosition,
wrapText,
@@ -27,7 +28,6 @@ import * as textElementUtils from "../element/textElement";
import { ROUNDNESS, VERTICAL_ALIGN } from "../constants";
import { vi } from "vitest";
import { arrayToMap } from "../utils";
import React from "react";
const renderInteractiveScene = vi.spyOn(
InteractiveCanvas,
@@ -80,7 +80,7 @@ describe("Test Linear Elements", () => {
],
roundness,
});
h.elements = [line];
API.setElements([line]);
mouse.clickAt(p1[0], p1[1]);
return line;
@@ -108,7 +108,7 @@ describe("Test Linear Elements", () => {
roundness,
});
mutateElement(line, { points: line.points });
h.elements = [line];
API.setElements([line]);
mouse.clickAt(p1[0], p1[1]);
return line;
};
@@ -786,7 +786,7 @@ describe("Test Linear Elements", () => {
it("in-editor dragging a line point covered by another element", () => {
createTwoPointerLinearElement("line");
const line = h.elements[0] as ExcalidrawLinearElement;
h.elements = [
API.setElements([
line,
API.createElement({
type: "rectangle",
@@ -797,7 +797,7 @@ describe("Test Linear Elements", () => {
backgroundColor: "red",
fillStyle: "solid",
}),
];
]);
const dragEndPositionOffset = [100, 100] as const;
API.setSelectedElements([line]);
enterLineEditingMode(line, true);
@@ -854,7 +854,7 @@ describe("Test Linear Elements", () => {
}
});
const updatedTextElement = { ...textElement, originalText: text };
h.elements = [...elements, updatedTextElement];
API.setElements([...elements, updatedTextElement]);
return { textElement: updatedTextElement, container };
};
@@ -968,17 +968,13 @@ describe("Test Linear Elements", () => {
target: { value: DEFAULT_TEXT },
});
await new Promise((r) => setTimeout(r, 0));
editor.blur();
Keyboard.exitTextEditor(editor);
expect(arrow.boundElements).toStrictEqual([
{ id: text.id, type: "text" },
]);
expect((h.elements[1] as ExcalidrawTextElementWithContainer).text)
.toMatchInlineSnapshot(`
"Online whiteboard
collaboration made
easy"
`);
expect(
(h.elements[1] as ExcalidrawTextElementWithContainer).text,
).toMatchSnapshot();
});
it("should bind text to arrow when clicked on arrow and enter pressed", async () => {
@@ -998,21 +994,16 @@ describe("Test Linear Elements", () => {
".excalidraw-textEditorContainer > textarea",
) as HTMLTextAreaElement;
await new Promise((r) => setTimeout(r, 0));
fireEvent.change(editor, {
target: { value: DEFAULT_TEXT },
});
editor.blur();
Keyboard.exitTextEditor(editor);
expect(arrow.boundElements).toStrictEqual([
{ id: textElement.id, type: "text" },
]);
expect((h.elements[1] as ExcalidrawTextElementWithContainer).text)
.toMatchInlineSnapshot(`
"Online whiteboard
collaboration made
easy"
`);
expect(
(h.elements[1] as ExcalidrawTextElementWithContainer).text,
).toMatchSnapshot();
});
it("should not bind text to line when double clicked", async () => {
@@ -1059,11 +1050,7 @@ describe("Test Linear Elements", () => {
"y": 60,
}
`);
expect(textElement.text).toMatchInlineSnapshot(`
"Online whiteboard
collaboration made
easy"
`);
expect(textElement.text).toMatchSnapshot();
expect(
LinearElementEditor.getElementAbsoluteCoords(
container,
@@ -1103,11 +1090,9 @@ describe("Test Linear Elements", () => {
"y": 45,
}
`);
expect((h.elements[1] as ExcalidrawTextElementWithContainer).text)
.toMatchInlineSnapshot(`
"Online whiteboard
collaboration made easy"
`);
expect(
(h.elements[1] as ExcalidrawTextElementWithContainer).text,
).toMatchSnapshot();
expect(
LinearElementEditor.getElementAbsoluteCoords(
container,
@@ -1143,11 +1128,7 @@ describe("Test Linear Elements", () => {
"y": 10,
}
`);
expect(textElement.text).toMatchInlineSnapshot(`
"Online whiteboard
collaboration made
easy"
`);
expect(textElement.text).toMatchSnapshot();
const points = LinearElementEditor.getPointsGlobalCoordinates(
container,
elementsMap,
@@ -1171,10 +1152,7 @@ describe("Test Linear Elements", () => {
"y": -5,
}
`);
expect(textElement.text).toMatchInlineSnapshot(`
"Online whiteboard
collaboration made easy"
`);
expect(textElement.text).toMatchSnapshot();
});
it("should not render vertical align tool when element selected", () => {
@@ -1207,9 +1185,8 @@ describe("Test Linear Elements", () => {
const editor = document.querySelector(
".excalidraw-textEditorContainer > textarea",
) as HTMLTextAreaElement;
await new Promise((r) => setTimeout(r, 0));
fireEvent.change(editor, { target: { value: DEFAULT_TEXT } });
editor.blur();
Keyboard.exitTextEditor(editor);
const textElement = h.elements[2] as ExcalidrawTextElementWithContainer;
@@ -1223,10 +1200,7 @@ describe("Test Linear Elements", () => {
font,
getBoundTextMaxWidth(arrow, null),
),
).toMatchInlineSnapshot(`
"Online whiteboard
collaboration made easy"
`);
).toMatchSnapshot();
const handleBindTextResizeSpy = vi.spyOn(
textElementUtils,
"handleBindTextResize",
@@ -1252,11 +1226,7 @@ describe("Test Linear Elements", () => {
font,
getBoundTextMaxWidth(arrow, null),
),
).toMatchInlineSnapshot(`
"Online whiteboard
collaboration made
easy"
`);
).toMatchSnapshot();
});
it("should not render horizontal align tool when element selected", () => {
@@ -1280,7 +1250,7 @@ describe("Test Linear Elements", () => {
expect(text.x).toBe(0);
expect(text.y).toBe(0);
h.elements = [h.elements[0], text];
API.setElements([h.elements[0], text]);
const container = h.elements[0];
API.setSelectedElements([container, text]);
@@ -1358,20 +1328,25 @@ describe("Test Linear Elements", () => {
const line = createThreePointerLinearElement("arrow");
const [origStartX, origStartY] = [line.x, line.y];
LinearElementEditor.movePoints(
line,
[
{ index: 0, point: [line.points[0][0] + 10, line.points[0][1] + 10] },
{
index: line.points.length - 1,
point: [
line.points[line.points.length - 1][0] - 10,
line.points[line.points.length - 1][1] - 10,
],
},
],
h.scene,
);
act(() => {
LinearElementEditor.movePoints(
line,
[
{
index: 0,
point: [line.points[0][0] + 10, line.points[0][1] + 10],
},
{
index: line.points.length - 1,
point: [
line.points[line.points.length - 1][0] - 10,
line.points[line.points.length - 1][1] - 10,
],
},
],
h.scene,
);
});
expect(line.x).toBe(origStartX + 10);
expect(line.y).toBe(origStartY + 10);
+14 -11
View File
@@ -1,5 +1,6 @@
import React from "react";
import ReactDOM from "react-dom";
import { render, fireEvent } from "./test-utils";
import { render, fireEvent, act } from "./test-utils";
import { Excalidraw } from "../index";
import * as StaticScene from "../renderer/staticScene";
import * as InteractiveCanvas from "../renderer/interactiveScene";
@@ -80,22 +81,24 @@ describe("move element", () => {
const rectB = UI.createElement("rectangle", { x: 200, y: 0, size: 300 });
const arrow = UI.createElement("arrow", { x: 110, y: 50, size: 80 });
const elementsMap = h.app.scene.getNonDeletedElementsMap();
// bind line to two rectangles
bindOrUnbindLinearElement(
arrow.get() as NonDeleted<ExcalidrawLinearElement>,
rectA.get() as ExcalidrawRectangleElement,
rectB.get() as ExcalidrawRectangleElement,
elementsMap,
{} as Scene,
);
act(() => {
// bind line to two rectangles
bindOrUnbindLinearElement(
arrow.get() as NonDeleted<ExcalidrawLinearElement>,
rectA.get() as ExcalidrawRectangleElement,
rectB.get() as ExcalidrawRectangleElement,
elementsMap,
{} as Scene,
);
});
// select the second rectangle
new Pointer("mouse").clickOn(rectB);
expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(
`20`,
`19`,
);
expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`17`);
expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`16`);
expect(h.state.selectionElement).toBeNull();
expect(h.elements.length).toEqual(3);
expect(h.state.selectedElementIds[rectB.id]).toBeTruthy();
@@ -1,3 +1,4 @@
import React from "react";
import ReactDOM from "react-dom";
import {
render,
@@ -1,9 +1,11 @@
import React from "react";
import { vi } from "vitest";
import { Excalidraw, StoreAction } from "../../index";
import type { ExcalidrawImperativeAPI } from "../../types";
import { resolvablePromise } from "../../utils";
import { render } from "../test-utils";
import { Pointer } from "../helpers/ui";
import { API } from "../helpers/api";
describe("event callbacks", () => {
const h = window.h;
@@ -27,7 +29,7 @@ describe("event callbacks", () => {
const origBackgroundColor = h.state.viewBackgroundColor;
excalidrawAPI.onChange(onChange);
excalidrawAPI.updateScene({
API.updateScene({
appState: { viewBackgroundColor: "red" },
storeAction: StoreAction.CAPTURE,
});
+1 -1
View File
@@ -15,5 +15,5 @@ export const updateTextEditor = (
value: string,
) => {
fireEvent.change(editor, { target: { value } });
editor.dispatchEvent(new Event("input"));
fireEvent.input(editor);
};
@@ -1,3 +1,4 @@
import React from "react";
import ReactDOM from "react-dom";
import type { ExcalidrawElement } from "../element/types";
import { CODES, KEYS } from "../keys";
@@ -55,7 +56,7 @@ beforeEach(async () => {
finger2.reset();
await render(<Excalidraw handleKeyboardGlobally={true} />);
h.setState({ height: 768, width: 1024 });
API.setAppState({ height: 768, width: 1024 });
});
afterEach(() => {
@@ -757,7 +758,7 @@ describe("regression tests", () => {
width: 500,
height: 500,
});
h.elements = [rect1, rect2];
API.setElements([rect1, rect2]);
mouse.select(rect1);
@@ -793,7 +794,7 @@ describe("regression tests", () => {
width: 500,
height: 500,
});
h.elements = [rect1, rect2];
API.setElements([rect1, rect2]);
mouse.select(rect1);
+8 -7
View File
@@ -1,3 +1,4 @@
import React from "react";
import ReactDOM from "react-dom";
import { render } from "./test-utils";
import { reseed } from "../random";
@@ -537,7 +538,7 @@ describe("text element", () => {
describe("image element", () => {
it("resizes", async () => {
const image = API.createElement({ type: "image", width: 100, height: 100 });
h.elements = [image];
API.setElements([image]);
UI.resize(image, "ne", [-20, -30]);
expect(image.x).toBeCloseTo(0);
@@ -550,7 +551,7 @@ describe("image element", () => {
it("flips while resizing", async () => {
const image = API.createElement({ type: "image", width: 100, height: 100 });
h.elements = [image];
API.setElements([image]);
UI.resize(image, "sw", [150, -150]);
expect(image.x).toBeCloseTo(100);
@@ -563,7 +564,7 @@ describe("image element", () => {
it("resizes with locked/unlocked aspect ratio", async () => {
const image = API.createElement({ type: "image", width: 100, height: 100 });
h.elements = [image];
API.setElements([image]);
UI.resize(image, "ne", [30, -20]);
expect(image.x).toBeCloseTo(0);
@@ -581,7 +582,7 @@ describe("image element", () => {
it("resizes from center", async () => {
const image = API.createElement({ type: "image", width: 100, height: 100 });
h.elements = [image];
API.setElements([image]);
UI.resize(image, "nw", [25, 15], { alt: true });
expect(image.x).toBeCloseTo(15);
@@ -598,7 +599,7 @@ describe("image element", () => {
width: 100,
height: 100,
});
h.elements = [image];
API.setElements([image]);
const arrow = UI.createElement("arrow", {
x: -30,
y: 50,
@@ -971,7 +972,7 @@ describe("multiple selection", () => {
width: 120,
height: 80,
});
h.elements = [topImage, bottomImage];
API.setElements([topImage, bottomImage]);
const selectionWidth = 200;
const selectionHeight = 230;
@@ -1043,7 +1044,7 @@ describe("multiple selection", () => {
height: 100,
angle: (Math.PI * 7) / 6,
});
h.elements = [image];
API.setElements([image]);
const line = UI.createElement("line", {
x: 60,
@@ -1,3 +1,4 @@
import React from "react";
import ReactDOM from "react-dom";
import { render } from "./test-utils";
import { reseed } from "../random";
@@ -88,7 +88,7 @@ describe("exportToSvg", () => {
);
expect(svgElement.getAttribute("filter")).toMatchInlineSnapshot(
'"_themeFilter_1883f3"',
`"_themeFilter_1883f3"`,
);
});
+4 -3
View File
@@ -1,3 +1,4 @@
import React from "react";
import {
mockBoundingClientRect,
render,
@@ -98,13 +99,13 @@ describe("appState", () => {
const zoom = h.state.zoom.value;
// Assert we scroll properly when zoomed in
h.setState({ zoom: { value: (zoom * 1.1) as typeof zoom } });
API.setAppState({ zoom: { value: (zoom * 1.1) as typeof zoom } });
scrollTest();
// Assert we scroll properly when zoomed out
h.setState({ zoom: { value: (zoom * 0.9) as typeof zoom } });
API.setAppState({ zoom: { value: (zoom * 0.9) as typeof zoom } });
scrollTest();
// Assert we scroll properly with normal zoom
h.setState({ zoom: { value: zoom } });
API.setAppState({ zoom: { value: zoom } });
scrollTest();
restoreOriginalGetBoundingClientRect();
});
+7 -6
View File
@@ -1,3 +1,4 @@
import React from "react";
import ReactDOM from "react-dom";
import {
render,
@@ -59,7 +60,7 @@ describe("box-selection", () => {
height: 50,
});
h.elements = [rect1, rect2];
API.setElements([rect1, rect2]);
mouse.downAt(175, -20);
mouse.moveTo(85, 70);
@@ -87,7 +88,7 @@ describe("box-selection", () => {
fillStyle: "solid",
});
h.elements = [rect1];
API.setElements([rect1]);
mouse.downAt(75, -20);
mouse.moveTo(-15, 70);
@@ -132,7 +133,7 @@ describe("inner box-selection", () => {
width: 50,
height: 50,
});
h.elements = [rect1, rect2, rect3];
API.setElements([rect1, rect2, rect3]);
Keyboard.withModifierKeys({ ctrl: true }, () => {
mouse.downAt(40, 40);
mouse.moveTo(290, 290);
@@ -168,7 +169,7 @@ describe("inner box-selection", () => {
height: 50,
groupIds: ["A"],
});
h.elements = [rect1, rect2, rect3];
API.setElements([rect1, rect2, rect3]);
Keyboard.withModifierKeys({ ctrl: true }, () => {
mouse.downAt(40, 40);
@@ -206,7 +207,7 @@ describe("inner box-selection", () => {
height: 50,
groupIds: ["A"],
});
h.elements = [rect1, rect2, rect3];
API.setElements([rect1, rect2, rect3]);
Keyboard.withModifierKeys({ ctrl: true }, () => {
mouse.downAt(rect2.x - 20, rect2.y - 20);
mouse.moveTo(rect2.x + rect2.width + 10, rect2.y + rect2.height + 10);
@@ -506,7 +507,7 @@ describe("selectedElementIds stability", () => {
height: 10,
});
h.elements = [rectangle];
API.setElements([rectangle]);
const selectedElementIds_1 = h.state.selectedElementIds;
@@ -1,3 +1,4 @@
import React from "react";
import { KEYS } from "../keys";
import { Excalidraw } from "../index";
import { API } from "./helpers/api";
+21 -16
View File
@@ -1,13 +1,12 @@
import "pepjs";
import type { RenderResult, RenderOptions } from "@testing-library/react";
import { act } from "@testing-library/react";
import { render, queries, waitFor, fireEvent } from "@testing-library/react";
import * as toolQueries from "./queries/toolQueries";
import type { ImportedDataState } from "../data/types";
import { STORAGE_KEYS } from "../../../excalidraw-app/app_constants";
import type { SceneData } from "../types";
import { getSelectedElements } from "../scene/selection";
import type { ExcalidrawElement } from "../element/types";
import { UI } from "./helpers/ui";
@@ -67,6 +66,12 @@ const renderApp: TestRenderFn = async (ui, options) => {
if (!interactiveCanvas) {
throw new Error("not initialized yet");
}
// hack-awaiting app.initialScene() which solves some test race conditions
// (later we may switch this with proper event listener)
if (window.h.state.isLoading) {
throw new Error("still loading");
}
});
return renderResult;
@@ -118,10 +123,6 @@ const initLocalStorage = (data: ImportedDataState) => {
}
};
export const updateSceneData = (data: SceneData) => {
(window.collab as any).excalidrawAPI.updateScene(data);
};
const originalGetBoundingClientRect =
global.window.HTMLDivElement.prototype.getBoundingClientRect;
@@ -166,20 +167,24 @@ export const withExcalidrawDimensions = async (
cb: () => void,
) => {
mockBoundingClientRect(dimensions);
// @ts-ignore
h.app.refreshViewportBreakpoints();
// @ts-ignore
h.app.refreshEditorBreakpoints();
window.h.app.refresh();
act(() => {
// @ts-ignore
h.app.refreshViewportBreakpoints();
// @ts-ignore
h.app.refreshEditorBreakpoints();
window.h.app.refresh();
});
await cb();
restoreOriginalGetBoundingClientRect();
// @ts-ignore
h.app.refreshViewportBreakpoints();
// @ts-ignore
h.app.refreshEditorBreakpoints();
window.h.app.refresh();
act(() => {
// @ts-ignore
h.app.refreshViewportBreakpoints();
// @ts-ignore
h.app.refreshEditorBreakpoints();
window.h.app.refresh();
});
};
export const restoreOriginalGetBoundingClientRect = () => {
+11 -4
View File
@@ -1,7 +1,8 @@
import React from "react";
import { Excalidraw } from "../index";
import type { ExcalidrawImperativeAPI } from "../types";
import { resolvablePromise } from "../utils";
import { render } from "./test-utils";
import { act, render } from "./test-utils";
import { Pointer } from "./helpers/ui";
describe("setActiveTool()", () => {
@@ -28,7 +29,9 @@ describe("setActiveTool()", () => {
it("should set the active tool type", async () => {
expect(h.state.activeTool.type).toBe("selection");
excalidrawAPI.setActiveTool({ type: "rectangle" });
act(() => {
excalidrawAPI.setActiveTool({ type: "rectangle" });
});
expect(h.state.activeTool.type).toBe("rectangle");
mouse.down(10, 10);
@@ -39,7 +42,9 @@ describe("setActiveTool()", () => {
it("should support tool locking", async () => {
expect(h.state.activeTool.type).toBe("selection");
excalidrawAPI.setActiveTool({ type: "rectangle", locked: true });
act(() => {
excalidrawAPI.setActiveTool({ type: "rectangle", locked: true });
});
expect(h.state.activeTool.type).toBe("rectangle");
mouse.down(10, 10);
@@ -50,7 +55,9 @@ describe("setActiveTool()", () => {
it("should set custom tool", async () => {
expect(h.state.activeTool.type).toBe("selection");
excalidrawAPI.setActiveTool({ type: "custom", customType: "comment" });
act(() => {
excalidrawAPI.setActiveTool({ type: "custom", customType: "comment" });
});
expect(h.state.activeTool.type).toBe("custom");
expect(h.state.activeTool.customType).toBe("comment");
});
+5 -4
View File
@@ -1,10 +1,11 @@
import React from "react";
import { render, GlobalTestState } from "./test-utils";
import { Excalidraw } from "../index";
import { KEYS } from "../keys";
import { Keyboard, Pointer, UI } from "./helpers/ui";
import { CURSOR_TYPE } from "../constants";
import { API } from "./helpers/api";
const { h } = window;
const mouse = new Pointer("mouse");
const touch = new Pointer("touch");
const pen = new Pointer("pen");
@@ -16,14 +17,14 @@ describe("view mode", () => {
});
it("after switching to view mode cursor type should be pointer", async () => {
h.setState({ viewModeEnabled: true });
API.setAppState({ viewModeEnabled: true });
expect(GlobalTestState.interactiveCanvas.style.cursor).toBe(
CURSOR_TYPE.GRAB,
);
});
it("after switching to view mode, moving, clicking, and pressing space key cursor type should be pointer", async () => {
h.setState({ viewModeEnabled: true });
API.setAppState({ viewModeEnabled: true });
pointerTypes.forEach((pointerType) => {
const pointer = pointerType;
@@ -58,7 +59,7 @@ describe("view mode", () => {
);
}
h.setState({ viewModeEnabled: true });
API.setAppState({ viewModeEnabled: true });
expect(GlobalTestState.interactiveCanvas.style.cursor).toBe(
CURSOR_TYPE.GRAB,
);
+44 -39
View File
@@ -1,5 +1,6 @@
import React from "react";
import ReactDOM from "react-dom";
import { render } from "./test-utils";
import { act, render } from "./test-utils";
import { Excalidraw } from "../index";
import { reseed } from "../random";
import {
@@ -86,31 +87,35 @@ const populateElements = (
);
// initialize `boundElements` on containers, if applicable
h.elements = newElements.map((element, index, elements) => {
const nextElement = elements[index + 1];
if (
nextElement &&
"containerId" in nextElement &&
element.id === nextElement.containerId
) {
return {
...element,
boundElements: [{ type: "text", id: nextElement.id }],
};
}
return element;
});
API.setElements(
newElements.map((element, index, elements) => {
const nextElement = elements[index + 1];
if (
nextElement &&
"containerId" in nextElement &&
element.id === nextElement.containerId
) {
return {
...element,
boundElements: [{ type: "text", id: nextElement.id }],
};
}
return element;
}),
);
h.setState({
...selectGroupsForSelectedElements(
{ ...h.state, ...appState, selectedElementIds },
h.elements,
h.state,
null,
),
...appState,
selectedElementIds,
} as AppState);
act(() => {
h.setState({
...selectGroupsForSelectedElements(
{ ...h.state, ...appState, selectedElementIds },
h.elements,
h.state,
null,
),
...appState,
selectedElementIds,
} as AppState);
});
return selectedElementIds;
};
@@ -140,7 +145,7 @@ const assertZindex = ({
}) => {
const selectedElementIds = populateElements(elements, appState);
operations.forEach(([action, expected]) => {
h.app.actionManager.executeAction(action);
API.executeAction(action);
expect(h.elements.map((element) => element.id)).toEqual(expected);
expect(h.state.selectedElementIds).toEqual(selectedElementIds);
});
@@ -894,7 +899,7 @@ describe("z-index manipulation", () => {
{ id: "A", isSelected: true },
{ id: "B", isSelected: true },
]);
h.app.actionManager.executeAction(actionDuplicateSelection);
API.executeAction(actionDuplicateSelection);
expect(h.elements).toMatchObject([
{ id: "A" },
{ id: "A_copy" },
@@ -906,7 +911,7 @@ describe("z-index manipulation", () => {
{ id: "A", groupIds: ["g1"], isSelected: true },
{ id: "B", groupIds: ["g1"], isSelected: true },
]);
h.app.actionManager.executeAction(actionDuplicateSelection);
API.executeAction(actionDuplicateSelection);
expect(h.elements).toMatchObject([
{ id: "A" },
{ id: "B" },
@@ -927,7 +932,7 @@ describe("z-index manipulation", () => {
{ id: "B", groupIds: ["g1"], isSelected: true },
{ id: "C" },
]);
h.app.actionManager.executeAction(actionDuplicateSelection);
API.executeAction(actionDuplicateSelection);
expect(h.elements).toMatchObject([
{ id: "A" },
{ id: "B" },
@@ -949,7 +954,7 @@ describe("z-index manipulation", () => {
{ id: "B", groupIds: ["g1"], isSelected: true },
{ id: "C", isSelected: true },
]);
h.app.actionManager.executeAction(actionDuplicateSelection);
API.executeAction(actionDuplicateSelection);
expect(h.elements.map((element) => element.id)).toEqual([
"A",
"B",
@@ -965,7 +970,7 @@ describe("z-index manipulation", () => {
{ id: "C", groupIds: ["g2"], isSelected: true },
{ id: "D", groupIds: ["g2"], isSelected: true },
]);
h.app.actionManager.executeAction(actionDuplicateSelection);
API.executeAction(actionDuplicateSelection);
expect(h.elements.map((element) => element.id)).toEqual([
"A",
"B",
@@ -987,7 +992,7 @@ describe("z-index manipulation", () => {
selectedGroupIds: { g1: true },
},
);
h.app.actionManager.executeAction(actionDuplicateSelection);
API.executeAction(actionDuplicateSelection);
expect(h.elements.map((element) => element.id)).toEqual([
"A",
"B",
@@ -1007,7 +1012,7 @@ describe("z-index manipulation", () => {
selectedGroupIds: { g2: true },
},
);
h.app.actionManager.executeAction(actionDuplicateSelection);
API.executeAction(actionDuplicateSelection);
expect(h.elements.map((element) => element.id)).toEqual([
"A",
"B",
@@ -1030,7 +1035,7 @@ describe("z-index manipulation", () => {
selectedGroupIds: { g2: true, g4: true },
},
);
h.app.actionManager.executeAction(actionDuplicateSelection);
API.executeAction(actionDuplicateSelection);
expect(h.elements.map((element) => element.id)).toEqual([
"A",
"B",
@@ -1054,7 +1059,7 @@ describe("z-index manipulation", () => {
],
{ editingGroupId: "g1" },
);
h.app.actionManager.executeAction(actionDuplicateSelection);
API.executeAction(actionDuplicateSelection);
expect(h.elements.map((element) => element.id)).toEqual([
"A",
"A_copy",
@@ -1070,7 +1075,7 @@ describe("z-index manipulation", () => {
],
{ editingGroupId: "g1" },
);
h.app.actionManager.executeAction(actionDuplicateSelection);
API.executeAction(actionDuplicateSelection);
expect(h.elements.map((element) => element.id)).toEqual([
"A",
"B",
@@ -1086,7 +1091,7 @@ describe("z-index manipulation", () => {
],
{ editingGroupId: "g1" },
);
h.app.actionManager.executeAction(actionDuplicateSelection);
API.executeAction(actionDuplicateSelection);
expect(h.elements.map((element) => element.id)).toEqual([
"A",
"A_copy",
@@ -1102,7 +1107,7 @@ describe("z-index manipulation", () => {
{ id: "B" },
{ id: "C", groupIds: ["g1"], isSelected: true },
]);
h.app.actionManager.executeAction(actionDuplicateSelection);
API.executeAction(actionDuplicateSelection);
expect(h.elements.map((element) => element.id)).toEqual([
"A",
"C",
@@ -1120,7 +1125,7 @@ describe("z-index manipulation", () => {
{ id: "D" },
]);
expect(h.state.selectedGroupIds).toEqual({ g1: true });
h.app.actionManager.executeAction(actionDuplicateSelection);
API.executeAction(actionDuplicateSelection);
expect(h.elements.map((element) => element.id)).toEqual([
"A",
"B",