2026-10-06 07:55:27 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
"""
|
|
|
|
|
test_prompts.py — Comprehensive unit and integration test suite for:
|
|
|
|
|
1. PromptManager persistence, defaults, formulation, deletion, and pin sorting.
|
|
|
|
|
2. Compact persistent prompt shelf in View 1 (Agent Chat) and 1-click auto-fill.
|
|
|
|
|
3. Expanded Prompt Library modal ('P') with Vim hjkl navigation, preview scroll, and Enter/click fill.
|
|
|
|
|
4. Searchable chat history sends ('s' / Ctrl-R) aggregating session and history cache.
|
|
|
|
|
5. 'p' pinning chat sent messages to the prompt fill / list in history search and transcript view.
|
|
|
|
|
6. Horizontal scrolling of prompt shelf via chips bounds, navigation buttons, wheel, and '[' / ']'.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import unittest
|
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
|
import importlib.util
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
import tempfile
|
|
|
|
|
import time
|
|
|
|
|
import json
|
|
|
|
|
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 TestPromptManager(unittest.TestCase):
|
|
|
|
|
"""Test suite covering PromptManager storage, formulation, and pin ranking."""
|
|
|
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
|
self.temp_dir = tempfile.TemporaryDirectory()
|
|
|
|
|
self.storage_file = Path(self.temp_dir.name) / "test_prompts.json"
|
|
|
|
|
self.pm = muse_mod.PromptManager(storage_path=self.storage_file)
|
|
|
|
|
|
|
|
|
|
def tearDown(self):
|
|
|
|
|
self.temp_dir.cleanup()
|
|
|
|
|
|
|
|
|
|
def test_default_prompts_seeded_and_persisted(self):
|
|
|
|
|
"""Verify PromptManager initializes with default prompts and writes to disk."""
|
|
|
|
|
self.assertTrue(self.storage_file.exists())
|
|
|
|
|
prompts = self.pm.get_sorted_prompts()
|
|
|
|
|
self.assertGreaterEqual(len(prompts), 5)
|
|
|
|
|
titles = [p["title"] for p in prompts]
|
|
|
|
|
self.assertIn("Fleet Audit", titles)
|
|
|
|
|
self.assertIn("Work Order", titles)
|
|
|
|
|
self.assertIn("CDP Inspect", titles)
|
|
|
|
|
|
|
|
|
|
def test_add_and_delete_prompt(self):
|
|
|
|
|
"""Verify formulation of custom prompt skills and subsequent deletion."""
|
|
|
|
|
new_p = self.pm.add_prompt(
|
|
|
|
|
title="Custom Health Probe",
|
|
|
|
|
prompt_text="Run detailed tcpdump and box health probes on all nodes.",
|
|
|
|
|
tags=["probe", "custom"],
|
|
|
|
|
pinned=True
|
|
|
|
|
)
|
|
|
|
|
self.assertEqual(new_p["title"], "Custom Health Probe")
|
|
|
|
|
self.assertTrue(new_p["pinned"])
|
|
|
|
|
|
|
|
|
|
# Check persistence
|
|
|
|
|
reloaded = muse_mod.PromptManager(storage_path=self.storage_file)
|
|
|
|
|
prompts = reloaded.get_sorted_prompts()
|
|
|
|
|
self.assertTrue(any(p["id"] == new_p["id"] for p in prompts))
|
|
|
|
|
|
|
|
|
|
# Delete
|
|
|
|
|
self.assertTrue(self.pm.delete_prompt(new_p["id"]))
|
|
|
|
|
prompts_after = self.pm.get_sorted_prompts()
|
|
|
|
|
self.assertFalse(any(p["id"] == new_p["id"] for p in prompts_after))
|
|
|
|
|
|
|
|
|
|
def test_toggle_pin_prompt(self):
|
|
|
|
|
"""Verify toggle_pin_prompt alters pinned flag and affects sorted order."""
|
|
|
|
|
prompts = self.pm.get_sorted_prompts()
|
|
|
|
|
target = prompts[-1] # Get unpinned prompt
|
|
|
|
|
target_id = target["id"]
|
|
|
|
|
|
|
|
|
|
new_state = self.pm.toggle_pin_prompt(target_id)
|
|
|
|
|
self.assertTrue(new_state)
|
|
|
|
|
|
|
|
|
|
# In sorted prompts, newly pinned should be among first
|
|
|
|
|
sorted_p = self.pm.get_sorted_prompts()
|
|
|
|
|
first_few_ids = [p["id"] for p in sorted_p[:4]]
|
|
|
|
|
self.assertIn(target_id, first_few_ids)
|
|
|
|
|
|
|
|
|
|
def test_pin_chat_send(self):
|
|
|
|
|
"""Verify pinning a chat sent message formats title, pins to top, and persists."""
|
|
|
|
|
sent_text = "Investigate high CDP drop rates and verify routing table on node 646."
|
|
|
|
|
prompt, is_new = self.pm.pin_chat_send(sent_text, node="646")
|
|
|
|
|
self.assertTrue(is_new)
|
|
|
|
|
self.assertTrue(prompt["pinned"])
|
|
|
|
|
self.assertEqual(prompt["prompt"], sent_text)
|
|
|
|
|
self.assertIn("Investigate high CDP", prompt["title"])
|
|
|
|
|
|
|
|
|
|
# Pinned chat send should be first in sorted prompts
|
|
|
|
|
sorted_p = self.pm.get_sorted_prompts()
|
|
|
|
|
self.assertEqual(sorted_p[0]["id"], prompt["id"])
|
|
|
|
|
|
|
|
|
|
# Pinning same text again should mark pinned without creating duplicate
|
|
|
|
|
p2, is_new2 = self.pm.pin_chat_send(sent_text, node="646")
|
|
|
|
|
self.assertFalse(is_new2)
|
|
|
|
|
self.assertEqual(p2["id"], prompt["id"])
|
|
|
|
|
|
|
|
|
|
def test_toggle_pin_chat_send(self):
|
|
|
|
|
"""Verify toggle_pin_chat_send flips pinned state between True and False."""
|
|
|
|
|
sent_text = "Investigate high CDP drop rates and verify routing table."
|
|
|
|
|
p1, is_new = self.pm.toggle_pin_chat_send(sent_text, node="646")
|
|
|
|
|
self.assertTrue(is_new)
|
|
|
|
|
self.assertTrue(p1["pinned"])
|
|
|
|
|
|
|
|
|
|
# Toggle again: should unpin
|
|
|
|
|
p2, is_new2 = self.pm.toggle_pin_chat_send(sent_text, node="646")
|
|
|
|
|
self.assertFalse(is_new2)
|
|
|
|
|
self.assertFalse(p2["pinned"])
|
|
|
|
|
|
|
|
|
|
# Toggle again: should re-pin
|
|
|
|
|
p3, is_new3 = self.pm.toggle_pin_chat_send(sent_text, node="646")
|
|
|
|
|
self.assertFalse(is_new3)
|
|
|
|
|
self.assertTrue(p3["pinned"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestPromptTUIIntegration(unittest.TestCase):
|
|
|
|
|
"""Test suite covering TUI compact shelf, click auto-fill, modals, and sends search."""
|
|
|
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
|
self.temp_dir = tempfile.TemporaryDirectory()
|
|
|
|
|
self.storage_file = Path(self.temp_dir.name) / "test_prompts.json"
|
|
|
|
|
|
|
|
|
|
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.data.stop()
|
|
|
|
|
self.tui.safe_addstr = MagicMock()
|
|
|
|
|
|
|
|
|
|
# Replace prompt manager with isolated instance
|
|
|
|
|
self.tui.prompt_manager = muse_mod.PromptManager(storage_path=self.storage_file)
|
|
|
|
|
self.tui.prompts = self.tui.prompt_manager.get_sorted_prompts()
|
|
|
|
|
|
|
|
|
|
# Clear history cache to isolate unit test data
|
|
|
|
|
self.tui.data.history_cache.clear()
|
|
|
|
|
|
|
|
|
|
# Seed sample chat history
|
|
|
|
|
self.tui.data.history_cache[("muse", "thread_1")] = [
|
|
|
|
|
{"role": "user", "text": "Check all docker network bridges.", "message_id": "m1"},
|
|
|
|
|
{"role": "assistant", "text": "All docker network bridges are operational.", "message_id": "m2"},
|
|
|
|
|
{"role": "user", "text": "Inspect CDP latency on pip and opm.", "message_id": "m3"},
|
|
|
|
|
]
|
|
|
|
|
self.tui.data.active_node = "muse"
|
|
|
|
|
self.tui.data.active_thread_id = "thread_1"
|
|
|
|
|
self.tui.data.active_thread_title = "thread_1"
|
|
|
|
|
self.tui.input_history = ["Recent typed command from session"]
|
|
|
|
|
|
|
|
|
|
def tearDown(self):
|
|
|
|
|
self.temp_dir.cleanup()
|
|
|
|
|
|
|
|
|
|
def test_compact_prompt_shelf_render_and_chip_bounds(self):
|
|
|
|
|
"""Verify _render_prompt_shelf populates chip bounds and button boundaries."""
|
|
|
|
|
self.tui._render_prompt_shelf(y=24, x=0, h=2, w=100)
|
|
|
|
|
bounds = self.tui._prompt_chip_bounds
|
|
|
|
|
self.assertGreater(len(bounds), 0, "Prompt chips should be registered with bounding boxes")
|
|
|
|
|
|
|
|
|
|
first_start, first_end, first_prompt = bounds[0]
|
|
|
|
|
self.assertGreater(first_end, first_start)
|
|
|
|
|
self.assertIn("title", first_prompt)
|
|
|
|
|
self.assertTrue(hasattr(self.tui, "_btn_expand_bounds"))
|
|
|
|
|
self.assertTrue(hasattr(self.tui, "_btn_sends_bounds"))
|
|
|
|
|
|
|
|
|
|
def test_one_click_auto_fill_from_shelf(self):
|
|
|
|
|
"""Verify clicking a prompt chip immediately auto-fills input_buf and sets INSERT mode."""
|
|
|
|
|
self.tui._render_prompt_shelf(y=24, x=0, h=2, w=100)
|
|
|
|
|
first_start, first_end, first_prompt = self.tui._prompt_chip_bounds[0]
|
|
|
|
|
|
|
|
|
|
# Simulate click on chip
|
|
|
|
|
click_x = (first_start + first_end) // 2
|
|
|
|
|
click_y = 25 # Row 1 of prompt shelf (chip row)
|
|
|
|
|
res = self.tui._handle_mouse(mx=click_x, my=click_y, bstate=curses.BUTTON1_CLICKED)
|
|
|
|
|
self.assertTrue(res)
|
|
|
|
|
|
|
|
|
|
# Input buffer should contain prompt text
|
|
|
|
|
self.assertEqual(self.tui.input_buf, first_prompt["prompt"])
|
|
|
|
|
self.assertEqual(self.tui.input_cursor, len(first_prompt["prompt"]))
|
|
|
|
|
self.assertEqual(self.tui.editor_mode, "INSERT")
|
|
|
|
|
self.assertIn("Filled prompt", self.tui.toast_msg)
|
|
|
|
|
|
|
|
|
|
def test_expand_prompts_modal_and_vim_navigation(self):
|
|
|
|
|
"""Verify pressing 'P' expands modal and hjkl keys navigate and Enter fills."""
|
|
|
|
|
# Press capital 'P' in NORMAL mode
|
|
|
|
|
self.tui.editor_mode = "NORMAL"
|
|
|
|
|
self.tui.focus_pane = "transcript"
|
|
|
|
|
res = self.tui._handle_key(ord('P'))
|
|
|
|
|
self.assertTrue(res)
|
|
|
|
|
self.assertEqual(self.tui.modal, "prompts")
|
|
|
|
|
self.assertEqual(self.tui.prompt_sel_idx, 0)
|
|
|
|
|
|
|
|
|
|
# 'j' moves down
|
|
|
|
|
self.tui._handle_key(ord('j'))
|
|
|
|
|
self.assertEqual(self.tui.prompt_sel_idx, 1)
|
|
|
|
|
|
|
|
|
|
# 'k' moves up
|
|
|
|
|
self.tui._handle_key(ord('k'))
|
|
|
|
|
self.assertEqual(self.tui.prompt_sel_idx, 0)
|
|
|
|
|
|
|
|
|
|
# 'G' jumps to last
|
|
|
|
|
self.tui._handle_key(ord('G'))
|
|
|
|
|
self.assertEqual(self.tui.prompt_sel_idx, len(self.tui.prompts) - 1)
|
|
|
|
|
|
|
|
|
|
# 'g' jumps to first
|
|
|
|
|
self.tui._handle_key(ord('g'))
|
|
|
|
|
self.assertEqual(self.tui.prompt_sel_idx, 0)
|
|
|
|
|
|
|
|
|
|
# Enter fills selected prompt and switches to INSERT
|
|
|
|
|
expected_text = self.tui.prompts[0]["prompt"]
|
|
|
|
|
self.tui._handle_key(10) # Enter
|
|
|
|
|
self.assertIsNone(self.tui.modal)
|
|
|
|
|
self.assertEqual(self.tui.input_buf, expected_text)
|
|
|
|
|
self.assertEqual(self.tui.editor_mode, "INSERT")
|
|
|
|
|
|
|
|
|
|
def test_searchable_chat_history_sends(self):
|
|
|
|
|
"""Verify get_chat_sends aggregates past sends and filters with query."""
|
|
|
|
|
sends = self.tui.get_chat_sends()
|
|
|
|
|
self.assertGreaterEqual(len(sends), 3)
|
|
|
|
|
texts = [s["text"] for s in sends]
|
|
|
|
|
self.assertIn("Recent typed command from session", texts)
|
|
|
|
|
self.assertIn("Check all docker network bridges.", texts)
|
|
|
|
|
self.assertIn("Inspect CDP latency on pip and opm.", texts)
|
|
|
|
|
|
|
|
|
|
# Filter query
|
|
|
|
|
filtered = self.tui.get_chat_sends("docker")
|
|
|
|
|
self.assertEqual(len(filtered), 1)
|
|
|
|
|
self.assertEqual(filtered[0]["text"], "Check all docker network bridges.")
|
|
|
|
|
|
|
|
|
|
def test_pin_chat_send_to_prompt_fill_in_history_search(self):
|
|
|
|
|
"""Verify 'p' in history_search toggles pin and unpin for highlighted send."""
|
|
|
|
|
self.tui.modal = "history_search"
|
|
|
|
|
self.tui.modal_input_buf = ""
|
|
|
|
|
self.tui.history_search_sel = 0
|
|
|
|
|
|
|
|
|
|
initial_prompts_count = len(self.tui.prompts)
|
|
|
|
|
sends = self.tui.get_chat_sends()
|
|
|
|
|
target_send_text = sends[0]["text"]
|
|
|
|
|
|
|
|
|
|
# Press 'p': 1st time pins
|
|
|
|
|
res = self.tui._handle_key(ord('p'))
|
|
|
|
|
self.assertTrue(res)
|
|
|
|
|
|
|
|
|
|
self.assertEqual(len(self.tui.prompts), initial_prompts_count + 1)
|
|
|
|
|
top_prompt = self.tui.prompts[0]
|
|
|
|
|
self.assertTrue(top_prompt["pinned"])
|
|
|
|
|
self.assertEqual(top_prompt["prompt"], target_send_text)
|
|
|
|
|
self.assertIn("Pinned", self.tui.toast_msg)
|
|
|
|
|
|
|
|
|
|
# Press 'p': 2nd time UNPINS
|
|
|
|
|
res2 = self.tui._handle_key(ord('p'))
|
|
|
|
|
self.assertTrue(res2)
|
|
|
|
|
|
|
|
|
|
target_prompt_obj = [p for p in self.tui.prompt_manager.prompts if p["prompt"] == target_send_text][0]
|
|
|
|
|
self.assertFalse(target_prompt_obj["pinned"])
|
|
|
|
|
self.assertIn("Unpinned", self.tui.toast_msg)
|
|
|
|
|
|
|
|
|
|
# Press 'p': 3rd time RE-PINS
|
|
|
|
|
res3 = self.tui._handle_key(ord('p'))
|
|
|
|
|
self.assertTrue(res3)
|
|
|
|
|
self.assertTrue(target_prompt_obj["pinned"])
|
|
|
|
|
self.assertIn("Pinned", self.tui.toast_msg)
|
|
|
|
|
|
|
|
|
|
def test_history_search_mouse_toggle_pin(self):
|
|
|
|
|
"""Verify clicking the pin column in history_search toggles pin/unpin."""
|
|
|
|
|
self.tui.modal = "history_search"
|
|
|
|
|
self.tui.modal_input_buf = ""
|
|
|
|
|
self.tui.history_search_sel = 0
|
|
|
|
|
|
|
|
|
|
sends = self.tui.get_chat_sends()
|
|
|
|
|
target_text = sends[0]["text"]
|
|
|
|
|
|
|
|
|
|
h, w = self.tui.stdscr.getmaxyx()
|
2026-10-07 00:25:46 +00:00
|
|
|
modal_w = min(84 if self.tui.modal in ("prompts", "history_search") else 74, w - 6)
|
|
|
|
|
modal_h = min(22 if self.tui.modal in ("prompts", "history_search") else 20, h - 4)
|
2026-10-06 07:55:27 +00:00
|
|
|
top_y = (h - modal_h) // 2
|
|
|
|
|
left_x = (w - modal_w) // 2
|
|
|
|
|
|
|
|
|
|
# 1st click pins
|
|
|
|
|
click_y = top_y + 4
|
|
|
|
|
click_x = left_x + 4
|
|
|
|
|
handled = self.tui._handle_mouse(click_x, click_y, curses.BUTTON1_CLICKED)
|
|
|
|
|
self.assertTrue(handled)
|
|
|
|
|
target_prompt = [p for p in self.tui.prompt_manager.prompts if p["prompt"] == target_text][0]
|
|
|
|
|
self.assertTrue(target_prompt["pinned"])
|
|
|
|
|
self.assertIn("Pinned", self.tui.toast_msg)
|
|
|
|
|
|
|
|
|
|
# 2nd click unpins
|
|
|
|
|
handled2 = self.tui._handle_mouse(click_x, click_y, curses.BUTTON1_CLICKED)
|
|
|
|
|
self.assertTrue(handled2)
|
|
|
|
|
self.assertFalse(target_prompt["pinned"])
|
|
|
|
|
self.assertIn("Unpinned", self.tui.toast_msg)
|
|
|
|
|
|
|
|
|
|
def test_transcript_view_p_pins_latest_send(self):
|
|
|
|
|
"""Verify pressing 'p' on transcript pins the current thread's latest sent message."""
|
|
|
|
|
self.tui.modal = None
|
|
|
|
|
self.tui.editor_mode = "NORMAL"
|
|
|
|
|
self.tui.focus_pane = "transcript"
|
|
|
|
|
|
|
|
|
|
initial_count = len(self.tui.prompts)
|
|
|
|
|
# In current thread, latest user message is "Inspect CDP latency on pip and opm."
|
|
|
|
|
res = self.tui._handle_key(ord('p'))
|
|
|
|
|
self.assertTrue(res)
|
|
|
|
|
|
|
|
|
|
self.assertEqual(len(self.tui.prompts), initial_count + 1)
|
|
|
|
|
self.assertEqual(self.tui.prompts[0]["prompt"], "Inspect CDP latency on pip and opm.")
|
|
|
|
|
self.assertTrue(self.tui.prompts[0]["pinned"])
|
|
|
|
|
self.assertIn("Pinned", self.tui.toast_msg)
|
|
|
|
|
|
|
|
|
|
def test_sidebar_p_still_toggles_thread_pin(self):
|
|
|
|
|
"""Verify pressing 'p' when focus is on sidebar preserves thread pinning."""
|
|
|
|
|
self.tui.modal = None
|
|
|
|
|
self.tui.editor_mode = "NORMAL"
|
|
|
|
|
self.tui.focus_pane = "sidebar"
|
|
|
|
|
|
|
|
|
|
# Ensure thread_1 is unpinned initially
|
|
|
|
|
self.tui.data.pinned_threads.setdefault("muse", set()).discard("thread_1")
|
|
|
|
|
|
|
|
|
|
# Mock threads cache
|
|
|
|
|
self.tui.data.threads_cache["muse"] = [
|
|
|
|
|
{"session_id": "thread_1", "title": "Test Chat", "is_main": False}
|
|
|
|
|
]
|
|
|
|
|
self.tui.thread_sel_idx = 0
|
|
|
|
|
|
|
|
|
|
res = self.tui._handle_key(ord('p'))
|
|
|
|
|
self.assertTrue(res)
|
|
|
|
|
self.assertIn("thread_1", self.tui.data.pinned_threads.get("muse", set()))
|
|
|
|
|
self.assertIn("Pinned chat", self.tui.toast_msg)
|
|
|
|
|
|
|
|
|
|
# Clean up disk state
|
|
|
|
|
self.tui.data.toggle_pin_thread("muse", "thread_1")
|
|
|
|
|
|
|
|
|
|
def test_bracket_keys_scroll_prompt_shelf(self):
|
|
|
|
|
"""Verify '[' and ']' scroll the compact prompt shelf horizontally."""
|
|
|
|
|
self.tui.editor_mode = "NORMAL"
|
|
|
|
|
self.tui.focus_pane = "transcript"
|
|
|
|
|
self.tui.prompt_scroll_idx = 0
|
|
|
|
|
|
|
|
|
|
self.tui._handle_key(ord(']'))
|
|
|
|
|
self.assertEqual(self.tui.prompt_scroll_idx, 1)
|
|
|
|
|
|
|
|
|
|
self.tui._handle_key(ord('['))
|
|
|
|
|
self.assertEqual(self.tui.prompt_scroll_idx, 0)
|
|
|
|
|
|
|
|
|
|
def test_slash_prompt_and_sends_commands(self):
|
|
|
|
|
"""Verify /prompt save and /sends slash commands formulate and open modals."""
|
|
|
|
|
self.tui.input_buf = "Check loop telemetry on all nodes"
|
|
|
|
|
self.tui._execute_input_line("/prompt save Telemetry Check")
|
|
|
|
|
|
|
|
|
|
prompts = self.tui.prompt_manager.get_sorted_prompts()
|
|
|
|
|
saved = [p for p in prompts if p["title"] == "Telemetry Check"]
|
|
|
|
|
self.assertEqual(len(saved), 1)
|
|
|
|
|
self.assertEqual(saved[0]["prompt"], "Check loop telemetry on all nodes")
|
|
|
|
|
|
|
|
|
|
# /prompt add
|
|
|
|
|
self.tui._execute_input_line("/prompt add Quick Probe | ping all nodes")
|
|
|
|
|
saved_add = [p for p in self.tui.prompt_manager.get_sorted_prompts() if p["title"] == "Quick Probe"]
|
|
|
|
|
self.assertEqual(len(saved_add), 1)
|
|
|
|
|
self.assertEqual(saved_add[0]["prompt"], "ping all nodes")
|
|
|
|
|
|
|
|
|
|
# /prompt del
|
|
|
|
|
self.tui._execute_input_line(f"/prompt del {saved_add[0]['id']}")
|
|
|
|
|
saved_del = [p for p in self.tui.prompt_manager.get_sorted_prompts() if p["title"] == "Quick Probe"]
|
|
|
|
|
self.assertEqual(len(saved_del), 0)
|
|
|
|
|
|
|
|
|
|
# /sends opens history search
|
|
|
|
|
self.tui._execute_input_line("/sends docker")
|
|
|
|
|
self.assertEqual(self.tui.modal, "history_search")
|
|
|
|
|
self.assertEqual(self.tui.modal_input_buf, "docker")
|
|
|
|
|
|
|
|
|
|
def test_shelf_buttons_mouse_clicks(self):
|
|
|
|
|
"""Verify clicking buttons on row 0 of prompt shelf opens modals and scrolls."""
|
|
|
|
|
self.tui._render_prompt_shelf(y=24, x=0, h=2, w=100)
|
|
|
|
|
|
|
|
|
|
# Click [P:Expand]
|
|
|
|
|
exp_x = (self.tui._btn_expand_bounds[0] + self.tui._btn_expand_bounds[1]) // 2
|
|
|
|
|
self.tui._handle_mouse(mx=exp_x, my=24, bstate=curses.BUTTON1_CLICKED)
|
|
|
|
|
self.assertEqual(self.tui.modal, "prompts")
|
|
|
|
|
self.tui.modal = None
|
|
|
|
|
|
|
|
|
|
# Click [🔍 Sends]
|
|
|
|
|
sends_x = (self.tui._btn_sends_bounds[0] + self.tui._btn_sends_bounds[1]) // 2
|
|
|
|
|
self.tui._handle_mouse(mx=sends_x, my=24, bstate=curses.BUTTON1_CLICKED)
|
|
|
|
|
self.assertEqual(self.tui.modal, "history_search")
|
|
|
|
|
self.tui.modal = None
|
|
|
|
|
|
|
|
|
|
# Click [►]
|
|
|
|
|
next_x = (self.tui._btn_next_bounds[0] + self.tui._btn_next_bounds[1]) // 2
|
|
|
|
|
self.tui.prompt_scroll_idx = 0
|
|
|
|
|
self.tui._handle_mouse(mx=next_x, my=24, bstate=curses.BUTTON1_CLICKED)
|
|
|
|
|
self.assertEqual(self.tui.prompt_scroll_idx, 1)
|
|
|
|
|
|
|
|
|
|
# Click [◄]
|
|
|
|
|
prev_x = (self.tui._btn_prev_bounds[0] + self.tui._btn_prev_bounds[1]) // 2
|
|
|
|
|
self.tui._handle_mouse(mx=prev_x, my=24, bstate=curses.BUTTON1_CLICKED)
|
|
|
|
|
self.assertEqual(self.tui.prompt_scroll_idx, 0)
|
|
|
|
|
|
|
|
|
|
def test_mouse_wheel_scrolling_on_shelf(self):
|
|
|
|
|
"""Verify mouse wheel up/down over prompt shelf scrolls chips."""
|
|
|
|
|
self.tui._render_prompt_shelf(y=24, x=0, h=2, w=100)
|
|
|
|
|
self.tui.prompt_scroll_idx = 0
|
|
|
|
|
|
|
|
|
|
# Wheel down over prompt shelf (my=25)
|
|
|
|
|
self.tui._handle_mouse(mx=50, my=25, bstate=0x200000)
|
|
|
|
|
self.assertEqual(self.tui.prompt_scroll_idx, 1)
|
|
|
|
|
|
|
|
|
|
# Wheel up over prompt shelf (my=25)
|
|
|
|
|
self.tui._handle_mouse(mx=50, my=25, bstate=0x10000)
|
|
|
|
|
self.assertEqual(self.tui.prompt_scroll_idx, 0)
|
|
|
|
|
|
|
|
|
|
def test_prompt_modal_pin_toggle_and_delete(self):
|
|
|
|
|
"""Verify 'p' toggles pin and 'd' deletes in prompt modal."""
|
|
|
|
|
self.tui.modal = "prompts"
|
|
|
|
|
self.tui.prompt_sel_idx = 0
|
|
|
|
|
target = self.tui.prompts[0]
|
|
|
|
|
initial_pin = target.get("pinned", False)
|
|
|
|
|
|
|
|
|
|
# Press 'p' to toggle pin
|
|
|
|
|
self.tui._handle_key(ord('p'))
|
|
|
|
|
toggled = [p for p in self.tui.prompts if p["id"] == target["id"]][0]
|
|
|
|
|
self.assertEqual(toggled.get("pinned", False), not initial_pin)
|
|
|
|
|
|
|
|
|
|
# Now select the prompt and press 'd' to delete
|
|
|
|
|
target_idx = [i for i, p in enumerate(self.tui.prompts) if p["id"] == target["id"]][0]
|
|
|
|
|
self.tui.prompt_sel_idx = target_idx
|
|
|
|
|
self.tui._handle_key(ord('d'))
|
|
|
|
|
self.assertFalse(any(p["id"] == target["id"] for p in self.tui.prompts))
|
|
|
|
|
|
|
|
|
|
def test_formulate_prompt_modal(self):
|
|
|
|
|
"""Verify prompt_formulate modal creates and saves new prompt."""
|
|
|
|
|
self.tui.modal = "prompt_formulate"
|
|
|
|
|
self.tui.input_buf = "Draft message from input composer"
|
|
|
|
|
self.tui.modal_input_buf = "New Diagnostic"
|
|
|
|
|
self.tui.modal_input_cursor = len(self.tui.modal_input_buf)
|
|
|
|
|
|
|
|
|
|
# Press Enter
|
|
|
|
|
self.tui._handle_key(10)
|
|
|
|
|
self.assertIsNone(self.tui.modal)
|
|
|
|
|
|
|
|
|
|
# Check prompt exists in manager
|
|
|
|
|
matching = [p for p in self.tui.prompts if p["title"] == "New Diagnostic"]
|
|
|
|
|
self.assertEqual(len(matching), 1)
|
|
|
|
|
self.assertEqual(matching[0]["prompt"], "Draft message from input composer")
|
|
|
|
|
self.assertTrue(matching[0]["pinned"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
unittest.main()
|