From 56f8bc092da1ea08c0412c6dca25bd0a91330c25 Mon Sep 17 00:00:00 2001 From: Pete Hunt Date: Thu, 28 May 2020 01:56:18 -0700 Subject: [PATCH] Tests for groups, more test utils (#1669) --- src/actions/actionGroup.ts | 4 +- src/element/newElement.ts | 3 +- .../regressionTests.test.tsx.snap | 2852 ++++++++++++++++- src/tests/regressionTests.test.tsx | 743 +++-- 4 files changed, 3300 insertions(+), 302 deletions(-) diff --git a/src/actions/actionGroup.ts b/src/actions/actionGroup.ts index 3538cf2d..746c6b3d 100644 --- a/src/actions/actionGroup.ts +++ b/src/actions/actionGroup.ts @@ -1,6 +1,5 @@ import { KEYS } from "../keys"; import { register } from "./register"; -import nanoid from "nanoid"; import { newElementWith } from "../element/mutateElement"; import { getSelectedElements } from "../scene"; import { @@ -13,6 +12,7 @@ import { isElementInGroup, } from "../groups"; import { getNonDeletedElements } from "../element"; +import { randomId } from "../random"; export const actionGroup = register({ name: "group", @@ -46,7 +46,7 @@ export const actionGroup = register({ return { appState, elements, commitToHistory: false }; } } - const newGroupId = nanoid(); + const newGroupId = randomId(); const updatedElements = elements.map((element) => { if (!appState.selectedElementIds[element.id]) { return element; diff --git a/src/element/newElement.ts b/src/element/newElement.ts index 68da072d..a9ab789a 100644 --- a/src/element/newElement.ts +++ b/src/element/newElement.ts @@ -11,7 +11,6 @@ import { import { measureText, getFontString } from "../utils"; import { randomInteger, randomId } from "../random"; import { newElementWith } from "./mutateElement"; -import nanoid from "nanoid"; import { getNewGroupIdsForDuplication } from "../groups"; type ElementConstructorOpts = { @@ -183,7 +182,7 @@ export const duplicateElement = >( editingGroupId, (groupId) => { if (!groupIdMapForOperation.has(groupId)) { - groupIdMapForOperation.set(groupId, nanoid()); + groupIdMapForOperation.set(groupId, randomId()); } return groupIdMapForOperation.get(groupId)!; }, diff --git a/src/tests/__snapshots__/regressionTests.test.tsx.snap b/src/tests/__snapshots__/regressionTests.test.tsx.snap index 45bd1b7c..748a2bb4 100644 --- a/src/tests/__snapshots__/regressionTests.test.tsx.snap +++ b/src/tests/__snapshots__/regressionTests.test.tsx.snap @@ -1,5 +1,389 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`regression tests adjusts z order when grouping: [end of test] appState 1`] = ` +Object { + "collaborators": Map {}, + "currentItemBackgroundColor": "transparent", + "currentItemFillStyle": "hachure", + "currentItemFontFamily": 1, + "currentItemFontSize": 20, + "currentItemOpacity": 100, + "currentItemRoughness": 1, + "currentItemStrokeColor": "#000000", + "currentItemStrokeStyle": "solid", + "currentItemStrokeWidth": 1, + "currentItemTextAlign": "left", + "cursorButton": "up", + "cursorX": 0, + "cursorY": 0, + "draggingElement": null, + "editingElement": null, + "editingGroupId": null, + "elementLocked": false, + "elementType": "selection", + "errorMessage": null, + "exportBackground": true, + "isCollaborating": false, + "isLoading": false, + "isResizing": false, + "isRotating": false, + "lastPointerDownWith": "mouse", + "multiElement": null, + "name": "Unbenannt-201933152653", + "openMenu": null, + "resizingElement": null, + "scrollX": 0, + "scrollY": 0, + "scrolledOutside": false, + "selectedElementIds": Object { + "id0": true, + "id2": true, + "id3": true, + "id4": true, + }, + "selectedGroupIds": Object { + "id5": true, + }, + "selectionElement": null, + "shouldAddWatermark": false, + "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, + "username": "", + "viewBackgroundColor": "#ffffff", + "zenModeEnabled": false, + "zoom": 1, +} +`; + +exports[`regression tests adjusts z order when grouping: [end of test] element 0 1`] = ` +Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id1", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 449462985, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 453191, + "width": 10, + "x": 30, + "y": 10, +} +`; + +exports[`regression tests adjusts z order when grouping: [end of test] element 1 1`] = ` +Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id5", + ], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 3, + "versionNonce": 1014066025, + "width": 10, + "x": 10, + "y": 10, +} +`; + +exports[`regression tests adjusts z order when grouping: [end of test] element 2 1`] = ` +Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id5", + ], + "height": 10, + "id": "id2", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 401146281, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 3, + "versionNonce": 238820263, + "width": 10, + "x": 50, + "y": 10, +} +`; + +exports[`regression tests adjusts z order when grouping: [end of test] history 1`] = ` +Object { + "recording": false, + "redoStack": Array [], + "stateHistory": Array [ + Object { + "appState": Object { + "name": "Unbenannt-201933152653", + "selectedElementIds": Object { + "id0": true, + }, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 1278240551, + "width": 10, + "x": 10, + "y": 10, + }, + ], + }, + Object { + "appState": Object { + "name": "Unbenannt-201933152653", + "selectedElementIds": Object { + "id1": true, + }, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 1278240551, + "width": 10, + "x": 10, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id1", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 449462985, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 453191, + "width": 10, + "x": 30, + "y": 10, + }, + ], + }, + Object { + "appState": Object { + "name": "Unbenannt-201933152653", + "selectedElementIds": Object { + "id2": true, + }, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 1278240551, + "width": 10, + "x": 10, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id1", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 449462985, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 453191, + "width": 10, + "x": 30, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id2", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 401146281, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 2019559783, + "width": 10, + "x": 50, + "y": 10, + }, + ], + }, + Object { + "appState": Object { + "name": "Unbenannt-201933152653", + "selectedElementIds": Object { + "id0": true, + "id2": true, + "id3": true, + "id4": true, + }, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id1", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 449462985, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 453191, + "width": 10, + "x": 30, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id5", + ], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 3, + "versionNonce": 1014066025, + "width": 10, + "x": 10, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id5", + ], + "height": 10, + "id": "id2", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 401146281, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 3, + "versionNonce": 238820263, + "width": 10, + "x": 50, + "y": 10, + }, + ], + }, + ], +} +`; + +exports[`regression tests adjusts z order when grouping: [end of test] number of elements 1`] = `3`; + +exports[`regression tests adjusts z order when grouping: [end of test] number of renders 1`] = `19`; + exports[`regression tests alt-drag duplicates an element: [end of test] appState 1`] = ` Object { "collaborators": Map {}, @@ -1396,7 +1780,389 @@ Object { exports[`regression tests click-drag to select a group: [end of test] number of elements 1`] = `3`; -exports[`regression tests click-drag to select a group: [end of test] number of renders 1`] = `17`; +exports[`regression tests click-drag to select a group: [end of test] number of renders 1`] = `18`; + +exports[`regression tests double click to edit a group: [end of test] appState 1`] = ` +Object { + "collaborators": Map {}, + "currentItemBackgroundColor": "transparent", + "currentItemFillStyle": "hachure", + "currentItemFontFamily": 1, + "currentItemFontSize": 20, + "currentItemOpacity": 100, + "currentItemRoughness": 1, + "currentItemStrokeColor": "#000000", + "currentItemStrokeStyle": "solid", + "currentItemStrokeWidth": 1, + "currentItemTextAlign": "left", + "cursorButton": "up", + "cursorX": 0, + "cursorY": 0, + "draggingElement": null, + "editingElement": null, + "editingGroupId": "id3", + "elementLocked": false, + "elementType": "selection", + "errorMessage": null, + "exportBackground": true, + "isCollaborating": false, + "isLoading": false, + "isResizing": false, + "isRotating": false, + "lastPointerDownWith": "mouse", + "multiElement": null, + "name": "Unbenannt-201933152653", + "openMenu": null, + "resizingElement": null, + "scrollX": 0, + "scrollY": 0, + "scrolledOutside": false, + "selectedElementIds": Object { + "id2": true, + }, + "selectedGroupIds": Object {}, + "selectionElement": null, + "shouldAddWatermark": false, + "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, + "username": "", + "viewBackgroundColor": "#ffffff", + "zenModeEnabled": false, + "zoom": 1, +} +`; + +exports[`regression tests double click to edit a group: [end of test] element 0 1`] = ` +Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id3", + ], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 3, + "versionNonce": 1150084233, + "width": 10, + "x": 10, + "y": 10, +} +`; + +exports[`regression tests double click to edit a group: [end of test] element 1 1`] = ` +Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id3", + ], + "height": 10, + "id": "id1", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 449462985, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 3, + "versionNonce": 1116226695, + "width": 10, + "x": 30, + "y": 10, +} +`; + +exports[`regression tests double click to edit a group: [end of test] element 2 1`] = ` +Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id3", + ], + "height": 10, + "id": "id2", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 401146281, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 3, + "versionNonce": 1014066025, + "width": 10, + "x": 50, + "y": 10, +} +`; + +exports[`regression tests double click to edit a group: [end of test] history 1`] = ` +Object { + "recording": false, + "redoStack": Array [], + "stateHistory": Array [ + Object { + "appState": Object { + "name": "Unbenannt-201933152653", + "selectedElementIds": Object { + "id0": true, + }, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 1278240551, + "width": 10, + "x": 10, + "y": 10, + }, + ], + }, + Object { + "appState": Object { + "name": "Unbenannt-201933152653", + "selectedElementIds": Object { + "id1": true, + }, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 1278240551, + "width": 10, + "x": 10, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id1", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 449462985, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 453191, + "width": 10, + "x": 30, + "y": 10, + }, + ], + }, + Object { + "appState": Object { + "name": "Unbenannt-201933152653", + "selectedElementIds": Object { + "id2": true, + }, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 1278240551, + "width": 10, + "x": 10, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id1", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 449462985, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 453191, + "width": 10, + "x": 30, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id2", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 401146281, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 2019559783, + "width": 10, + "x": 50, + "y": 10, + }, + ], + }, + Object { + "appState": Object { + "name": "Unbenannt-201933152653", + "selectedElementIds": Object { + "id0": true, + "id1": true, + "id2": true, + }, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id3", + ], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 3, + "versionNonce": 1150084233, + "width": 10, + "x": 10, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id3", + ], + "height": 10, + "id": "id1", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 449462985, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 3, + "versionNonce": 1116226695, + "width": 10, + "x": 30, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id3", + ], + "height": 10, + "id": "id2", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 401146281, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 3, + "versionNonce": 1014066025, + "width": 10, + "x": 50, + "y": 10, + }, + ], + }, + ], +} +`; + +exports[`regression tests double click to edit a group: [end of test] number of elements 1`] = `3`; + +exports[`regression tests double click to edit a group: [end of test] number of renders 1`] = `17`; exports[`regression tests draw every type of shape: [end of test] appState 1`] = ` Object { @@ -1591,6 +2357,90 @@ Object { `; exports[`regression tests draw every type of shape: [end of test] element 5 1`] = ` +Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 20, + "id": "id5", + "isDeleted": false, + "lastCommittedPoint": Array [ + 0, + 20, + ], + "opacity": 100, + "points": Array [ + Array [ + 0, + 0, + ], + Array [ + 10, + 10, + ], + Array [ + 0, + 20, + ], + ], + "roughness": 1, + "seed": 1505387817, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "arrow", + "version": 7, + "versionNonce": 1723083209, + "width": 10, + "x": 110, + "y": 10, +} +`; + +exports[`regression tests draw every type of shape: [end of test] element 6 1`] = ` +Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 20, + "id": "id6", + "isDeleted": false, + "lastCommittedPoint": Array [ + 0, + 20, + ], + "opacity": 100, + "points": Array [ + Array [ + 0, + 0, + ], + Array [ + 10, + 10, + ], + Array [ + 0, + 20, + ], + ], + "roughness": 1, + "seed": 760410951, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "line", + "version": 7, + "versionNonce": 406373543, + "width": 10, + "x": 120, + "y": 10, +} +`; + +exports[`regression tests draw every type of shape: [end of test] element 7 1`] = ` Object { "angle": 0, "backgroundColor": "transparent", @@ -1612,15 +2462,15 @@ Object { ], ], "roughness": 1, - "seed": 640725609, + "seed": 941653321, "strokeColor": "#000000", "strokeStyle": "solid", "strokeWidth": 1, "type": "draw", "version": 3, - "versionNonce": 941653321, + "versionNonce": 1402203177, "width": 10, - "x": 30, + "x": 130, "y": 10, } `; @@ -2045,7 +2895,7 @@ Object { "appState": Object { "name": "Untitled-201933152653", "selectedElementIds": Object { - "id7": true, + "id5": true, }, "viewBackgroundColor": "#ffffff", }, @@ -2177,6 +3027,862 @@ Object { "x": 90, "y": 10, }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id5", + "isDeleted": false, + "lastCommittedPoint": Array [ + 10, + 10, + ], + "opacity": 100, + "points": Array [ + Array [ + 0, + 0, + ], + Array [ + 10, + 10, + ], + ], + "roughness": 1, + "seed": 1505387817, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "arrow", + "version": 5, + "versionNonce": 81784553, + "width": 10, + "x": 110, + "y": 10, + }, + ], + }, + Object { + "appState": Object { + "name": "Untitled-201933152653", + "selectedElementIds": Object { + "id5": true, + }, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 1278240551, + "width": 10, + "x": 10, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id1", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 449462985, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "diamond", + "version": 2, + "versionNonce": 453191, + "width": 10, + "x": 30, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id2", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 401146281, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "ellipse", + "version": 2, + "versionNonce": 2019559783, + "width": 10, + "x": 50, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id3", + "isDeleted": false, + "lastCommittedPoint": null, + "opacity": 100, + "points": Array [ + Array [ + 0, + 0, + ], + Array [ + 10, + 10, + ], + ], + "roughness": 1, + "seed": 1150084233, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "arrow", + "version": 3, + "versionNonce": 1014066025, + "width": 10, + "x": 70, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id4", + "isDeleted": false, + "lastCommittedPoint": null, + "opacity": 100, + "points": Array [ + Array [ + 0, + 0, + ], + Array [ + 10, + 10, + ], + ], + "roughness": 1, + "seed": 238820263, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "line", + "version": 3, + "versionNonce": 1604849351, + "width": 10, + "x": 90, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 20, + "id": "id5", + "isDeleted": false, + "lastCommittedPoint": Array [ + 0, + 20, + ], + "opacity": 100, + "points": Array [ + Array [ + 0, + 0, + ], + Array [ + 10, + 10, + ], + Array [ + 0, + 20, + ], + ], + "roughness": 1, + "seed": 1505387817, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "arrow", + "version": 7, + "versionNonce": 1723083209, + "width": 10, + "x": 110, + "y": 10, + }, + ], + }, + Object { + "appState": Object { + "name": "Untitled-201933152653", + "selectedElementIds": Object { + "id6": true, + }, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 1278240551, + "width": 10, + "x": 10, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id1", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 449462985, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "diamond", + "version": 2, + "versionNonce": 453191, + "width": 10, + "x": 30, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id2", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 401146281, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "ellipse", + "version": 2, + "versionNonce": 2019559783, + "width": 10, + "x": 50, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id3", + "isDeleted": false, + "lastCommittedPoint": null, + "opacity": 100, + "points": Array [ + Array [ + 0, + 0, + ], + Array [ + 10, + 10, + ], + ], + "roughness": 1, + "seed": 1150084233, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "arrow", + "version": 3, + "versionNonce": 1014066025, + "width": 10, + "x": 70, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id4", + "isDeleted": false, + "lastCommittedPoint": null, + "opacity": 100, + "points": Array [ + Array [ + 0, + 0, + ], + Array [ + 10, + 10, + ], + ], + "roughness": 1, + "seed": 238820263, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "line", + "version": 3, + "versionNonce": 1604849351, + "width": 10, + "x": 90, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 20, + "id": "id5", + "isDeleted": false, + "lastCommittedPoint": Array [ + 0, + 20, + ], + "opacity": 100, + "points": Array [ + Array [ + 0, + 0, + ], + Array [ + 10, + 10, + ], + Array [ + 0, + 20, + ], + ], + "roughness": 1, + "seed": 1505387817, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "arrow", + "version": 7, + "versionNonce": 1723083209, + "width": 10, + "x": 110, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id6", + "isDeleted": false, + "lastCommittedPoint": Array [ + 10, + 10, + ], + "opacity": 100, + "points": Array [ + Array [ + 0, + 0, + ], + Array [ + 10, + 10, + ], + ], + "roughness": 1, + "seed": 760410951, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "line", + "version": 5, + "versionNonce": 1898319239, + "width": 10, + "x": 120, + "y": 10, + }, + ], + }, + Object { + "appState": Object { + "name": "Untitled-201933152653", + "selectedElementIds": Object { + "id6": true, + }, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 1278240551, + "width": 10, + "x": 10, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id1", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 449462985, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "diamond", + "version": 2, + "versionNonce": 453191, + "width": 10, + "x": 30, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id2", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 401146281, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "ellipse", + "version": 2, + "versionNonce": 2019559783, + "width": 10, + "x": 50, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id3", + "isDeleted": false, + "lastCommittedPoint": null, + "opacity": 100, + "points": Array [ + Array [ + 0, + 0, + ], + Array [ + 10, + 10, + ], + ], + "roughness": 1, + "seed": 1150084233, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "arrow", + "version": 3, + "versionNonce": 1014066025, + "width": 10, + "x": 70, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id4", + "isDeleted": false, + "lastCommittedPoint": null, + "opacity": 100, + "points": Array [ + Array [ + 0, + 0, + ], + Array [ + 10, + 10, + ], + ], + "roughness": 1, + "seed": 238820263, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "line", + "version": 3, + "versionNonce": 1604849351, + "width": 10, + "x": 90, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 20, + "id": "id5", + "isDeleted": false, + "lastCommittedPoint": Array [ + 0, + 20, + ], + "opacity": 100, + "points": Array [ + Array [ + 0, + 0, + ], + Array [ + 10, + 10, + ], + Array [ + 0, + 20, + ], + ], + "roughness": 1, + "seed": 1505387817, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "arrow", + "version": 7, + "versionNonce": 1723083209, + "width": 10, + "x": 110, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 20, + "id": "id6", + "isDeleted": false, + "lastCommittedPoint": Array [ + 0, + 20, + ], + "opacity": 100, + "points": Array [ + Array [ + 0, + 0, + ], + Array [ + 10, + 10, + ], + Array [ + 0, + 20, + ], + ], + "roughness": 1, + "seed": 760410951, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "line", + "version": 7, + "versionNonce": 406373543, + "width": 10, + "x": 120, + "y": 10, + }, + ], + }, + Object { + "appState": Object { + "name": "Untitled-201933152653", + "selectedElementIds": Object { + "id7": true, + }, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 1278240551, + "width": 10, + "x": 10, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id1", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 449462985, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "diamond", + "version": 2, + "versionNonce": 453191, + "width": 10, + "x": 30, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id2", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 401146281, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "ellipse", + "version": 2, + "versionNonce": 2019559783, + "width": 10, + "x": 50, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id3", + "isDeleted": false, + "lastCommittedPoint": null, + "opacity": 100, + "points": Array [ + Array [ + 0, + 0, + ], + Array [ + 10, + 10, + ], + ], + "roughness": 1, + "seed": 1150084233, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "arrow", + "version": 3, + "versionNonce": 1014066025, + "width": 10, + "x": 70, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id4", + "isDeleted": false, + "lastCommittedPoint": null, + "opacity": 100, + "points": Array [ + Array [ + 0, + 0, + ], + Array [ + 10, + 10, + ], + ], + "roughness": 1, + "seed": 238820263, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "line", + "version": 3, + "versionNonce": 1604849351, + "width": 10, + "x": 90, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 20, + "id": "id5", + "isDeleted": false, + "lastCommittedPoint": Array [ + 0, + 20, + ], + "opacity": 100, + "points": Array [ + Array [ + 0, + 0, + ], + Array [ + 10, + 10, + ], + Array [ + 0, + 20, + ], + ], + "roughness": 1, + "seed": 1505387817, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "arrow", + "version": 7, + "versionNonce": 1723083209, + "width": 10, + "x": 110, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 20, + "id": "id6", + "isDeleted": false, + "lastCommittedPoint": Array [ + 0, + 20, + ], + "opacity": 100, + "points": Array [ + Array [ + 0, + 0, + ], + Array [ + 10, + 10, + ], + Array [ + 0, + 20, + ], + ], + "roughness": 1, + "seed": 760410951, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "line", + "version": 7, + "versionNonce": 406373543, + "width": 10, + "x": 120, + "y": 10, + }, Object { "angle": 0, "backgroundColor": "transparent", @@ -2198,15 +3904,15 @@ Object { ], ], "roughness": 1, - "seed": 640725609, + "seed": 941653321, "strokeColor": "#000000", "strokeStyle": "solid", "strokeWidth": 1, "type": "draw", "version": 3, - "versionNonce": 941653321, + "versionNonce": 1402203177, "width": 10, - "x": 30, + "x": 130, "y": 10, }, ], @@ -2215,9 +3921,9 @@ Object { } `; -exports[`regression tests draw every type of shape: [end of test] number of elements 1`] = `6`; +exports[`regression tests draw every type of shape: [end of test] number of elements 1`] = `8`; -exports[`regression tests draw every type of shape: [end of test] number of renders 1`] = `38`; +exports[`regression tests draw every type of shape: [end of test] number of renders 1`] = `46`; exports[`regression tests hotkey 2 selects rectangle tool: [end of test] appState 1`] = ` Object { @@ -3779,6 +5485,626 @@ exports[`regression tests hotkey x selects draw tool: [end of test] number of el exports[`regression tests hotkey x selects draw tool: [end of test] number of renders 1`] = `6`; +exports[`regression tests make a group and duplicate it: [end of test] appState 1`] = ` +Object { + "collaborators": Map {}, + "currentItemBackgroundColor": "transparent", + "currentItemFillStyle": "hachure", + "currentItemFontFamily": 1, + "currentItemFontSize": 20, + "currentItemOpacity": 100, + "currentItemRoughness": 1, + "currentItemStrokeColor": "#000000", + "currentItemStrokeStyle": "solid", + "currentItemStrokeWidth": 1, + "currentItemTextAlign": "left", + "cursorButton": "up", + "cursorX": 0, + "cursorY": 0, + "draggingElement": null, + "editingElement": null, + "editingGroupId": null, + "elementLocked": false, + "elementType": "selection", + "errorMessage": null, + "exportBackground": true, + "isCollaborating": false, + "isLoading": false, + "isResizing": false, + "isRotating": false, + "lastPointerDownWith": "mouse", + "multiElement": null, + "name": "Unbenannt-201933152653", + "openMenu": null, + "resizingElement": null, + "scrollX": 0, + "scrollY": 0, + "scrolledOutside": false, + "selectedElementIds": Object { + "id0": true, + "id1": true, + "id2": true, + "id3": true, + "id5": true, + }, + "selectedGroupIds": Object { + "id4": true, + }, + "selectionElement": null, + "shouldAddWatermark": false, + "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, + "username": "", + "viewBackgroundColor": "#ffffff", + "zenModeEnabled": false, + "zoom": 1, +} +`; + +exports[`regression tests make a group and duplicate it: [end of test] element 0 1`] = ` +Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id7", + ], + "height": 10, + "id": "id6", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 915032327, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 5, + "versionNonce": 81784553, + "width": 10, + "x": 10, + "y": 10, +} +`; + +exports[`regression tests make a group and duplicate it: [end of test] element 1 1`] = ` +Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id7", + ], + "height": 10, + "id": "id8", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 747212839, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 5, + "versionNonce": 1723083209, + "width": 10, + "x": 30, + "y": 10, +} +`; + +exports[`regression tests make a group and duplicate it: [end of test] element 2 1`] = ` +Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id7", + ], + "height": 10, + "id": "id9", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 760410951, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 5, + "versionNonce": 1006504105, + "width": 10, + "x": 50, + "y": 10, +} +`; + +exports[`regression tests make a group and duplicate it: [end of test] element 3 1`] = ` +Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id4", + ], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 4, + "versionNonce": 1505387817, + "width": 10, + "x": 20, + "y": 20, +} +`; + +exports[`regression tests make a group and duplicate it: [end of test] element 4 1`] = ` +Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id4", + ], + "height": 10, + "id": "id1", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 449462985, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 4, + "versionNonce": 23633383, + "width": 10, + "x": 40, + "y": 20, +} +`; + +exports[`regression tests make a group and duplicate it: [end of test] element 5 1`] = ` +Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id4", + ], + "height": 10, + "id": "id2", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 401146281, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 4, + "versionNonce": 493213705, + "width": 10, + "x": 60, + "y": 20, +} +`; + +exports[`regression tests make a group and duplicate it: [end of test] history 1`] = ` +Object { + "recording": false, + "redoStack": Array [], + "stateHistory": Array [ + Object { + "appState": Object { + "name": "Unbenannt-201933152653", + "selectedElementIds": Object { + "id0": true, + }, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 1278240551, + "width": 10, + "x": 10, + "y": 10, + }, + ], + }, + Object { + "appState": Object { + "name": "Unbenannt-201933152653", + "selectedElementIds": Object { + "id1": true, + }, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 1278240551, + "width": 10, + "x": 10, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id1", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 449462985, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 453191, + "width": 10, + "x": 30, + "y": 10, + }, + ], + }, + Object { + "appState": Object { + "name": "Unbenannt-201933152653", + "selectedElementIds": Object { + "id2": true, + }, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 1278240551, + "width": 10, + "x": 10, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id1", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 449462985, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 453191, + "width": 10, + "x": 30, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id2", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 401146281, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 2019559783, + "width": 10, + "x": 50, + "y": 10, + }, + ], + }, + Object { + "appState": Object { + "name": "Unbenannt-201933152653", + "selectedElementIds": Object { + "id0": true, + "id1": true, + "id2": true, + "id3": true, + }, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id4", + ], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 3, + "versionNonce": 1014066025, + "width": 10, + "x": 10, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id4", + ], + "height": 10, + "id": "id1", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 449462985, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 3, + "versionNonce": 238820263, + "width": 10, + "x": 30, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id4", + ], + "height": 10, + "id": "id2", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 401146281, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 3, + "versionNonce": 400692809, + "width": 10, + "x": 50, + "y": 10, + }, + ], + }, + Object { + "appState": Object { + "name": "Unbenannt-201933152653", + "selectedElementIds": Object { + "id0": true, + "id1": true, + "id2": true, + "id3": true, + "id5": true, + }, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id7", + ], + "height": 10, + "id": "id6", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 915032327, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 5, + "versionNonce": 81784553, + "width": 10, + "x": 10, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id7", + ], + "height": 10, + "id": "id8", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 747212839, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 5, + "versionNonce": 1723083209, + "width": 10, + "x": 30, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id7", + ], + "height": 10, + "id": "id9", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 760410951, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 5, + "versionNonce": 1006504105, + "width": 10, + "x": 50, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id4", + ], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 4, + "versionNonce": 1505387817, + "width": 10, + "x": 20, + "y": 20, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id4", + ], + "height": 10, + "id": "id1", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 449462985, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 4, + "versionNonce": 23633383, + "width": 10, + "x": 40, + "y": 20, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id4", + ], + "height": 10, + "id": "id2", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 401146281, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 4, + "versionNonce": 493213705, + "width": 10, + "x": 60, + "y": 20, + }, + ], + }, + ], +} +`; + +exports[`regression tests make a group and duplicate it: [end of test] number of elements 1`] = `6`; + +exports[`regression tests make a group and duplicate it: [end of test] number of renders 1`] = `21`; + exports[`regression tests noop interaction after undo shouldn't create history entry: [end of test] appState 1`] = ` Object { "collaborators": Map {}, @@ -12089,7 +14415,7 @@ exports[`regression tests resize an element, trying every resize handle: [unresi exports[`regression tests resize an element, trying every resize handle: [unresize handle sw (-5, -5)] number of renders 1`] = `36`; -exports[`regression tests shift-click to select a group, then drag: [end of test] appState 1`] = ` +exports[`regression tests shift-click to multiselect, then drag: [end of test] appState 1`] = ` Object { "collaborators": Map {}, "currentItemBackgroundColor": "transparent", @@ -12143,7 +14469,7 @@ Object { } `; -exports[`regression tests shift-click to select a group, then drag: [end of test] element 0 1`] = ` +exports[`regression tests shift-click to multiselect, then drag: [end of test] element 0 1`] = ` Object { "angle": 0, "backgroundColor": "transparent", @@ -12167,7 +14493,7 @@ Object { } `; -exports[`regression tests shift-click to select a group, then drag: [end of test] element 1 1`] = ` +exports[`regression tests shift-click to multiselect, then drag: [end of test] element 1 1`] = ` Object { "angle": 0, "backgroundColor": "transparent", @@ -12191,7 +14517,7 @@ Object { } `; -exports[`regression tests shift-click to select a group, then drag: [end of test] history 1`] = ` +exports[`regression tests shift-click to multiselect, then drag: [end of test] history 1`] = ` Object { "recording": false, "redoStack": Array [], @@ -12342,9 +14668,9 @@ Object { } `; -exports[`regression tests shift-click to select a group, then drag: [end of test] number of elements 1`] = `2`; +exports[`regression tests shift-click to multiselect, then drag: [end of test] number of elements 1`] = `2`; -exports[`regression tests shift-click to select a group, then drag: [end of test] number of renders 1`] = `17`; +exports[`regression tests shift-click to multiselect, then drag: [end of test] number of renders 1`] = `17`; exports[`regression tests spacebar + drag scrolls the canvas: [end of test] appState 1`] = ` Object { @@ -12378,8 +14704,8 @@ Object { "name": "Untitled-201933152653", "openMenu": null, "resizingElement": null, - "scrollX": 10, - "scrollY": 10, + "scrollX": 60, + "scrollY": 60, "scrolledOutside": false, "selectedElementIds": Object {}, "selectedGroupIds": Object {}, @@ -12406,6 +14732,474 @@ exports[`regression tests spacebar + drag scrolls the canvas: [end of test] numb exports[`regression tests spacebar + drag scrolls the canvas: [end of test] number of renders 1`] = `5`; +exports[`regression tests supports nested groups: [end of test] appState 1`] = ` +Object { + "collaborators": Map {}, + "currentItemBackgroundColor": "transparent", + "currentItemFillStyle": "hachure", + "currentItemFontFamily": 1, + "currentItemFontSize": 20, + "currentItemOpacity": 100, + "currentItemRoughness": 1, + "currentItemStrokeColor": "#000000", + "currentItemStrokeStyle": "solid", + "currentItemStrokeWidth": 1, + "currentItemTextAlign": "left", + "cursorButton": "up", + "cursorX": 0, + "cursorY": 0, + "draggingElement": null, + "editingElement": null, + "editingGroupId": "id3", + "elementLocked": false, + "elementType": "selection", + "errorMessage": null, + "exportBackground": true, + "isCollaborating": false, + "isLoading": false, + "isResizing": false, + "isRotating": false, + "lastPointerDownWith": "mouse", + "multiElement": null, + "name": "Unbenannt-201933152653", + "openMenu": null, + "resizingElement": null, + "scrollX": 0, + "scrollY": 0, + "scrolledOutside": false, + "selectedElementIds": Object { + "id1": true, + }, + "selectedGroupIds": Object {}, + "selectionElement": null, + "shouldAddWatermark": false, + "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, + "username": "", + "viewBackgroundColor": "#ffffff", + "zenModeEnabled": false, + "zoom": 1, +} +`; + +exports[`regression tests supports nested groups: [end of test] element 0 1`] = ` +Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id3", + ], + "height": 10, + "id": "id1", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 449462985, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 3, + "versionNonce": 1116226695, + "width": 10, + "x": 30, + "y": 10, +} +`; + +exports[`regression tests supports nested groups: [end of test] element 1 1`] = ` +Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id5", + "id3", + ], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 4, + "versionNonce": 400692809, + "width": 10, + "x": 10, + "y": 10, +} +`; + +exports[`regression tests supports nested groups: [end of test] element 2 1`] = ` +Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id5", + "id3", + ], + "height": 10, + "id": "id2", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 401146281, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 4, + "versionNonce": 1604849351, + "width": 10, + "x": 50, + "y": 10, +} +`; + +exports[`regression tests supports nested groups: [end of test] history 1`] = ` +Object { + "recording": false, + "redoStack": Array [], + "stateHistory": Array [ + Object { + "appState": Object { + "name": "Unbenannt-201933152653", + "selectedElementIds": Object { + "id0": true, + }, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 1278240551, + "width": 10, + "x": 10, + "y": 10, + }, + ], + }, + Object { + "appState": Object { + "name": "Unbenannt-201933152653", + "selectedElementIds": Object { + "id1": true, + }, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 1278240551, + "width": 10, + "x": 10, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id1", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 449462985, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 453191, + "width": 10, + "x": 30, + "y": 10, + }, + ], + }, + Object { + "appState": Object { + "name": "Unbenannt-201933152653", + "selectedElementIds": Object { + "id2": true, + }, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 1278240551, + "width": 10, + "x": 10, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id1", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 449462985, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 453191, + "width": 10, + "x": 30, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [], + "height": 10, + "id": "id2", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 401146281, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 2, + "versionNonce": 2019559783, + "width": 10, + "x": 50, + "y": 10, + }, + ], + }, + Object { + "appState": Object { + "name": "Unbenannt-201933152653", + "selectedElementIds": Object { + "id0": true, + "id1": true, + "id2": true, + }, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id3", + ], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 3, + "versionNonce": 1150084233, + "width": 10, + "x": 10, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id3", + ], + "height": 10, + "id": "id1", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 449462985, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 3, + "versionNonce": 1116226695, + "width": 10, + "x": 30, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id3", + ], + "height": 10, + "id": "id2", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 401146281, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 3, + "versionNonce": 1014066025, + "width": 10, + "x": 50, + "y": 10, + }, + ], + }, + Object { + "appState": Object { + "name": "Unbenannt-201933152653", + "selectedElementIds": Object { + "id0": true, + "id2": true, + "id4": true, + }, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id3", + ], + "height": 10, + "id": "id1", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 449462985, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 3, + "versionNonce": 1116226695, + "width": 10, + "x": 30, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id5", + "id3", + ], + "height": 10, + "id": "id0", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 337897, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 4, + "versionNonce": 400692809, + "width": 10, + "x": 10, + "y": 10, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "fillStyle": "hachure", + "groupIds": Array [ + "id5", + "id3", + ], + "height": 10, + "id": "id2", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 401146281, + "strokeColor": "#000000", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "rectangle", + "version": 4, + "versionNonce": 1604849351, + "width": 10, + "x": 50, + "y": 10, + }, + ], + }, + ], +} +`; + +exports[`regression tests supports nested groups: [end of test] number of elements 1`] = `3`; + +exports[`regression tests supports nested groups: [end of test] number of renders 1`] = `30`; + exports[`regression tests two-finger scroll works: [end of test] appState 1`] = ` Object { "collaborators": Map {}, @@ -12433,16 +15227,16 @@ Object { "isLoading": false, "isResizing": false, "isRotating": false, - "lastPointerDownWith": "mouse", + "lastPointerDownWith": "touch", "multiElement": null, "name": "Untitled-201933152653", "openMenu": null, "resizingElement": null, - "scrollX": 8, - "scrollY": -9, + "scrollX": 11, + "scrollY": -5, "scrolledOutside": false, "selectedElementIds": Object { - "id1": true, + "id0": true, }, "selectedGroupIds": Object {}, "selectionElement": null, @@ -12452,7 +15246,7 @@ Object { "username": "", "viewBackgroundColor": "#ffffff", "zenModeEnabled": false, - "zoom": 1, + "zoom": 1.99, } `; @@ -12599,8 +15393,8 @@ Object { "version": 11, "versionNonce": 1315507081, "width": 10, - "x": 10, - "y": 30, + "x": 50, + "y": 10, } `; @@ -12695,8 +15489,8 @@ Object { "version": 7, "versionNonce": 400692809, "width": 10, - "x": 10, - "y": 30, + "x": 50, + "y": 10, }, ], }, @@ -12783,8 +15577,8 @@ Object { "version": 5, "versionNonce": 1014066025, "width": 10, - "x": 10, - "y": 30, + "x": 50, + "y": 10, }, ], }, diff --git a/src/tests/regressionTests.test.tsx b/src/tests/regressionTests.test.tsx index eb6d4ea7..bad6ac1a 100644 --- a/src/tests/regressionTests.test.tsx +++ b/src/tests/regressionTests.test.tsx @@ -20,111 +20,66 @@ const clickTool = (toolName: ToolName) => { fireEvent.click(getByToolName(toolName)); }; -let lastClientX = 0; -let lastClientY = 0; -let pointerType: "mouse" | "pen" | "touch" = "mouse"; +let altKey = false; +let shiftKey = false; +let ctrlKey = false; -const pointerDown = ( - clientX: number = lastClientX, - clientY: number = lastClientY, - altKey: boolean = false, - shiftKey: boolean = false, -) => { - lastClientX = clientX; - lastClientY = clientY; - fireEvent.pointerDown(canvas, { - clientX, - clientY, - altKey, +function withModifierKeys( + modifiers: { alt?: boolean; shift?: boolean; ctrl?: boolean }, + cb: () => void, +) { + const prevAltKey = altKey; + const prevShiftKey = shiftKey; + const prevCtrlKey = ctrlKey; + + altKey = !!modifiers.alt; + shiftKey = !!modifiers.shift; + ctrlKey = !!modifiers.ctrl; + + try { + cb(); + } finally { + altKey = prevAltKey; + shiftKey = prevShiftKey; + ctrlKey = prevCtrlKey; + } +} + +const hotkeyDown = (hotkey: Key) => { + const key = KEYS[hotkey]; + if (typeof key !== "string") { + throw new Error("must provide a hotkey, not a key code"); + } + keyDown(key); +}; + +const hotkeyUp = (hotkey: Key) => { + const key = KEYS[hotkey]; + if (typeof key !== "string") { + throw new Error("must provide a hotkey, not a key code"); + } + keyUp(key); +}; + +const keyDown = (key: string) => { + fireEvent.keyDown(document, { + key, + ctrlKey, shiftKey, - pointerId: 1, - pointerType, - }); -}; - -const pointer2Down = (clientX: number, clientY: number) => { - fireEvent.pointerDown(canvas, { - clientX, - clientY, - pointerId: 2, - pointerType, - }); -}; - -const pointer2Move = (clientX: number, clientY: number) => { - fireEvent.pointerMove(canvas, { - clientX, - clientY, - pointerId: 2, - pointerType, - }); -}; - -const pointer2Up = (clientX: number, clientY: number) => { - fireEvent.pointerUp(canvas, { - clientX, - clientY, - pointerId: 2, - pointerType, - }); -}; - -const pointerMove = ( - clientX: number = lastClientX, - clientY: number = lastClientY, - altKey: boolean = false, - shiftKey: boolean = false, -) => { - lastClientX = clientX; - lastClientY = clientY; - fireEvent.pointerMove(canvas, { - clientX, - clientY, altKey, - shiftKey, - pointerId: 1, - pointerType, + keyCode: key.toUpperCase().charCodeAt(0), + which: key.toUpperCase().charCodeAt(0), }); }; -const pointerUp = ( - clientX: number = lastClientX, - clientY: number = lastClientY, - altKey: boolean = false, - shiftKey: boolean = false, -) => { - lastClientX = clientX; - lastClientY = clientY; - fireEvent.pointerUp(canvas, { pointerId: 1, pointerType, shiftKey, altKey }); -}; - -const hotkeyDown = (key: Key) => { - fireEvent.keyDown(document, { key: KEYS[key] }); -}; - -const hotkeyUp = (key: Key) => { - fireEvent.keyUp(document, { - key: KEYS[key], - }); -}; - -const keyDown = ( - key: string, - ctrlKey: boolean = false, - shiftKey: boolean = false, -) => { - fireEvent.keyDown(document, { key, ctrlKey, shiftKey }); -}; - -const keyUp = ( - key: string, - ctrlKey: boolean = false, - shiftKey: boolean = false, -) => { +const keyUp = (key: string) => { fireEvent.keyUp(document, { key, ctrlKey, shiftKey, + altKey, + keyCode: key.toUpperCase().charCodeAt(0), + which: key.toUpperCase().charCodeAt(0), }); }; @@ -133,15 +88,80 @@ const hotkeyPress = (key: Key) => { hotkeyUp(key); }; -const keyPress = ( - key: string, - ctrlKey: boolean = false, - shiftKey: boolean = false, -) => { - keyDown(key, ctrlKey, shiftKey); - keyUp(key, ctrlKey, shiftKey); +const keyPress = (key: string) => { + keyDown(key); + keyUp(key); }; +class Pointer { + private clientX = 0; + private clientY = 0; + + constructor( + private readonly pointerType: "mouse" | "touch" | "pen", + private readonly pointerId = 1, + ) {} + + reset() { + this.clientX = 0; + this.clientY = 0; + } + + getPosition() { + return [this.clientX, this.clientY]; + } + + restorePosition(x = 0, y = 0) { + this.clientX = x; + this.clientY = y; + fireEvent.pointerMove(canvas, this.getEvent()); + } + + private getEvent() { + return { + clientX: this.clientX, + clientY: this.clientY, + pointerType: this.pointerType, + pointerId: this.pointerId, + altKey, + shiftKey, + ctrlKey, + }; + } + + move(dx: number, dy: number) { + if (dx !== 0 || dy !== 0) { + this.clientX += dx; + this.clientY += dy; + fireEvent.pointerMove(canvas, this.getEvent()); + } + } + + down(dx = 0, dy = 0) { + this.move(dx, dy); + fireEvent.pointerDown(canvas, this.getEvent()); + } + + up(dx = 0, dy = 0) { + this.move(dx, dy); + fireEvent.pointerUp(canvas, this.getEvent()); + } + + click(dx = 0, dy = 0) { + this.down(dx, dy); + this.up(); + } + + doubleClick(dx = 0, dy = 0) { + this.move(dx, dy); + fireEvent.doubleClick(canvas, this.getEvent()); + } +} + +const mouse = new Pointer("mouse"); +const finger1 = new Pointer("touch", 1); +const finger2 = new Pointer("touch", 2); + const clickLabeledElement = (label: string) => { const element = document.querySelector(`[aria-label='${label}']`); if (!element) { @@ -150,10 +170,12 @@ const clickLabeledElement = (label: string) => { fireEvent.click(element); }; +const getSelectedElements = (): ExcalidrawElement[] => { + return h.elements.filter((element) => h.state.selectedElementIds[element.id]); +}; + const getSelectedElement = (): ExcalidrawElement => { - const selectedElements = h.elements.filter( - (element) => h.state.selectedElementIds[element.id], - ); + const selectedElements = getSelectedElements(); if (selectedElements.length !== 1) { throw new Error( `expected 1 selected element; got ${selectedElements.length}`, @@ -168,7 +190,7 @@ function getStateHistory() { } type HandlerRectanglesRet = keyof ReturnType; -const getResizeHandles = () => { +const getResizeHandles = (pointerType: "mouse" | "touch" | "pen") => { const rects = handlerRectangles( getSelectedElement(), h.state.zoom, @@ -214,7 +236,11 @@ beforeEach(() => { h.history.clear(); reseed(7); setDateTimeForTests("201933152653"); - pointerType = "mouse"; + + mouse.reset(); + finger1.reset(); + finger2.reset(); + altKey = ctrlKey = shiftKey = false; const renderResult = render(); @@ -229,68 +255,68 @@ afterEach(() => { describe("regression tests", () => { it("draw every type of shape", () => { clickTool("rectangle"); - pointerDown(10, 10); - pointerMove(20, 20); - pointerUp(); + mouse.down(10, 10); + mouse.up(10, 10); clickTool("diamond"); - pointerDown(30, 10); - pointerMove(40, 20); - pointerUp(); + mouse.down(10, -10); + mouse.up(10, 10); clickTool("ellipse"); - pointerDown(50, 10); - pointerMove(60, 20); - pointerUp(); + mouse.down(10, -10); + mouse.up(10, 10); clickTool("arrow"); - pointerDown(70, 10); - pointerMove(80, 20); - pointerUp(); + mouse.down(10, -10); + mouse.up(10, 10); clickTool("line"); - pointerDown(90, 10); - pointerMove(100, 20); - pointerUp(); + mouse.down(10, -10); + mouse.up(10, 10); clickTool("arrow"); - pointerDown(10, 30); - pointerUp(); - pointerMove(20, 40); - pointerUp(); - pointerMove(10, 50); - pointerUp(); + mouse.click(10, -10); + mouse.click(10, 10); + mouse.click(-10, 10); hotkeyPress("ENTER"); clickTool("line"); - pointerDown(30, 30); - pointerUp(); - pointerMove(40, 40); - pointerUp(); - pointerMove(30, 50); - pointerUp(); + mouse.click(10, -20); + mouse.click(10, 10); + mouse.click(-10, 10); hotkeyPress("ENTER"); clickTool("draw"); - pointerDown(30, 10); - pointerMove(40, 20); - pointerUp(); + mouse.down(10, -20); + mouse.up(10, 10); + + expect(h.elements.map((element) => element.type)).toEqual([ + "rectangle", + "diamond", + "ellipse", + "arrow", + "line", + "arrow", + "line", + "draw", + ]); }); it("click to select a shape", () => { clickTool("rectangle"); - pointerDown(10, 10); - pointerMove(20, 20); - pointerUp(); + mouse.down(10, 10); + mouse.up(10, 10); + + const firstRectPos = mouse.getPosition(); clickTool("rectangle"); - pointerDown(30, 10); - pointerMove(40, 20); - pointerUp(); + mouse.down(10, -10); + mouse.up(10, 10); const prevSelectedId = getSelectedElement().id; - pointerDown(10, 10); - pointerUp(); + mouse.restorePosition(...firstRectPos); + mouse.click(); + expect(getSelectedElement().id).not.toEqual(prevSelectedId); }); @@ -306,9 +332,8 @@ describe("regression tests", () => { it(`hotkey ${key} selects ${shape} tool`, () => { keyPress(key); - pointerDown(10, 10); - pointerMove(20, 20); - pointerUp(); + mouse.down(10, 10); + mouse.up(10, 10); expect(getSelectedElement().type).toBe(shape); }); @@ -317,9 +342,8 @@ describe("regression tests", () => { it("change the properties of a shape", () => { clickTool("rectangle"); - pointerDown(10, 10); - pointerMove(20, 20); - pointerUp(); + mouse.down(10, 10); + mouse.up(10, 10); clickLabeledElement("Background"); clickLabeledElement("#fa5252"); @@ -331,18 +355,18 @@ describe("regression tests", () => { it("resize an element, trying every resize handle", () => { clickTool("rectangle"); - pointerDown(10, 10); - pointerMove(20, 20); - pointerUp(); + mouse.down(10, 10); + mouse.up(10, 10); - const resizeHandles = getResizeHandles(); + const resizeHandles = getResizeHandles("mouse"); delete resizeHandles.rotation; // exclude rotation handle for (const handlePos in resizeHandles) { const [x, y] = resizeHandles[handlePos as keyof typeof resizeHandles]; const { width: prevWidth, height: prevHeight } = getSelectedElement(); - pointerDown(x, y); - pointerMove(x - 5, y - 5); - pointerUp(); + mouse.restorePosition(x, y); + mouse.down(); + mouse.up(-5, -5); + const { width: nextWidthNegative, height: nextHeightNegative, @@ -352,17 +376,18 @@ describe("regression tests", () => { ).toBeTruthy(); checkpoint(`resize handle ${handlePos} (-5, -5)`); - pointerDown(); - pointerMove(x, y); - pointerUp(); + mouse.down(); + mouse.up(5, 5); + const { width, height } = getSelectedElement(); expect(width).toBe(prevWidth); expect(height).toBe(prevHeight); checkpoint(`unresize handle ${handlePos} (-5, -5)`); - pointerDown(x, y); - pointerMove(x + 5, y + 5); - pointerUp(); + mouse.restorePosition(x, y); + mouse.down(); + mouse.up(5, 5); + const { width: nextWidthPositive, height: nextHeightPositive, @@ -372,9 +397,9 @@ describe("regression tests", () => { ).toBeTruthy(); checkpoint(`resize handle ${handlePos} (+5, +5)`); - pointerDown(); - pointerMove(x, y); - pointerUp(); + mouse.down(); + mouse.up(-5, -5); + const { width: finalWidth, height: finalHeight } = getSelectedElement(); expect(finalWidth).toBe(prevWidth); expect(finalHeight).toBe(prevHeight); @@ -385,14 +410,12 @@ describe("regression tests", () => { it("click on an element and drag it", () => { clickTool("rectangle"); - pointerDown(10, 10); - pointerMove(20, 20); - pointerUp(); + mouse.down(10, 10); + mouse.up(10, 10); const { x: prevX, y: prevY } = getSelectedElement(); - pointerDown(10, 10); - pointerMove(20, 20); - pointerUp(); + mouse.down(-10, -10); + mouse.up(10, 10); const { x: nextX, y: nextY } = getSelectedElement(); expect(nextX).toBeGreaterThan(prevX); @@ -400,9 +423,8 @@ describe("regression tests", () => { checkpoint("dragged"); - pointerDown(); - pointerMove(10, 10); - pointerUp(); + mouse.down(); + mouse.up(-10, -10); const { x, y } = getSelectedElement(); expect(x).toBe(prevX); @@ -411,16 +433,18 @@ describe("regression tests", () => { it("alt-drag duplicates an element", () => { clickTool("rectangle"); - pointerDown(10, 10); - pointerMove(20, 20); - pointerUp(); + mouse.down(10, 10); + mouse.up(10, 10); expect( h.elements.filter((element) => element.type === "rectangle").length, ).toBe(1); - pointerDown(10, 10, true); - pointerMove(20, 20, true); - pointerUp(20, 20, true); + + withModifierKeys({ alt: true }, () => { + mouse.down(-10, -10); + mouse.up(10, 10); + }); + expect( h.elements.filter((element) => element.type === "rectangle").length, ).toBe(2); @@ -428,23 +452,23 @@ describe("regression tests", () => { it("click-drag to select a group", () => { clickTool("rectangle"); - pointerDown(10, 10); - pointerMove(20, 20); - pointerUp(); + mouse.down(10, 10); + mouse.up(10, 10); clickTool("rectangle"); - pointerDown(30, 10); - pointerMove(40, 20); - pointerUp(); + mouse.down(10, -10); + mouse.up(10, 10); + + const finalPosition = mouse.getPosition(); clickTool("rectangle"); - pointerDown(50, 10); - pointerMove(60, 20); - pointerUp(); + mouse.down(10, -10); + mouse.up(10, 10); - pointerDown(0, 0); - pointerMove(45, 25); - pointerUp(); + mouse.restorePosition(0, 0); + mouse.down(); + mouse.restorePosition(...finalPosition); + mouse.up(5, 5); expect( h.elements.filter((element) => h.state.selectedElementIds[element.id]) @@ -452,27 +476,28 @@ describe("regression tests", () => { ).toBe(2); }); - it("shift-click to select a group, then drag", () => { + it("shift-click to multiselect, then drag", () => { clickTool("rectangle"); - pointerDown(10, 10); - pointerMove(20, 20); - pointerUp(); + mouse.down(10, 10); + mouse.up(10, 10); clickTool("rectangle"); - pointerDown(30, 10); - pointerMove(40, 20); - pointerUp(); + mouse.down(10, -10); + mouse.up(10, 10); const prevRectsXY = h.elements .filter((element) => element.type === "rectangle") .map((element) => ({ x: element.x, y: element.y })); - pointerDown(10, 10); - pointerUp(); - pointerDown(30, 10, false, true); - pointerUp(); - pointerDown(30, 10); - pointerMove(40, 20); - pointerUp(); + + mouse.reset(); + mouse.click(10, 10); + withModifierKeys({ shift: true }, () => { + mouse.click(20, 0); + }); + + mouse.down(); + mouse.up(10, 10); + h.elements .filter((element) => element.type === "rectangle") .forEach((element, i) => { @@ -483,46 +508,41 @@ describe("regression tests", () => { it("pinch-to-zoom works", () => { expect(h.state.zoom).toBe(1); - pointerType = "touch"; - pointerDown(50, 50); - pointer2Down(60, 50); - pointerMove(40, 50); - pointer2Move(60, 50); + finger1.down(50, 50); + finger2.down(60, 50); + finger1.move(-10, 0); expect(h.state.zoom).toBeGreaterThan(1); const zoomed = h.state.zoom; - pointerMove(45, 50); - pointer2Move(55, 50); + finger1.move(5, 0); + finger2.move(-5, 0); expect(h.state.zoom).toBeLessThan(zoomed); - pointerUp(45, 50); - pointer2Up(55, 50); }); it("two-finger scroll works", () => { const startScrollY = h.state.scrollY; - pointerDown(50, 50); - pointer2Down(60, 50); - pointerMove(50, 40); - pointer2Move(60, 40); - pointerUp(50, 40); - pointer2Up(60, 40); + finger1.down(50, 50); + finger2.down(60, 50); + + finger1.up(0, -10); + finger2.up(0, -10); expect(h.state.scrollY).toBeLessThan(startScrollY); const startScrollX = h.state.scrollX; - pointerDown(50, 50); - pointer2Down(50, 60); - pointerMove(60, 50); - pointer2Move(60, 60); - pointerUp(60, 50); - pointer2Up(60, 60); + + finger1.restorePosition(50, 50); + finger2.restorePosition(50, 60); + finger1.down(); + finger2.down(); + finger1.up(10, 0); + finger2.up(10, 0); expect(h.state.scrollX).toBeGreaterThan(startScrollX); }); it("spacebar + drag scrolls the canvas", () => { const { scrollX: startScrollX, scrollY: startScrollY } = h.state; hotkeyDown("SPACE"); - pointerDown(50, 50); - pointerMove(60, 60); - pointerUp(); + mouse.down(50, 50); + mouse.up(60, 60); hotkeyUp("SPACE"); const { scrollX, scrollY } = h.state; expect(scrollX).not.toEqual(startScrollX); @@ -531,46 +551,46 @@ describe("regression tests", () => { it("arrow keys", () => { clickTool("rectangle"); - pointerDown(10, 10); - pointerMove(20, 20); - pointerUp(); + mouse.down(10, 10); + mouse.up(10, 10); hotkeyPress("ARROW_LEFT"); hotkeyPress("ARROW_LEFT"); hotkeyPress("ARROW_RIGHT"); hotkeyPress("ARROW_UP"); hotkeyPress("ARROW_UP"); hotkeyPress("ARROW_DOWN"); + expect(h.elements[0].x).toBe(9); + expect(h.elements[0].y).toBe(9); }); it("undo/redo drawing an element", () => { clickTool("rectangle"); - pointerDown(10, 10); - pointerMove(20, 20); - pointerUp(); + mouse.down(10, 10); + mouse.up(10, 10); clickTool("rectangle"); - pointerDown(30, 10); - pointerMove(40, 20); - pointerUp(); + mouse.down(10, -10); + mouse.up(10, 10); clickTool("arrow"); - pointerDown(10, 30); - pointerUp(); - pointerMove(20, 40); - pointerDown(20, 40); - pointerUp(); - pointerMove(10, 50); - pointerDown(10, 50); - pointerUp(); + mouse.click(10, -10); + mouse.click(10, 10); + mouse.click(-10, 10); hotkeyPress("ENTER"); expect(h.elements.filter((element) => !element.isDeleted).length).toBe(3); - keyPress("z", true); // press twice for multi arrow - keyPress("z", true); + withModifierKeys({ ctrl: true }, () => { + keyPress("z"); + keyPress("z"); + }); expect(h.elements.filter((element) => !element.isDeleted).length).toBe(2); - keyPress("z", true); + withModifierKeys({ ctrl: true }, () => { + keyPress("z"); + }); expect(h.elements.filter((element) => !element.isDeleted).length).toBe(1); - keyPress("z", true, true); + withModifierKeys({ ctrl: true, shift: true }, () => { + keyPress("z"); + }); expect(h.elements.filter((element) => !element.isDeleted).length).toBe(2); }); @@ -581,37 +601,48 @@ describe("regression tests", () => { expect(getStateHistory().length).toBe(0); clickTool("rectangle"); - pointerDown(10, 10); - pointerMove(20, 20); - pointerUp(); + mouse.down(10, 10); + mouse.up(10, 10); + + const firstElementEndPoint = mouse.getPosition(); clickTool("rectangle"); - pointerDown(30, 10); - pointerMove(40, 20); - pointerUp(); + mouse.down(10, -10); + mouse.up(10, 10); + + const secondElementEndPoint = mouse.getPosition(); expect(getStateHistory().length).toBe(2); - keyPress("z", true); + withModifierKeys({ ctrl: true }, () => { + keyPress("z"); + }); + expect(getStateHistory().length).toBe(1); - // clicking an element shouldn't addu to history - pointerDown(10, 10); - pointerUp(); + // clicking an element shouldn't add to history + mouse.restorePosition(...firstElementEndPoint); + mouse.click(); expect(getStateHistory().length).toBe(1); - keyPress("z", true, true); + withModifierKeys({ shift: true, ctrl: true }, () => { + keyPress("z"); + }); + expect(getStateHistory().length).toBe(2); - // clicking an element shouldn't addu to history - pointerDown(10, 10); - pointerUp(); + // clicking an element shouldn't add to history + mouse.click(); expect(getStateHistory().length).toBe(2); + const firstSelectedElementId = getSelectedElement().id; + // same for clicking the element just redo-ed - pointerDown(30, 10); - pointerUp(); + mouse.restorePosition(...secondElementEndPoint); + mouse.click(); expect(getStateHistory().length).toBe(2); + + expect(getSelectedElement().id).not.toEqual(firstSelectedElementId); }); it("zoom hotkeys", () => { @@ -635,4 +666,178 @@ describe("regression tests", () => { // switching to german, `hachure` label should no longer exist expect(screen.queryByText(/hachure/i)).toBeNull(); }); + + it("make a group and duplicate it", () => { + clickTool("rectangle"); + mouse.down(10, 10); + mouse.up(10, 10); + + clickTool("rectangle"); + mouse.down(10, -10); + mouse.up(10, 10); + + clickTool("rectangle"); + mouse.down(10, -10); + mouse.up(10, 10); + const end = mouse.getPosition(); + + mouse.reset(); + mouse.down(); + mouse.restorePosition(...end); + mouse.up(); + + expect(h.elements.length).toBe(3); + for (const element of h.elements) { + expect(element.groupIds.length).toBe(0); + expect(h.state.selectedElementIds[element.id]).toBe(true); + } + + withModifierKeys({ ctrl: true }, () => { + keyPress("g"); + }); + + for (const element of h.elements) { + expect(element.groupIds.length).toBe(1); + } + + withModifierKeys({ alt: true }, () => { + mouse.restorePosition(...end); + mouse.down(); + mouse.up(10, 10); + }); + + expect(h.elements.length).toBe(6); + const groups = new Set(); + for (const element of h.elements) { + for (const groupId of element.groupIds) { + groups.add(groupId); + } + } + + expect(groups.size).toBe(2); + }); + + it("double click to edit a group", () => { + clickTool("rectangle"); + mouse.down(10, 10); + mouse.up(10, 10); + + clickTool("rectangle"); + mouse.down(10, -10); + mouse.up(10, 10); + + clickTool("rectangle"); + mouse.down(10, -10); + mouse.up(10, 10); + + withModifierKeys({ ctrl: true }, () => { + keyPress("a"); + keyPress("g"); + }); + + expect(getSelectedElements().length).toBe(3); + expect(h.state.editingGroupId).toBe(null); + mouse.doubleClick(); + expect(getSelectedElements().length).toBe(1); + expect(h.state.editingGroupId).not.toBe(null); + }); + + it("adjusts z order when grouping", () => { + const positions = []; + + clickTool("rectangle"); + mouse.down(10, 10); + mouse.up(10, 10); + positions.push(mouse.getPosition()); + + clickTool("rectangle"); + mouse.down(10, -10); + mouse.up(10, 10); + positions.push(mouse.getPosition()); + + clickTool("rectangle"); + mouse.down(10, -10); + mouse.up(10, 10); + positions.push(mouse.getPosition()); + + const ids = h.elements.map((element) => element.id); + + mouse.restorePosition(...positions[0]); + mouse.click(); + mouse.restorePosition(...positions[2]); + withModifierKeys({ shift: true }, () => { + mouse.click(); + }); + withModifierKeys({ ctrl: true }, () => { + keyPress("g"); + }); + + expect(h.elements.map((element) => element.id)).toEqual([ + ids[1], + ids[0], + ids[2], + ]); + }); + + it("supports nested groups", () => { + const positions: number[][] = []; + + clickTool("rectangle"); + mouse.down(10, 10); + mouse.up(10, 10); + positions.push(mouse.getPosition()); + + clickTool("rectangle"); + mouse.down(10, -10); + mouse.up(10, 10); + positions.push(mouse.getPosition()); + + clickTool("rectangle"); + mouse.down(10, -10); + mouse.up(10, 10); + positions.push(mouse.getPosition()); + + withModifierKeys({ ctrl: true }, () => { + keyPress("a"); + keyPress("g"); + }); + + mouse.doubleClick(); + withModifierKeys({ shift: true }, () => { + mouse.restorePosition(...positions[0]); + mouse.click(); + }); + withModifierKeys({ ctrl: true }, () => { + keyPress("g"); + }); + + const groupIds = h.elements[2].groupIds; + expect(groupIds.length).toBe(2); + expect(h.elements[1].groupIds).toEqual(groupIds); + expect(h.elements[0].groupIds).toEqual(groupIds.slice(1)); + + mouse.click(50, 50); + expect(getSelectedElements().length).toBe(0); + mouse.restorePosition(...positions[0]); + mouse.click(); + expect(getSelectedElements().length).toBe(3); + expect(h.state.editingGroupId).toBe(null); + + mouse.doubleClick(); + expect(getSelectedElements().length).toBe(2); + expect(h.state.editingGroupId).toBe(groupIds[1]); + + mouse.doubleClick(); + expect(getSelectedElements().length).toBe(1); + expect(h.state.editingGroupId).toBe(groupIds[0]); + + // click out of the group + mouse.restorePosition(...positions[1]); + mouse.click(); + expect(getSelectedElements().length).toBe(0); + mouse.click(); + expect(getSelectedElements().length).toBe(3); + mouse.doubleClick(); + expect(getSelectedElements().length).toBe(1); + }); });