diff --git a/excalidraw-app/data/LocalData.ts b/excalidraw-app/data/LocalData.ts index d09d8aa88e..18f554390c 100644 --- a/excalidraw-app/data/LocalData.ts +++ b/excalidraw-app/data/LocalData.ts @@ -11,6 +11,7 @@ */ import { clearAppStateForLocalStorage } from "@excalidraw/excalidraw/appState"; +import { stringifyWithPrecision } from "@excalidraw/excalidraw/data/json"; import { CANVAS_SEARCH_TAB, DEFAULT_SIDEBAR, @@ -89,7 +90,7 @@ const saveDataStateToLocalStorage = ( localStorage.setItem( STORAGE_KEYS.LOCAL_STORAGE_ELEMENTS, - JSON.stringify(getNonDeletedElements(elements)), + stringifyWithPrecision(getNonDeletedElements(elements)), ); localStorage.setItem( STORAGE_KEYS.LOCAL_STORAGE_APP_STATE, diff --git a/excalidraw-app/data/firebase.ts b/excalidraw-app/data/firebase.ts index 11177d90aa..f608825083 100644 --- a/excalidraw-app/data/firebase.ts +++ b/excalidraw-app/data/firebase.ts @@ -1,6 +1,7 @@ import { reconcileElements } from "@excalidraw/excalidraw"; import { MIME_TYPES, toBrandedType } from "@excalidraw/common"; import { decompressData } from "@excalidraw/excalidraw/data/encode"; +import { stringifyWithPrecision } from "@excalidraw/excalidraw/data/json"; import { encryptData, decryptData, @@ -94,7 +95,7 @@ const encryptElements = async ( key: string, elements: readonly ExcalidrawElement[], ): Promise<{ ciphertext: ArrayBuffer; iv: Uint8Array }> => { - const json = JSON.stringify(elements); + const json = stringifyWithPrecision(elements); const encoded = new TextEncoder().encode(json); const { encryptedBuffer, iv } = await encryptData(key, encoded); diff --git a/packages/common/src/utils.ts b/packages/common/src/utils.ts index e979323649..883166c8f6 100644 --- a/packages/common/src/utils.ts +++ b/packages/common/src/utils.ts @@ -1,4 +1,4 @@ -import { average } from "@excalidraw/math"; +import { average, round } from "@excalidraw/math"; import type { GlobalCoord } from "@excalidraw/math"; @@ -429,11 +429,21 @@ export const viewportCoordsToSceneCoords = ( scrollX: number; scrollY: number; }, + decimals: number = 2, ) => { const x = (clientX - offsetLeft) / zoom.value - scrollX; const y = (clientY - offsetTop) / zoom.value - scrollY; - return { x, y } as GlobalCoord; + if (decimals === 0) { + return toBrandedType({ x, y }); + } + + const precision = Math.pow(10, decimals); + + return toBrandedType({ + x: round(x, precision), + y: round(y, precision), + }); }; export const sceneCoordsToViewportCoords = ( diff --git a/packages/element/tests/align.test.tsx b/packages/element/tests/align.test.tsx index b796793690..c211b6adb4 100644 --- a/packages/element/tests/align.test.tsx +++ b/packages/element/tests/align.test.tsx @@ -72,123 +72,123 @@ describe("aligning", () => { it("aligns two objects correctly to the top", () => { createAndSelectTwoRectangles(); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(110); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(110); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(110); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(110); Keyboard.withModifierKeys({ ctrl: true, shift: true }, () => { Keyboard.keyPress(KEYS.ARROW_UP); }); // Check if x position did not change - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(110); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(110); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(0); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(0); }); it("aligns two objects correctly to the bottom", () => { createAndSelectTwoRectangles(); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(110); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(110); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(110); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(110); Keyboard.withModifierKeys({ ctrl: true, shift: true }, () => { Keyboard.keyPress(KEYS.ARROW_DOWN); }); // Check if x position did not change - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(110); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(110); - expect(API.getSelectedElements()[0].y).toEqual(110); - expect(API.getSelectedElements()[1].y).toEqual(110); + expect(API.getSelectedElements()[0].y).toBeCloseTo(110); + expect(API.getSelectedElements()[1].y).toBeCloseTo(110); }); it("aligns two objects correctly to the left", () => { createAndSelectTwoRectangles(); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(110); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(110); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(110); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(110); Keyboard.withModifierKeys({ ctrl: true, shift: true }, () => { Keyboard.keyPress(KEYS.ARROW_LEFT); }); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(0); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(0); // Check if y position did not change - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(110); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(110); }); it("aligns two objects correctly to the right", () => { createAndSelectTwoRectangles(); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(110); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(110); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(110); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(110); Keyboard.withModifierKeys({ ctrl: true, shift: true }, () => { Keyboard.keyPress(KEYS.ARROW_RIGHT); }); - expect(API.getSelectedElements()[0].x).toEqual(110); - expect(API.getSelectedElements()[1].x).toEqual(110); + expect(API.getSelectedElements()[0].x).toBeCloseTo(110); + expect(API.getSelectedElements()[1].x).toBeCloseTo(110); // Check if y position did not change - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(110); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(110); }); it("centers two objects with different sizes correctly vertically", () => { createAndSelectTwoRectanglesWithDifferentSizes(); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(110); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(110); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(110); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(110); API.executeAction(actionAlignVerticallyCentered); // Check if x position did not change - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(110); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(110); - expect(API.getSelectedElements()[0].y).toEqual(60); - expect(API.getSelectedElements()[1].y).toEqual(55); + expect(API.getSelectedElements()[0].y).toBeCloseTo(60); + expect(API.getSelectedElements()[1].y).toBeCloseTo(55); }); it("centers two objects with different sizes correctly horizontally", () => { createAndSelectTwoRectanglesWithDifferentSizes(); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(110); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(110); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(110); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(110); API.executeAction(actionAlignHorizontallyCentered); - expect(API.getSelectedElements()[0].x).toEqual(60); - expect(API.getSelectedElements()[1].x).toEqual(55); + expect(API.getSelectedElements()[0].x).toBeCloseTo(60); + expect(API.getSelectedElements()[1].x).toBeCloseTo(55); // Check if y position did not change - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(110); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(110); }); const createAndSelectGroupAndRectangle = () => { @@ -226,85 +226,85 @@ describe("aligning", () => { it("aligns a group with another element correctly to the top", () => { createAndSelectGroupAndRectangle(); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(100); - expect(API.getSelectedElements()[2].y).toEqual(200); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(100); + expect(API.getSelectedElements()[2].y).toBeCloseTo(200); API.executeAction(actionAlignTop); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(100); - expect(API.getSelectedElements()[2].y).toEqual(0); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(100); + expect(API.getSelectedElements()[2].y).toBeCloseTo(0); }); it("aligns a group with another element correctly to the bottom", () => { createAndSelectGroupAndRectangle(); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(100); - expect(API.getSelectedElements()[2].y).toEqual(200); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(100); + expect(API.getSelectedElements()[2].y).toBeCloseTo(200); API.executeAction(actionAlignBottom); - expect(API.getSelectedElements()[0].y).toEqual(100); - expect(API.getSelectedElements()[1].y).toEqual(200); - expect(API.getSelectedElements()[2].y).toEqual(200); + expect(API.getSelectedElements()[0].y).toBeCloseTo(100); + expect(API.getSelectedElements()[1].y).toBeCloseTo(200); + expect(API.getSelectedElements()[2].y).toBeCloseTo(200); }); it("aligns a group with another element correctly to the left", () => { createAndSelectGroupAndRectangle(); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(100); - expect(API.getSelectedElements()[2].x).toEqual(200); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(100); + expect(API.getSelectedElements()[2].x).toBeCloseTo(200); API.executeAction(actionAlignLeft); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(100); - expect(API.getSelectedElements()[2].x).toEqual(0); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(100); + expect(API.getSelectedElements()[2].x).toBeCloseTo(0); }); it("aligns a group with another element correctly to the right", () => { createAndSelectGroupAndRectangle(); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(100); - expect(API.getSelectedElements()[2].x).toEqual(200); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(100); + expect(API.getSelectedElements()[2].x).toBeCloseTo(200); API.executeAction(actionAlignRight); - expect(API.getSelectedElements()[0].x).toEqual(100); - expect(API.getSelectedElements()[1].x).toEqual(200); - expect(API.getSelectedElements()[2].x).toEqual(200); + expect(API.getSelectedElements()[0].x).toBeCloseTo(100); + expect(API.getSelectedElements()[1].x).toBeCloseTo(200); + expect(API.getSelectedElements()[2].x).toBeCloseTo(200); }); it("centers a group with another element correctly vertically", () => { createAndSelectGroupAndRectangle(); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(100); - expect(API.getSelectedElements()[2].y).toEqual(200); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(100); + expect(API.getSelectedElements()[2].y).toBeCloseTo(200); API.executeAction(actionAlignVerticallyCentered); - expect(API.getSelectedElements()[0].y).toEqual(50); - expect(API.getSelectedElements()[1].y).toEqual(150); - expect(API.getSelectedElements()[2].y).toEqual(100); + expect(API.getSelectedElements()[0].y).toBeCloseTo(50); + expect(API.getSelectedElements()[1].y).toBeCloseTo(150); + expect(API.getSelectedElements()[2].y).toBeCloseTo(100); }); it("centers a group with another element correctly horizontally", () => { createAndSelectGroupAndRectangle(); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(100); - expect(API.getSelectedElements()[2].x).toEqual(200); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(100); + expect(API.getSelectedElements()[2].x).toBeCloseTo(200); API.executeAction(actionAlignHorizontallyCentered); - expect(API.getSelectedElements()[0].x).toEqual(50); - expect(API.getSelectedElements()[1].x).toEqual(150); - expect(API.getSelectedElements()[2].x).toEqual(100); + expect(API.getSelectedElements()[0].x).toBeCloseTo(50); + expect(API.getSelectedElements()[1].x).toBeCloseTo(150); + expect(API.getSelectedElements()[2].x).toBeCloseTo(100); }); const createAndSelectTwoGroups = () => { @@ -354,97 +354,97 @@ describe("aligning", () => { it("aligns two groups correctly to the top", () => { createAndSelectTwoGroups(); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(100); - expect(API.getSelectedElements()[2].y).toEqual(200); - expect(API.getSelectedElements()[3].y).toEqual(300); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(100); + expect(API.getSelectedElements()[2].y).toBeCloseTo(200); + expect(API.getSelectedElements()[3].y).toBeCloseTo(300); API.executeAction(actionAlignTop); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(100); - expect(API.getSelectedElements()[2].y).toEqual(0); - expect(API.getSelectedElements()[3].y).toEqual(100); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(100); + expect(API.getSelectedElements()[2].y).toBeCloseTo(0); + expect(API.getSelectedElements()[3].y).toBeCloseTo(100); }); it("aligns two groups correctly to the bottom", () => { createAndSelectTwoGroups(); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(100); - expect(API.getSelectedElements()[2].y).toEqual(200); - expect(API.getSelectedElements()[3].y).toEqual(300); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(100); + expect(API.getSelectedElements()[2].y).toBeCloseTo(200); + expect(API.getSelectedElements()[3].y).toBeCloseTo(300); API.executeAction(actionAlignBottom); - expect(API.getSelectedElements()[0].y).toEqual(200); - expect(API.getSelectedElements()[1].y).toEqual(300); - expect(API.getSelectedElements()[2].y).toEqual(200); - expect(API.getSelectedElements()[3].y).toEqual(300); + expect(API.getSelectedElements()[0].y).toBeCloseTo(200); + expect(API.getSelectedElements()[1].y).toBeCloseTo(300); + expect(API.getSelectedElements()[2].y).toBeCloseTo(200); + expect(API.getSelectedElements()[3].y).toBeCloseTo(300); }); it("aligns two groups correctly to the left", () => { createAndSelectTwoGroups(); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(100); - expect(API.getSelectedElements()[2].x).toEqual(200); - expect(API.getSelectedElements()[3].x).toEqual(300); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(100); + expect(API.getSelectedElements()[2].x).toBeCloseTo(200); + expect(API.getSelectedElements()[3].x).toBeCloseTo(300); API.executeAction(actionAlignLeft); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(100); - expect(API.getSelectedElements()[2].x).toEqual(0); - expect(API.getSelectedElements()[3].x).toEqual(100); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(100); + expect(API.getSelectedElements()[2].x).toBeCloseTo(0); + expect(API.getSelectedElements()[3].x).toBeCloseTo(100); }); it("aligns two groups correctly to the right", () => { createAndSelectTwoGroups(); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(100); - expect(API.getSelectedElements()[2].x).toEqual(200); - expect(API.getSelectedElements()[3].x).toEqual(300); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(100); + expect(API.getSelectedElements()[2].x).toBeCloseTo(200); + expect(API.getSelectedElements()[3].x).toBeCloseTo(300); API.executeAction(actionAlignRight); - expect(API.getSelectedElements()[0].x).toEqual(200); - expect(API.getSelectedElements()[1].x).toEqual(300); - expect(API.getSelectedElements()[2].x).toEqual(200); - expect(API.getSelectedElements()[3].x).toEqual(300); + expect(API.getSelectedElements()[0].x).toBeCloseTo(200); + expect(API.getSelectedElements()[1].x).toBeCloseTo(300); + expect(API.getSelectedElements()[2].x).toBeCloseTo(200); + expect(API.getSelectedElements()[3].x).toBeCloseTo(300); }); it("centers two groups correctly vertically", () => { createAndSelectTwoGroups(); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(100); - expect(API.getSelectedElements()[2].y).toEqual(200); - expect(API.getSelectedElements()[3].y).toEqual(300); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(100); + expect(API.getSelectedElements()[2].y).toBeCloseTo(200); + expect(API.getSelectedElements()[3].y).toBeCloseTo(300); API.executeAction(actionAlignVerticallyCentered); - expect(API.getSelectedElements()[0].y).toEqual(100); - expect(API.getSelectedElements()[1].y).toEqual(200); - expect(API.getSelectedElements()[2].y).toEqual(100); - expect(API.getSelectedElements()[3].y).toEqual(200); + expect(API.getSelectedElements()[0].y).toBeCloseTo(100); + expect(API.getSelectedElements()[1].y).toBeCloseTo(200); + expect(API.getSelectedElements()[2].y).toBeCloseTo(100); + expect(API.getSelectedElements()[3].y).toBeCloseTo(200); }); it("centers two groups correctly horizontally", () => { createAndSelectTwoGroups(); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(100); - expect(API.getSelectedElements()[2].x).toEqual(200); - expect(API.getSelectedElements()[3].x).toEqual(300); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(100); + expect(API.getSelectedElements()[2].x).toBeCloseTo(200); + expect(API.getSelectedElements()[3].x).toBeCloseTo(300); API.executeAction(actionAlignHorizontallyCentered); - expect(API.getSelectedElements()[0].x).toEqual(100); - expect(API.getSelectedElements()[1].x).toEqual(200); - expect(API.getSelectedElements()[2].x).toEqual(100); - expect(API.getSelectedElements()[3].x).toEqual(200); + expect(API.getSelectedElements()[0].x).toBeCloseTo(100); + expect(API.getSelectedElements()[1].x).toBeCloseTo(200); + expect(API.getSelectedElements()[2].x).toBeCloseTo(100); + expect(API.getSelectedElements()[3].x).toBeCloseTo(200); }); const createAndSelectNestedGroupAndRectangle = () => { @@ -497,97 +497,97 @@ describe("aligning", () => { it("aligns nested group and other element correctly to the top", () => { createAndSelectNestedGroupAndRectangle(); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(100); - expect(API.getSelectedElements()[2].y).toEqual(200); - expect(API.getSelectedElements()[3].y).toEqual(300); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(100); + expect(API.getSelectedElements()[2].y).toBeCloseTo(200); + expect(API.getSelectedElements()[3].y).toBeCloseTo(300); API.executeAction(actionAlignTop); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(100); - expect(API.getSelectedElements()[2].y).toEqual(200); - expect(API.getSelectedElements()[3].y).toEqual(0); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(100); + expect(API.getSelectedElements()[2].y).toBeCloseTo(200); + expect(API.getSelectedElements()[3].y).toBeCloseTo(0); }); it("aligns nested group and other element correctly to the bottom", () => { createAndSelectNestedGroupAndRectangle(); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(100); - expect(API.getSelectedElements()[2].y).toEqual(200); - expect(API.getSelectedElements()[3].y).toEqual(300); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(100); + expect(API.getSelectedElements()[2].y).toBeCloseTo(200); + expect(API.getSelectedElements()[3].y).toBeCloseTo(300); API.executeAction(actionAlignBottom); - expect(API.getSelectedElements()[0].y).toEqual(100); - expect(API.getSelectedElements()[1].y).toEqual(200); - expect(API.getSelectedElements()[2].y).toEqual(300); - expect(API.getSelectedElements()[3].y).toEqual(300); + expect(API.getSelectedElements()[0].y).toBeCloseTo(100); + expect(API.getSelectedElements()[1].y).toBeCloseTo(200); + expect(API.getSelectedElements()[2].y).toBeCloseTo(300); + expect(API.getSelectedElements()[3].y).toBeCloseTo(300); }); it("aligns nested group and other element correctly to the left", () => { createAndSelectNestedGroupAndRectangle(); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(100); - expect(API.getSelectedElements()[2].x).toEqual(200); - expect(API.getSelectedElements()[3].x).toEqual(300); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(100); + expect(API.getSelectedElements()[2].x).toBeCloseTo(200); + expect(API.getSelectedElements()[3].x).toBeCloseTo(300); API.executeAction(actionAlignLeft); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(100); - expect(API.getSelectedElements()[2].x).toEqual(200); - expect(API.getSelectedElements()[3].x).toEqual(0); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(100); + expect(API.getSelectedElements()[2].x).toBeCloseTo(200); + expect(API.getSelectedElements()[3].x).toBeCloseTo(0); }); it("aligns nested group and other element correctly to the right", () => { createAndSelectNestedGroupAndRectangle(); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(100); - expect(API.getSelectedElements()[2].x).toEqual(200); - expect(API.getSelectedElements()[3].x).toEqual(300); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(100); + expect(API.getSelectedElements()[2].x).toBeCloseTo(200); + expect(API.getSelectedElements()[3].x).toBeCloseTo(300); API.executeAction(actionAlignRight); - expect(API.getSelectedElements()[0].x).toEqual(100); - expect(API.getSelectedElements()[1].x).toEqual(200); - expect(API.getSelectedElements()[2].x).toEqual(300); - expect(API.getSelectedElements()[3].x).toEqual(300); + expect(API.getSelectedElements()[0].x).toBeCloseTo(100); + expect(API.getSelectedElements()[1].x).toBeCloseTo(200); + expect(API.getSelectedElements()[2].x).toBeCloseTo(300); + expect(API.getSelectedElements()[3].x).toBeCloseTo(300); }); it("centers nested group and other element correctly vertically", () => { createAndSelectNestedGroupAndRectangle(); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(100); - expect(API.getSelectedElements()[2].y).toEqual(200); - expect(API.getSelectedElements()[3].y).toEqual(300); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(100); + expect(API.getSelectedElements()[2].y).toBeCloseTo(200); + expect(API.getSelectedElements()[3].y).toBeCloseTo(300); API.executeAction(actionAlignVerticallyCentered); - expect(API.getSelectedElements()[0].y).toEqual(50); - expect(API.getSelectedElements()[1].y).toEqual(150); - expect(API.getSelectedElements()[2].y).toEqual(250); - expect(API.getSelectedElements()[3].y).toEqual(150); + expect(API.getSelectedElements()[0].y).toBeCloseTo(50); + expect(API.getSelectedElements()[1].y).toBeCloseTo(150); + expect(API.getSelectedElements()[2].y).toBeCloseTo(250); + expect(API.getSelectedElements()[3].y).toBeCloseTo(150); }); it("centers nested group and other element correctly horizontally", () => { createAndSelectNestedGroupAndRectangle(); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(100); - expect(API.getSelectedElements()[2].x).toEqual(200); - expect(API.getSelectedElements()[3].x).toEqual(300); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(100); + expect(API.getSelectedElements()[2].x).toBeCloseTo(200); + expect(API.getSelectedElements()[3].x).toBeCloseTo(300); API.executeAction(actionAlignHorizontallyCentered); - expect(API.getSelectedElements()[0].x).toEqual(50); - expect(API.getSelectedElements()[1].x).toEqual(150); - expect(API.getSelectedElements()[2].x).toEqual(250); - expect(API.getSelectedElements()[3].x).toEqual(150); + expect(API.getSelectedElements()[0].x).toBeCloseTo(50); + expect(API.getSelectedElements()[1].x).toBeCloseTo(150); + expect(API.getSelectedElements()[2].x).toBeCloseTo(250); + expect(API.getSelectedElements()[3].x).toBeCloseTo(150); }); const createGroupAndSelectInEditGroupMode = () => { @@ -622,68 +622,68 @@ describe("aligning", () => { it("aligns elements within a group while in group edit mode correctly to the top", () => { createGroupAndSelectInEditGroupMode(); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(100); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(100); API.executeAction(actionAlignTop); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(0); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(0); }); it("aligns elements within a group while in group edit mode correctly to the bottom", () => { createGroupAndSelectInEditGroupMode(); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(100); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(100); API.executeAction(actionAlignBottom); - expect(API.getSelectedElements()[0].y).toEqual(100); - expect(API.getSelectedElements()[1].y).toEqual(100); + expect(API.getSelectedElements()[0].y).toBeCloseTo(100); + expect(API.getSelectedElements()[1].y).toBeCloseTo(100); }); it("aligns elements within a group while in group edit mode correctly to the left", () => { createGroupAndSelectInEditGroupMode(); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(100); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(100); API.executeAction(actionAlignLeft); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(0); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(0); }); it("aligns elements within a group while in group edit mode correctly to the right", () => { createGroupAndSelectInEditGroupMode(); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(100); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(100); API.executeAction(actionAlignRight); - expect(API.getSelectedElements()[0].x).toEqual(100); - expect(API.getSelectedElements()[1].x).toEqual(100); + expect(API.getSelectedElements()[0].x).toBeCloseTo(100); + expect(API.getSelectedElements()[1].x).toBeCloseTo(100); }); it("aligns elements within a group while in group edit mode correctly to the vertical center", () => { createGroupAndSelectInEditGroupMode(); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(100); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(100); API.executeAction(actionAlignVerticallyCentered); - expect(API.getSelectedElements()[0].y).toEqual(50); - expect(API.getSelectedElements()[1].y).toEqual(50); + expect(API.getSelectedElements()[0].y).toBeCloseTo(50); + expect(API.getSelectedElements()[1].y).toBeCloseTo(50); }); it("aligns elements within a group while in group edit mode correctly to the horizontal center", () => { createGroupAndSelectInEditGroupMode(); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(100); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(100); API.executeAction(actionAlignHorizontallyCentered); - expect(API.getSelectedElements()[0].x).toEqual(50); - expect(API.getSelectedElements()[1].x).toEqual(50); + expect(API.getSelectedElements()[0].x).toBeCloseTo(50); + expect(API.getSelectedElements()[1].x).toBeCloseTo(50); }); const createNestedGroupAndSelectInEditGroupMode = () => { @@ -735,80 +735,80 @@ describe("aligning", () => { it("aligns element and nested group while in group edit mode correctly to the top", () => { createNestedGroupAndSelectInEditGroupMode(); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(100); - expect(API.getSelectedElements()[2].y).toEqual(200); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(100); + expect(API.getSelectedElements()[2].y).toBeCloseTo(200); API.executeAction(actionAlignTop); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(100); - expect(API.getSelectedElements()[2].y).toEqual(0); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(100); + expect(API.getSelectedElements()[2].y).toBeCloseTo(0); }); it("aligns element and nested group while in group edit mode correctly to the bottom", () => { createNestedGroupAndSelectInEditGroupMode(); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(100); - expect(API.getSelectedElements()[2].y).toEqual(200); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(100); + expect(API.getSelectedElements()[2].y).toBeCloseTo(200); API.executeAction(actionAlignBottom); - expect(API.getSelectedElements()[0].y).toEqual(100); - expect(API.getSelectedElements()[1].y).toEqual(200); - expect(API.getSelectedElements()[2].y).toEqual(200); + expect(API.getSelectedElements()[0].y).toBeCloseTo(100); + expect(API.getSelectedElements()[1].y).toBeCloseTo(200); + expect(API.getSelectedElements()[2].y).toBeCloseTo(200); }); it("aligns element and nested group while in group edit mode correctly to the left", () => { createNestedGroupAndSelectInEditGroupMode(); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(100); - expect(API.getSelectedElements()[2].x).toEqual(200); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(100); + expect(API.getSelectedElements()[2].x).toBeCloseTo(200); API.executeAction(actionAlignLeft); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(100); - expect(API.getSelectedElements()[2].x).toEqual(0); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(100); + expect(API.getSelectedElements()[2].x).toBeCloseTo(0); }); it("aligns element and nested group while in group edit mode correctly to the right", () => { createNestedGroupAndSelectInEditGroupMode(); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(100); - expect(API.getSelectedElements()[2].x).toEqual(200); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(100); + expect(API.getSelectedElements()[2].x).toBeCloseTo(200); API.executeAction(actionAlignRight); - expect(API.getSelectedElements()[0].x).toEqual(100); - expect(API.getSelectedElements()[1].x).toEqual(200); - expect(API.getSelectedElements()[2].x).toEqual(200); + expect(API.getSelectedElements()[0].x).toBeCloseTo(100); + expect(API.getSelectedElements()[1].x).toBeCloseTo(200); + expect(API.getSelectedElements()[2].x).toBeCloseTo(200); }); it("aligns element and nested group while in group edit mode correctly to the vertical center", () => { createNestedGroupAndSelectInEditGroupMode(); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(100); - expect(API.getSelectedElements()[2].y).toEqual(200); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(100); + expect(API.getSelectedElements()[2].y).toBeCloseTo(200); API.executeAction(actionAlignVerticallyCentered); - expect(API.getSelectedElements()[0].y).toEqual(50); - expect(API.getSelectedElements()[1].y).toEqual(150); - expect(API.getSelectedElements()[2].y).toEqual(100); + expect(API.getSelectedElements()[0].y).toBeCloseTo(50); + expect(API.getSelectedElements()[1].y).toBeCloseTo(150); + expect(API.getSelectedElements()[2].y).toBeCloseTo(100); }); it("aligns elements and nested group within a group while in group edit mode correctly to the horizontal center", () => { createNestedGroupAndSelectInEditGroupMode(); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(100); - expect(API.getSelectedElements()[2].x).toEqual(200); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(100); + expect(API.getSelectedElements()[2].x).toBeCloseTo(200); API.executeAction(actionAlignHorizontallyCentered); - expect(API.getSelectedElements()[0].x).toEqual(50); - expect(API.getSelectedElements()[1].x).toEqual(150); - expect(API.getSelectedElements()[2].x).toEqual(100); + expect(API.getSelectedElements()[0].x).toBeCloseTo(50); + expect(API.getSelectedElements()[1].x).toBeCloseTo(150); + expect(API.getSelectedElements()[2].x).toBeCloseTo(100); }); const createAndSelectSingleGroup = () => { @@ -834,68 +834,68 @@ describe("aligning", () => { it("aligns elements within a single-selected group correctly to the top", () => { createAndSelectSingleGroup(); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(100); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(100); API.executeAction(actionAlignTop); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(0); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(0); }); it("aligns elements within a single-selected group correctly to the bottom", () => { createAndSelectSingleGroup(); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(100); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(100); API.executeAction(actionAlignBottom); - expect(API.getSelectedElements()[0].y).toEqual(100); - expect(API.getSelectedElements()[1].y).toEqual(100); + expect(API.getSelectedElements()[0].y).toBeCloseTo(100); + expect(API.getSelectedElements()[1].y).toBeCloseTo(100); }); it("aligns elements within a single-selected group correctly to the left", () => { createAndSelectSingleGroup(); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(100); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(100); API.executeAction(actionAlignLeft); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(0); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(0); }); it("aligns elements within a single-selected group correctly to the right", () => { createAndSelectSingleGroup(); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(100); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(100); API.executeAction(actionAlignRight); - expect(API.getSelectedElements()[0].x).toEqual(100); - expect(API.getSelectedElements()[1].x).toEqual(100); + expect(API.getSelectedElements()[0].x).toBeCloseTo(100); + expect(API.getSelectedElements()[1].x).toBeCloseTo(100); }); it("aligns elements within a single-selected group correctly to the vertical center", () => { createAndSelectSingleGroup(); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(100); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(100); API.executeAction(actionAlignVerticallyCentered); - expect(API.getSelectedElements()[0].y).toEqual(50); - expect(API.getSelectedElements()[1].y).toEqual(50); + expect(API.getSelectedElements()[0].y).toBeCloseTo(50); + expect(API.getSelectedElements()[1].y).toBeCloseTo(50); }); it("aligns elements within a single-selected group correctly to the horizontal center", () => { createAndSelectSingleGroup(); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(100); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(100); API.executeAction(actionAlignHorizontallyCentered); - expect(API.getSelectedElements()[0].x).toEqual(50); - expect(API.getSelectedElements()[1].x).toEqual(50); + expect(API.getSelectedElements()[0].x).toBeCloseTo(50); + expect(API.getSelectedElements()[1].x).toBeCloseTo(50); }); const createAndSelectSingleGroupWithNestedGroup = () => { @@ -934,79 +934,79 @@ describe("aligning", () => { it("aligns elements within a single-selected group containing a nested group correctly to the top", () => { createAndSelectSingleGroupWithNestedGroup(); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(100); - expect(API.getSelectedElements()[2].y).toEqual(200); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(100); + expect(API.getSelectedElements()[2].y).toBeCloseTo(200); API.executeAction(actionAlignTop); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(100); - expect(API.getSelectedElements()[2].y).toEqual(0); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(100); + expect(API.getSelectedElements()[2].y).toBeCloseTo(0); }); it("aligns elements within a single-selected group containing a nested group correctly to the bottom", () => { createAndSelectSingleGroupWithNestedGroup(); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(100); - expect(API.getSelectedElements()[2].y).toEqual(200); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(100); + expect(API.getSelectedElements()[2].y).toBeCloseTo(200); API.executeAction(actionAlignBottom); - expect(API.getSelectedElements()[0].y).toEqual(100); - expect(API.getSelectedElements()[1].y).toEqual(200); - expect(API.getSelectedElements()[2].y).toEqual(200); + expect(API.getSelectedElements()[0].y).toBeCloseTo(100); + expect(API.getSelectedElements()[1].y).toBeCloseTo(200); + expect(API.getSelectedElements()[2].y).toBeCloseTo(200); }); it("aligns elements within a single-selected group containing a nested group correctly to the left", () => { createAndSelectSingleGroupWithNestedGroup(); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(100); - expect(API.getSelectedElements()[2].x).toEqual(200); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(100); + expect(API.getSelectedElements()[2].x).toBeCloseTo(200); API.executeAction(actionAlignLeft); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(100); - expect(API.getSelectedElements()[2].x).toEqual(0); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(100); + expect(API.getSelectedElements()[2].x).toBeCloseTo(0); }); it("aligns elements within a single-selected group containing a nested group correctly to the right", () => { createAndSelectSingleGroupWithNestedGroup(); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(100); - expect(API.getSelectedElements()[2].x).toEqual(200); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(100); + expect(API.getSelectedElements()[2].x).toBeCloseTo(200); API.executeAction(actionAlignRight); - expect(API.getSelectedElements()[0].x).toEqual(100); - expect(API.getSelectedElements()[1].x).toEqual(200); - expect(API.getSelectedElements()[2].x).toEqual(200); + expect(API.getSelectedElements()[0].x).toBeCloseTo(100); + expect(API.getSelectedElements()[1].x).toBeCloseTo(200); + expect(API.getSelectedElements()[2].x).toBeCloseTo(200); }); it("aligns elements within a single-selected group containing a nested group correctly to the vertical center", () => { createAndSelectSingleGroupWithNestedGroup(); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(100); - expect(API.getSelectedElements()[2].y).toEqual(200); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(100); + expect(API.getSelectedElements()[2].y).toBeCloseTo(200); API.executeAction(actionAlignVerticallyCentered); - expect(API.getSelectedElements()[0].y).toEqual(50); - expect(API.getSelectedElements()[1].y).toEqual(150); - expect(API.getSelectedElements()[2].y).toEqual(100); + expect(API.getSelectedElements()[0].y).toBeCloseTo(50); + expect(API.getSelectedElements()[1].y).toBeCloseTo(150); + expect(API.getSelectedElements()[2].y).toBeCloseTo(100); }); it("aligns elements within a single-selected group containing a nested group correctly to the horizontal center", () => { createAndSelectSingleGroupWithNestedGroup(); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(100); - expect(API.getSelectedElements()[2].x).toEqual(200); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(100); + expect(API.getSelectedElements()[2].x).toBeCloseTo(200); API.executeAction(actionAlignHorizontallyCentered); - expect(API.getSelectedElements()[0].x).toEqual(50); - expect(API.getSelectedElements()[1].x).toEqual(150); - expect(API.getSelectedElements()[2].x).toEqual(100); + expect(API.getSelectedElements()[0].x).toBeCloseTo(50); + expect(API.getSelectedElements()[1].x).toBeCloseTo(150); + expect(API.getSelectedElements()[2].x).toBeCloseTo(100); }); }); diff --git a/packages/element/tests/distribute.test.tsx b/packages/element/tests/distribute.test.tsx index b59567e42c..a5e6ea6542 100644 --- a/packages/element/tests/distribute.test.tsx +++ b/packages/element/tests/distribute.test.tsx @@ -76,53 +76,53 @@ describe("distributing", () => { it("should distribute selected elements horizontally", async () => { createAndSelectThreeRectanglesWithGap(); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(10); - expect(API.getSelectedElements()[2].x).toEqual(300); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(10); + expect(API.getSelectedElements()[2].x).toBeCloseTo(300); API.executeAction(distributeHorizontally); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(150); - expect(API.getSelectedElements()[2].x).toEqual(300); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(150); + expect(API.getSelectedElements()[2].x).toBeCloseTo(300); }); it("should distribute selected elements vertically", async () => { createAndSelectThreeRectanglesWithGap(); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(10); - expect(API.getSelectedElements()[2].y).toEqual(300); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(10); + expect(API.getSelectedElements()[2].y).toBeCloseTo(300); API.executeAction(distributeVertically); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(150); - expect(API.getSelectedElements()[2].y).toEqual(300); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(150); + expect(API.getSelectedElements()[2].y).toBeCloseTo(300); }); it("should distribute selected elements horizontally based on their centers", async () => { createAndSelectThreeRectanglesWithoutGap(); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(10); - expect(API.getSelectedElements()[2].x).toEqual(200); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(10); + expect(API.getSelectedElements()[2].x).toBeCloseTo(200); API.executeAction(distributeHorizontally); - expect(API.getSelectedElements()[0].x).toEqual(0); - expect(API.getSelectedElements()[1].x).toEqual(50); - expect(API.getSelectedElements()[2].x).toEqual(200); + expect(API.getSelectedElements()[0].x).toBeCloseTo(0); + expect(API.getSelectedElements()[1].x).toBeCloseTo(50); + expect(API.getSelectedElements()[2].x).toBeCloseTo(200); }); it("should distribute selected elements vertically with based on their centers", async () => { createAndSelectThreeRectanglesWithoutGap(); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(10); - expect(API.getSelectedElements()[2].y).toEqual(200); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(10); + expect(API.getSelectedElements()[2].y).toBeCloseTo(200); API.executeAction(distributeVertically); - expect(API.getSelectedElements()[0].y).toEqual(0); - expect(API.getSelectedElements()[1].y).toEqual(50); - expect(API.getSelectedElements()[2].y).toEqual(200); + expect(API.getSelectedElements()[0].y).toBeCloseTo(0); + expect(API.getSelectedElements()[1].y).toBeCloseTo(50); + expect(API.getSelectedElements()[2].y).toBeCloseTo(200); }); }); diff --git a/packages/element/tests/linearElementEditor.test.tsx b/packages/element/tests/linearElementEditor.test.tsx index 9485dcb222..5b4e94cf4d 100644 --- a/packages/element/tests/linearElementEditor.test.tsx +++ b/packages/element/tests/linearElementEditor.test.tsx @@ -1318,8 +1318,8 @@ describe("Test Linear Elements", () => { expect(arrow.endBinding?.elementId).toBe(rect.id); expect(arrow.width).toBeCloseTo(404); - expect(rect.x).toBe(400); - expect(rect.y).toBe(0); + expect(rect.x).toBeCloseTo(400); + expect(rect.y).toBeCloseTo(0); expect( wrapText( textElement.originalText, @@ -1340,9 +1340,8 @@ describe("Test Linear Elements", () => { expect(rect.x).toBe(200); expect(rect.y).toBe(0); expect(handleBindTextResizeSpy).toHaveBeenCalledWith( - h.elements[0], + h.elements[1], h.app.scene, - "nw", false, ); expect( diff --git a/packages/excalidraw/clipboard.ts b/packages/excalidraw/clipboard.ts index 6de0b12eab..838077a17a 100644 --- a/packages/excalidraw/clipboard.ts +++ b/packages/excalidraw/clipboard.ts @@ -25,6 +25,8 @@ import type { NonDeletedExcalidrawElement, } from "@excalidraw/element/types"; +import { stringifyWithPrecision } from "./data/json"; + import { ExcalidrawError } from "./errors"; import { createFile, @@ -188,7 +190,7 @@ export const serializeAsClipboardJSON = ({ files: files ? _files : undefined, }; - return JSON.stringify(contents); + return stringifyWithPrecision(contents); }; export const copyToClipboard = async ( diff --git a/packages/excalidraw/data/json.ts b/packages/excalidraw/data/json.ts index fc34203262..1a161f152b 100644 --- a/packages/excalidraw/data/json.ts +++ b/packages/excalidraw/data/json.ts @@ -22,6 +22,41 @@ import type { ImportedLibraryData, } from "./types"; +const SCALAR_ROUNDED_KEYS = new Set(["x", "y", "width", "height"]); + +// JSON.stringify encodes \x00 as \u0000 (6-char literal sequence) in the output +// string. We use this as a sentinel so we can strip the surrounding quotes +// afterward, emitting raw number tokens without a float round-trip. +const PRECISION_SENTINEL = "\x00"; +const PRECISION_SENTINEL_RE = /"\\u0000([^"]+)\\u0000"/g; + +export const stringifyWithPrecision = ( + value: unknown, + precision = 2, + space?: number | string, +): string => { + const fmt = (n: number) => + `${PRECISION_SENTINEL}${n.toFixed(precision)}${PRECISION_SENTINEL}`; + + return JSON.stringify( + value, + (key, val) => { + if (SCALAR_ROUNDED_KEYS.has(key) && typeof val === "number") { + return fmt(val); + } + if (key === "points" && Array.isArray(val)) { + return (val as number[][]).map((pt) => + Array.isArray(pt) + ? pt.map((n) => (typeof n === "number" ? fmt(n) : n)) + : pt, + ); + } + return val; + }, + space, + ).replace(PRECISION_SENTINEL_RE, "$1"); +}; + export type JSONExportData = { elements: readonly NonDeleted[]; appState: AppState; @@ -71,7 +106,7 @@ export const serializeAsJSON = ( undefined, }; - return JSON.stringify(data, null, 2); + return stringifyWithPrecision(data, 2, 2); }; export const saveAsJSON = async ({ @@ -141,7 +176,7 @@ export const serializeLibraryAsJSON = (libraryItems: LibraryItems) => { source: getExportSource(), libraryItems, }; - return JSON.stringify(data, null, 2); + return stringifyWithPrecision(data, 2, 2); }; export const saveLibraryAsJSON = async (libraryItems: LibraryItems) => { diff --git a/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap index 7162ed5f91..a169ae87c0 100644 --- a/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap @@ -1464,7 +1464,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings "versionNonce": 493213705, "width": 20, "x": -10, - "y": 0, + "y": "0.00000", } `; @@ -1516,7 +1516,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings "version": 3, "width": 20, "x": -10, - "y": 0, + "y": "0.00000", }, "inserted": { "isDeleted": true, @@ -1795,7 +1795,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings "versionNonce": 493213705, "width": 20, "x": -10, - "y": 0, + "y": "0.00000", } `; @@ -1847,7 +1847,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings "version": 3, "width": 20, "x": -10, - "y": 0, + "y": "0.00000", }, "inserted": { "isDeleted": true, @@ -2490,7 +2490,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates "resizingElement": null, "scrollX": 0, "scrollY": 0, - "scrolledOutside": false, + "scrolledOutside": true, "searchMatches": null, "selectedElementIds": { "id3": true, @@ -2854,7 +2854,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group "versionNonce": 915032327, "width": 20, "x": -10, - "y": 0, + "y": "0.00000", } `; @@ -2940,7 +2940,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group "version": 3, "width": 20, "x": -10, - "y": 0, + "y": "0.00000", }, "inserted": { "isDeleted": true, @@ -3221,7 +3221,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "versionNonce": 1402203177, "width": 20, "x": -10, - "y": 0, + "y": "0.00000", } `; @@ -3305,7 +3305,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s "version": 3, "width": 20, "x": -10, - "y": 0, + "y": "0.00000", }, "inserted": { "isDeleted": true, @@ -3744,7 +3744,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e "versionNonce": 2019559783, "width": 20, "x": -10, - "y": 0, + "y": "0.00000", } `; @@ -3796,7 +3796,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e "version": 3, "width": 20, "x": -10, - "y": 0, + "y": "0.00000", }, "inserted": { "isDeleted": true, @@ -4067,7 +4067,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el "versionNonce": 2019559783, "width": 20, "x": -10, - "y": 0, + "y": "0.00000", } `; @@ -4119,7 +4119,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el "version": 3, "width": 20, "x": -10, - "y": 0, + "y": "0.00000", }, "inserted": { "isDeleted": true, @@ -4361,7 +4361,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "versionNonce": 1006504105, "width": 20, "x": -10, - "y": 0, + "y": "0.00000", } `; @@ -4445,7 +4445,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung "version": 3, "width": 20, "x": -10, - "y": 0, + "y": "0.00000", }, "inserted": { "isDeleted": true, @@ -5646,7 +5646,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi "versionNonce": 1150084233, "width": 10, "x": -10, - "y": 0, + "y": "0.00000", } `; @@ -5678,7 +5678,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi "versionNonce": 23633383, "width": 10, "x": 12, - "y": 0, + "y": "0.00000", } `; @@ -5730,7 +5730,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi "version": 3, "width": 10, "x": -10, - "y": 0, + "y": "0.00000", }, "inserted": { "isDeleted": true, @@ -5784,7 +5784,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi "version": 3, "width": 10, "x": 12, - "y": 0, + "y": "0.00000", }, "inserted": { "isDeleted": true, @@ -6867,7 +6867,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro "versionNonce": 1723083209, "width": 10, "x": -10, - "y": 0, + "y": "0.00000", } `; @@ -6901,7 +6901,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro "versionNonce": 760410951, "width": 10, "x": 12, - "y": 0, + "y": "0.00000", } `; @@ -6953,7 +6953,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro "version": 3, "width": 10, "x": -10, - "y": 0, + "y": "0.00000", }, "inserted": { "isDeleted": true, @@ -7007,7 +7007,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro "version": 3, "width": 10, "x": 12, - "y": 0, + "y": "0.00000", }, "inserted": { "isDeleted": true, diff --git a/packages/excalidraw/tests/__snapshots__/export.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/export.test.tsx.snap index 48cc148196..5ac548ccdd 100644 --- a/packages/excalidraw/tests/__snapshots__/export.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/export.test.tsx.snap @@ -1,7 +1,7 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`export > export svg-embedded scene > svg-embdedded scene export output 1`] = ` -"eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nHVTS27bMFx1MDAxMN33XHUwMDE0grItXHUwMDEy2UW68C7NXHUwMDA3zVwiXdRcdTAwMDW6KLpgxLE0ME1cdTAwMTLkKLZrXHUwMDE4yDG661x1MDAxNXOEXGZpVTTlRFx1MDAwMlxi8M3vzZvh7kNRlLS1UM6KXHUwMDEyNrVQKJ1Yl1x1MDAxZlx1MDAwM/5cdTAwMDTOo9Fsmsa7N52ro2dLZGdcdTAwMTdcdTAwMTfKcEBrPM0+VVV1XGJcdTAwMDJcdTAwMDUr0OTZ7Vx1MDAxN9+LYlx1MDAxN0+2oFxmoVfRLVx1MDAwMv/rXHUwMDEybCihXHUwMDFihqrhts1ua5TUMjL5PEAtYNNSjlx03SjIXHUwMDAyPTmzhGujjFx1MDAwYlx1MDAxNc8mXHUwMDEw/lT0UdTLxplOy8GHnNDeXG7HzSS/XHUwMDA1KjWnbczOerBa5ajGz57idIS/XHUwMDE3xUWbVoNcdTAwMGaCTVx1MDAwNtRYUSOF5idV6lwiMLT3Mmr7O3FyYlx1MDAwNfdBXFzdKTXAqCVsxmBssa+WXHUwMDE5PIDMXHUwMDE4pOGfYN+MrnN50d/w3CmmWFxi5SFcdFx1MDAxYlxu3qadyIp2VlxuXHUwMDFh1VWol2M/3rPlXHUwMDFiuePesKIv//4+XHUwMDFmjchomuOfQHBaZeidWKFcbppeZimuXHUwMDE0NqHPUsHiaNTcLCHv92AmY5O15nxcdTAwMDI1uFPhjcNcdTAwMDa1UD/epCc6Mt/BXHUwMDFmXGKS6+C4c/g6bPP59DJcdTAwMWH2fMZZl8LaObFebD28Kd5cdTAwMDeUo1ZcdTAwMGZcdTAwMTiBTW1G6MFIuNXiUY11LJ9cdTAwMTDWX07X/2xcdTAwMTG/nng/godOXHUwMDExznnUNfFcdTAwMWWEee5cdTAwMDK/feTHb1x1MDAwM3po/1xua2IoWiJ9