1
- import { beforeAll , describe , expect , test , vi } from 'vitest'
1
+ import { afterEach , beforeEach , describe , expect , test , vi } from 'vitest'
2
2
import * as React from 'react'
3
3
import { render } from '@testing-library/react'
4
4
5
5
import * as coreModule from '@tanstack/query-core'
6
6
import {
7
7
HydrationBoundary ,
8
- QueryCache ,
9
8
QueryClient ,
10
9
QueryClientProvider ,
11
10
dehydrate ,
@@ -14,33 +13,32 @@ import {
14
13
import { createQueryClient , sleep } from './utils'
15
14
16
15
describe ( 'React hydration' , ( ) => {
17
- const fetchData : ( value : string ) => Promise < string > = ( value ) =>
18
- new Promise ( ( res ) => setTimeout ( ( ) => res ( value ) , 10 ) )
19
- const dataQuery : ( key : [ string ] ) => Promise < string > = ( key ) =>
20
- fetchData ( key [ 0 ] )
21
16
let stringifiedState : string
22
17
23
- beforeAll ( async ( ) => {
24
- const queryCache = new QueryCache ( )
25
- const queryClient = createQueryClient ( { queryCache } )
26
- await queryClient . prefetchQuery ( {
18
+ beforeEach ( async ( ) => {
19
+ vi . useFakeTimers ( )
20
+ const queryClient = createQueryClient ( )
21
+ queryClient . prefetchQuery ( {
27
22
queryKey : [ 'string' ] ,
28
- queryFn : ( ) => dataQuery ( [ 'stringCached' ] ) ,
23
+ queryFn : ( ) => sleep ( 10 ) . then ( ( ) => [ 'stringCached' ] ) ,
29
24
} )
25
+ await vi . advanceTimersByTimeAsync ( 10 )
30
26
const dehydrated = dehydrate ( queryClient )
31
27
stringifiedState = JSON . stringify ( dehydrated )
32
28
queryClient . clear ( )
33
29
} )
30
+ afterEach ( ( ) => {
31
+ vi . useRealTimers ( )
32
+ } )
34
33
35
34
test ( 'should hydrate queries to the cache on context' , async ( ) => {
36
35
const dehydratedState = JSON . parse ( stringifiedState )
37
- const queryCache = new QueryCache ( )
38
- const queryClient = createQueryClient ( { queryCache } )
36
+ const queryClient = createQueryClient ( )
39
37
40
38
function Page ( ) {
41
39
const { data } = useQuery ( {
42
40
queryKey : [ 'string' ] ,
43
- queryFn : ( ) => dataQuery ( [ 'string' ] ) ,
41
+ queryFn : ( ) => sleep ( 20 ) . then ( ( ) => [ 'string' ] ) ,
44
42
} )
45
43
return (
46
44
< div >
@@ -56,25 +54,23 @@ describe('React hydration', () => {
56
54
</ HydrationBoundary >
57
55
</ QueryClientProvider > ,
58
56
)
59
-
60
- await rendered . findByText ( 'stringCached' )
61
- await rendered . findByText ( 'string' )
57
+ await vi . advanceTimersByTimeAsync ( 1 )
58
+ expect ( rendered . getByText ( 'stringCached' ) ) . toBeInTheDocument ( )
59
+ await vi . advanceTimersByTimeAsync ( 20 )
60
+ expect ( rendered . getByText ( 'string' ) ) . toBeInTheDocument ( )
62
61
queryClient . clear ( )
63
62
} )
64
63
65
64
test ( 'should hydrate queries to the cache on custom context' , async ( ) => {
66
- const queryCacheOuter = new QueryCache ( )
67
- const queryCacheInner = new QueryCache ( )
68
-
69
- const queryClientInner = new QueryClient ( { queryCache : queryCacheInner } )
70
- const queryClientOuter = new QueryClient ( { queryCache : queryCacheOuter } )
65
+ const queryClientInner = new QueryClient ( )
66
+ const queryClientOuter = new QueryClient ( )
71
67
72
68
const dehydratedState = JSON . parse ( stringifiedState )
73
69
74
70
function Page ( ) {
75
71
const { data } = useQuery ( {
76
72
queryKey : [ 'string' ] ,
77
- queryFn : ( ) => dataQuery ( [ 'string' ] ) ,
73
+ queryFn : ( ) => sleep ( 20 ) . then ( ( ) => [ 'string' ] ) ,
78
74
} )
79
75
return (
80
76
< div >
@@ -93,8 +89,10 @@ describe('React hydration', () => {
93
89
</ QueryClientProvider > ,
94
90
)
95
91
96
- await rendered . findByText ( 'stringCached' )
97
- await rendered . findByText ( 'string' )
92
+ await vi . advanceTimersByTimeAsync ( 1 )
93
+ expect ( rendered . getByText ( 'stringCached' ) ) . toBeInTheDocument ( )
94
+ await vi . advanceTimersByTimeAsync ( 20 )
95
+ expect ( rendered . getByText ( 'string' ) ) . toBeInTheDocument ( )
98
96
99
97
queryClientInner . clear ( )
100
98
queryClientOuter . clear ( )
@@ -103,13 +101,12 @@ describe('React hydration', () => {
103
101
describe ( 'ReactQueryCacheProvider with hydration support' , ( ) => {
104
102
test ( 'should hydrate new queries if queries change' , async ( ) => {
105
103
const dehydratedState = JSON . parse ( stringifiedState )
106
- const queryCache = new QueryCache ( )
107
- const queryClient = createQueryClient ( { queryCache } )
104
+ const queryClient = createQueryClient ( )
108
105
109
106
function Page ( { queryKey } : { queryKey : [ string ] } ) {
110
107
const { data } = useQuery ( {
111
108
queryKey,
112
- queryFn : ( ) => dataQuery ( queryKey ) ,
109
+ queryFn : ( ) => sleep ( 20 ) . then ( ( ) => queryKey ) ,
113
110
} )
114
111
return (
115
112
< div >
@@ -126,20 +123,23 @@ describe('React hydration', () => {
126
123
</ QueryClientProvider > ,
127
124
)
128
125
129
- await rendered . findByText ( 'string' )
126
+ await vi . advanceTimersByTimeAsync ( 1 )
127
+ expect ( rendered . getByText ( 'stringCached' ) ) . toBeInTheDocument ( )
128
+ await vi . advanceTimersByTimeAsync ( 20 )
129
+ expect ( rendered . getByText ( 'string' ) ) . toBeInTheDocument ( )
130
130
131
- const intermediateCache = new QueryCache ( )
132
- const intermediateClient = createQueryClient ( {
133
- queryCache : intermediateCache ,
134
- } )
135
- await intermediateClient . prefetchQuery ( {
131
+ const intermediateClient = createQueryClient ( )
132
+
133
+ intermediateClient . prefetchQuery ( {
136
134
queryKey : [ 'string' ] ,
137
- queryFn : ( ) => dataQuery ( [ 'should change' ] ) ,
135
+ queryFn : ( ) => sleep ( 20 ) . then ( ( ) => [ 'should change' ] ) ,
138
136
} )
139
- await intermediateClient . prefetchQuery ( {
137
+ await vi . advanceTimersByTimeAsync ( 20 )
138
+ intermediateClient . prefetchQuery ( {
140
139
queryKey : [ 'added' ] ,
141
- queryFn : ( ) => dataQuery ( [ 'added' ] ) ,
140
+ queryFn : ( ) => sleep ( 20 ) . then ( ( ) => [ 'added' ] ) ,
142
141
} )
142
+ await vi . advanceTimersByTimeAsync ( 20 )
143
143
const dehydrated = dehydrate ( intermediateClient )
144
144
intermediateClient . clear ( )
145
145
@@ -154,14 +154,14 @@ describe('React hydration', () => {
154
154
155
155
// Existing observer should not have updated at this point,
156
156
// as that would indicate a side effect in the render phase
157
- rendered . getByText ( 'string' )
157
+ expect ( rendered . getByText ( 'string' ) ) . toBeInTheDocument ( )
158
158
// New query data should be available immediately
159
- rendered . getByText ( 'added' )
159
+ expect ( rendered . getByText ( 'added' ) ) . toBeInTheDocument ( )
160
160
161
- await sleep ( 10 )
161
+ await vi . advanceTimersByTimeAsync ( 20 )
162
162
// After effects phase has had time to run, the observer should have updated
163
163
expect ( rendered . queryByText ( 'string' ) ) . toBeNull ( )
164
- rendered . getByText ( 'should change' )
164
+ expect ( rendered . getByText ( 'should change' ) ) . toBeInTheDocument ( )
165
165
166
166
queryClient . clear ( )
167
167
} )
@@ -174,13 +174,12 @@ describe('React hydration', () => {
174
174
// since they don't have any observers on the current page that would update.
175
175
test ( 'should hydrate new but not existing queries if transition is aborted' , async ( ) => {
176
176
const initialDehydratedState = JSON . parse ( stringifiedState )
177
- const queryCache = new QueryCache ( )
178
- const queryClient = createQueryClient ( { queryCache } )
177
+ const queryClient = createQueryClient ( )
179
178
180
179
function Page ( { queryKey } : { queryKey : [ string ] } ) {
181
180
const { data } = useQuery ( {
182
181
queryKey,
183
- queryFn : ( ) => dataQuery ( queryKey ) ,
182
+ queryFn : ( ) => sleep ( 20 ) . then ( ( ) => queryKey ) ,
184
183
} )
185
184
return (
186
185
< div >
@@ -197,20 +196,23 @@ describe('React hydration', () => {
197
196
</ QueryClientProvider > ,
198
197
)
199
198
200
- await rendered . findByText ( 'string' )
199
+ await vi . advanceTimersByTimeAsync ( 1 )
200
+ expect ( rendered . getByText ( 'stringCached' ) ) . toBeInTheDocument ( )
201
+ await vi . advanceTimersByTimeAsync ( 20 )
202
+ expect ( rendered . getByText ( 'string' ) ) . toBeInTheDocument ( )
201
203
202
- const intermediateCache = new QueryCache ( )
203
- const intermediateClient = createQueryClient ( {
204
- queryCache : intermediateCache ,
205
- } )
206
- await intermediateClient . prefetchQuery ( {
204
+ const intermediateClient = createQueryClient ( )
205
+ intermediateClient . prefetchQuery ( {
207
206
queryKey : [ 'string' ] ,
208
- queryFn : ( ) => dataQuery ( [ 'should not change' ] ) ,
207
+ queryFn : ( ) => sleep ( 20 ) . then ( ( ) => [ 'should not change' ] ) ,
209
208
} )
210
- await intermediateClient . prefetchQuery ( {
209
+ await vi . advanceTimersByTimeAsync ( 20 )
210
+ intermediateClient . prefetchQuery ( {
211
211
queryKey : [ 'added' ] ,
212
- queryFn : ( ) => dataQuery ( [ 'added' ] ) ,
212
+ queryFn : ( ) => sleep ( 20 ) . then ( ( ) => [ 'added' ] ) ,
213
213
} )
214
+ await vi . advanceTimersByTimeAsync ( 20 )
215
+
214
216
const newDehydratedState = dehydrate ( intermediateClient )
215
217
intermediateClient . clear ( )
216
218
@@ -250,31 +252,30 @@ describe('React hydration', () => {
250
252
)
251
253
252
254
// This query existed before the transition so it should stay the same
253
- rendered . getByText ( 'string' )
255
+ expect ( rendered . getByText ( 'string' ) ) . toBeInTheDocument ( )
254
256
expect ( rendered . queryByText ( 'should not change' ) ) . toBeNull ( )
255
257
// New query data should be available immediately because it was
256
258
// hydrated in the previous transition, even though the new dehydrated
257
259
// state did not contain it
258
- rendered . getByText ( 'added' )
260
+ expect ( rendered . getByText ( 'added' ) ) . toBeInTheDocument ( )
259
261
} )
260
262
261
- await sleep ( 10 )
263
+ await vi . advanceTimersByTimeAsync ( 20 )
262
264
// It should stay the same even after effects have had a chance to run
263
- rendered . getByText ( 'string' )
265
+ expect ( rendered . getByText ( 'string' ) ) . toBeInTheDocument ( )
264
266
expect ( rendered . queryByText ( 'should not change' ) ) . toBeNull ( )
265
267
266
268
queryClient . clear ( )
267
269
} )
268
270
269
271
test ( 'should hydrate queries to new cache if cache changes' , async ( ) => {
270
272
const dehydratedState = JSON . parse ( stringifiedState )
271
- const queryCache = new QueryCache ( )
272
- const queryClient = createQueryClient ( { queryCache } )
273
+ const queryClient = createQueryClient ( )
273
274
274
275
function Page ( ) {
275
276
const { data } = useQuery ( {
276
277
queryKey : [ 'string' ] ,
277
- queryFn : ( ) => dataQuery ( [ 'string' ] ) ,
278
+ queryFn : ( ) => sleep ( 20 ) . then ( ( ) => [ 'string' ] ) ,
278
279
} )
279
280
return (
280
281
< div >
@@ -291,12 +292,11 @@ describe('React hydration', () => {
291
292
</ QueryClientProvider > ,
292
293
)
293
294
294
- await rendered . findByText ( 'string' )
295
-
296
- const newClientQueryCache = new QueryCache ( )
297
- const newClientQueryClient = createQueryClient ( {
298
- queryCache : newClientQueryCache ,
299
- } )
295
+ await vi . advanceTimersByTimeAsync ( 1 )
296
+ expect ( rendered . getByText ( 'stringCached' ) ) . toBeInTheDocument ( )
297
+ await vi . advanceTimersByTimeAsync ( 20 )
298
+ expect ( rendered . getByText ( 'string' ) ) . toBeInTheDocument ( )
299
+ const newClientQueryClient = createQueryClient ( )
300
300
301
301
rendered . rerender (
302
302
< QueryClientProvider client = { newClientQueryClient } >
@@ -306,17 +306,16 @@ describe('React hydration', () => {
306
306
</ QueryClientProvider > ,
307
307
)
308
308
309
- await sleep ( 10 )
310
- rendered . getByText ( 'string' )
309
+ await vi . advanceTimersByTimeAsync ( 20 )
310
+ expect ( rendered . getByText ( 'string' ) ) . toBeInTheDocument ( )
311
311
312
312
queryClient . clear ( )
313
313
newClientQueryClient . clear ( )
314
314
} )
315
315
} )
316
316
317
317
test ( 'should not hydrate queries if state is null' , async ( ) => {
318
- const queryCache = new QueryCache ( )
319
- const queryClient = createQueryClient ( { queryCache } )
318
+ const queryClient = createQueryClient ( )
320
319
321
320
const hydrateSpy = vi . spyOn ( coreModule , 'hydrate' )
322
321
@@ -332,15 +331,19 @@ describe('React hydration', () => {
332
331
</ QueryClientProvider > ,
333
332
)
334
333
335
- expect ( hydrateSpy ) . toHaveBeenCalledTimes ( 0 )
334
+ await Promise . all (
335
+ Array . from ( { length : 1000 } ) . map ( async ( _ , index ) => {
336
+ await vi . advanceTimersByTimeAsync ( index )
337
+ expect ( hydrateSpy ) . toHaveBeenCalledTimes ( 0 )
338
+ } ) ,
339
+ )
336
340
337
341
hydrateSpy . mockRestore ( )
338
342
queryClient . clear ( )
339
343
} )
340
344
341
345
test ( 'should not hydrate queries if state is undefined' , async ( ) => {
342
- const queryCache = new QueryCache ( )
343
- const queryClient = createQueryClient ( { queryCache } )
346
+ const queryClient = createQueryClient ( )
344
347
345
348
const hydrateSpy = vi . spyOn ( coreModule , 'hydrate' )
346
349
@@ -356,7 +359,12 @@ describe('React hydration', () => {
356
359
</ QueryClientProvider > ,
357
360
)
358
361
359
- expect ( hydrateSpy ) . toHaveBeenCalledTimes ( 0 )
362
+ await Promise . all (
363
+ Array . from ( { length : 1000 } ) . map ( async ( _ , index ) => {
364
+ await vi . advanceTimersByTimeAsync ( index )
365
+ expect ( hydrateSpy ) . toHaveBeenCalledTimes ( 0 )
366
+ } ) ,
367
+ )
360
368
361
369
hydrateSpy . mockRestore ( )
362
370
queryClient . clear ( )
0 commit comments