From 0668d257ca38b0a8eedaba5d8278e449b5e6e8e1 Mon Sep 17 00:00:00 2001 From: operator Date: Sat, 10 Oct 2026 11:45:05 -0400 Subject: [PATCH] test(tui): add headless Box TUI render test suite and support box tui work shorthand --- bin/muse-tui.py | 2 +- bin/super-cli.py | 2 + tests/test_box_tui_render.py | 139 +++++++++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 tests/test_box_tui_render.py diff --git a/bin/muse-tui.py b/bin/muse-tui.py index 9d3e94d..666a7dd 100755 --- a/bin/muse-tui.py +++ b/bin/muse-tui.py @@ -8029,7 +8029,7 @@ def main(): parser.add_argument("--account", "-a", choices=VALID_NODES, default=None, help="Initial agent account (default: top of list)") parser.add_argument("--thread", "-t", help="Initial thread UUID to open") parser.add_argument("--mode", "-m", choices=["muse", "box"], default="muse", help="TUI mode (default: muse)") - parser.add_argument("--tab", choices=["chat", "fleet", "approvals", "jobs", "tmux", "logs", "ssh"], default=None, help="Initial Box tab (box mode only)") + parser.add_argument("--tab", choices=["chat", "fleet", "approvals", "jobs", "tmux", "logs", "ssh", "work"], default=None, help="Initial Box tab (box mode only)") args = parser.parse_args() try: diff --git a/bin/super-cli.py b/bin/super-cli.py index ef2a2a4..951bca6 100755 --- a/bin/super-cli.py +++ b/bin/super-cli.py @@ -7685,6 +7685,8 @@ def main(): cmd = [sys.executable, str(BIN_DIR / "box-onboard-tui.py")] + tui_args[1:] elif tui_args and tui_args[0] in ("fleet", "oversight"): cmd = [sys.executable, str(BIN_DIR / "box-fleet-tui.py")] + tui_args[1:] + elif tui_args and tui_args[0] in ("chat", "fleet-status", "jobs", "logs", "ssh", "work"): + cmd = [sys.executable, str(BIN_DIR / "muse-tui.py"), "--mode", "box", "--tab", tui_args[0]] + tui_args[1:] else: cmd = [sys.executable, str(BIN_DIR / "muse-tui.py"), "--mode", "box"] + tui_args res = subprocess.run(cmd) diff --git a/tests/test_box_tui_render.py b/tests/test_box_tui_render.py new file mode 100644 index 0000000..9d3ae8e --- /dev/null +++ b/tests/test_box_tui_render.py @@ -0,0 +1,139 @@ +"""test_box_tui_render.py — Headless verification of Box TUI and Work Pipeline screens.""" + +import unittest +from unittest.mock import MagicMock, patch +import os +import sys +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parent.parent +BIN_DIR = REPO_ROOT / "bin" +if str(BIN_DIR) not in sys.path: + sys.path.insert(0, str(BIN_DIR)) + +import importlib.util +spec = importlib.util.spec_from_file_location("muse_tui", str(BIN_DIR / "muse-tui.py")) +muse_tui = importlib.util.module_from_spec(spec) +spec.loader.exec_module(muse_tui) + + +class TestBoxTUIHeadless(unittest.TestCase): + def setUp(self): + self.mock_stdscr = MagicMock() + self.mock_stdscr.getmaxyx.return_value = (40, 120) + self.mock_stdscr.getch.return_value = -1 + + def test_box_tui_initialization_work_tab(self): + app = muse_tui.MuseTUI(self.mock_stdscr, initial_mode="box", initial_tab="work") + self.assertEqual(app.mode, "box") + self.assertEqual(app.box_tab, 7) + self.assertIn("8: Work Pipeline", muse_tui.BOX_TABS) + + def test_render_work_view(self): + app = muse_tui.MuseTUI(self.mock_stdscr, initial_mode="box", initial_tab="work") + # Populate mock cognitive cache and work issues + app.data.cognitive_cache = { + "muse": { + "node": "muse", + "status": "IDLE", + "cognitive_lock": False, + "screen": {"url": "https://muse.ai/", "title": "Chat - muse"} + }, + "pip": { + "node": "pip", + "status": "SIDECHAT_IDLE", + "cognitive_lock": False, + "screen": {"url": "https://muse.ai/thread/123", "title": "Chat - pip"} + } + } + app.data.work_issues_cache = [ + {"number": 218, "state": "open", "title": "Autonomous TUI upgrade", "assignee": {"username": "dev"}} + ] + app.data.work_prs_cache = [ + {"number": 219, "state": "open", "merged": False, "title": "feat: box tui", "head": {"ref": "feature/tui"}} + ] + + # Call _render_work_view directly + app._render_work_view(2, 0, 35, 120) + self.assertTrue(self.mock_stdscr.addstr.called) + + def test_box_tui_key_actions_on_work_tab(self): + app = muse_tui.MuseTUI(self.mock_stdscr, initial_mode="box", initial_tab="work") + app.data.nodes = ["muse", "pip", "646", "opm", "def", "dev"] + + # 'w' opens work_dispatch modal + handled = app._handle_key(ord('w')) + self.assertTrue(handled) + self.assertEqual(app.modal, "work_dispatch") + + # Esc closes modal + handled = app._handle_key(27) + self.assertTrue(handled) + self.assertIsNone(app.modal) + + # Tab navigation: '1' switches to Chat (tab 0), '8' switches to Work (tab 7) + app._handle_key(ord('1')) + self.assertEqual(app.box_tab, 0) + app._handle_key(ord('8')) + self.assertEqual(app.box_tab, 7) + + def test_work_dispatch_modal_text_input_and_cycle(self): + app = muse_tui.MuseTUI(self.mock_stdscr, initial_mode="box", initial_tab="work") + app.data.nodes = ["muse", "pip", "646", "opm", "def", "dev"] + app.modal = "work_dispatch" + app.modal_input_buf = "" + app.modal_input_cursor = 0 + app.dispatch_target_node = "dev" + + # Type chars 'Build #12' + for c in "Build #12": + app._handle_key(ord(c)) + self.assertEqual(app.modal_input_buf, "Build #12") + + # Tab cycles worker + app._handle_key(ord('\t')) + self.assertNotEqual(app.dispatch_target_node, "dev") + + def test_agent_thoughts_modal(self): + app = muse_tui.MuseTUI(self.mock_stdscr, initial_mode="box", initial_tab="work") + app.selected_thought_node = "def" + app.agent_thought_data = { + "screen": { + "url": "https://muse.ai/", + "title": "Chat - def", + "is_generating": False, + "recent_paragraphs": ["Inspecting workspace...", "Code updated."] + } + } + app.modal = "agent_thoughts" + # Render modal + app._render_modal(40, 120) + self.assertTrue(self.mock_stdscr.addstr.called) + + # Esc closes modal + app._handle_key(27) + self.assertIsNone(app.modal) + + def test_agent_profile_menu_modal(self): + app = muse_tui.MuseTUI(self.mock_stdscr, initial_mode="box", initial_tab="work") + app.selected_menu_node = "pip" + app.menu_tab = "activity" + app.agent_menu_data = { + "tab": "activity", + "data": { + "items": [{"title": "Session started", "detail": "Active", "time": "12m ago"}] + } + } + app.modal = "agent_menu" + # Render modal + app._render_modal(40, 120) + self.assertTrue(self.mock_stdscr.addstr.called) + + # Tab key cycles menu tabs + with patch.object(app, "_async_load_menu") as mock_load: + app._handle_key(ord('\t')) + mock_load.assert_called_with("pip", "upcoming") + + +if __name__ == "__main__": + unittest.main()