patched-undo-peasy
Version:
undo/redo for easy peasy
188 lines (164 loc) • 5.15 kB
text/typescript
import "chai/register-should";
import { createStore, action, Action, Store, Computed, computed } from "patched-peasy";
import { undoable, WithUndo, ModelWithUndo } from "../UndoRedoState";
import { undoRedo as undoRedoMiddleware } from "../UndoRedoMiddleware";
import { assert, should } from "chai";
interface Model extends WithUndo {
count: number;
increment: Action<Model>;
}
const simpleModel: Model = undoable({
count: 0,
increment: action((state) => {
state.count++;
}),
});
interface ViewModel extends WithUndo {
count: number;
view: number;
increment: Action<ViewModel>;
doubleView: Action<ViewModel>;
countSquared: Computed<ViewModel, number>;
}
const viewModel: ViewModel = undoable({
count: 0,
view: 7,
doubleView: action((state) => {
state.view *= 2;
}),
increment: action((state) => {
state.count++;
}),
countSquared: computed(
[(model) => model.view],
(view) => view * view)
});
function makeStore() {
const store = createStore(undoable(simpleModel), {
middleware: [undoRedoMiddleware()],
});
const actions = store.getActions();
actions.undoSave();
return { store, actions };
}
function makeViewStore() {
const store = createStore(undoable(viewModel), {
middleware: [undoRedoMiddleware({ noSaveKeys, noSaveActions })],
});
const actions = store.getActions();
actions.undoSave();
return { store, actions };
}
function noSaveKeys(key: string): boolean {
return key === "view";
}
function noSaveActions(actionType: string): boolean {
return actionType.startsWith("@action.doubleView");
}
test("save an action", () => {
const { store, actions } = makeStore();
actions.increment();
(store.getState().undoHistory?.current as any).count.should.equal(1);
store.getState().count.should.equal(1);
});
test("save two actions", () => {
const { store, actions } = makeStore();
actions.increment();
actions.increment();
const history = store.getState().undoHistory;
(history.current as any).count.should.equal(2);
(history.undo[0] as any).count.should.equal(0);
history.undo.length.should.equal(2);
});
test("reset saved", () => {
const { store, actions } = makeStore();
actions.increment();
actions.increment();
actions.undoReset();
const history = store.getState().undoHistory;
history.redo.length.should.equal(0);
history.undo.length.should.equal(0);
const expectCount = store.getState().count;
(history.current as any).count.should.equal(expectCount);
});
test("undo an action", () => {
const { store, actions } = makeStore();
actions.increment();
actions.undoUndo();
const history = store.getState().undoHistory;
store.getState().count.should.equal(0);
history.undo.length.should.equal(0);
});
test("undo two actions", () => {
const { store, actions } = makeStore();
actions.increment();
actions.increment();
actions.undoUndo();
actions.undoUndo();
const history = store.getState().undoHistory;
store.getState().count.should.equal(0);
history.undo.length.should.equal(0);
(history.current as any).count.should.equal(0);
});
test("two actions, then undo", () => {
const { store, actions } = makeStore();
actions.undoReset();
actions.increment();
actions.increment();
actions.undoUndo();
const history = store.getState().undoHistory;
store.getState().count.should.equal(1);
history.undo.length.should.equal(1);
history.redo.length.should.equal(1);
(history.current as any).count.should.equal(1);
});
test("redo", () => {
const { store, actions } = makeStore();
actions.increment();
actions.increment();
actions.increment();
store.getState().count.should.equal(3);
actions.undoUndo();
actions.undoUndo();
store.getState().count.should.equal(1);
actions.undoRedo();
store.getState().count.should.equal(2);
const history = store.getState().undoHistory;
history.undo.length.should.equal(2);
history.redo.length.should.equal(1);
(history.current as any).count.should.equal(2);
});
test("undo empty doesn't crash", () => {
const { actions } = makeStore();
actions.undoUndo();
});
test("undo empty doesn't crash", () => {
const { actions } = makeStore();
actions.undoRedo();
});
test("don't save view keys", () => {
const { actions } = makeStore();
});
test("views are not saved", () => {
const { store } = makeViewStore();
const history = store.getState().undoHistory;
assert((history.current as any).view === undefined);
});
test("views are restored by undo/redo", () => {
const { store, actions } = makeViewStore();
actions.increment();
actions.doubleView();
actions.undoUndo();
store.getState().view.should.equal(viewModel.view * 2);
});
test("views actions are not saved", () => {
const { store, actions } = makeViewStore();
actions.doubleView();
store.getState().undoHistory.undo.length.should.equal(0);
});
test("computed values are not saved", () => {
const { store, actions } = makeViewStore();
store.getState().countSquared.should.equal(49);
const current = store.getState().undoHistory.current as any; //?
Object.keys(current).includes("countSquared").should.equal(false);
});