@@ -5,12 +5,12 @@ import {
5
5
sleep ,
6
6
} from '@tanstack/query-test-utils'
7
7
import {
8
+ CancelledError ,
8
9
Query ,
9
10
QueryClient ,
10
11
QueryObserver ,
11
12
dehydrate ,
12
13
hydrate ,
13
- isCancelledError ,
14
14
} from '..'
15
15
import { hashQueryKeyByOptions } from '../utils'
16
16
import { mockOnlineManagerIsOnline , setIsServer } from './utils'
@@ -190,14 +190,52 @@ describe('query', () => {
190
190
await promise
191
191
expect . unreachable ( )
192
192
} catch {
193
- expect ( isCancelledError ( result ) ) . toBe ( true )
194
- expect ( result instanceof Error ) . toBe ( true )
193
+ expect ( result ) . toBeInstanceOf ( CancelledError )
195
194
} finally {
196
195
// Reset visibilityState to original value
197
196
visibilityMock . mockRestore ( )
198
197
}
199
198
} )
200
199
200
+ test ( 'should not throw a CancelledError when fetchQuery is in progress and the last observer unsubscribes when AbortSignal is consumed' , async ( ) => {
201
+ const key = queryKey ( )
202
+
203
+ const observer = new QueryObserver ( queryClient , {
204
+ queryKey : key ,
205
+ queryFn : async ( ) => {
206
+ await sleep ( 100 )
207
+ return 'data'
208
+ } ,
209
+ } )
210
+
211
+ const unsubscribe = observer . subscribe ( ( ) => undefined )
212
+ await vi . advanceTimersByTimeAsync ( 100 )
213
+
214
+ expect ( queryCache . find ( { queryKey : key } ) ?. state . data ) . toBe ( 'data' )
215
+
216
+ const promise = queryClient . fetchQuery ( {
217
+ queryKey : key ,
218
+ queryFn : async ( { signal } ) => {
219
+ await sleep ( 100 )
220
+ return 'data2' + String ( signal )
221
+ } ,
222
+ } )
223
+
224
+ // Ensure the fetch is in progress
225
+ await vi . advanceTimersByTimeAsync ( 10 )
226
+
227
+ // Unsubscribe while fetch is in progress
228
+ unsubscribe ( )
229
+ // await queryClient.cancelQueries()
230
+
231
+ await vi . advanceTimersByTimeAsync ( 90 )
232
+
233
+ // Fetch should complete successfully without throwing a CancelledError
234
+ await expect ( promise ) . resolves . toBe ( 'data' )
235
+
236
+ expect ( queryCache . find ( { queryKey : key } ) ?. state . data ) . toBe ( 'data' )
237
+ } )
238
+
201
239
test ( 'should provide context to queryFn' , ( ) => {
202
240
const key = queryKey ( )
203
241
@@ -330,7 +368,7 @@ describe('query', () => {
330
368
expect ( signal . aborted ) . toBe ( true )
331
369
expect ( onAbort ) . toHaveBeenCalledTimes ( 1 )
332
370
expect ( abortListener ) . toHaveBeenCalledTimes ( 1 )
333
- expect ( isCancelledError ( error ) ) . toBe ( true )
371
+ expect ( error ) . toBeInstanceOf ( CancelledError )
334
372
} )
335
373
336
374
test ( 'should not continue if explicitly cancelled' , async ( ) => {
@@ -362,7 +400,7 @@ describe('query', () => {
362
400
await vi . advanceTimersByTimeAsync ( 100 )
363
401
364
402
expect ( queryFn ) . toHaveBeenCalledTimes ( 1 )
365
- expect ( isCancelledError ( error ) ) . toBe ( true )
403
+ expect ( error ) . toBeInstanceOf ( CancelledError )
366
404
} )
367
405
368
406
test ( 'should not error if reset while pending' , async ( ) => {
@@ -375,7 +413,18 @@ describe('query', () => {
375
413
throw new Error ( )
376
414
} )
377
415
378
- queryClient . fetchQuery ( { queryKey : key , queryFn, retry : 3 , retryDelay : 10 } )
416
+ let error
417
+
418
+ queryClient
419
+ . fetchQuery ( {
420
+ queryKey : key ,
421
+ queryFn,
422
+ retry : 3 ,
423
+ retryDelay : 10 ,
424
+ } )
425
+ . catch ( ( e ) => {
426
+ error = e
427
+ } )
379
428
380
429
// Ensure the query is pending
381
430
const query = queryCache . find ( { queryKey : key } ) !
@@ -390,6 +439,12 @@ describe('query', () => {
390
439
expect ( queryFn ) . toHaveBeenCalledTimes ( 1 ) // have been called,
391
440
expect ( query . state . error ) . toBe ( null ) // not have an error, and
392
441
expect ( query . state . fetchStatus ) . toBe ( 'idle' ) // not be loading any longer
442
+ expect ( query . state . data ) . toBe ( undefined ) // have no data
443
+
444
+ // the call to fetchQuery must reject
445
+ // because it was reset and not reverted
446
+ // so it would resolve with undefined otherwise
447
+ expect ( error ) . toBeInstanceOf ( CancelledError )
393
448
} )
394
449
395
450
test ( 'should reset to default state when created from hydration' , async ( ) => {
@@ -426,7 +481,7 @@ describe('query', () => {
426
481
await vi . advanceTimersByTimeAsync ( 100 )
427
482
428
483
expect ( queryFn ) . toHaveBeenCalledTimes ( 1 )
429
- expect ( isCancelledError ( query . state . error ) ) . toBe ( true )
484
+ expect ( query . state . error ) . toBeInstanceOf ( CancelledError )
430
485
const result = query . fetch ( )
431
486
await vi . advanceTimersByTimeAsync ( 50 )
432
487
await expect ( result ) . resolves . toBe ( 'data' )
@@ -459,7 +514,7 @@ describe('query', () => {
459
514
await vi . advanceTimersByTimeAsync ( 10 )
460
515
461
516
expect ( query . state . error ) . toBe ( error )
462
- expect ( isCancelledError ( query . state . error ) ) . toBe ( false )
517
+ expect ( query . state . error ) . not . toBeInstanceOf ( CancelledError )
463
518
} )
464
519
465
520
test ( 'the previous query status should be kept when refetching' , async ( ) => {
@@ -897,7 +952,7 @@ describe('query', () => {
897
952
unsubscribe ( )
898
953
} )
899
954
900
- test ( 'should always revert to idle state (#5958 )' , async ( ) => {
955
+ test ( 'should always revert to idle state (#5968 )' , async ( ) => {
901
956
let mockedData = [ 1 ]
902
957
903
958
const key = queryKey ( )
@@ -931,24 +986,69 @@ describe('query', () => {
931
986
const unsubscribe = observer . subscribe ( ( ) => undefined )
932
987
await vi . advanceTimersByTimeAsync ( 50 ) // let it resolve
933
988
989
+ expect ( observer . getCurrentResult ( ) . data ) . toBe ( '1' )
990
+ expect ( observer . getCurrentResult ( ) . fetchStatus ) . toBe ( 'idle' )
991
+
934
992
mockedData = [ 1 , 2 ] // update "server" state in the background
935
993
936
- queryClient . invalidateQueries ( { queryKey : key } )
937
- queryClient . invalidateQueries ( { queryKey : key } )
994
+ void queryClient . invalidateQueries ( { queryKey : key } )
995
+ await vi . advanceTimersByTimeAsync ( 5 )
996
+ void queryClient . invalidateQueries ( { queryKey : key } )
997
+ await vi . advanceTimersByTimeAsync ( 5 )
938
998
unsubscribe ( ) // unsubscribe to simulate unmount
999
+ await vi . advanceTimersByTimeAsync ( 5 )
1000
+
1001
+ // reverted to previous data and idle fetchStatus
1002
+ expect ( queryCache . find ( { queryKey : key } ) ?. state ) . toMatchObject ( {
1003
+ status : 'success' ,
1004
+ data : '1' ,
1005
+ fetchStatus : 'idle' ,
1006
+ } )
939
1007
940
1008
// set up a new observer to simulate a mount of new component
941
1009
const newObserver = new QueryObserver ( queryClient , {
942
1010
queryKey : key ,
943
1011
queryFn,
944
1012
} )
945
-
946
1013
const spy = vi . fn ( )
947
1014
newObserver . subscribe ( ( { data } ) => spy ( data ) )
948
1015
await vi . advanceTimersByTimeAsync ( 60 ) // let it resolve
949
1016
expect ( spy ) . toHaveBeenCalledWith ( '1 - 2' )
950
1017
} )
951
1018
1019
+ test ( 'should not reject a promise when silently cancelled in the background' , async ( ) => {
1020
+ const key = queryKey ( )
1021
+
1022
+ let x = 0
1023
+
1024
+ queryClient . setQueryData ( key , 'initial' )
1025
+ const queryFn = vi . fn ( ) . mockImplementation ( async ( ) => {
1026
+ await sleep ( 100 )
1027
+ return 'data' + x
1028
+ } )
1029
+
1030
+ const promise = queryClient . fetchQuery ( {
1031
+ queryKey : key ,
1032
+ queryFn,
1033
+ } )
1034
+
1035
+ await vi . advanceTimersByTimeAsync ( 10 )
1036
+
1037
+ expect ( queryFn ) . toHaveBeenCalledTimes ( 1 )
1038
+
1039
+ x = 1
1040
+
1041
+ // cancel ongoing re-fetches
1042
+ void queryClient . refetchQueries ( { queryKey : key } , { cancelRefetch : true } )
1043
+
1044
+ await vi . advanceTimersByTimeAsync ( 10 )
1045
+
1046
+ // The promise should not reject
1047
+ await vi . waitFor ( ( ) => expect ( promise ) . resolves . toBe ( 'data1' ) )
1048
+
1049
+ expect ( queryFn ) . toHaveBeenCalledTimes ( 2 )
1050
+ } )
1051
+
952
1052
it ( 'should have an error log when queryFn data is not serializable' , async ( ) => {
953
1053
const consoleMock = vi . spyOn ( console , 'error' )
954
1054
0 commit comments