@@ -465,4 +465,87 @@ describe('configuration', () => {
465
465
466
466
expect ( baseBaseQuery ) . toHaveBeenCalledOnce ( )
467
467
} )
468
+
469
+ test ( 'retryCondition receives abort signal and stops retrying when cache entry is removed' , async ( ) => {
470
+ let capturedSignal : AbortSignal | undefined
471
+ let retryAttempts = 0
472
+
473
+ const baseBaseQuery = vi . fn <
474
+ Parameters < BaseQueryFn > ,
475
+ ReturnType < BaseQueryFn >
476
+ > ( )
477
+
478
+ // Always return an error to trigger retries
479
+ baseBaseQuery . mockResolvedValue ( { error : 'network error' } )
480
+
481
+ let retryConditionCalled = false
482
+
483
+ const baseQuery = retry ( baseBaseQuery , {
484
+ retryCondition : ( error , args , { attempt, baseQueryApi } ) => {
485
+ retryConditionCalled = true
486
+ retryAttempts = attempt
487
+ capturedSignal = baseQueryApi . signal
488
+
489
+ // Stop retrying if the signal is aborted
490
+ if ( baseQueryApi . signal . aborted ) {
491
+ return false
492
+ }
493
+
494
+ // Otherwise, retry up to 10 times
495
+ return attempt <= 10
496
+ } ,
497
+ backoff : async ( ) => {
498
+ // Short backoff for faster test
499
+ await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) )
500
+ } ,
501
+ } )
502
+
503
+ const api = createApi ( {
504
+ baseQuery,
505
+ endpoints : ( build ) => ( {
506
+ getTest : build . query < string , number > ( {
507
+ query : ( id ) => ( { url : `test/${ id } ` } ) ,
508
+ keepUnusedDataFor : 0.01 , // Very short timeout (10ms)
509
+ } ) ,
510
+ } ) ,
511
+ } )
512
+
513
+ const storeRef = setupApiStore ( api , undefined , {
514
+ withoutTestLifecycles : true ,
515
+ } )
516
+
517
+ // Start the query
518
+ const queryPromise = storeRef . store . dispatch (
519
+ api . endpoints . getTest . initiate ( 1 ) ,
520
+ )
521
+
522
+ // Wait for the first retry to happen so we capture the signal
523
+ await loopTimers ( 2 )
524
+
525
+ // Verify the retry condition was called and we have a signal
526
+ expect ( retryConditionCalled ) . toBe ( true )
527
+ expect ( capturedSignal ) . toBeDefined ( )
528
+ expect ( capturedSignal ! . aborted ) . toBe ( false )
529
+
530
+ // Unsubscribe to trigger cache removal
531
+ queryPromise . unsubscribe ( )
532
+
533
+ // Wait for the cache entry to be removed (keepUnusedDataFor: 0.01s = 10ms)
534
+ await vi . advanceTimersByTimeAsync ( 50 )
535
+
536
+ // Allow some time for more retries to potentially happen
537
+ await loopTimers ( 3 )
538
+
539
+ // The signal should now be aborted
540
+ expect ( capturedSignal ! . aborted ) . toBe ( true )
541
+
542
+ // We should have stopped retrying early due to the abort signal
543
+ // If abort signal wasn't working, we'd see many more retry attempts
544
+ expect ( retryAttempts ) . toBeLessThan ( 10 )
545
+
546
+ // The base query should have been called at least once (initial attempt)
547
+ // but not the full 10+ times it would without abort signal
548
+ expect ( baseBaseQuery ) . toHaveBeenCalled ( )
549
+ expect ( baseBaseQuery . mock . calls . length ) . toBeLessThan ( 10 )
550
+ } )
468
551
} )
0 commit comments