@@ -381,6 +381,126 @@ describe('loader skip or exec', () => {
381381 expect ( loader ) . toHaveBeenCalledTimes ( 1 )
382382 } )
383383
384+ test ( 'does not error if cache gc clears an in-flight preload' , async ( ) => {
385+ let resolveLoader : ( ( value : { ok : true } ) => void ) | undefined
386+ const consoleErrorSpy = vi
387+ . spyOn ( console , 'error' )
388+ . mockImplementation ( ( ) => { } )
389+
390+ const loader : Loader = vi . fn (
391+ ( ) =>
392+ new Promise < { ok : true } > ( ( resolve ) => {
393+ resolveLoader = resolve
394+ } ) ,
395+ )
396+
397+ const rootRoute = new BaseRootRoute ( { } )
398+ const fooRoute = new BaseRoute ( {
399+ getParentRoute : ( ) => rootRoute ,
400+ path : '/foo' ,
401+ loader,
402+ preloadGcTime : 0 ,
403+ } )
404+
405+ const router = createTestRouter ( {
406+ routeTree : rootRoute . addChildren ( [ fooRoute ] ) ,
407+ history : createMemoryHistory ( ) ,
408+ defaultPreloadGcTime : 0 ,
409+ } )
410+
411+ const preloadPromise = router . preloadRoute ( { to : '/foo' } )
412+ await Promise . resolve ( )
413+
414+ expect (
415+ router . stores . cachedMatchesSnapshot . state . some (
416+ ( match ) => match . routeId === fooRoute . id ,
417+ ) ,
418+ ) . toBe ( true )
419+
420+ router . clearExpiredCache ( )
421+
422+ expect (
423+ router . stores . cachedMatchesSnapshot . state . some (
424+ ( match ) => match . routeId === fooRoute . id ,
425+ ) ,
426+ ) . toBe ( false )
427+
428+ resolveLoader ?.( { ok : true } )
429+
430+ await preloadPromise
431+ // the route load won't throw, but it will log errors to the console if any
432+ expect ( consoleErrorSpy ) . not . toHaveBeenCalled ( )
433+
434+ expect (
435+ router . stores . cachedMatchesSnapshot . state . some (
436+ ( match ) => match . routeId === fooRoute . id ,
437+ ) ,
438+ ) . toBe ( false )
439+ consoleErrorSpy . mockRestore ( )
440+ } )
441+
442+ test ( 'does not error when invalidate clears an in-flight preload' , async ( ) => {
443+ let resolveFooLoader : ( ( value : { ok : true } ) => void ) | undefined
444+ const consoleErrorSpy = vi
445+ . spyOn ( console , 'error' )
446+ . mockImplementation ( ( ) => { } )
447+
448+ const fooLoader : Loader = vi . fn (
449+ ( ) =>
450+ new Promise < { ok : true } > ( ( resolve ) => {
451+ resolveFooLoader = resolve
452+ } ) ,
453+ )
454+ const barLoader : Loader = vi . fn ( ( ) => ( { ok : true } ) )
455+
456+ const rootRoute = new BaseRootRoute ( { } )
457+ const fooRoute = new BaseRoute ( {
458+ getParentRoute : ( ) => rootRoute ,
459+ path : '/foo' ,
460+ loader : fooLoader ,
461+ preloadGcTime : 0 ,
462+ } )
463+ const barRoute = new BaseRoute ( {
464+ getParentRoute : ( ) => rootRoute ,
465+ path : '/bar' ,
466+ loader : barLoader ,
467+ } )
468+
469+ const router = createTestRouter ( {
470+ routeTree : rootRoute . addChildren ( [ fooRoute , barRoute ] ) ,
471+ history : createMemoryHistory ( ) ,
472+ defaultPreloadGcTime : 0 ,
473+ } )
474+
475+ await router . navigate ( { to : '/bar' } )
476+
477+ const preloadPromise = router . preloadRoute ( { to : '/foo' } )
478+ await Promise . resolve ( )
479+
480+ expect (
481+ router . stores . cachedMatchesSnapshot . state . some (
482+ ( match ) => match . routeId === fooRoute . id ,
483+ ) ,
484+ ) . toBe ( true )
485+
486+ const invalidatePromise = router . invalidate ( )
487+ await Promise . resolve ( )
488+
489+ resolveFooLoader ?.( { ok : true } )
490+
491+ await Promise . all ( [ preloadPromise , invalidatePromise ] )
492+
493+ expect ( barLoader ) . toHaveBeenCalledTimes ( 2 )
494+ // the route load won't throw, but it will log errors to the console if any
495+ expect ( consoleErrorSpy ) . not . toHaveBeenCalled ( )
496+ expect (
497+ router . stores . cachedMatchesSnapshot . state . some (
498+ ( match ) => match . routeId === fooRoute . id ,
499+ ) ,
500+ ) . toBe ( false )
501+ consoleErrorSpy . mockRestore ( )
502+ } )
503+
384504 test ( 'exec if rejected preload (notFound)' , async ( ) => {
385505 const loader : Loader = vi . fn ( async ( { preload } ) => {
386506 if ( preload ) throw notFound ( )
0 commit comments