Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions src/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,17 @@ function undo (history) {

if (past.length <= 0) return history

return {
past: past.slice(0, past.length - 1), // remove last element from past
present: past[past.length - 1], // set element as new present
future: [
const newFuture = history.wasFiltered
? future // if the last `present` was filtered, don't store it in the future
: [
present, // old present state is in the future now
...future
]

return {
past: past.slice(0, past.length - 1), // remove last element from past
present: past[past.length - 1], // set element as new present
future: newFuture
}
}

Expand Down Expand Up @@ -146,9 +150,6 @@ export default function undoable (reducer, rawConfig = {}) {
history = config.history = createHistory(state)
debug.log('initialHistory initialized: initialState is not a history', config.history)
}

// do not store initialState in the history again
history.wasFiltered = true
}

let res
Expand Down
100 changes: 86 additions & 14 deletions test/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,19 +153,48 @@ function runTestWithConfig (testConfig, initialStoreState, label) {
describe('Actions', () => {
it('should not record unwanted actions', () => {
if (testConfig && testConfig.FOR_TEST_ONLY_excludedActions) {
// don't record this action in history
let decrementedState = mockUndoableReducer(mockInitialState, { type: testConfig.FOR_TEST_ONLY_excludedActions[0] })
expect(decrementedState.past).to.deep.equal(mockInitialState.past)
expect(decrementedState.future).to.deep.equal(mockInitialState.future)
const excludedAction = { type: testConfig.FOR_TEST_ONLY_excludedActions[0] }
const notFilteredReducer = undoable(countReducer, { ...testConfig, filter: null })
let expected = {
...notFilteredReducer(mockInitialState, excludedAction),
// because action is filtered, this state should be indicated as filtered
wasFiltered: true
}
// should store state (to store the previous present caused by a not filtered action into the past)
let actual = mockUndoableReducer(mockInitialState, excludedAction)
expect(actual).to.deep.equal(expected)
// but not this one... (keeping the presents caused by filtered actions out of the past)
expected = {
...expected,
present: notFilteredReducer(expected, excludedAction).present
}
actual = mockUndoableReducer(actual, excludedAction)
expect(actual).to.deep.equal(expected)
}

if (testConfig && testConfig.FOR_TEST_ONLY_includeActions) {
// only record this action in history
let tmpState = mockUndoableReducer(mockInitialState, { type: testConfig.FOR_TEST_ONLY_includeActions[0] })
let expected = { ...tmpState, present: tmpState.present + 1 }
// and not this one...
tmpState = mockUndoableReducer(tmpState, { type: 'INCREMENT' })
expect(tmpState).to.deep.equal(expected)
// should record this action's state in history
const includedAction = { type: testConfig.FOR_TEST_ONLY_includeActions[0] }
const excludedAction = { type: 'INCREMENT' }
const commonInitialState = mockUndoableReducer(mockInitialState, includedAction)

const notFilteredReducer = undoable(countReducer, { ...testConfig, filter: null })
let expected = notFilteredReducer(commonInitialState, excludedAction)
expected = {
...expected,
// because increment action is filtered, this state should be indicated as filtered
wasFiltered: true
}
// and this one, (to store the previous present caused by a not filtered action into the past)
let actual = mockUndoableReducer(commonInitialState, excludedAction)
expect(actual).to.deep.equal(expected)
// but not this one... (keeping the presents caused by filtered actions out of the past)
expected = {
...expected,
present: notFilteredReducer(expected, excludedAction).present
}
actual = mockUndoableReducer(actual, excludedAction)
expect(actual).to.deep.equal(expected)
}
})

Expand Down Expand Up @@ -246,6 +275,23 @@ function runTestWithConfig (testConfig, initialStoreState, label) {
expect(undoInitialState.present).to.deep.equal(mockInitialState.present)
}
})

it('should undo to last not filtered state', () => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice 👍

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah great we have some tests for this.

if (testConfig && testConfig.FOR_TEST_ONLY_excludedActions) {
const excludedAction = { type: testConfig.FOR_TEST_ONLY_excludedActions[0] }
const includedAction = { type: 'INCREMENT' }
// handle excluded action on a not filtered initial state
let state = mockUndoableReducer(mockInitialState, excludedAction)
// handle excluded action 2
state = mockUndoableReducer(state, excludedAction)
// handle not excluded action
const preUndoState = mockUndoableReducer(state, includedAction)
// undo
state = mockUndoableReducer(preUndoState, ActionCreators.undo())
// should undo to (not filtered) initial present
expect(state.present).to.deep.equal(preUndoState.past[preUndoState.past.length - 1])
}
})
})

describe('Redo', () => {
Expand All @@ -257,7 +303,11 @@ function runTestWithConfig (testConfig, initialStoreState, label) {
})

it('should change present state to equal state before undo', () => {
expect(redoState.present).to.equal(incrementedState.present)
// skip this test if steps are filtered out,
// because the action might have been was filtered it won't redo to it's state
if (testConfig && !testConfig.FOR_TEST_ONLY_includeActions) {
expect(redoState.present).to.equal(incrementedState.present)
}
})

it('should change present state to first element of \'future\'', () => {
Expand Down Expand Up @@ -290,6 +340,20 @@ function runTestWithConfig (testConfig, initialStoreState, label) {
expect(secondRedoState.present).to.deep.equal(redoState.present)
}
})

it('should not redo to filtered state', () => {
if (testConfig && testConfig.FOR_TEST_ONLY_excludedActions) {
const excludedAction = { type: testConfig.FOR_TEST_ONLY_excludedActions[0] }
// handle excluded action on a not filtered initial state
let state = mockUndoableReducer(mockInitialState, excludedAction)
// undo
let postRedoState = mockUndoableReducer(state, ActionCreators.undo())
// redo
state = mockUndoableReducer(postRedoState, ActionCreators.redo())
// redo should be ignored, because future state wasn't stored
expect(state).to.deep.equal(postRedoState)
}
})
})

describe('JumpToPast', () => {
Expand All @@ -312,8 +376,12 @@ function runTestWithConfig (testConfig, initialStoreState, label) {
})

it('should increase the length of future if successful', () => {
if (incrementedState.past.length > jumpToPastIndex) {
expect(jumpToPastState.future.length).to.be.above(incrementedState.future.length)
// skip this test if steps are filtered out,
// because the action might have been was filtered it won't be added to the future
if (testConfig && !testConfig.FOR_TEST_ONLY_includeActions) {
if (incrementedState.past.length > jumpToPastIndex) {
expect(jumpToPastState.future.length).to.be.above(incrementedState.future.length)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check. Makes sense

}
}
})

Expand Down Expand Up @@ -382,7 +450,11 @@ function runTestWithConfig (testConfig, initialStoreState, label) {
})

it('-2 steps should result in same state as two times undo', () => {
expect(doubleUndoState).to.deep.equal(jumpToPastState)
// skip this test if steps are filtered out,
// because the double undo would be out of bounds and thus ignored
if (testConfig && !testConfig.FOR_TEST_ONLY_includeActions) {
expect(doubleUndoState).to.deep.equal(jumpToPastState)
}
})

it('+2 steps should result in same state as two times redo', () => {
Expand Down