Преглед изворни кода

fix: undo/redo when exiting view mode (#8024)

Co-authored-by: dwelle <[email protected]>
VatsalSoni_13 пре 1 година
родитељ
комит
afe52c89a7

+ 10 - 2
packages/excalidraw/actions/actionHistory.tsx

@@ -65,7 +65,10 @@ export const createUndoAction: ActionCreator = (history, store) => ({
   PanelComponent: ({ updateData, data }) => {
   PanelComponent: ({ updateData, data }) => {
     const { isUndoStackEmpty } = useEmitter<HistoryChangedEvent>(
     const { isUndoStackEmpty } = useEmitter<HistoryChangedEvent>(
       history.onHistoryChangedEmitter,
       history.onHistoryChangedEmitter,
-      new HistoryChangedEvent(),
+      new HistoryChangedEvent(
+        history.isUndoStackEmpty,
+        history.isRedoStackEmpty,
+      ),
     );
     );
 
 
     return (
     return (
@@ -76,6 +79,7 @@ export const createUndoAction: ActionCreator = (history, store) => ({
         onClick={updateData}
         onClick={updateData}
         size={data?.size || "medium"}
         size={data?.size || "medium"}
         disabled={isUndoStackEmpty}
         disabled={isUndoStackEmpty}
+        data-testid="button-undo"
       />
       />
     );
     );
   },
   },
@@ -103,7 +107,10 @@ export const createRedoAction: ActionCreator = (history, store) => ({
   PanelComponent: ({ updateData, data }) => {
   PanelComponent: ({ updateData, data }) => {
     const { isRedoStackEmpty } = useEmitter(
     const { isRedoStackEmpty } = useEmitter(
       history.onHistoryChangedEmitter,
       history.onHistoryChangedEmitter,
-      new HistoryChangedEvent(),
+      new HistoryChangedEvent(
+        history.isUndoStackEmpty,
+        history.isRedoStackEmpty,
+      ),
     );
     );
 
 
     return (
     return (
@@ -114,6 +121,7 @@ export const createRedoAction: ActionCreator = (history, store) => ({
         onClick={updateData}
         onClick={updateData}
         size={data?.size || "medium"}
         size={data?.size || "medium"}
         disabled={isRedoStackEmpty}
         disabled={isRedoStackEmpty}
+        data-testid="button-redo"
       />
       />
     );
     );
   },
   },

Разлика између датотеке није приказан због своје велике величине
+ 124 - 124
packages/excalidraw/tests/__snapshots__/history.test.tsx.snap


+ 125 - 2
packages/excalidraw/tests/history.test.tsx

@@ -10,8 +10,9 @@ import { Excalidraw } from "../index";
 import { Keyboard, Pointer, UI } from "./helpers/ui";
 import { Keyboard, Pointer, UI } from "./helpers/ui";
 import { API } from "./helpers/api";
 import { API } from "./helpers/api";
 import { getDefaultAppState } from "../appState";
 import { getDefaultAppState } from "../appState";
-import { fireEvent, waitFor } from "@testing-library/react";
+import { fireEvent, queryByTestId, waitFor } from "@testing-library/react";
 import { createUndoAction, createRedoAction } from "../actions/actionHistory";
 import { createUndoAction, createRedoAction } from "../actions/actionHistory";
+import { actionToggleViewMode } from "../actions/actionToggleViewMode";
 import { EXPORT_DATA_TYPES, MIME_TYPES } from "../constants";
 import { EXPORT_DATA_TYPES, MIME_TYPES } from "../constants";
 import type { AppState, ExcalidrawImperativeAPI } from "../types";
 import type { AppState, ExcalidrawImperativeAPI } from "../types";
 import { arrayToMap, resolvablePromise } from "../utils";
 import { arrayToMap, resolvablePromise } from "../utils";
@@ -49,7 +50,6 @@ const checkpoint = (name: string) => {
   expect(renderStaticScene.mock.calls.length).toMatchSnapshot(
   expect(renderStaticScene.mock.calls.length).toMatchSnapshot(
     `[${name}] number of renders`,
     `[${name}] number of renders`,
   );
   );
-
   // `scrolledOutside` does not appear to be stable between test runs
   // `scrolledOutside` does not appear to be stable between test runs
   // `selectedLinearElemnt` includes `startBindingElement` containing seed and versionNonce
   // `selectedLinearElemnt` includes `startBindingElement` containing seed and versionNonce
   const {
   const {
@@ -1688,6 +1688,129 @@ describe("history", () => {
         ]);
         ]);
       });
       });
     });
     });
+
+    it("should disable undo/redo buttons when stacks empty", async () => {
+      const { container } = await render(
+        <Excalidraw
+          initialData={{
+            elements: [API.createElement({ type: "rectangle", id: "A" })],
+          }}
+        />,
+      );
+
+      const undoAction = createUndoAction(h.history, h.store);
+      const redoAction = createRedoAction(h.history, h.store);
+
+      await waitFor(() => {
+        expect(h.elements).toEqual([expect.objectContaining({ id: "A" })]);
+        expect(h.history.isUndoStackEmpty).toBeTruthy();
+        expect(h.history.isRedoStackEmpty).toBeTruthy();
+      });
+
+      const undoButton = queryByTestId(container, "button-undo");
+      const redoButton = queryByTestId(container, "button-redo");
+
+      expect(undoButton).toBeDisabled();
+      expect(redoButton).toBeDisabled();
+
+      const rectangle = UI.createElement("rectangle");
+      expect(h.elements).toEqual([
+        expect.objectContaining({ id: "A" }),
+        expect.objectContaining({ id: rectangle.id }),
+      ]);
+
+      expect(h.history.isUndoStackEmpty).toBeFalsy();
+      expect(h.history.isRedoStackEmpty).toBeTruthy();
+      expect(undoButton).not.toBeDisabled();
+      expect(redoButton).toBeDisabled();
+
+      act(() => h.app.actionManager.executeAction(undoAction));
+
+      expect(h.history.isUndoStackEmpty).toBeTruthy();
+      expect(h.history.isRedoStackEmpty).toBeFalsy();
+      expect(undoButton).toBeDisabled();
+      expect(redoButton).not.toBeDisabled();
+
+      act(() => h.app.actionManager.executeAction(redoAction));
+
+      expect(h.history.isUndoStackEmpty).toBeFalsy();
+      expect(h.history.isRedoStackEmpty).toBeTruthy();
+      expect(undoButton).not.toBeDisabled();
+      expect(redoButton).toBeDisabled();
+    });
+
+    it("remounting undo/redo buttons should initialize undo/redo state correctly", async () => {
+      const { container } = await render(
+        <Excalidraw
+          initialData={{
+            elements: [API.createElement({ type: "rectangle", id: "A" })],
+          }}
+        />,
+      );
+
+      const undoAction = createUndoAction(h.history, h.store);
+
+      await waitFor(() => {
+        expect(h.elements).toEqual([expect.objectContaining({ id: "A" })]);
+        expect(h.history.isUndoStackEmpty).toBeTruthy();
+        expect(h.history.isRedoStackEmpty).toBeTruthy();
+      });
+
+      expect(queryByTestId(container, "button-undo")).toBeDisabled();
+      expect(queryByTestId(container, "button-redo")).toBeDisabled();
+
+      // testing undo button
+      // -----------------------------------------------------------------------
+
+      const rectangle = UI.createElement("rectangle");
+      expect(h.elements).toEqual([
+        expect.objectContaining({ id: "A" }),
+        expect.objectContaining({ id: rectangle.id }),
+      ]);
+
+      expect(h.history.isUndoStackEmpty).toBeFalsy();
+      expect(h.history.isRedoStackEmpty).toBeTruthy();
+      expect(queryByTestId(container, "button-undo")).not.toBeDisabled();
+      expect(queryByTestId(container, "button-redo")).toBeDisabled();
+
+      act(() => h.app.actionManager.executeAction(actionToggleViewMode));
+      expect(h.state.viewModeEnabled).toBe(true);
+
+      expect(queryByTestId(container, "button-undo")).toBeNull();
+      expect(queryByTestId(container, "button-redo")).toBeNull();
+
+      act(() => h.app.actionManager.executeAction(actionToggleViewMode));
+      expect(h.state.viewModeEnabled).toBe(false);
+
+      await waitFor(() => {
+        expect(queryByTestId(container, "button-undo")).not.toBeDisabled();
+        expect(queryByTestId(container, "button-redo")).toBeDisabled();
+      });
+
+      // testing redo button
+      // -----------------------------------------------------------------------
+
+      act(() => h.app.actionManager.executeAction(undoAction));
+
+      expect(h.history.isUndoStackEmpty).toBeTruthy();
+      expect(h.history.isRedoStackEmpty).toBeFalsy();
+      expect(queryByTestId(container, "button-undo")).toBeDisabled();
+      expect(queryByTestId(container, "button-redo")).not.toBeDisabled();
+
+      act(() => h.app.actionManager.executeAction(actionToggleViewMode));
+      expect(h.state.viewModeEnabled).toBe(true);
+
+      expect(queryByTestId(container, "button-undo")).toBeNull();
+      expect(queryByTestId(container, "button-redo")).toBeNull();
+
+      act(() => h.app.actionManager.executeAction(actionToggleViewMode));
+      expect(h.state.viewModeEnabled).toBe(false);
+
+      expect(h.history.isUndoStackEmpty).toBeTruthy();
+      expect(h.history.isRedoStackEmpty).toBeFalsy();
+      expect(queryByTestId(container, "button-undo")).toBeDisabled();
+      expect(queryByTestId(container, "button-redo")).not.toBeDisabled();
+    });
   });
   });
 
 
   describe("multiplayer undo/redo", () => {
   describe("multiplayer undo/redo", () => {

Неке датотеке нису приказане због велике количине промена