Add an undo stack size limit to paint

This commit is contained in:
DD 2018-02-26 17:34:17 -05:00
parent c39c495f8f
commit b93ce53694
2 changed files with 84 additions and 2 deletions

View file

@ -4,6 +4,7 @@ const UNDO = 'scratch-paint/undo/UNDO';
const REDO = 'scratch-paint/undo/REDO'; const REDO = 'scratch-paint/undo/REDO';
const SNAPSHOT = 'scratch-paint/undo/SNAPSHOT'; const SNAPSHOT = 'scratch-paint/undo/SNAPSHOT';
const CLEAR = 'scratch-paint/undo/CLEAR'; const CLEAR = 'scratch-paint/undo/CLEAR';
const MAX_STACK_SIZE = 100;
const initialState = { const initialState = {
stack: [], stack: [],
pointer: -1 pointer: -1
@ -35,6 +36,14 @@ const reducer = function (state, action) {
log.warn(`Couldn't create undo snapshot, no data provided`); log.warn(`Couldn't create undo snapshot, no data provided`);
return state; return state;
} }
// Overflowed or about to overflow
if (state.pointer >= MAX_STACK_SIZE - 1) {
return {
// Make a stack of size MAX_STACK_SIZE, cutting off the oldest snapshots.
stack: state.stack.slice(state.pointer - MAX_STACK_SIZE + 2, state.pointer + 1).concat(action.snapshot),
pointer: MAX_STACK_SIZE - 1
};
}
return { return {
// Performing an action clears the redo stack // Performing an action clears the redo stack
stack: state.stack.slice(0, state.pointer + 1).concat(action.snapshot), stack: state.stack.slice(0, state.pointer + 1).concat(action.snapshot),
@ -75,5 +84,6 @@ export {
undo, undo,
redo, redo,
undoSnapshot, undoSnapshot,
clearUndoState clearUndoState,
MAX_STACK_SIZE
}; };

View file

@ -1,6 +1,6 @@
/* eslint-env jest */ /* eslint-env jest */
import undoReducer from '../../src/reducers/undo'; import undoReducer from '../../src/reducers/undo';
import {undoSnapshot, undo, redo, clearUndoState} from '../../src/reducers/undo'; import {undoSnapshot, undo, redo, clearUndoState, MAX_STACK_SIZE} from '../../src/reducers/undo';
test('initialState', () => { test('initialState', () => {
let defaultState; let defaultState;
@ -139,3 +139,75 @@ test('undoSnapshotCantRedo', () => {
expect(newReduxState.stack[0]).toEqual(reduxState.stack[0]); expect(newReduxState.stack[0]).toEqual(reduxState.stack[0]);
expect(newReduxState.stack[1]).toEqual(state3); expect(newReduxState.stack[1]).toEqual(state3);
}); });
test('snapshotAtMaxStackSize', () => {
let defaultState;
const getState = function (num) {
return {state: num};
};
// Push MAX_STACK_SIZE states
let num = 1;
let reduxState = undoReducer(defaultState /* state */, undoSnapshot([getState(num)]) /* action */);
for (num = 2; num <= MAX_STACK_SIZE; num++) {
reduxState = undoReducer(reduxState /* state */, undoSnapshot([getState(num)]) /* action */);
}
expect(reduxState.pointer).toEqual(MAX_STACK_SIZE - 1);
expect(reduxState.stack).toHaveLength(MAX_STACK_SIZE);
expect(reduxState.stack[0].state).toEqual(1);
// Push one more
reduxState = undoReducer(reduxState /* state */, undoSnapshot([getState(num)]) /* action */);
// Stack size stays the same
expect(reduxState.pointer).toEqual(MAX_STACK_SIZE - 1);
expect(reduxState.stack).toHaveLength(MAX_STACK_SIZE);
expect(reduxState.stack[0].state).toEqual(2); // State 1 was cut off
expect(reduxState.stack[MAX_STACK_SIZE - 1].state).toEqual(MAX_STACK_SIZE + 1); // Newest added state is at end
});
test('undoRedoAtMaxStackSize', () => {
let defaultState;
const getState = function (num) {
return {state: num};
};
// Push MAX_STACK_SIZE states
let num = 1;
let reduxState = undoReducer(defaultState /* state */, undoSnapshot([getState(num)]) /* action */);
for (num = 2; num <= MAX_STACK_SIZE; num++) {
reduxState = undoReducer(reduxState /* state */, undoSnapshot([getState(num)]) /* action */);
}
// Undo twice and redo
reduxState = undoReducer(reduxState /* state */, undo() /* action */);
reduxState = undoReducer(reduxState /* state */, undo() /* action */);
reduxState = undoReducer(reduxState /* state */, redo() /* action */);
expect(reduxState.pointer).toEqual(MAX_STACK_SIZE - 2);
expect(reduxState.stack).toHaveLength(MAX_STACK_SIZE);
expect(reduxState.stack[0].state).toEqual(1);
});
test('undoSnapshotAtMaxStackSize', () => {
let defaultState;
const getState = function (num) {
return {state: num};
};
// Push MAX_STACK_SIZE states
let num = 1;
let reduxState = undoReducer(defaultState /* state */, undoSnapshot([getState(num)]) /* action */);
for (num = 2; num <= MAX_STACK_SIZE; num++) {
reduxState = undoReducer(reduxState /* state */, undoSnapshot([getState(num)]) /* action */);
}
// Undo twice and then take a snapshot
reduxState = undoReducer(reduxState /* state */, undo() /* action */);
reduxState = undoReducer(reduxState /* state */, undo() /* action */);
reduxState = undoReducer(reduxState /* state */, undoSnapshot([getState(num)]) /* action */);
expect(reduxState.pointer).toEqual(MAX_STACK_SIZE - 2);
expect(reduxState.stack).toHaveLength(MAX_STACK_SIZE - 1);
expect(reduxState.stack[0].state).toEqual(1);
expect(reduxState.stack[MAX_STACK_SIZE - 2].state).toEqual(MAX_STACK_SIZE + 1); // Newest added state is at end
expect(reduxState.stack[MAX_STACK_SIZE - 3].state).toEqual(MAX_STACK_SIZE - 2); // Old redo state is gone
});