Skip to content

Commit 95d3f33

Browse files
test(middleware/immer): add runtime tests for immer middleware (#3471)
* test(middleware/immer): add runtime tests for immer middleware * test(middleware/immer): rename non-function-updater test to describe produce bypass The previous name ("should merge state when given a non-function updater") implied merge semantics, which is vanilla setState's behavior. What the test actually exercises is immer.ts:79, where a non-function updater bypasses `produce`. Renaming to "should not apply produce when updater is not a function" per @sukvvon's review feedback.
1 parent 3201328 commit 95d3f33

1 file changed

Lines changed: 74 additions & 0 deletions

File tree

tests/immer.test.tsx

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { create } from 'zustand'
3+
import { immer } from 'zustand/middleware/immer'
4+
import { createStore } from 'zustand/vanilla'
5+
6+
describe('immer middleware', () => {
7+
it('should mutate state via an immer draft (function updater)', () => {
8+
type State = { count: number; inc: () => void }
9+
const useBoundStore = create<State>()(
10+
immer((set) => ({
11+
count: 0,
12+
inc: () =>
13+
set((state) => {
14+
state.count += 1
15+
}),
16+
})),
17+
)
18+
19+
useBoundStore.getState().inc()
20+
expect(useBoundStore.getState().count).toBe(1)
21+
})
22+
23+
it('should not apply produce when updater is not a function', () => {
24+
type State = { count: number; name: string }
25+
const useBoundStore = create<State>()(
26+
immer(() => ({ count: 0, name: 'zustand' })),
27+
)
28+
29+
useBoundStore.setState({ count: 5 })
30+
expect(useBoundStore.getState()).toEqual({ count: 5, name: 'zustand' })
31+
})
32+
33+
it('should replace the entire state when replace flag is true', () => {
34+
type State = { a: number; b?: number }
35+
const useBoundStore = create<State>()(immer(() => ({ a: 1, b: 2 })))
36+
37+
useBoundStore.setState({ a: 9 }, true)
38+
expect(useBoundStore.getState()).toEqual({ a: 9 })
39+
})
40+
41+
it('should produce nested state updates via a draft without mutating the previous state', () => {
42+
type State = {
43+
user: { name: string; tags: string[] }
44+
addTag: (tag: string) => void
45+
}
46+
const useBoundStore = create<State>()(
47+
immer((set) => ({
48+
user: { name: 'bear', tags: ['a'] },
49+
addTag: (tag) =>
50+
set((state) => {
51+
state.user.tags.push(tag)
52+
}),
53+
})),
54+
)
55+
56+
const previousUser = useBoundStore.getState().user
57+
useBoundStore.getState().addTag('b')
58+
59+
expect(useBoundStore.getState().user.tags).toEqual(['a', 'b'])
60+
expect(useBoundStore.getState().user).not.toBe(previousUser)
61+
expect(previousUser.tags).toEqual(['a'])
62+
})
63+
64+
it('should work with vanilla createStore', () => {
65+
type State = { count: number }
66+
const store = createStore<State>()(immer(() => ({ count: 0 })))
67+
68+
store.setState((state) => {
69+
state.count = 10
70+
})
71+
72+
expect(store.getState().count).toBe(10)
73+
})
74+
})

0 commit comments

Comments
 (0)