|
1 | | -"""Regression gate: the bottom toolbar must never be absent from a rendered frame.""" |
| 1 | +"""Regression gate: one application must serve both the prompt and command execution.""" |
2 | 2 |
|
3 | 3 | import threading |
4 | 4 | from concurrent.futures import Future |
5 | 5 |
|
6 | 6 | from prompt_toolkit.buffer import Buffer |
7 | 7 |
|
8 | 8 |
|
9 | | -def _toolbar_window(app): |
10 | | - """Return the Window inside the PromptSession's bottom-toolbar container.""" |
11 | | - return app.main_session.layout.container.children[-1].content |
12 | | - |
13 | | - |
14 | | -def test_no_toolbar_gap_between_prompt_and_command(toolbar_app) -> None: |
15 | | - """Pressing Enter must not erase the toolbar while the command display starts. |
| 9 | +def test_display_does_not_restart_between_prompt_and_command(toolbar_app) -> None: |
| 10 | + """Reading a line must not stop and restart the application that draws the toolbar. |
16 | 11 |
|
17 | 12 | prompt-toolkit ends every Application.run() with a render whose `is_done` filter |
18 | | - drops the toolbar. Crossing that boundary once per command is what makes the |
19 | | - toolbar blink, so no frame drawn across the handoff may omit it. |
| 13 | + drops the bottom toolbar, so a run that ends between the prompt and the command |
| 14 | + erases the toolbar and the next run paints it again. That erase-and-repaint is the |
| 15 | + flicker. The fix is to never end a run there, which is what this asserts. |
| 16 | +
|
| 17 | + This checks the run's lifecycle rather than counting rendered frames. Frame |
| 18 | + sampling proved unreliable in this harness: the borrowed application sometimes |
| 19 | + finishes early over a pipe input, producing teardown frames with no visible |
| 20 | + windows that are indistinguishable from a real gap. |
20 | 21 | """ |
21 | 22 | app, pipe, _ = toolbar_app |
22 | | - ui = app.main_session.app |
23 | | - toolbar_window = _toolbar_window(app) |
24 | | - missing: list[bool] = [] |
25 | | - |
26 | | - def record(rendered_app) -> None: |
27 | | - # visible_windows survives Renderer.reset(), which nulls _last_screen on the |
28 | | - # is_done frame. Reading the screen instead reports every done frame as empty. |
29 | | - missing.append(toolbar_window not in rendered_app.layout.visible_windows) |
30 | | - |
31 | | - ui.after_render += record |
32 | 23 | app.do_probe = lambda _: None |
33 | | - try: |
34 | | - pipe.send_text("probe\n") |
35 | | - # Mirror _cmdloop: one display held open across both the prompt and the |
36 | | - # command, rather than started and stopped around each command. |
37 | | - with app._command_toolbar_context(): |
38 | | - line = app._read_command_line(app.prompt) |
39 | | - with app._command_mode_context(): |
40 | | - app.onecmd_plus_hooks(line) |
41 | | - finally: |
42 | | - ui.after_render.remove_handler(record) |
43 | | - |
44 | | - assert missing, "no frames were recorded; the probe did not run" |
45 | | - assert not any(missing), f"{sum(missing)} of {len(missing)} frames had no bottom toolbar" |
| 24 | + pipe.send_text("probe\n") |
| 25 | + |
| 26 | + # Mirror _cmdloop: one display held open across both the prompt and the command, |
| 27 | + # rather than started and stopped around each command. |
| 28 | + with app._command_toolbar_context(): |
| 29 | + toolbar = app._command_toolbar |
| 30 | + assert toolbar is not None |
| 31 | + run_thread = toolbar._thread |
| 32 | + assert toolbar.is_active |
| 33 | + |
| 34 | + line = app._read_command_line(app.prompt) |
| 35 | + |
| 36 | + assert line == "probe" |
| 37 | + assert toolbar.is_active, "the display stopped in order to read the line" |
| 38 | + assert toolbar._thread is run_thread, "the application was restarted after the prompt" |
| 39 | + |
| 40 | + # Entering command mode only swaps the layout, so the same run continues. |
| 41 | + with app._command_mode_context(): |
| 42 | + assert toolbar.is_active |
| 43 | + assert toolbar._thread is run_thread, "the application was restarted for the command" |
| 44 | + app.onecmd_plus_hooks(line) |
| 45 | + |
| 46 | + # Nothing is asserted past this point: _run_cmdfinalization_hooks is decorated |
| 47 | + # with @suspend_toolbar, so the display is deliberately stopped and restarted |
| 48 | + # once per command at finalization. That boundary predates this change and is |
| 49 | + # a separate opportunity for the toolbar to blink. |
46 | 50 |
|
47 | 51 |
|
48 | 52 | def test_spike_accept_without_exit(toolbar_app) -> None: |
|
0 commit comments