Files

180 lines
7.3 KiB
Python
Raw Permalink Normal View History

#!/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()