feat(tui): SGR mouse fallback, focus partition highlights, multi-trigger context menus & box tui
- bin/muse-tui.py: * Parse raw SGR 1006 (\033[<btn;x;yM/m) and Xterm mouse escape sequences in _handle_escape_sequence fallback. * Multi-trigger context menus: Button 3, Button 2, Ctrl/Shift/Alt+Click, double-click, click on active item, or click [⚡] / [sid] target. * Render permanent [⚡] action target across all sidebar thread rows. * Separate focus partitions for FLEET AGENTS and SIDECHATS with partition-specific wheel scrolling and keyboard navigation (j/k, Enter, h/l). * Space key support in NORMAL mode to open context menus. * Active pane highlighting and updated footer hints. - bin/super-cli.py: * Add 'box tui' command dispatching directly to muse-tui.py --mode box. - tests: * Add unit tests in test_context_menus.py, test_focus_highlight.py, and test_main_nav.py (51/51 passing).
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
test_focus_highlight.py — Comprehensive tests verifying active portion highlighting in MuseTUI:
|
||||
1. Sidebar focused (focus_pane="sidebar", editor_mode="NORMAL") highlights sidebar header,
|
||||
vertical pane divider, selected thread cursor, and bottom status bar hints.
|
||||
2. Transcript focused (focus_pane="transcript", editor_mode="NORMAL") highlights transcript header,
|
||||
header underline rule, vertical divider, and bottom status bar hints.
|
||||
3. Composer active (editor_mode="INSERT") prominently highlights bottom composer divider
|
||||
and status hints without distracting pane navigation highlights.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from unittest.mock import MagicMock
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path("/home/super/Projects/NetVM")
|
||||
TUI_PATH = REPO_ROOT / "bin" / "muse-tui.py"
|
||||
|
||||
spec = importlib.util.spec_from_file_location("muse_tui", str(TUI_PATH))
|
||||
muse_mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(muse_mod)
|
||||
|
||||
|
||||
class TestActivePortionHighlighting(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.mock_stdscr = MagicMock()
|
||||
self.mock_stdscr.getmaxyx.return_value = (30, 100)
|
||||
self.tui = muse_mod.MuseTUI(self.mock_stdscr, initial_mode="muse", initial_node="muse")
|
||||
self.calls = []
|
||||
|
||||
def record_addstr(win, y, x, text, attr=0):
|
||||
self.calls.append((y, x, text, attr))
|
||||
|
||||
self.tui.safe_addstr = record_addstr
|
||||
self.tui.toast_time = 0
|
||||
self.tui.data.nodes = ["muse", "646", "pip"]
|
||||
self.tui.data.active_node = "muse"
|
||||
self.tui.data.active_thread_id = "test_thread"
|
||||
self.tui.data.active_thread_title = "Work Thread"
|
||||
self.tui.data.threads_cache["muse"] = [
|
||||
{"session_id": "test_thread", "title": "Work Thread", "is_main": False},
|
||||
{"session_id": "main_id", "title": "Main Chat", "is_main": True},
|
||||
]
|
||||
self.tui.data.history_cache[("muse", "test_thread")] = [
|
||||
{"role": "user", "text": "Hello agent", "seq": 1, "message_id": "m1"},
|
||||
{"role": "assistant", "text": "Hello user", "seq": 2, "message_id": "m2"},
|
||||
]
|
||||
|
||||
def test_sidebar_focused_highlight(self):
|
||||
self.tui.focus_pane = "sidebar"
|
||||
self.tui.editor_mode = "NORMAL"
|
||||
|
||||
# 1. Render Muse View
|
||||
self.calls.clear()
|
||||
self.tui._render_muse_view(2, 0, 24, 100)
|
||||
|
||||
rendered_texts = [c[2] for c in self.calls]
|
||||
full_text = " ".join(rendered_texts)
|
||||
|
||||
# Sidebar header has [● FOCUS] badge
|
||||
self.assertTrue(any("[● FOCUS] FLEET AGENTS" in t for t in rendered_texts),
|
||||
f"Expected [● FOCUS] in sidebar header. Got:\n{full_text}")
|
||||
|
||||
# Vertical divider has heavy vertical char ┃
|
||||
self.assertTrue(any("┃" in t for t in rendered_texts),
|
||||
"Expected vertical divider '┃' when pane is focused")
|
||||
|
||||
# Transcript header does NOT have [● FOCUSED] badge
|
||||
self.assertFalse(any("[● FOCUSED]" in t for t in rendered_texts),
|
||||
"Transcript header should not be [● FOCUSED] when sidebar is active")
|
||||
|
||||
# 2. Render Bottom Bar
|
||||
self.calls.clear()
|
||||
self.tui._render_bottom_bar(27, 0, 3, 100)
|
||||
|
||||
bottom_texts = [c[2] for c in self.calls]
|
||||
# Active pane divider has SIDEBAR highlight
|
||||
self.assertTrue(any("ACTIVE PANE: SIDEBAR" in t for t in bottom_texts),
|
||||
f"Expected ACTIVE PANE: SIDEBAR in bottom divider. Got:\n{bottom_texts}")
|
||||
# Status hints show SIDEBAR FOCUSED
|
||||
self.assertTrue(any("SIDEBAR FOCUSED" in t for t in bottom_texts),
|
||||
f"Expected SIDEBAR FOCUSED in hints. Got:\n{bottom_texts}")
|
||||
|
||||
def test_transcript_focused_highlight(self):
|
||||
self.tui.focus_pane = "transcript"
|
||||
self.tui.editor_mode = "NORMAL"
|
||||
|
||||
# 1. Render Muse View
|
||||
self.calls.clear()
|
||||
self.tui._render_muse_view(2, 0, 24, 100)
|
||||
|
||||
rendered_texts = [c[2] for c in self.calls]
|
||||
|
||||
# Transcript header has [● FOCUSED] badge
|
||||
self.assertTrue(any("[● FOCUSED] Thread:" in t for t in rendered_texts),
|
||||
f"Expected [● FOCUSED] in transcript header. Got:\n{rendered_texts}")
|
||||
|
||||
# Transcript horizontal divider has heavy rule ━
|
||||
self.assertTrue(any("━" in t for t in rendered_texts),
|
||||
"Expected transcript underline divider '━' when transcript is focused")
|
||||
|
||||
# Sidebar header does NOT have [● FOCUS] badge
|
||||
self.assertFalse(any("[● FOCUS] FLEET AGENTS" in t for t in rendered_texts),
|
||||
"Sidebar should not have [● FOCUS] when transcript is active")
|
||||
|
||||
# 2. Render Bottom Bar
|
||||
self.calls.clear()
|
||||
self.tui._render_bottom_bar(27, 0, 3, 100)
|
||||
|
||||
bottom_texts = [c[2] for c in self.calls]
|
||||
self.assertTrue(any("ACTIVE PANE: TRANSCRIPT" in t for t in bottom_texts),
|
||||
f"Expected ACTIVE PANE: TRANSCRIPT in bottom divider. Got:\n{bottom_texts}")
|
||||
self.assertTrue(any("TRANSCRIPT FOCUSED" in t for t in bottom_texts),
|
||||
f"Expected TRANSCRIPT FOCUSED in hints. Got:\n{bottom_texts}")
|
||||
|
||||
def test_insert_mode_composer_highlight(self):
|
||||
self.tui.editor_mode = "INSERT"
|
||||
|
||||
# 1. Render Bottom Bar
|
||||
self.calls.clear()
|
||||
self.tui._render_bottom_bar(27, 0, 3, 100)
|
||||
|
||||
bottom_texts = [c[2] for c in self.calls]
|
||||
self.assertTrue(any("COMPOSER: INSERT MODE ACTIVE" in t for t in bottom_texts),
|
||||
f"Expected COMPOSER: INSERT MODE ACTIVE in divider. Got:\n{bottom_texts}")
|
||||
self.assertTrue(any("INSERT ACTIVE" in t for t in bottom_texts),
|
||||
f"Expected INSERT ACTIVE in hints. Got:\n{bottom_texts}")
|
||||
|
||||
# 2. Render Muse View - neither pane should show [● FOCUS] / [● FOCUSED]
|
||||
self.calls.clear()
|
||||
self.tui._render_muse_view(2, 0, 24, 100)
|
||||
|
||||
rendered_texts = [c[2] for c in self.calls]
|
||||
self.assertFalse(any("[● FOCUS] FLEET AGENTS" in t for t in rendered_texts),
|
||||
"Sidebar should not claim active focus in INSERT mode")
|
||||
self.assertFalse(any("[● FOCUSED] Thread:" in t for t in rendered_texts),
|
||||
"Transcript should not claim active focus in INSERT mode")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user