Skip to content

Commit 467b3b2

Browse files
broBinChenxiaobin
andauthored
test: add unit tests for usePrevious hook (#278)
Co-authored-by: xiaobin <xiaobin_chen@fzzixun.com>
1 parent a1970e2 commit 467b3b2

File tree

1 file changed

+270
-0
lines changed

1 file changed

+270
-0
lines changed
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
import { ref, computed, nextTick } from 'vue'
2+
import usePrevious from '../index'
3+
4+
describe('usePrevious', () => {
5+
describe('Basic functionality', () => {
6+
it('should be defined', () => {
7+
expect(usePrevious).toBeDefined()
8+
})
9+
10+
it('should return undefined as initial previous value', () => {
11+
const state = ref(0)
12+
const previousValue = usePrevious(state)
13+
expect(previousValue.value).toBeUndefined()
14+
})
15+
16+
it('should return readonly ref', () => {
17+
const state = ref(0)
18+
const previousValue = usePrevious(state)
19+
expect(previousValue.value).toBeUndefined()
20+
21+
// Should not be able to modify the returned value directly
22+
// readonly refs in Vue log warnings but don't throw errors
23+
expect(() => {
24+
// @ts-expect-error - testing readonly behavior
25+
previousValue.value = 1
26+
}).not.toThrow()
27+
28+
// But the value should remain unchanged
29+
expect(previousValue.value).toBeUndefined()
30+
})
31+
})
32+
33+
describe('State tracking', () => {
34+
it('should track previous value when state changes', async () => {
35+
const state = ref(0)
36+
const previousValue = usePrevious(state)
37+
38+
// Initial previous value should be undefined
39+
expect(previousValue.value).toBeUndefined()
40+
41+
// Update state
42+
state.value = 1
43+
await nextTick()
44+
45+
// Previous value should now be 0
46+
expect(previousValue.value).toBe(0)
47+
48+
// Update state again
49+
state.value = 2
50+
await nextTick()
51+
52+
// Previous value should now be 1
53+
expect(previousValue.value).toBe(1)
54+
})
55+
56+
it('should work with string values', async () => {
57+
const state = ref('hello')
58+
const previousValue = usePrevious(state)
59+
60+
expect(previousValue.value).toBeUndefined()
61+
62+
state.value = 'world'
63+
await nextTick()
64+
65+
expect(previousValue.value).toBe('hello')
66+
67+
state.value = 'vue'
68+
await nextTick()
69+
70+
expect(previousValue.value).toBe('world')
71+
})
72+
73+
it('should work with object values', async () => {
74+
const state = ref({ a: 1 })
75+
const previousValue = usePrevious(state)
76+
77+
expect(previousValue.value).toBeUndefined()
78+
79+
const newObj = { a: 2 }
80+
state.value = newObj
81+
await nextTick()
82+
83+
expect(previousValue.value).toEqual({ a: 1 })
84+
85+
state.value = { a: 3 }
86+
await nextTick()
87+
88+
expect(previousValue.value).toStrictEqual(newObj)
89+
})
90+
91+
it('should work with computed ref', async () => {
92+
const baseValue = ref(10)
93+
const computedValue = computed(() => baseValue.value * 2)
94+
const previousValue = usePrevious(computedValue)
95+
96+
expect(previousValue.value).toBeUndefined()
97+
98+
baseValue.value = 20
99+
await nextTick()
100+
101+
expect(previousValue.value).toBe(20) // previous computed value (10 * 2)
102+
103+
baseValue.value = 30
104+
await nextTick()
105+
106+
expect(previousValue.value).toBe(40) // previous computed value (20 * 2)
107+
})
108+
})
109+
110+
describe('Custom shouldUpdate function', () => {
111+
it('should use custom shouldUpdate function', async () => {
112+
const state = ref(0)
113+
const shouldUpdate = vi.fn((prev, next) => {
114+
if (prev === undefined) return true
115+
return Math.abs(prev - next) > 1
116+
})
117+
const previousValue = usePrevious(state, shouldUpdate)
118+
119+
expect(previousValue.value).toBeUndefined()
120+
121+
// First change should always update (from undefined)
122+
state.value = 1
123+
await nextTick()
124+
125+
expect(previousValue.value).toBeUndefined()
126+
expect(shouldUpdate).toHaveBeenCalled()
127+
128+
// Change by 1, should not update due to custom logic
129+
state.value = 2
130+
await nextTick()
131+
132+
expect(previousValue.value).toBe(0)
133+
134+
// Change by 2, should update
135+
state.value = 4
136+
await nextTick()
137+
138+
expect(previousValue.value).toBe(2)
139+
expect(shouldUpdate).toHaveBeenCalledTimes(4) // Initial + 3 updates
140+
})
141+
142+
it('should handle undefined values in custom shouldUpdate', async () => {
143+
const state = ref<number | undefined>(undefined)
144+
const shouldUpdate = vi.fn((prev, next) => prev !== next)
145+
const previousValue = usePrevious(state, shouldUpdate)
146+
147+
expect(previousValue.value).toBeUndefined()
148+
149+
state.value = 1
150+
await nextTick()
151+
152+
expect(previousValue.value).toBeUndefined()
153+
expect(shouldUpdate).toHaveBeenCalledWith(undefined, 1)
154+
155+
state.value = undefined
156+
await nextTick()
157+
158+
expect(previousValue.value).toBe(1)
159+
expect(shouldUpdate).toHaveBeenCalledWith(1, undefined)
160+
})
161+
162+
it('should not update when shouldUpdate returns false', async () => {
163+
const state = ref(0)
164+
const shouldUpdate = vi.fn(() => false)
165+
const previousValue = usePrevious(state, shouldUpdate)
166+
167+
expect(previousValue.value).toBeUndefined()
168+
169+
state.value = 1
170+
await nextTick()
171+
172+
expect(previousValue.value).toBeUndefined()
173+
expect(shouldUpdate).toHaveBeenCalledWith(undefined, 1)
174+
175+
state.value = 2
176+
await nextTick()
177+
178+
expect(previousValue.value).toBeUndefined()
179+
expect(shouldUpdate).toHaveBeenCalledWith(undefined, 2)
180+
})
181+
})
182+
183+
describe('Default shouldUpdate behavior', () => {
184+
it('should use Object.is for comparison by default', async () => {
185+
const state = ref(0)
186+
const previousValue = usePrevious(state)
187+
188+
// Same value should not update
189+
state.value = 0
190+
await nextTick()
191+
192+
expect(previousValue.value).toBeUndefined()
193+
194+
// Different value should update
195+
state.value = 1
196+
await nextTick()
197+
198+
expect(previousValue.value).toBe(0)
199+
})
200+
201+
it('should handle NaN values correctly', async () => {
202+
const state = ref(NaN)
203+
const previousValue = usePrevious(state)
204+
205+
expect(previousValue.value).toBeUndefined()
206+
207+
// NaN should equal NaN with Object.is
208+
state.value = NaN
209+
await nextTick()
210+
211+
expect(previousValue.value).toBeUndefined()
212+
213+
// Change to a different value
214+
state.value = 1
215+
await nextTick()
216+
217+
expect(previousValue.value).toBeNaN()
218+
})
219+
})
220+
221+
describe('Edge cases', () => {
222+
it('should handle rapid state changes', async () => {
223+
const state = ref(0)
224+
const previousValue = usePrevious(state)
225+
226+
// Rapid changes
227+
state.value = 1
228+
state.value = 2
229+
state.value = 3
230+
await nextTick()
231+
232+
// Should track the previous value before the final change
233+
expect(previousValue.value).toBe(0)
234+
})
235+
236+
it('should handle boolean values', async () => {
237+
const state = ref(true)
238+
const previousValue = usePrevious(state)
239+
240+
expect(previousValue.value).toBeUndefined()
241+
242+
state.value = false
243+
await nextTick()
244+
245+
expect(previousValue.value).toBe(true)
246+
247+
state.value = true
248+
await nextTick()
249+
250+
expect(previousValue.value).toBe(false)
251+
})
252+
253+
it('should handle null and undefined values', async () => {
254+
const state = ref<string | null | undefined>(null)
255+
const previousValue = usePrevious(state)
256+
257+
expect(previousValue.value).toBeUndefined()
258+
259+
state.value = undefined
260+
await nextTick()
261+
262+
expect(previousValue.value).toBeNull()
263+
264+
state.value = 'hello'
265+
await nextTick()
266+
267+
expect(previousValue.value).toBeUndefined()
268+
})
269+
})
270+
})

0 commit comments

Comments
 (0)