@@ -691,9 +691,21 @@ def stop_mid_page() -> None:
691691 # later command output keeps the toolbar rather than scrolling it away.
692692 assert display ._proxy is not None
693693 assert all (stream .serializer is None for stream in display ._streams )
694- harness .app .poutput ("after the pager" )
695- assert wait_for (lambda : any ("after the pager" in row for row in terminal .screen .display ))
696- assert terminal .screen .display [- 1 ].startswith ("STATUS" )
694+ output_rendered = threading .Event ()
695+
696+ def after_output_render (_app ) -> None :
697+ # Proxy output precedes its asynchronous redraw. Inspect a completed frame,
698+ # not the transient screen between the write and the toolbar repaint.
699+ rows = terminal .screen .display
700+ if any ("after the pager" in row for row in rows ) and rows [- 1 ].startswith ("STATUS" ):
701+ output_rendered .set ()
702+
703+ display .app .after_render += after_output_render
704+ try :
705+ harness .app .poutput ("after the pager" )
706+ assert output_rendered .wait (5 )
707+ finally :
708+ display .app .after_render -= after_output_render
697709 harness .app .main_session .bottom_toolbar = "RECOVERED"
698710 display .app .invalidate ()
699711 assert wait_for (lambda : terminal .screen .display [- 1 ].startswith ("RECOVERED" ))
@@ -707,7 +719,11 @@ def test_the_pager_draws_its_content_over_the_reserved_toolbar(self, terminal_ha
707719 assert harness .app .reserved_toolbar .bridge ._render_suppressed is True
708720 assert terminal .screen .margins is None
709721
710- def test_pager_teardown_restores_the_display_even_if_leaving_raises (self , terminal_harness , monkeypatch ) -> None :
722+ @pytest .mark .parametrize ("quit_key" , [False , True ])
723+ @pytest .mark .parametrize ("failure" , ["erase" , "request_absolute_cursor_position" ])
724+ def test_pager_teardown_restores_the_display_even_if_leaving_raises (
725+ self , terminal_harness , monkeypatch , quit_key , failure
726+ ) -> None :
711727 """If the display cannot run the pager's exit on its own loop -- here the exit's erase
712728 raises -- page() must still put the display back itself. Left as the pager's, the
713729 full-screen flag and editing mode would carry into the next main prompt."""
@@ -726,27 +742,43 @@ def make_pager(*args: Any, **kwargs: Any) -> Any:
726742 bindings = display .app .key_bindings
727743 editing_mode = display .app .editing_mode
728744
729- def erase_fails () -> None :
730- raise ValueError ("erase failed" )
745+ original = getattr (display .app .renderer , failure )
746+ forced_close = threading .Event ()
747+
748+ def exit_fails () -> None :
749+ monkeypatch .setattr (display .app .renderer , failure , original )
750+ raise ValueError ("exit failed" )
731751
732752 def drive () -> None :
733753 assert wait_for (lambda : terminal .screen .display [0 ].startswith ("row 000" ))
734- # The exit's first act is an erase; make it raise, then end the pager without
735- # its quit key so the exit runs from page()'s own teardown.
736- monkeypatch .setattr (display .app .renderer , "erase" , erase_fails )
737- created [0 ].closed .set ()
754+ monkeypatch .setattr (display .app .renderer , failure , exit_fails )
755+ if quit_key :
756+ harness .pipe .send_text ("q" )
757+ if not wait_for (created [0 ].closed .is_set , timeout = 3 ):
758+ forced_close .set ()
759+ created [0 ].closed .set ()
760+ else :
761+ created [0 ].closed .set ()
738762
739763 with ThreadPoolExecutor () as executor :
740764 future = executor .submit (drive )
741- with pytest .raises (ValueError , match = "erase failed" ):
765+ with pytest .raises (ValueError , match = "exit failed" ):
742766 display .page (PAGER_BODY , chop = False )
743767 future .result (timeout = 5 )
744768
769+ assert not forced_close .is_set (), "the quit callback failed to release page()"
745770 assert display .app .full_screen is False
746771 assert display .app .renderer .full_screen is False
747772 assert display .app .layout is display ._layout
748773 assert display .app .key_bindings is display ._bindings or display .app .key_bindings is bindings
749774 assert display .app .editing_mode is editing_mode
775+ assert harness .app .reserved_toolbar .bridge ._render_suppressed is True
776+ harness .app .stdout .write ("PARTIAL" )
777+ harness .app .stdout .flush ()
778+ display ._call_in_ui (display .app ._redraw )
779+ harness .app .stdout .write ("END\n " )
780+ harness .app .stdout .flush ()
781+ assert any ("PARTIALEND" in row for row in terminal .screen .display )
750782
751783 def test_output_that_fits_is_printed_without_a_pager (self , terminal_harness ) -> None :
752784 harness , terminal = terminal_harness
0 commit comments