-
-
Notifications
You must be signed in to change notification settings - Fork 186
Description
This might be a duplicate of #268, but I think this weighs in favor of treating #268 as a real bug (instead of as-designed) and actually fixing it. The short version is as follows. I created a slice with createSlice from reduxjs/redux-toolkit with an incrementNextId action:
export interface SliceState {
next_id: number;
}
const INITIAL_STATE: SliceState = {
next_id: 1,
}
export const slice = createSlice({
name: "slice",
initialState: INITIAL_STATE,
reducers: {
incrementNextId: { /* ... */ }
},
})
export const {
incrementNextId,
} = slice.actions
export const slice_reducer = slice.reducer
export const undoable_slice_reducer = undoable(slice_reducer)Here's the rub. Using the plain reducer works just like it should according to Redux's instructions on how to test reducers:
describe("plain increment", () => {
// this passes
it("should increment next_id", () => {
var state = slice.slice_reducer(undefined, slice.incrementNextId({}))
expect(state.next_id).toEqual(2)
})
})Using the wrapped reducer does not with a nearly identical test (the only difference is which reducer is used and how we inspect state):
describe("undoable increment", () => {
// this fails
it("should increment next_id", () => {
var state = slice.undoable_slice_reducer(undefined, slice.incrementNextId({}))
expect(state.present.next_id).toEqual(2) // nope, the value is actually 1
})
})If I modify the second test as follows, it passes, which doesn't seem right:
describe("undoable increment", () => {
// this works, but it shouldn't
it("should increment next_id", () => {
var state = slice.undoable_slice_reducer(undefined, slice.incrementNextId({})) // ignored?
state = slice.undoable_slice_reducer(state, slice.incrementNextId({})) // this time, with feeling!
expect(state.present.next_id).toEqual(2) // works now?
})
})Here's where things get totally off the rails. This fails both tests:
describe("undoable increment", () => {
// this fails
it("should increment next_id", () => {
var state = slice.undoable_slice_reducer(undefined, slice.incrementNextId({}))
expect(state.present.next_id).toEqual(2) // nope, the value is actually 1
})
// this fails, too
it("should also increment next_id", () => {
var state = slice.undoable_slice_reducer(undefined, slice.incrementNextId({}))
expect(state.present.next_id).toEqual(2) // nope, the value is actually 1
})
})If I make the above modification to the first test, both tests now pass:
describe("undoable increment", () => {
// this works, but it shouldn't
it("should increment next_id", () => {
var state = slice.undoable_slice_reducer(undefined, slice.incrementNextId({})) // ignored?
state = slice.undoable_slice_reducer(state, slice.incrementNextId({})) // this time, with feeling!
expect(state.present.next_id).toEqual(2) // works now?
})
// this now…works?
it("should also increment next_id", () => {
var state = slice.undoable_slice_reducer(undefined, slice.incrementNextId({}))
expect(state.present.next_id).toEqual(2) // now this passes; WTF?!
})
})If I move that approach to the second test, only it passes:
describe("undoable increment", () => {
// this fails
it("should increment next_id", () => {
var state = slice.undoable_slice_reducer(undefined, slice.incrementNextId({}))
expect(state.present.next_id).toEqual(2) // nope, the value is actually 1
})
// this now…works?
it("should also increment next_id", () => {
var state = slice.undoable_slice_reducer(undefined, slice.incrementNextId({})) // ignored?
state = slice.undoable_slice_reducer(state, slice.incrementNextId({})) // this time, with feeling!
expect(state.present.next_id).toEqual(2) // works now?
})
})Complete source code is available via posita/redux-toolkit-undo-clash.