@@ -653,6 +653,124 @@ describe('owner-lifecycle', () => {
653653 expect ( world . opened . map ( pane => pane . id ) ) . toEqual ( [ 'diff' ] )
654654 } )
655655
656+ test ( '/resume refreshes a retained pane when its close is denied and restored history has no edits' , async ( $ , on ) => {
657+ const world = lifecycleWorld ( on )
658+ await $ . session . start ( Fixtures . SESSION )
659+ await $ . command . run ( Fixtures . DIFF )
660+ await world . clock . advance ( Fixtures . SETTLE_MS )
661+ expect ( Fixtures . textOf ( await $ . ui . render ( Fixtures . PANE ) ) ) . toContain ( 'app.ts' )
662+
663+ Object . assign ( world . script , MOVED )
664+ world . state . denyClose = true
665+ await $ . command . run ( Fixtures . RESUME )
666+ await world . clock . advance ( Fixtures . SETTLE_MS )
667+
668+ expect ( world . pane . visible ) . toBe ( true )
669+ const text = Fixtures . textOf ( await $ . ui . render ( Fixtures . PANE ) )
670+ expect ( text ) . toContain ( 'other.ts' )
671+ expect ( text ) . not . toContain ( 'app.ts' )
672+ expect ( world . opened ) . toHaveLength ( 1 )
673+ expect ( await $ . command . run ( Fixtures . DIFF ) ) . toEqual ( { text : Names . PANEL_HIDDEN_TEXT } )
674+ expect ( world . pane . visible ) . toBe ( false )
675+ } )
676+
677+ test ( '/resume reports a denied close and reuses the retained pane for restored edits' , async ( $ , on ) => {
678+ const world = lifecycleWorld ( on )
679+ await $ . session . start ( Fixtures . SESSION )
680+ await $ . command . run ( Fixtures . DIFF )
681+ await world . clock . advance ( Fixtures . SETTLE_MS )
682+
683+ Object . assign ( world . script , MOVED )
684+ world . state . transcript = Fixtures . EDITED_TRANSCRIPT
685+ world . state . denyClose = true
686+ await $ . command . run ( Fixtures . RESUME )
687+ await world . clock . advance ( Fixtures . SETTLE_MS )
688+
689+ expect ( world . logs . join ( '\n' ) ) . toContain ( 'test close denied' )
690+ expect ( world . pane . visible ) . toBe ( true )
691+ expect ( world . opened ) . toHaveLength ( 1 )
692+ expect ( world . closed ) . toHaveLength ( 1 )
693+ const text = Fixtures . textOf ( await $ . ui . render ( Fixtures . PANE ) )
694+ expect ( text ) . toContain ( 'other.ts' )
695+ expect ( text ) . not . toContain ( 'app.ts' )
696+ } )
697+
698+ test ( '/resume waits for its session timing before refreshing a pane whose close was denied' , async ( $ , on ) => {
699+ const world = lifecycleWorld ( on )
700+ filesWrittenAt ( on , 2000 )
701+ world . state . startedAt = 100
702+ await $ . session . start ( Fixtures . SESSION )
703+ await $ . command . run ( Fixtures . DIFF )
704+ await world . clock . advance ( 5000 )
705+ const before = world . reads . length
706+
707+ Object . assign ( world . script , MOVED )
708+ world . state . startedAt = 5000
709+ world . state . delayUsage = 2000
710+ world . state . denyClose = true
711+ const resuming = $ . command . run ( Fixtures . RESUME )
712+ await world . clock . settle ( )
713+ expect ( world . reads ) . toHaveLength ( before )
714+ await world . clock . advance ( 3000 )
715+ await resuming
716+ await world . clock . advance ( Fixtures . SETTLE_MS )
717+
718+ const text = Fixtures . textOf ( await $ . ui . render ( Fixtures . PANE ) )
719+ expect ( text ) . toContain ( 'No changes this session' )
720+ expect ( text ) . toContain ( '1 file edited before this session' )
721+ } )
722+
723+ test ( 'a delayed denied resume close does not revive a pane closed by a later request' , async ( $ , on ) => {
724+ const world = lifecycleWorld ( on )
725+ await $ . session . start ( Fixtures . SESSION )
726+ await $ . command . run ( Fixtures . DIFF )
727+ await world . clock . advance ( Fixtures . SETTLE_MS )
728+ const before = world . reads . length
729+ world . state . delayClose = 1000
730+ world . state . denyClose = true
731+ const resuming = $ . command . run ( Fixtures . RESUME )
732+ await world . clock . settle ( )
733+ expect ( world . closed ) . toHaveLength ( 1 )
734+
735+ world . state . delayClose = 0
736+ expect ( await $ . command . run ( Fixtures . DIFF ) ) . toEqual ( { text : Names . PANEL_HIDDEN_TEXT } )
737+ await world . clock . advance ( 2000 )
738+ await resuming
739+ await world . clock . advance ( Fixtures . SETTLE_MS )
740+
741+ expect ( world . pane . visible ) . toBe ( false )
742+ expect ( world . opened ) . toHaveLength ( 1 )
743+ expect ( world . reads . slice ( before ) ) . toEqual ( [ ] )
744+ } )
745+
746+ test ( 'an older denied resume close cannot reset a newer clear or discard its refresh' , async ( $ , on ) => {
747+ const world = lifecycleWorld ( on )
748+ filesWrittenAt ( on , 2000 )
749+ world . state . startedAt = 100
750+ await $ . session . start ( Fixtures . SESSION )
751+ await $ . command . run ( Fixtures . DIFF )
752+ await world . clock . advance ( 5000 )
753+ world . state . delayClose = 1000
754+ world . state . denyClose = true
755+ const resuming = $ . command . run ( Fixtures . RESUME )
756+ await world . clock . settle ( )
757+ expect ( world . closed ) . toHaveLength ( 1 )
758+
759+ world . state . delayClose = 0
760+ world . state . delayRead = 2000
761+ world . state . startedAt = 5000
762+ Object . assign ( world . script , MOVED )
763+ await $ . command . run ( Fixtures . CLEAR )
764+ await world . clock . advance ( 4000 )
765+ await resuming
766+ await world . clock . advance ( Fixtures . SETTLE_MS )
767+
768+ expect ( world . pane . visible ) . toBe ( true )
769+ const text = Fixtures . textOf ( await $ . ui . render ( Fixtures . PANE ) )
770+ expect ( text ) . toContain ( 'No changes this session' )
771+ expect ( text ) . toContain ( '1 file edited before this session' )
772+ } )
773+
656774 test ( '/resume consumes a pending refresh when restored history has no edits' , async ( $ , on ) => {
657775 const world = lifecycleWorld ( on )
658776 await $ . session . start ( Fixtures . SESSION )
0 commit comments