From a3b117dabd22f2c8ea3a0aa57b30e7e644834228 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Tue, 12 May 2026 19:06:54 -0400 Subject: [PATCH] feat(api): Expand API hooks with drag and right_click actions --- src/api_hook_client.py | 12 +++++++++++ src/app_controller.py | 13 ++++++++++++ tests/test_api_hook_client.py | 27 ++++++++++++++++++++++++- tests/test_process_pending_gui_tasks.py | 22 +++++++++++++++++++- 4 files changed, 72 insertions(+), 2 deletions(-) diff --git a/src/api_hook_client.py b/src/api_hook_client.py index 8465b80..a3da066 100644 --- a/src/api_hook_client.py +++ b/src/api_hook_client.py @@ -190,6 +190,18 @@ class ApiHookClient: """ return self.set_value(item, value) + def drag(self, src_item: str, dst_item: str) -> dict[str, Any]: + """ + Simulates a drag and drop operation. + """ + return self.push_event("drag", {"src_item": src_item, "dst_item": dst_item}) + + def right_click(self, item: str) -> dict[str, Any]: + """ + Simulates a right-click on an item. + """ + return self.push_event("right_click", {"item": item}) + def get_gui_state(self) -> dict[str, Any]: """ Returns the full GUI state available via the hook API. diff --git a/src/app_controller.py b/src/app_controller.py index 17296a5..da6ec13 100644 --- a/src/app_controller.py +++ b/src/app_controller.py @@ -682,6 +682,8 @@ class AppController: 'btn_rebuild_rag_index': self._rebuild_rag_index, 'btn_clear_summary_cache': self._handle_clear_summary_cache, } + self._drag_actions: dict[str, Callable[..., Any]] = {} + self._right_clickable_actions: dict[str, Callable[..., Any]] = {} self._predefined_callbacks: dict[str, Callable[..., Any]] = { '_test_callback_func_write_to_file': self._test_callback_func_write_to_file, '_set_env_var': lambda k, v: os.environ.update({k: v}), @@ -910,6 +912,17 @@ class AppController: func() except Exception: func() + elif action == "drag": + src_item = task.get("src_item") + dst_item = task.get("dst_item") + if src_item in self._drag_actions: + func = self._drag_actions[src_item] + func(dst_item=dst_item) + elif action == "right_click": + item = task.get("item") + if item in self._right_clickable_actions: + func = self._right_clickable_actions[item] + func() elif action == "select_list_item": item = task.get("listbox", task.get("item")) value = task.get("item_value", task.get("value")) diff --git a/tests/test_api_hook_client.py b/tests/test_api_hook_client.py index e411c06..834f7fd 100644 --- a/tests/test_api_hook_client.py +++ b/tests/test_api_hook_client.py @@ -81,4 +81,29 @@ def test_get_node_status() -> None: } status = client.get_node_status("T1") assert status["status"] == "todo" - mock_make.assert_any_call('GET', '/api/mma/node/T1') \ No newline at end of file + mock_make.assert_any_call('GET', '/api/mma/node/T1') + +def test_drag_success() -> None: + """Test that drag correctly sends a POST request to the /api/gui endpoint.""" + client = ApiHookClient() + with patch.object(client, '_make_request') as mock_make: + mock_make.return_value = {"status": "queued"} + res = client.drag("src_id", "dst_id") + assert res["status"] == "queued" + mock_make.assert_any_call('POST', '/api/gui', data={ + "action": "drag", + "src_item": "src_id", + "dst_item": "dst_id" + }) + +def test_right_click_success() -> None: + """Test that right_click correctly sends a POST request to the /api/gui endpoint.""" + client = ApiHookClient() + with patch.object(client, '_make_request') as mock_make: + mock_make.return_value = {"status": "queued"} + res = client.right_click("item_id") + assert res["status"] == "queued" + mock_make.assert_any_call('POST', '/api/gui', data={ + "action": "right_click", + "item": "item_id" + }) \ No newline at end of file diff --git a/tests/test_process_pending_gui_tasks.py b/tests/test_process_pending_gui_tasks.py index 9e18c48..7ede74b 100644 --- a/tests/test_process_pending_gui_tasks.py +++ b/tests/test_process_pending_gui_tasks.py @@ -1,6 +1,6 @@ from typing import Generator import pytest -from unittest.mock import patch +from unittest.mock import patch, Mock from src import ai_client from src.gui_2 import App @@ -46,3 +46,23 @@ def test_gcli_path_updates_adapter(app_instance: App) -> None: app_instance.controller._process_pending_gui_tasks() assert ai_client._gemini_cli_adapter is not None assert ai_client._gemini_cli_adapter.binary_path == '/new/path/to/gemini' + +def test_process_pending_gui_tasks_drag(app_instance: App) -> None: + """Test that the drag action is correctly processed and dispatches to the registered callback.""" + mock_callback = Mock() + app_instance.controller._drag_actions["src_id"] = mock_callback + app_instance.controller._pending_gui_tasks = [ + {"action": "drag", "src_item": "src_id", "dst_item": "dst_id"} + ] + app_instance.controller._process_pending_gui_tasks() + mock_callback.assert_called_once_with(dst_item="dst_id") + +def test_process_pending_gui_tasks_right_click(app_instance: App) -> None: + """Test that the right_click action is correctly processed and dispatches to the registered callback.""" + mock_callback = Mock() + app_instance.controller._right_clickable_actions["item_id"] = mock_callback + app_instance.controller._pending_gui_tasks = [ + {"action": "right_click", "item": "item_id"} + ] + app_instance.controller._process_pending_gui_tasks() + mock_callback.assert_called_once()