@@ -386,7 +386,52 @@ describe('appUpdates', () => {
386386 expect ( ( result as Error ) . message ) . toBe (
387387 'Failed to check for updates. Reload the app and try again.'
388388 ) ;
389- expect ( fetchMock ) . not . toHaveBeenCalled ( ) ;
389+ expect ( fetchMock ) . toHaveBeenCalledTimes ( 1 ) ;
390+ } ) ;
391+
392+ it ( 'falls back to the hosted shell check when one service worker probe fails' , async ( ) => {
393+ const currentRegistration = createRegistration ( '/current' ) ;
394+ const secondaryRegistration = createRegistration ( '/secondary' ) ;
395+ secondaryRegistration . update . mockRejectedValueOnce ( new TypeError ( 'network failed' ) ) ;
396+ fetchMock . mockResolvedValueOnce (
397+ new Response (
398+ `
399+ <!doctype html>
400+ <html>
401+ <head>
402+ <link rel="stylesheet" href="/assets/index-next.css" />
403+ </head>
404+ <body>
405+ <script type="module" src="/assets/index-next.js"></script>
406+ </body>
407+ </html>
408+ ` ,
409+ { status : 200 , headers : { 'Content-Type' : 'text/html' } }
410+ )
411+ ) ;
412+
413+ Object . defineProperty ( window , 'navigator' , {
414+ configurable : true ,
415+ value : {
416+ serviceWorker : {
417+ controller : { postMessage : vi . fn ( ) } ,
418+ getRegistration : vi . fn ( ) . mockResolvedValue ( currentRegistration ) ,
419+ getRegistrations : vi . fn ( ) . mockResolvedValue ( [ currentRegistration , secondaryRegistration ] ) ,
420+ ready : Promise . resolve ( currentRegistration ) ,
421+ addEventListener : vi . fn ( ) ,
422+ removeEventListener : vi . fn ( ) ,
423+ } ,
424+ } ,
425+ } ) ;
426+
427+ const resultPromise = checkForAppUpdates ( ) ;
428+ await vi . runAllTimersAsync ( ) ;
429+
430+ await expect ( resultPromise ) . resolves . toEqual ( {
431+ kind : 'update-available' ,
432+ message : 'A newer hosted app version is ready to apply.' ,
433+ canApply : true ,
434+ } ) ;
390435 } ) ;
391436
392437 it ( 'returns once any registration confirms an update' , async ( ) => {
@@ -771,6 +816,64 @@ describe('appUpdates', () => {
771816 expect ( mockReloadWithTelemetry ) . toHaveBeenCalledWith ( 'apply_pending_app_update' ) ;
772817 } ) ;
773818
819+ it ( 'clears a stale hosted shell update detection after a later up-to-date check' , async ( ) => {
820+ fetchMock
821+ . mockResolvedValueOnce (
822+ new Response (
823+ `
824+ <!doctype html>
825+ <html>
826+ <head>
827+ <link rel="stylesheet" href="/assets/index-next.css" />
828+ </head>
829+ <body>
830+ <script type="module" src="/assets/index-next.js"></script>
831+ </body>
832+ </html>
833+ ` ,
834+ { status : 200 , headers : { 'Content-Type' : 'text/html' } }
835+ )
836+ )
837+ . mockResolvedValueOnce (
838+ new Response (
839+ `
840+ <!doctype html>
841+ <html>
842+ <head>
843+ <link rel="stylesheet" href="/assets/index-current.css" />
844+ </head>
845+ <body>
846+ <script type="module" src="/assets/index-current.js"></script>
847+ </body>
848+ </html>
849+ ` ,
850+ { status : 200 , headers : { 'Content-Type' : 'text/html' } }
851+ )
852+ )
853+ . mockRejectedValueOnce ( new TypeError ( 'network failed' ) ) ;
854+
855+ const firstCheckPromise = checkForAppUpdates ( ) ;
856+ await vi . runAllTimersAsync ( ) ;
857+ await expect ( firstCheckPromise ) . resolves . toEqual ( {
858+ kind : 'update-available' ,
859+ message : 'A newer hosted app version is ready to apply.' ,
860+ canApply : true ,
861+ } ) ;
862+
863+ const secondCheckPromise = checkForAppUpdates ( ) ;
864+ await vi . runAllTimersAsync ( ) ;
865+ await expect ( secondCheckPromise ) . resolves . toEqual ( {
866+ kind : 'up-to-date' ,
867+ message : 'You are already on the latest available web app version.' ,
868+ canApply : false ,
869+ } ) ;
870+
871+ await applyPendingAppUpdate ( ) ;
872+
873+ expect ( mockClearClientCachesAndServiceWorkers ) . not . toHaveBeenCalled ( ) ;
874+ expect ( mockReloadWithTelemetry ) . not . toHaveBeenCalled ( ) ;
875+ } ) ;
876+
774877 it ( 'reports hosted updates even when service workers are unavailable' , async ( ) => {
775878 mockHasServiceWorker . mockReturnValue ( false ) ;
776879 fetchMock . mockResolvedValueOnce (
0 commit comments