@@ -553,4 +553,116 @@ describe('createStore', () => {
553553 expect ( logAlertSpy ) . not . toHaveBeenCalledWith ( expect . stringContaining ( 'dropping cached connection and reopening' ) , expect . anything ( ) ) ;
554554 } ) ;
555555 } ) ;
556+
557+ describe ( 'visibilitychange probe' , ( ) => {
558+ function simulateVisibilityChange ( state : string ) {
559+ Object . defineProperty ( document , 'visibilityState' , { value : state , writable : true , configurable : true } ) ;
560+ document . dispatchEvent ( new Event ( 'visibilitychange' ) ) ;
561+ }
562+
563+ afterEach ( ( ) => {
564+ Object . defineProperty ( document , 'visibilityState' , { value : 'visible' , writable : true , configurable : true } ) ;
565+ } ) ;
566+
567+ it ( 'should drop stale dbp when probe detects connection lost on foreground' , async ( ) => {
568+ const store = createStore ( uniqueDBName ( ) , STORE_NAME ) ;
569+
570+ await store ( 'readwrite' , ( s ) => {
571+ s . put ( 'value' , 'key1' ) ;
572+ return IDB . promisifyRequest ( s . transaction ) ;
573+ } ) ;
574+
575+ simulateVisibilityChange ( 'hidden' ) ;
576+
577+ const original = IDBDatabase . prototype . transaction ;
578+ let probeIntercepted = false ;
579+ jest . spyOn ( IDBDatabase . prototype , 'transaction' ) . mockImplementation ( function ( this : IDBDatabase , ...args ) {
580+ if ( ! probeIntercepted ) {
581+ probeIntercepted = true ;
582+ throw new DOMException ( 'Connection to Indexed Database server lost. Refresh the page to try again' , 'UnknownError' ) ;
583+ }
584+ return original . apply ( this , args ) ;
585+ } ) ;
586+
587+ simulateVisibilityChange ( 'visible' ) ;
588+
589+ await new Promise ( ( resolve ) => {
590+ setTimeout ( resolve , 0 ) ;
591+ } ) ;
592+
593+ jest . restoreAllMocks ( ) ;
594+
595+ const result = await store ( 'readonly' , ( s ) => IDB . promisifyRequest ( s . get ( 'key1' ) ) ) ;
596+ expect ( result ) . toBe ( 'value' ) ;
597+ expect ( logInfoSpy ) . toHaveBeenCalledWith ( 'IDB visibilitychange probe: connection lost, dropping cached connection' , expect . objectContaining ( { dbName : expect . any ( String ) } ) ) ;
598+ } ) ;
599+
600+ it ( 'should not probe when no connection exists yet' , async ( ) => {
601+ const dbName = uniqueDBName ( ) ;
602+ createStore ( dbName , STORE_NAME ) ;
603+
604+ simulateVisibilityChange ( 'hidden' ) ;
605+ simulateVisibilityChange ( 'visible' ) ;
606+
607+ await new Promise ( ( resolve ) => {
608+ setTimeout ( resolve , 0 ) ;
609+ } ) ;
610+
611+ // No probe log for this specific store (dbp was never set)
612+ expect ( logInfoSpy ) . not . toHaveBeenCalledWith ( expect . stringContaining ( 'visibilitychange probe' ) , expect . objectContaining ( { dbName} ) ) ;
613+ } ) ;
614+
615+ it ( 'should keep connection when probe succeeds' , async ( ) => {
616+ const store = createStore ( uniqueDBName ( ) , STORE_NAME ) ;
617+
618+ await store ( 'readwrite' , ( s ) => {
619+ s . put ( 'value' , 'key1' ) ;
620+ return IDB . promisifyRequest ( s . transaction ) ;
621+ } ) ;
622+
623+ simulateVisibilityChange ( 'hidden' ) ;
624+ simulateVisibilityChange ( 'visible' ) ;
625+
626+ await new Promise ( ( resolve ) => {
627+ setTimeout ( resolve , 0 ) ;
628+ } ) ;
629+
630+ const result = await store ( 'readonly' , ( s ) => IDB . promisifyRequest ( s . get ( 'key1' ) ) ) ;
631+ expect ( result ) . toBe ( 'value' ) ;
632+ expect ( logInfoSpy ) . not . toHaveBeenCalledWith ( expect . stringContaining ( 'visibilitychange probe' ) , expect . anything ( ) ) ;
633+ } ) ;
634+
635+ it ( 'should drop dbp when probe throws InvalidStateError' , async ( ) => {
636+ const store = createStore ( uniqueDBName ( ) , STORE_NAME ) ;
637+
638+ await store ( 'readwrite' , ( s ) => {
639+ s . put ( 'value' , 'key1' ) ;
640+ return IDB . promisifyRequest ( s . transaction ) ;
641+ } ) ;
642+
643+ simulateVisibilityChange ( 'hidden' ) ;
644+
645+ const original = IDBDatabase . prototype . transaction ;
646+ let callCount = 0 ;
647+ jest . spyOn ( IDBDatabase . prototype , 'transaction' ) . mockImplementation ( function ( this : IDBDatabase , ...args ) {
648+ callCount ++ ;
649+ if ( callCount === 1 ) {
650+ throw new DOMException ( 'The database connection is closing.' , 'InvalidStateError' ) ;
651+ }
652+ return original . apply ( this , args ) ;
653+ } ) ;
654+
655+ simulateVisibilityChange ( 'visible' ) ;
656+
657+ await new Promise ( ( resolve ) => {
658+ setTimeout ( resolve , 0 ) ;
659+ } ) ;
660+
661+ jest . restoreAllMocks ( ) ;
662+
663+ const result = await store ( 'readonly' , ( s ) => IDB . promisifyRequest ( s . get ( 'key1' ) ) ) ;
664+ expect ( result ) . toBe ( 'value' ) ;
665+ expect ( logInfoSpy ) . toHaveBeenCalledWith ( 'IDB visibilitychange probe: connection lost, dropping cached connection' , expect . objectContaining ( { dbName : expect . any ( String ) } ) ) ;
666+ } ) ;
667+ } ) ;
556668} ) ;
0 commit comments