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