| """Tests for CLI background command TUI refresh behavior. |
| |
| Ensures the TUI is properly refreshed before printing background task output |
| to prevent spinner/status bar overlap (#2718). |
| """ |
|
|
| import threading |
| from types import SimpleNamespace |
| from unittest.mock import MagicMock, patch |
|
|
| import pytest |
|
|
| from cli import HermesCLI |
|
|
|
|
| def _make_cli(): |
| """Create a minimal HermesCLI instance for testing.""" |
| cli_obj = HermesCLI.__new__(HermesCLI) |
| cli_obj.model = "test-model" |
| cli_obj._background_tasks = {} |
| cli_obj._background_task_counter = 0 |
| cli_obj.conversation_history = [] |
| cli_obj.agent = None |
| cli_obj._app = None |
| return cli_obj |
|
|
|
|
| class TestBackgroundCommandTuiRefresh: |
| """Tests for TUI refresh in background command output.""" |
|
|
| def test_invalidate_called_before_success_output(self): |
| """App.invalidate() is called before printing background success output.""" |
| cli_obj = _make_cli() |
| mock_app = MagicMock() |
| cli_obj._app = mock_app |
|
|
| |
| call_order = [] |
| original_invalidate = mock_app.invalidate |
|
|
| def track_invalidate(): |
| call_order.append("invalidate") |
| return original_invalidate() |
|
|
| mock_app.invalidate = track_invalidate |
|
|
| |
| with patch("builtins.print") as mock_print: |
| mock_print.side_effect = lambda *args, **kwargs: call_order.append("print") |
|
|
| |
| if cli_obj._app: |
| cli_obj._app.invalidate() |
| import time |
| time.sleep(0.01) |
| print() |
|
|
| |
| assert call_order[0] == "invalidate" |
| assert "print" in call_order |
|
|
| def test_invalidate_called_before_error_output(self): |
| """App.invalidate() is called before printing background error output.""" |
| cli_obj = _make_cli() |
| mock_app = MagicMock() |
| cli_obj._app = mock_app |
|
|
| call_order = [] |
| mock_app.invalidate.side_effect = lambda: call_order.append("invalidate") |
|
|
| with patch("builtins.print") as mock_print: |
| mock_print.side_effect = lambda *args, **kwargs: call_order.append("print") |
|
|
| |
| if cli_obj._app: |
| cli_obj._app.invalidate() |
| import time |
| time.sleep(0.01) |
| print() |
|
|
| assert call_order[0] == "invalidate" |
| assert "print" in call_order |
|
|
| def test_no_crash_when_app_is_none(self): |
| """No crash when _app is None (non-TUI mode).""" |
| cli_obj = _make_cli() |
| cli_obj._app = None |
|
|
| |
| if cli_obj._app: |
| cli_obj._app.invalidate() |
| |
|
|
| def test_background_task_thread_safety(self): |
| """Background task tracking is thread-safe.""" |
| cli_obj = _make_cli() |
|
|
| |
| task_id = "test_task_1" |
| cli_obj._background_tasks[task_id] = MagicMock() |
| assert task_id in cli_obj._background_tasks |
|
|
| |
| cli_obj._background_tasks.pop(task_id, None) |
| assert task_id not in cli_obj._background_tasks |
|
|