@@ -3,7 +3,18 @@ import React, { useState } from 'react'
3
3
import useSWR , { cache } from '../src'
4
4
import { sleep } from './utils'
5
5
6
+ // This has to be an async function to wait a microtask to flush updates
7
+ const advanceTimers = async ( ms : number ) => jest . advanceTimersByTime ( ms )
8
+
9
+ // This test heavily depends on setInterval/setTimeout timers, which makes tests slower and flaky.
10
+ // So we use Jest's fake timers
6
11
describe ( 'useSWR - refresh' , ( ) => {
12
+ beforeEach ( ( ) => {
13
+ jest . useFakeTimers ( )
14
+ } )
15
+ afterEach ( ( ) => {
16
+ jest . useRealTimers ( )
17
+ } )
7
18
it ( 'should rerender automatically on interval' , async ( ) => {
8
19
let count = 0
9
20
@@ -14,20 +25,21 @@ describe('useSWR - refresh', () => {
14
25
} )
15
26
return < div > count: { data } </ div >
16
27
}
17
- const { container } = render ( < Page /> )
28
+
29
+ render ( < Page /> )
18
30
19
31
// hydration
20
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot ( `" count: "` )
32
+ screen . getByText ( ' count:' )
21
33
22
34
// mount
23
35
await screen . findByText ( 'count: 0' )
24
36
25
- await act ( ( ) => sleep ( 210 ) ) // update
26
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot ( `" count: 1"` )
27
- await act ( ( ) => sleep ( 50 ) ) // no update
28
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot ( `" count: 1"` )
29
- await act ( ( ) => sleep ( 150 ) ) // update
30
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot ( `" count: 2"` )
37
+ await act ( ( ) => advanceTimers ( 200 ) ) // update
38
+ screen . getByText ( ' count: 1' )
39
+ await act ( ( ) => advanceTimers ( 50 ) ) // no update
40
+ screen . getByText ( ' count: 1' )
41
+ await act ( ( ) => advanceTimers ( 150 ) ) // update
42
+ screen . getByText ( ' count: 2' )
31
43
} )
32
44
33
45
it ( 'should dedupe requests combined with intervals' , async ( ) => {
@@ -36,26 +48,29 @@ describe('useSWR - refresh', () => {
36
48
function Page ( ) {
37
49
const { data } = useSWR ( 'dynamic-2' , ( ) => count ++ , {
38
50
refreshInterval : 100 ,
39
- dedupingInterval : 150
51
+ dedupingInterval : 500
40
52
} )
41
53
return < div > count: { data } </ div >
42
54
}
43
- const { container } = render ( < Page /> )
55
+
56
+ render ( < Page /> )
44
57
45
58
// hydration
46
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot ( `" count: "` )
59
+ screen . getByText ( ' count:' )
47
60
48
61
// mount
49
62
await screen . findByText ( 'count: 0' )
50
63
51
- await act ( ( ) => sleep ( 110 ) ) // no update (deduped)
52
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot ( `"count: 0"` )
53
- await act ( ( ) => sleep ( 100 ) ) // update
54
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot ( `"count: 1"` )
55
- await act ( ( ) => sleep ( 100 ) ) // no update (deduped)
56
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot ( `"count: 1"` )
57
- await act ( ( ) => sleep ( 100 ) ) // update
58
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot ( `"count: 2"` )
64
+ await act ( ( ) => advanceTimers ( 100 ) ) // no update (deduped)
65
+ screen . getByText ( 'count: 0' )
66
+ await act ( ( ) => advanceTimers ( 400 ) ) // reach dudupingInterval
67
+ await act ( ( ) => advanceTimers ( 100 ) ) // update
68
+ screen . getByText ( 'count: 1' )
69
+ await act ( ( ) => advanceTimers ( 100 ) ) // no update (deduped)
70
+ screen . getByText ( 'count: 1' )
71
+ await act ( ( ) => advanceTimers ( 400 ) ) // reach dudupingInterval
72
+ await act ( ( ) => advanceTimers ( 100 ) ) // update
73
+ screen . getByText ( 'count: 2' )
59
74
} )
60
75
61
76
it ( 'should update data upon interval changes' , async ( ) => {
@@ -72,46 +87,47 @@ describe('useSWR - refresh', () => {
72
87
</ div >
73
88
)
74
89
}
75
- const { container } = render ( < Page /> )
76
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot ( `"count: "` )
90
+
91
+ render ( < Page /> )
92
+ screen . getByText ( 'count:' )
77
93
78
94
// mount
79
95
await screen . findByText ( 'count: 0' )
80
96
81
- await act ( ( ) => sleep ( 110 ) )
82
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot ( `" count: 1"` )
83
- await act ( ( ) => sleep ( 25 ) )
84
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot ( `" count: 1"` )
85
- await act ( ( ) => sleep ( 75 ) )
86
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot ( `" count: 2"` )
87
- fireEvent . click ( container . firstElementChild )
97
+ await act ( ( ) => advanceTimers ( 100 ) )
98
+ screen . getByText ( ' count: 1' )
99
+ await act ( ( ) => advanceTimers ( 50 ) )
100
+ screen . getByText ( ' count: 1' )
101
+ await act ( ( ) => advanceTimers ( 50 ) )
102
+ screen . getByText ( ' count: 2' )
103
+ fireEvent . click ( screen . getByText ( 'count: 2' ) )
88
104
89
- await act ( ( ) => sleep ( 100 ) )
105
+ await act ( ( ) => advanceTimers ( 100 ) )
90
106
91
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot ( `" count: 2"` )
107
+ screen . getByText ( ' count: 2' )
92
108
93
- await act ( ( ) => sleep ( 60 ) )
109
+ await act ( ( ) => advanceTimers ( 50 ) )
94
110
95
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot ( `" count: 3"` )
111
+ screen . getByText ( ' count: 3' )
96
112
97
- await act ( ( ) => sleep ( 160 ) )
98
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot ( `" count: 4"` )
99
- fireEvent . click ( container . firstElementChild )
113
+ await act ( ( ) => advanceTimers ( 150 ) )
114
+ screen . getByText ( ' count: 4' )
115
+ fireEvent . click ( screen . getByText ( 'count: 4' ) )
100
116
await act ( ( ) => {
101
117
// it will clear 150ms timer and setup a new 200ms timer
102
- return sleep ( 150 )
118
+ return advanceTimers ( 150 )
103
119
} )
104
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot ( `" count: 4"` )
105
- await act ( ( ) => sleep ( 60 ) )
106
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot ( `" count: 5"` )
107
- fireEvent . click ( container . firstElementChild )
120
+ screen . getByText ( ' count: 4' )
121
+ await act ( ( ) => advanceTimers ( 50 ) )
122
+ screen . getByText ( ' count: 5' )
123
+ fireEvent . click ( screen . getByText ( 'count: 5' ) )
108
124
await act ( ( ) => {
109
125
// it will clear 200ms timer and stop
110
- return sleep ( 60 )
126
+ return advanceTimers ( 50 )
111
127
} )
112
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot ( `" count: 5"` )
113
- await act ( ( ) => sleep ( 60 ) )
114
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot ( `" count: 5"` )
128
+ screen . getByText ( ' count: 5' )
129
+ await act ( ( ) => advanceTimers ( 50 ) )
130
+ screen . getByText ( ' count: 5' )
115
131
} )
116
132
117
133
it ( 'should update data upon interval changes -- changes happened during revalidate' , async ( ) => {
@@ -137,68 +153,49 @@ describe('useSWR - refresh', () => {
137
153
</ div >
138
154
)
139
155
}
140
- const { container } = render ( < Page /> )
141
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot (
142
- `"count: 0"`
143
- )
156
+
157
+ render ( < Page /> )
158
+ screen . getByText ( 'count: 0' )
144
159
145
160
await screen . findByText ( 'count: 0 1' )
146
161
147
- await act ( ( ) => sleep ( 100 ) )
162
+ await act ( ( ) => advanceTimers ( 100 ) )
148
163
149
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot (
150
- `"count: 1 2"`
151
- )
164
+ screen . getByText ( 'count: 1 2' )
152
165
153
- await act ( ( ) => sleep ( 100 ) )
154
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot (
155
- `"count: 1 2"`
156
- )
166
+ await act ( ( ) => advanceTimers ( 100 ) )
167
+ screen . getByText ( 'count: 1 2' )
157
168
158
- await act ( ( ) => sleep ( 100 ) )
159
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot (
160
- `"count: 1 2"`
161
- )
169
+ await act ( ( ) => advanceTimers ( 100 ) )
170
+ screen . getByText ( 'count: 1 2' )
162
171
163
- await act ( ( ) => sleep ( 100 ) )
164
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot (
165
- `"count: 1 2"`
166
- )
172
+ await act ( ( ) => advanceTimers ( 100 ) )
173
+ screen . getByText ( 'count: 1 2' )
167
174
168
- fireEvent . click ( container . firstElementChild )
175
+ fireEvent . click ( screen . getByText ( 'count: 1 2' ) )
169
176
170
177
await act ( ( ) => {
171
178
// it will setup a new 100ms timer
172
- return sleep ( 50 )
179
+ return advanceTimers ( 50 )
173
180
} )
174
181
175
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot (
176
- `"count: 1 0"`
177
- )
182
+ screen . getByText ( 'count: 1 0' )
178
183
179
- await act ( ( ) => sleep ( 50 ) )
184
+ await act ( ( ) => advanceTimers ( 50 ) )
180
185
181
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot (
182
- `"count: 2 1"`
183
- )
186
+ screen . getByText ( 'count: 2 1' )
184
187
185
- await act ( ( ) => sleep ( 100 ) )
188
+ await act ( ( ) => advanceTimers ( 100 ) )
186
189
187
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot (
188
- `"count: 3 2"`
189
- )
190
+ screen . getByText ( 'count: 3 2' )
190
191
191
- await act ( ( ) => sleep ( 100 ) )
192
+ await act ( ( ) => advanceTimers ( 100 ) )
192
193
193
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot (
194
- `"count: 3 2"`
195
- )
194
+ screen . getByText ( 'count: 3 2' )
196
195
197
- await act ( ( ) => sleep ( 100 ) )
196
+ await act ( ( ) => advanceTimers ( 100 ) )
198
197
199
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot (
200
- `"count: 3 2"`
201
- )
198
+ screen . getByText ( 'count: 3 2' )
202
199
} )
203
200
204
201
it ( 'should allow use custom compare method' , async ( ) => {
@@ -227,9 +224,9 @@ describe('useSWR - refresh', () => {
227
224
return < button onClick = { ( ) => change ( ) } > { data . timestamp } </ button >
228
225
}
229
226
230
- const { container } = render ( < Page /> )
227
+ render ( < Page /> )
231
228
232
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot ( `" loading"` )
229
+ screen . getByText ( ' loading' )
233
230
234
231
await screen . findByText ( '1' )
235
232
expect ( fetcher ) . toBeCalledTimes ( 1 )
@@ -238,8 +235,8 @@ describe('useSWR - refresh', () => {
238
235
version : '1.0'
239
236
} )
240
237
241
- fireEvent . click ( container . firstElementChild )
242
- await act ( ( ) => sleep ( 1 ) )
238
+ fireEvent . click ( screen . getByText ( '1' ) )
239
+ await act ( ( ) => advanceTimers ( 1 ) )
243
240
expect ( fetcher ) . toBeCalledTimes ( 2 )
244
241
expect ( fetcher ) . toReturnWith ( {
245
242
timestamp : 2 ,
@@ -248,7 +245,7 @@ describe('useSWR - refresh', () => {
248
245
249
246
const cachedData = cache . get ( key )
250
247
expect ( cachedData . timestamp . toString ( ) ) . toEqual ( '1' )
251
- expect ( container . firstChild . textContent ) . toMatchInlineSnapshot ( `"1"` )
248
+ screen . getByText ( '1' )
252
249
} )
253
250
254
251
it ( 'should not let the previous interval timer to set new timer if key changes too fast' , async ( ) => {
@@ -268,33 +265,35 @@ describe('useSWR - refresh', () => {
268
265
> { `click me ${ data } ` } </ button >
269
266
)
270
267
}
271
- const { container } = render ( < Page /> )
268
+
269
+ render ( < Page /> )
272
270
273
271
// initial revalidate
274
- await act ( ( ) => sleep ( 200 ) )
272
+ await act ( ( ) => advanceTimers ( 200 ) )
275
273
expect ( fetcherWithToken ) . toBeCalledTimes ( 1 )
276
274
277
275
// first refresh
278
- await act ( ( ) => sleep ( 100 ) )
276
+ await act ( ( ) => advanceTimers ( 100 ) )
279
277
expect ( fetcherWithToken ) . toBeCalledTimes ( 2 )
280
278
expect ( fetcherWithToken ) . toHaveBeenLastCalledWith ( '0' )
281
- await act ( ( ) => sleep ( 200 ) )
279
+ await act ( ( ) => advanceTimers ( 200 ) )
282
280
283
281
// second refresh start
284
- await act ( ( ) => sleep ( 100 ) )
282
+ await act ( ( ) => advanceTimers ( 100 ) )
285
283
expect ( fetcherWithToken ) . toBeCalledTimes ( 3 )
286
284
expect ( fetcherWithToken ) . toHaveBeenLastCalledWith ( '0' )
287
285
// change the key during revalidation
288
286
// The second refresh will not start a new timer
289
- fireEvent . click ( container . firstElementChild )
287
+ fireEvent . click ( screen . getByText ( 'click me 0' ) )
290
288
291
289
// first refresh with new key 1
292
- await act ( ( ) => sleep ( 100 ) )
290
+ await act ( ( ) => advanceTimers ( 100 ) )
293
291
expect ( fetcherWithToken ) . toBeCalledTimes ( 4 )
294
292
expect ( fetcherWithToken ) . toHaveBeenLastCalledWith ( '1' )
295
- await act ( ( ) => sleep ( 210 ) )
293
+ await act ( ( ) => advanceTimers ( 200 ) )
296
294
297
295
// second refresh with new key 1
296
+ await act ( ( ) => advanceTimers ( 100 ) )
298
297
expect ( fetcherWithToken ) . toBeCalledTimes ( 5 )
299
298
expect ( fetcherWithToken ) . toHaveBeenLastCalledWith ( '1' )
300
299
} )
@@ -320,38 +319,40 @@ describe('useSWR - refresh', () => {
320
319
> { `click me ${ data } ` } </ button >
321
320
)
322
321
}
323
- const { container } = render ( < Page /> )
322
+
323
+ render ( < Page /> )
324
324
325
325
// initial revalidate
326
- await act ( ( ) => sleep ( 100 ) )
326
+ await act ( ( ) => advanceTimers ( 100 ) )
327
327
expect ( fetcherWithToken ) . toBeCalledTimes ( 1 )
328
328
expect ( onSuccess ) . toBeCalledTimes ( 1 )
329
329
expect ( onSuccess ) . toHaveLastReturnedWith ( `0-hash 0-hash` )
330
330
// first refresh
331
- await act ( ( ) => sleep ( 50 ) )
331
+ await act ( ( ) => advanceTimers ( 50 ) )
332
332
expect ( fetcherWithToken ) . toBeCalledTimes ( 2 )
333
333
expect ( fetcherWithToken ) . toHaveBeenLastCalledWith ( '0-hash' )
334
- await act ( ( ) => sleep ( 100 ) )
334
+ await act ( ( ) => advanceTimers ( 100 ) )
335
335
expect ( onSuccess ) . toBeCalledTimes ( 2 )
336
336
expect ( onSuccess ) . toHaveLastReturnedWith ( `0-hash 0-hash` )
337
337
338
338
// second refresh start
339
- await act ( ( ) => sleep ( 50 ) )
339
+ await act ( ( ) => advanceTimers ( 50 ) )
340
340
expect ( fetcherWithToken ) . toBeCalledTimes ( 3 )
341
341
expect ( fetcherWithToken ) . toHaveBeenLastCalledWith ( '0-hash' )
342
342
// change the key during revalidation
343
343
// The second refresh will not start a new timer
344
- fireEvent . click ( container . firstElementChild )
344
+ fireEvent . click ( screen . getByText ( 'click me 0-hash' ) )
345
345
346
346
// first refresh with new key 1
347
- await act ( ( ) => sleep ( 50 ) )
347
+ await act ( ( ) => advanceTimers ( 50 ) )
348
348
expect ( fetcherWithToken ) . toBeCalledTimes ( 4 )
349
349
expect ( fetcherWithToken ) . toHaveBeenLastCalledWith ( '1-hash' )
350
- await act ( ( ) => sleep ( 110 ) )
350
+ await act ( ( ) => advanceTimers ( 100 ) )
351
351
expect ( onSuccess ) . toBeCalledTimes ( 3 )
352
352
expect ( onSuccess ) . toHaveLastReturnedWith ( `1-hash 1-hash` )
353
353
354
354
// second refresh with new key 1
355
+ await act ( ( ) => advanceTimers ( 50 ) )
355
356
expect ( fetcherWithToken ) . toBeCalledTimes ( 5 )
356
357
expect ( fetcherWithToken ) . toHaveBeenLastCalledWith ( '1-hash' )
357
358
} )
0 commit comments