feat(tui): clean transcript style, right-click context menus, rate limits & dual copy
- Add clean line-by-line transcript style toggle ('b' key / /clean / /boxed)
- Implement right-click context menus for chat list, fleet agent panel, and transcript
- Add cooldown mode lock bypass (2-second double-confirm force sync)
- Implement dual copy support: clean text (strip reply metadata) vs full context
- Add clickable [📋 Copy] and [📑+ Context] buttons to message headers
- Add unit test suites for transcript cleaning, context menus, rate limits, and copy actions
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, patch
|
||||
import importlib.util
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path("/home/super/Projects/NetVM")
|
||||
sys.path.insert(0, str(REPO_ROOT / "bin"))
|
||||
|
||||
|
||||
class TestBlockedApprovals(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
spec = importlib.util.spec_from_file_location("muse_tui", str(REPO_ROOT / "bin" / "muse-tui.py"))
|
||||
cls.muse_tui = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(cls.muse_tui)
|
||||
|
||||
def setUp(self):
|
||||
self.mock_stdscr = MagicMock()
|
||||
self.mock_stdscr.getmaxyx.return_value = (40, 120)
|
||||
self.app = self.muse_tui.MuseTUI(self.mock_stdscr, initial_mode="muse", initial_node="muse")
|
||||
self.app.data = MagicMock()
|
||||
self.app.data.nodes = ["muse", "pip", "646", "opm", "def", "dev"]
|
||||
self.app.data.active_node = "muse"
|
||||
self.app.data.active_thread_id = "thread-1"
|
||||
self.app.data.active_thread_title = "Main Chat"
|
||||
self.app.data.lock = MagicMock()
|
||||
self.app.data.lock.__enter__.return_value = None
|
||||
self.app.data.lock.__exit__.return_value = None
|
||||
|
||||
def test_slash_blocked_opens_approvals_modal(self):
|
||||
self.app.data.approvals_cache = [
|
||||
{"node": "opm", "status": "PENDING", "title": "Review task", "is_trusted": True},
|
||||
{"node": "646", "status": "PENDING", "title": "SSH connect", "is_trusted": False},
|
||||
]
|
||||
self.app._execute_input_line("/blocked")
|
||||
self.assertEqual(self.app.modal, "approvals")
|
||||
self.assertEqual(self.app.approvals_sel_idx, 0)
|
||||
|
||||
self.app._execute_input_line("/Blocked")
|
||||
self.assertEqual(self.app.modal, "approvals")
|
||||
|
||||
self.app._execute_input_line("/approvals")
|
||||
self.assertEqual(self.app.modal, "approvals")
|
||||
|
||||
def test_approvals_modal_navigation_and_targeting(self):
|
||||
self.app.modal = "approvals"
|
||||
self.app.approvals_sel_idx = 0
|
||||
self.app.data.approvals_cache = [
|
||||
{"node": "muse", "status": "PENDING", "title": "Task 1"},
|
||||
{"node": "opm", "status": "PENDING", "title": "Task 2"},
|
||||
]
|
||||
|
||||
# Press 'j' (down)
|
||||
handled = self.app._handle_modal_key(ord('j'))
|
||||
self.assertTrue(handled)
|
||||
self.assertEqual(self.app.approvals_sel_idx, 1)
|
||||
|
||||
# Press '1' (allow) -> should target 'opm' (selected row), not 'muse' (active_node)
|
||||
with patch.object(self.app, "_async_resolve_approval") as mock_resolve:
|
||||
handled = self.app._handle_modal_key(ord('1'))
|
||||
self.assertTrue(handled)
|
||||
self.assertIsNone(self.app.modal)
|
||||
mock_resolve.assert_called_once_with("allow", "opm")
|
||||
|
||||
def test_approvals_modal_navigation_k(self):
|
||||
self.app.modal = "approvals"
|
||||
self.app.approvals_sel_idx = 1
|
||||
self.app.data.approvals_cache = [
|
||||
{"node": "muse", "status": "PENDING", "title": "Task 1"},
|
||||
{"node": "opm", "status": "PENDING", "title": "Task 2"},
|
||||
]
|
||||
|
||||
# Press 'k' (up)
|
||||
handled = self.app._handle_modal_key(ord('k'))
|
||||
self.assertTrue(handled)
|
||||
self.assertEqual(self.app.approvals_sel_idx, 0)
|
||||
|
||||
# Press '2' (always) -> should target 'muse'
|
||||
with patch.object(self.app, "_async_resolve_approval") as mock_resolve:
|
||||
handled = self.app._handle_modal_key(ord('2'))
|
||||
self.assertTrue(handled)
|
||||
mock_resolve.assert_called_once_with("always", "muse")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user