Sfoglia il codice sorgente

Fix broken history when eleemnt in update scene are optional

Marcel Mraz 2 mesi fa
parent
commit
60512f13d5

+ 2 - 1
packages/excalidraw/components/App.tsx

@@ -3907,6 +3907,7 @@ class App extends React.Component<AppProps, AppState> {
       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<AppProps, AppState> {
 
         this.store.scheduleMicroAction({
           action: captureUpdate,
-          elements: elements ?? [],
+          elements: nextElements,
           appState: observedAppState,
         });
       }

+ 152 - 0
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,

+ 31 - 0
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(
+        <Excalidraw
+          handleKeyboardGlobally={true}
+          initialData={{
+            elements: [rect],
+          }}
+        />,
+      );
+
+      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(<Excalidraw handleKeyboardGlobally={true} />);