/ tests / cli / test_cli_skin_integration.py
test_cli_skin_integration.py
  1  from types import SimpleNamespace
  2  from unittest.mock import MagicMock, patch
  3  
  4  from cli import HermesCLI, _rich_text_from_ansi
  5  from hermes_cli.skin_engine import get_active_skin, set_active_skin
  6  
  7  
  8  def _make_cli_stub():
  9      cli = HermesCLI.__new__(HermesCLI)
 10      cli._sudo_state = None
 11      cli._secret_state = None
 12      cli._approval_state = None
 13      cli._clarify_state = None
 14      cli._clarify_freetext = False
 15      cli._command_running = False
 16      cli._agent_running = False
 17      cli._voice_recording = False
 18      cli._voice_processing = False
 19      cli._voice_mode = False
 20      cli._command_spinner_frame = lambda: "⟳"
 21      cli._tui_style_base = {
 22          "prompt": "#fff",
 23          "input-area": "#fff",
 24          "input-rule": "#aaa",
 25          "prompt-working": "#888 italic",
 26      }
 27      cli._app = SimpleNamespace(style=None)
 28      cli._invalidate = MagicMock()
 29      return cli
 30  
 31  
 32  class TestCliSkinPromptIntegration:
 33      def test_default_prompt_fragments_use_default_symbol(self):
 34          cli = _make_cli_stub()
 35  
 36          set_active_skin("default")
 37          assert cli._get_tui_prompt_fragments() == [("class:prompt", "❯ ")]
 38  
 39      def test_ares_prompt_fragments_use_skin_symbol(self):
 40          cli = _make_cli_stub()
 41  
 42          set_active_skin("ares")
 43          assert cli._get_tui_prompt_fragments() == [("class:prompt", "⚔ ")]
 44  
 45      def test_secret_prompt_fragments_preserve_secret_state(self):
 46          cli = _make_cli_stub()
 47          cli._secret_state = {"response_queue": object()}
 48  
 49          set_active_skin("ares")
 50          assert cli._get_tui_prompt_fragments() == [("class:sudo-prompt", "🔑 ⚔ ")]
 51  
 52      def test_narrow_terminals_compact_voice_prompt_fragments(self):
 53          cli = _make_cli_stub()
 54          cli._voice_mode = True
 55  
 56          with patch.object(HermesCLI, "_get_tui_terminal_width", return_value=50):
 57              assert cli._get_tui_prompt_fragments() == [("class:voice-prompt", "🎤 ")]
 58  
 59      def test_narrow_terminals_compact_voice_recording_prompt_fragments(self):
 60          cli = _make_cli_stub()
 61          cli._voice_recording = True
 62          cli._voice_recorder = SimpleNamespace(current_rms=3000)
 63  
 64          with patch.object(HermesCLI, "_get_tui_terminal_width", return_value=50):
 65              frags = cli._get_tui_prompt_fragments()
 66  
 67          assert frags[0][0] == "class:voice-recording"
 68          assert frags[0][1].startswith("●")
 69          assert "❯" not in frags[0][1]
 70  
 71      def test_icon_only_skin_symbol_still_visible_in_special_states(self):
 72          cli = _make_cli_stub()
 73          cli._secret_state = {"response_queue": object()}
 74  
 75          with patch("hermes_cli.skin_engine.get_active_prompt_symbol", return_value="⚔ "):
 76              assert cli._get_tui_prompt_fragments() == [("class:sudo-prompt", "🔑 ⚔ ")]
 77  
 78      def test_build_tui_style_dict_uses_skin_overrides(self):
 79          cli = _make_cli_stub()
 80  
 81          set_active_skin("ares")
 82          skin = get_active_skin()
 83          style_dict = cli._build_tui_style_dict()
 84  
 85          assert style_dict["prompt"] == skin.get_color("prompt")
 86          assert style_dict["input-rule"] == skin.get_color("input_rule")
 87          assert style_dict["prompt-working"] == f"{skin.get_color('banner_dim')} italic"
 88          assert style_dict["approval-title"] == f"{skin.get_color('ui_warn')} bold"
 89  
 90      def test_apply_tui_skin_style_updates_running_app(self):
 91          cli = _make_cli_stub()
 92  
 93          set_active_skin("ares")
 94          assert cli._apply_tui_skin_style() is True
 95          assert cli._app.style is not None
 96          cli._invalidate.assert_called_once_with(min_interval=0.0)
 97  
 98      def test_handle_skin_command_refreshes_live_tui(self, capsys):
 99          cli = _make_cli_stub()
100  
101          with patch("cli.save_config_value", return_value=True):
102              cli._handle_skin_command("/skin ares")
103  
104          output = capsys.readouterr().out
105          assert "Skin set to: ares (saved)" in output
106          assert "Prompt + TUI colors updated." in output
107          assert cli._app.style is not None
108  
109  
110  class TestAnsiRichTextHelper:
111      def test_preserves_literal_brackets(self):
112          text = _rich_text_from_ansi("[notatag] literal")
113          assert text.plain == "[notatag] literal"
114  
115      def test_strips_ansi_but_keeps_plain_text(self):
116          text = _rich_text_from_ansi("\x1b[31mred\x1b[0m")
117          assert text.plain == "red"