mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-04-14 16:40:58 -04:00
fix: undo/redo when exiting view mode (#8024)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
parent
be4e127f6c
commit
afe52c89a7
3 changed files with 916 additions and 321 deletions
|
@ -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"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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", () => {
|
||||||
|
|
Loading…
Add table
Reference in a new issue