scratch-paint/test/unit/undo-reducer.test.js

213 lines
8.4 KiB
JavaScript

/* eslint-env jest */
import undoReducer from '../../src/reducers/undo';
import {undoSnapshot, undo, redo, clearUndoState, MAX_STACK_SIZE} from '../../src/reducers/undo';
test('initialState', () => {
let defaultState;
expect(undoReducer(defaultState /* state */, {type: 'anything'} /* action */)).toBeDefined();
expect(undoReducer(defaultState /* state */, {type: 'anything'} /* action */).pointer).toEqual(-1);
expect(undoReducer(defaultState /* state */, {type: 'anything'} /* action */).stack).toHaveLength(0);
});
test('snapshot', () => {
let defaultState;
const state1 = {state: 1};
const state2 = {state: 2};
let reduxState = undoReducer(defaultState /* state */, undoSnapshot([state1]) /* action */);
expect(reduxState.pointer).toEqual(0);
expect(reduxState.stack).toHaveLength(1);
expect(reduxState.stack[0]).toEqual(state1);
reduxState = undoReducer(reduxState /* state */, undoSnapshot([state2]) /* action */);
expect(reduxState.pointer).toEqual(1);
expect(reduxState.stack).toHaveLength(2);
expect(reduxState.stack[0]).toEqual(state1);
expect(reduxState.stack[1]).toEqual(state2);
});
test('invalidSnapshot', () => {
let defaultState;
const state1 = {state: 1};
const reduxState = undoReducer(defaultState /* state */, undoSnapshot([state1]) /* action */);
const newReduxState = undoReducer(reduxState /* state */, undoSnapshot() /* action */); // No snapshot provided
expect(reduxState).toEqual(newReduxState);
});
test('clearUndoState', () => {
let defaultState;
const state1 = {state: 1};
const state2 = {state: 2};
// Push 2 states then clear
const reduxState = undoReducer(defaultState /* state */, undoSnapshot([state1]) /* action */);
undoReducer(reduxState /* state */, undoSnapshot([state2]) /* action */);
const newReduxState = undoReducer(reduxState /* state */, clearUndoState() /* action */);
expect(newReduxState.pointer).toEqual(-1);
expect(newReduxState.stack).toHaveLength(0);
});
test('cantUndo', () => {
let defaultState;
const state1 = {state: 1};
// Undo when there's no undo stack
let reduxState = undoReducer(defaultState /* state */, undo() /* action */);
expect(reduxState.pointer).toEqual(-1);
expect(reduxState.stack).toHaveLength(0);
// Undo when there's only one state
reduxState = undoReducer(reduxState /* state */, undoSnapshot([state1]) /* action */);
reduxState = undoReducer(reduxState /* state */, undo() /* action */);
expect(reduxState.pointer).toEqual(0);
expect(reduxState.stack).toHaveLength(1);
});
test('cantRedo', () => {
let defaultState;
const state1 = {state: 1};
let reduxState = undoReducer(defaultState /* state */, undoSnapshot([state1]) /* action */);
// Redo when there's no redo stack
reduxState = undoReducer(reduxState /* state */, redo() /* action */);
expect(reduxState.pointer).toEqual(0);
expect(reduxState.stack).toHaveLength(1);
});
test('undo', () => {
let defaultState;
const state1 = {state: 1};
const state2 = {state: 2};
// Push 2 states then undo one
let reduxState = undoReducer(defaultState /* state */, undoSnapshot([state1]) /* action */);
reduxState = undoReducer(reduxState /* state */, undoSnapshot([state2]) /* action */);
reduxState = undoReducer(reduxState /* state */, undo() /* action */);
expect(reduxState.pointer).toEqual(0);
expect(reduxState.stack).toHaveLength(2);
expect(reduxState.stack[0]).toEqual(state1);
expect(reduxState.stack[1]).toEqual(state2);
});
test('redo', () => {
let defaultState;
const state1 = {state: 1};
const state2 = {state: 2};
// Push 2 states then undo one
let reduxState = undoReducer(defaultState /* state */, undoSnapshot([state1]) /* action */);
reduxState = undoReducer(reduxState /* state */, undoSnapshot([state2]) /* action */);
let newReduxState = undoReducer(reduxState /* state */, undo() /* action */);
// Now redo and check equality with previous state
newReduxState = undoReducer(newReduxState /* state */, redo() /* action */);
expect(newReduxState.pointer).toEqual(reduxState.pointer);
expect(newReduxState.stack).toHaveLength(reduxState.stack.length);
expect(newReduxState.stack[0]).toEqual(reduxState.stack[0]);
expect(reduxState.stack[1]).toEqual(reduxState.stack[1]);
});
test('undoSnapshotCantRedo', () => {
let defaultState;
const state1 = {state: 1};
const state2 = {state: 2};
const state3 = {state: 3};
// Push 2 states then undo
let reduxState = undoReducer(defaultState /* state */, undoSnapshot([state1]) /* action */);
reduxState = undoReducer(reduxState /* state */, undoSnapshot([state2]) /* action */);
reduxState = undoReducer(reduxState /* state */, undo() /* action */);
expect(reduxState.pointer).toEqual(0);
expect(reduxState.stack).toHaveLength(2);
// Snapshot
reduxState = undoReducer(reduxState /* state */, undoSnapshot([state3]) /* action */);
// Redo should do nothing
const newReduxState = undoReducer(reduxState /* state */, redo() /* action */);
expect(newReduxState.pointer).toEqual(reduxState.pointer);
expect(newReduxState.stack).toHaveLength(reduxState.stack.length);
expect(newReduxState.stack[0]).toEqual(reduxState.stack[0]);
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
});