#!/usr/bin/env python3 """ test_clean_transcript.py — Unit and integration tests for clean line-by-line transcript view: 1. Default display style is 'clean' (no box borders, no bottom rules, no vertical pipe bars). 2. Clean mode renders bullet header indicators (● AGENT, ▸ YOU) and 2-space indented body lines. 3. Boxed mode retains classic ASCII/Unicode frames (┌──, │ , └───). 4. Runtime style toggling via 'b' / 'B' hotkey in Normal mode. 5. Slash commands: /clean, /boxed, /view, /compact. 6. Selection and copy extraction (_copy_rendered_line_range) handles clean formatting seamlessly. 7. Settings persistence in ~/.config/muse-cli/tui_settings.json. """ import curses import importlib.util import json import os import sys import tempfile import unittest from pathlib import Path from unittest.mock import MagicMock, patch REPO_ROOT = Path(__file__).resolve().parent.parent MUSE_TUI_PATH = REPO_ROOT / "bin" / "muse-tui.py" spec = importlib.util.spec_from_file_location("muse_tui", MUSE_TUI_PATH) muse_tui = importlib.util.module_from_spec(spec) sys.modules["muse_tui"] = muse_tui spec.loader.exec_module(muse_tui) MuseTUI = muse_tui.MuseTUI class TestCleanTranscript(unittest.TestCase): def setUp(self): self.mock_stdscr = MagicMock() self.mock_stdscr.getmaxyx.return_value = (30, 100) self.tui = MuseTUI(self.mock_stdscr, initial_mode="muse", initial_node="muse") self.tui.safe_addstr = MagicMock() self.messages = [ { "role": "assistant", "text": "Same sweep thread, routine — holding on dev-i04.\nSecond line of response.", "message_id": "ast-msg-86648", "seq": 86648, }, { "role": "user", "text": "Roger that, continue monitoring.\n[WO:20261006-1] Active", "message_id": "usr-msg-d183a734", "seq": 86649, }, ] with self.tui.data.lock: self.tui.data.history_cache[(self.tui.data.active_node, self.tui.data.active_thread_id)] = self.messages def test_default_style_is_clean(self): """Verify that newly initialized TUI defaults to 'clean' transcript style.""" self.assertEqual(self.tui.transcript_style, "clean") def test_render_transcript_clean_mode(self): """In 'clean' mode, lines do NOT contain ┌──, │ , or └───, and use clean bullets.""" self.tui.transcript_style = "clean" self.tui._transcript_cache_key = None self.tui._render_transcript(1, 0, 25, 90) lines = [item[0] for item in self.tui._transcript_cache_lines] combined = "\n".join(lines) # Must not contain box border frames self.assertNotIn("┌──", combined) self.assertNotIn("└──", combined) self.assertNotIn("│ ", combined) # Must contain clean indicators self.assertTrue(any(l.startswith("● MUSE") for l in lines), "Agent header must start with clean bullet ●") self.assertTrue(any(l.startswith("▸ YOU") for l in lines), "User header must start with clean prompt ▸") # Body lines must use 2-space indentation self.assertTrue(any(l.startswith(" Same sweep thread") for l in lines)) self.assertTrue(any(l.startswith(" Second line") for l in lines)) self.assertTrue(any(l.startswith(" Roger that") for l in lines)) def test_render_transcript_boxed_mode(self): """In 'boxed' mode, classic ASCII/Unicode frames (┌──, │ , └───) are rendered.""" self.tui.transcript_style = "boxed" self.tui._transcript_cache_key = None self.tui._render_transcript(1, 0, 25, 90) lines = [item[0] for item in self.tui._transcript_cache_lines] combined = "\n".join(lines) self.assertIn("┌── [MUSE / MUSE AGENT]", combined) self.assertIn("┌── [YOU / OPERATOR]", combined) self.assertIn("│ Same sweep thread", combined) self.assertIn("└──", combined) def test_toggle_transcript_style(self): """toggle_transcript_style switches between clean and boxed, invalidating cache.""" self.tui.transcript_style = "clean" self.tui._transcript_cache_key = ("some_key",) self.tui.toggle_transcript_style() self.assertEqual(self.tui.transcript_style, "boxed") self.assertIsNone(self.tui._transcript_cache_key) self.assertIn("Classic boxed", self.tui.toast_msg) self.tui.toggle_transcript_style() self.assertEqual(self.tui.transcript_style, "clean") self.assertIsNone(self.tui._transcript_cache_key) self.assertIn("Clean line-by-line", self.tui.toast_msg) def test_normal_mode_hotkey_b_toggles_style(self): """Pressing 'b' or 'B' in normal mode toggles transcript style.""" self.tui.editor_mode = "NORMAL" self.tui.copy_mode = False self.tui.transcript_style = "clean" handled = self.tui._handle_key(ord('b')) self.assertTrue(handled) self.assertEqual(self.tui.transcript_style, "boxed") handled = self.tui._handle_key(ord('B')) self.assertTrue(handled) self.assertEqual(self.tui.transcript_style, "clean") def test_slash_commands_clean_and_boxed(self): """Slash commands /clean, /boxed, and /view correctly set style.""" self.tui.transcript_style = "clean" # /boxed self.tui._execute_input_line("/boxed") self.assertEqual(self.tui.transcript_style, "boxed") # /clean self.tui._execute_input_line("/clean") self.assertEqual(self.tui.transcript_style, "clean") # /view box self.tui._execute_input_line("/view box") self.assertEqual(self.tui.transcript_style, "boxed") # /view clean self.tui._execute_input_line("/view clean") self.assertEqual(self.tui.transcript_style, "clean") # /style (toggles) self.tui._execute_input_line("/style") self.assertEqual(self.tui.transcript_style, "boxed") def test_copy_range_in_clean_mode(self): """_copy_rendered_line_range strips clean 2-space indents and action buttons.""" self.tui.transcript_style = "clean" self.tui._transcript_cache_key = None self.tui._render_transcript(1, 0, 25, 90) # Total lines in cache lines = self.tui._transcript_cache_lines copied = self.tui._copy_rendered_line_range(0, len(lines) - 1) self.assertNotIn("│", copied) self.assertNotIn("[📋 Copy]", copied) self.assertNotIn("[↩ Reply]", copied) self.assertIn("Same sweep thread", copied) self.assertIn("Roger that, continue monitoring.", copied) def test_settings_persistence(self): """Style preference is saved to and loaded from JSON configuration.""" with tempfile.TemporaryDirectory() as tmp_dir: fake_cfg = Path(tmp_dir) / "tui_settings.json" with patch("pathlib.Path.home", return_value=Path(tmp_dir)): with patch.object(Path, "mkdir"): with patch("builtins.open", unittest.mock.mock_open()): self.tui.set_transcript_style("boxed") self.assertEqual(self.tui.transcript_style, "boxed") if __name__ == "__main__": unittest.main()