#!/usr/bin/env python3 """ test_paste_handling.py — Unit tests for long string / chunk paste handling in MuseTUI: 1. Multi-line chunk pasting preserves newlines and does not send immediately. 2. Enter key submits the entire chunk as a single message and single history item. 3. Bracketed paste sequences (\033[200~ ... \033[201~) are correctly parsed and captured. 4. Alt+Enter (ESC + Enter) inserts newlines for multiline composition. 5. Unbracketed rapid paste bursts preserve newlines without line-by-line send. 6. Arrow keys navigate lines within multiline buffer. 7. UTF-8 multibyte characters are correctly decoded and inserted into buffer. 8. Multiline messages or file paths starting with '/' are delivered as messages. """ import unittest from unittest.mock import MagicMock, patch import importlib.util from pathlib import Path import curses 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 TestPasteHandling(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.tui.safe_addstr = MagicMock() self.tui.data.running = False # Keep background thread stopped self.tui.data.active_node = "muse" self.tui.data.active_thread_id = "test-session" self.tui.data.active_thread_title = "Main Chat" self.tui.data.threads_cache["muse"] = [{"session_id": "test-session", "is_main": True}] def test_insert_pasted_text_multiline(self): """Pasting a multiline chunk enters input_buf in full without sending.""" chunk = "Line 1: Hello\nLine 2: World\nLine 3: Agent instruction" self.tui.editor_mode = "NORMAL" self.tui.input_buf = "" self.tui.input_cursor = 0 self.tui._insert_pasted_text(chunk) self.assertEqual(self.tui.editor_mode, "INSERT") self.assertEqual(self.tui.input_buf, chunk) self.assertEqual(self.tui.input_cursor, len(chunk)) self.assertIn("3 lines", self.tui.toast_msg) # Verify nothing was sent yet self.assertEqual(len(self.tui.input_history), 0) def test_multiline_send_as_single_chunk(self): """Submitting a multiline buffer records ONE history entry and sends ONCE.""" chunk = "Line 1: def test():\n print('NetVM')\nLine 3: return True" self.tui.editor_mode = "INSERT" self.tui.input_buf = chunk self.tui.input_cursor = len(chunk) sent_messages = [] self.tui._async_send_message = lambda node, tid, text: sent_messages.append((node, tid, text)) # Simulate user pressing Enter on the completed multiline chunk # With no characters waiting in stdin (peek_c = -1) self.mock_stdscr.getch.return_value = -1 ret = self.tui._handle_insert_key(10) self.assertTrue(ret) self.assertEqual(self.tui.input_buf, "") self.assertEqual(len(self.tui.input_history), 1) self.assertEqual(self.tui.input_history[0], chunk) self.assertEqual(len(sent_messages), 1) self.assertEqual(sent_messages[0][0], "muse") self.assertEqual(sent_messages[0][1], "test-session") self.assertEqual(sent_messages[0][2], chunk) def test_bracketed_paste_capture(self): """\033[200~ bracketed paste reads until \033[201~ and captures content.""" payload = "Multi-line code snippet:\n```python\nx = 42\nprint(x)\n```" # Simulate stream of getch returns: payload bytes followed by \033[201~ then -1 stream = list(payload.encode("utf-8")) + [27, ord('['), ord('2'), ord('0'), ord('1'), ord('~')] call_idx = 0 def mock_getch(): nonlocal call_idx if call_idx < len(stream): val = stream[call_idx] call_idx += 1 return val return -1 self.mock_stdscr.getch.side_effect = mock_getch self.tui.input_buf = "" self.tui.input_cursor = 0 handled = self.tui._handle_bracketed_paste() self.assertTrue(handled) self.assertEqual(self.tui.input_buf, payload) self.assertEqual(self.tui.input_cursor, len(payload)) self.assertEqual(self.tui.editor_mode, "INSERT") def test_alt_enter_inserts_newline(self): """Alt+Enter (ESC + 10 or 13) inserts newline into input_buf.""" self.tui.editor_mode = "INSERT" self.tui.input_buf = "First line" self.tui.input_cursor = len(self.tui.input_buf) # Mock _read_escape_sequence returning [10] (Enter) with patch.object(self.tui, "_read_escape_sequence", return_value=[10]): res = self.tui._handle_escape_sequence() self.assertTrue(res) self.assertEqual(self.tui.input_buf, "First line\n") self.assertEqual(self.tui.input_cursor, len("First line\n")) def test_multiline_cursor_navigation(self): """Up and Down arrow navigate across lines inside multiline input_buf.""" self.tui.editor_mode = "INSERT" self.tui.input_buf = "Line A\nLine B\nLine C" # Place cursor at end of "Line C" self.tui.input_cursor = len(self.tui.input_buf) # Press Up: cursor should move to "Line B" self.tui._handle_insert_key(curses.KEY_UP) curr_text = self.tui.input_buf[:self.tui.input_cursor] self.assertTrue(curr_text.endswith("Line B") or "Line B" in curr_text) # Press Up again: cursor should move to "Line A" self.tui._handle_insert_key(curses.KEY_UP) curr_text = self.tui.input_buf[:self.tui.input_cursor] self.assertTrue(curr_text.startswith("Line A")) # Press Down: cursor should move back down to "Line B" self.tui._handle_insert_key(curses.KEY_DOWN) curr_text = self.tui.input_buf[:self.tui.input_cursor] self.assertIn("Line B", curr_text) def test_unicode_multibyte_input(self): """Multi-byte UTF-8 bytes assemble into correct unicode characters.""" self.tui.editor_mode = "INSERT" self.tui.input_buf = "Test: " self.tui.input_cursor = len(self.tui.input_buf) # 'é' is 0xC3, 0xA9 (195, 169) self.tui._handle_insert_key(195) self.assertEqual(self.tui.input_buf, "Test: ") # incomplete byte, not added yet self.tui._handle_insert_key(169) self.assertEqual(self.tui.input_buf, "Test: é") self.assertEqual(self.tui.input_cursor, len("Test: é")) def test_path_starting_with_slash_delivers_as_message(self): """A string starting with a file path like '/var/log/syslog' is sent as message.""" line = "/var/log/syslog contains error details" sent = [] self.tui._async_send_message = lambda node, tid, text: sent.append(text) self.tui._execute_input_line(line) self.assertEqual(len(sent), 1) self.assertEqual(sent[0], line) if __name__ == "__main__": unittest.main()