undoables are not pure functions?
bjornicus opened this issue · comments
I was seeing some strange behavior in my app where the first time I dispatched an action which applied to my undoable the past array had a single empty array (was [[]] ) instead of containing what as previously the present state. Trying to simplify things down I came up with the following test case, which seems to indicate that undoable reducers aren't in fact pure functions
import undoable, { includeAction } from 'redux-undo';
function rawReducer(state = [], action) {
return [...state];
}
let undoableReducer = undoable(rawReducer, {
limit: 10,
filter: includeAction('ANY')
});
let initialState = {
past: [],
present: [1, 2],
future: [],
group: null,
_latestUnfiltered: [],
index: 0,
limit: 1
};
test('this test passes', () => {
expect(
undoableReducer(initialState, {
type: 'ANY',
playerId: 1,
currentTime: 0
}).past
).toEqual([initialState.present]);
});
test('this identical test does not (because it runs after the first one)', () => {
expect(
undoableReducer(initialState, {
type: 'ANY',
playerId: 1,
currentTime: 0
}).past
).toEqual([initialState.present]);
});
The first test passes, but the second one fails with:
Expected value to equal:
[[1, 2]]
Received:
[[]]
@bjornicus did you solve this? I've been working on a solution using pure functions here: https://github.com/gamb/fast-undo, any testing / feedback would be greatly appreciated :)
I don't actually remember if I worked around this or not, but thanks for the link; I'll definitely check it out next time I'm doing something redux undoable.
I think your problem first originated because present !== _latestUnfiltered
. You can fix it by setting them to the same object.
const present = [1,2]
let initialState = {
present,
_latestUnfiltered: present,
...
};
Long story short, undoable functions are not pure until after initialization:
undoableReducer(initialState || undefined, { type: '@@INIT' })
The reason the second test fails is that the empty list _latestUnfiltered
(not present
) is pushed into past
when an action is dispatched.
The first test only passes because the undoableReducer
has to initialize a starting state that is retained by closure. When that is done, a new history is created and _latestUnfiltered
is changed to present
. You can see this in action if you set the config option debug: true
.
For more consistent testing, I would initialize the reducer with a dummy state before running any tests. Looking at some of the tests might serve as an example.