diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index beca285e1..9b3fe0d8b 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -3907,6 +3907,7 @@ class App extends React.Component { const { elements, appState, collaborators, captureUpdate } = sceneData; if (captureUpdate) { + const nextElements = elements ? elements : undefined; const observedAppState = appState ? getObservedAppState({ ...this.store.snapshot.appState, @@ -3916,7 +3917,7 @@ class App extends React.Component { this.store.scheduleMicroAction({ action: captureUpdate, - elements: elements ?? [], + elements: nextElements, appState: observedAppState, }); } diff --git a/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap index 4d590b3b0..22b8519dc 100644 --- a/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap @@ -14820,6 +14820,158 @@ exports[`history > singleplayer undo/redo > should not end up with history entry ] `; +exports[`history > singleplayer undo/redo > should not modify anything on unrelated appstate change > [end of test] appState 1`] = ` +{ + "activeEmbeddable": null, + "activeLockedId": null, + "activeTool": { + "customType": null, + "fromSelection": false, + "lastActiveTool": null, + "locked": false, + "type": "selection", + }, + "collaborators": Map {}, + "contextMenu": null, + "croppingElementId": null, + "currentChartType": "bar", + "currentHoveredFontFamily": null, + "currentItemArrowType": "round", + "currentItemBackgroundColor": "transparent", + "currentItemEndArrowhead": "arrow", + "currentItemFillStyle": "solid", + "currentItemFontFamily": 5, + "currentItemFontSize": 20, + "currentItemOpacity": 100, + "currentItemRoughness": 1, + "currentItemRoundness": "sharp", + "currentItemStartArrowhead": null, + "currentItemStrokeColor": "#1e1e1e", + "currentItemStrokeStyle": "solid", + "currentItemStrokeWidth": 2, + "currentItemTextAlign": "left", + "cursorButton": "up", + "defaultSidebarDockedPreference": false, + "editingFrame": null, + "editingGroupId": null, + "editingLinearElement": null, + "editingTextElement": null, + "elementsToHighlight": null, + "errorMessage": null, + "exportBackground": true, + "exportEmbedScene": false, + "exportScale": 1, + "exportWithDarkMode": false, + "fileHandle": null, + "followedBy": Set {}, + "frameRendering": { + "clip": true, + "enabled": true, + "name": true, + "outline": true, + }, + "frameToHighlight": null, + "gridModeEnabled": false, + "gridSize": 20, + "gridStep": 5, + "height": 0, + "hoveredElementIds": {}, + "isBindingEnabled": true, + "isCropping": false, + "isLoading": false, + "isResizing": false, + "isRotating": false, + "lastPointerDownWith": "mouse", + "lockedMultiSelections": {}, + "multiElement": null, + "newElement": null, + "objectsSnapModeEnabled": false, + "offsetLeft": 0, + "offsetTop": 0, + "openDialog": null, + "openMenu": null, + "openPopup": null, + "openSidebar": null, + "originSnapOffset": { + "x": 0, + "y": 0, + }, + "pasteDialog": { + "data": null, + "shown": false, + }, + "penDetected": false, + "penMode": false, + "previousSelectedElementIds": {}, + "resizingElement": null, + "scrollX": 0, + "scrollY": 0, + "searchMatches": null, + "selectedElementIds": {}, + "selectedElementsAreBeingDragged": false, + "selectedGroupIds": {}, + "selectionElement": null, + "shouldCacheIgnoreZoom": false, + "showHyperlinkPopup": false, + "showWelcomeScreen": false, + "snapLines": [], + "startBoundElement": null, + "stats": { + "open": false, + "panels": 3, + }, + "suggestedBindings": [], + "theme": "light", + "toast": null, + "userToFollow": null, + "viewBackgroundColor": "#ffffff", + "viewModeEnabled": true, + "width": 0, + "zenModeEnabled": false, + "zoom": { + "value": 1, + }, +} +`; + +exports[`history > singleplayer undo/redo > should not modify anything on unrelated appstate change > [end of test] element 0 1`] = ` +{ + "angle": 0, + "backgroundColor": "transparent", + "boundElements": [], + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "id": "id0", + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": null, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "updated": 1, + "version": 2, + "width": 100, + "x": 0, + "y": 0, +} +`; + +exports[`history > singleplayer undo/redo > should not modify anything on unrelated appstate change > [end of test] number of elements 1`] = `1`; + +exports[`history > singleplayer undo/redo > should not modify anything on unrelated appstate change > [end of test] number of renders 1`] = `4`; + +exports[`history > singleplayer undo/redo > should not modify anything on unrelated appstate change > [end of test] redo stack 1`] = `[]`; + +exports[`history > singleplayer undo/redo > should not modify anything on unrelated appstate change > [end of test] undo stack 1`] = `[]`; + exports[`history > singleplayer undo/redo > should not override appstate changes when redo stack is not cleared > [end of test] appState 1`] = ` { "activeEmbeddable": null, diff --git a/packages/excalidraw/tests/history.test.tsx b/packages/excalidraw/tests/history.test.tsx index c2da25454..be7c5821f 100644 --- a/packages/excalidraw/tests/history.test.tsx +++ b/packages/excalidraw/tests/history.test.tsx @@ -243,6 +243,37 @@ describe("history", () => { ]); }); + it("should not modify anything on unrelated appstate change", async () => { + const rect = API.createElement({ type: "rectangle" }); + await render( + , + ); + + API.updateScene({ + appState: { + viewModeEnabled: true, + }, + captureUpdate: CaptureUpdateAction.NEVER, + }); + + await waitFor(() => { + expect(h.state.viewModeEnabled).toBe(true); + expect(API.getUndoStack().length).toBe(0); + expect(API.getRedoStack().length).toBe(0); + expect(h.elements).toEqual([ + expect.objectContaining({ id: rect.id, isDeleted: false }), + ]); + expect(h.store.snapshot.elements.get(rect.id)).toEqual( + expect.objectContaining({ id: rect.id, isDeleted: false }), + ); + }); + }); + it("should not clear the redo stack on standalone appstate change", async () => { await render();