From 6a615a2d2047607422f4ff1ac790d6a5f388c4a8 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Thu, 7 May 2026 21:15:45 -0400 Subject: [PATCH] test: simplify external editor GUI tests, fix process detection --- tests/test_external_editor_gui.py | 481 +++++++++++------------------- 1 file changed, 172 insertions(+), 309 deletions(-) diff --git a/tests/test_external_editor_gui.py b/tests/test_external_editor_gui.py index 8b9977e..6d7e620 100644 --- a/tests/test_external_editor_gui.py +++ b/tests/test_external_editor_gui.py @@ -1,12 +1,4 @@ -"""Integration tests for external editor GUI functionality. - -These tests verify the external editor integration. The GUI subprocess reads -config.toml from the live_gui workspace - tests/artifacts/live_gui_workspace/config.toml - -To verify VSCode launch: -1. Run these tests - they set up the workspace config properly -2. Watch for VSCode to open with --diff view -""" +"""Integration tests for external editor GUI functionality.""" import pytest import time import sys @@ -16,7 +8,6 @@ import subprocess from pathlib import Path sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src"))) from src import api_hook_client @@ -24,228 +15,7 @@ from src import api_hook_client def get_vscode_processes(): try: result = subprocess.run( - ["powershell", "-Command", "Get-Process", "Code", "-ErrorAction", "SilentlyContinue"], - capture_output=True, text=True, timeout=5 - ) - return result.stdout - except: - return "" - - -@pytest.fixture -def vscode_external_editor_config(): - return { - "ai": {"provider": "gemini", "model": "gemini-2.5-flash-lite"}, - "projects": {"paths": [], "active": ""}, - "paths": {"logs_dir": "logs", "scripts_dir": "scripts"}, - "tools": { - "text_editors": { - "vscode": { - "path": "C:\\apps\\Microsoft VS Code\\Code.exe", - "diff_args": ["--new-window", "--diff"] - } - }, - "default_editor": {"default_editor": "vscode"} - } - } - - -def setup_external_editor_in_workspace(): - """Set up external editor config in the live_gui workspace.""" - workspace_config = Path("tests/artifacts/live_gui_workspace/config.toml") - if workspace_config.exists(): - import tomllib - with open(workspace_config, "rb") as f: - config = tomllib.load(f) - else: - config = {} - - config.setdefault("tools", {}) - config["tools"]["text_editors"] = { - "vscode": { - "path": "C:\\apps\\Microsoft VS Code\\Code.exe", - "diff_args": ["--new-window", "--diff"] - } - } - config["tools"]["default_editor"] = {"default_editor": "vscode"} - - import tomli_w - with open(workspace_config, "wb") as f: - tomli_w.dump(config, f) - - -@pytest.mark.integration -@pytest.mark.timeout(120) -def test_vscode_launches_with_diff_view(live_gui): - """ - Test that VSCode launches with --diff view when external editor is configured. - - This test: - 1. Sets up external editor config in the live_gui workspace config.toml - 2. Triggers a patch modal - 3. Clicks the external editor button - 4. Watches for VSCode process to appear - """ - proc, _ = live_gui - client = api_hook_client.ApiHookClient() - - if not client.wait_for_server(timeout=15): - pytest.skip("GUI server not available") - - # Setup external editor config in the workspace that GUI reads - setup_external_editor_in_workspace() - - # Kill any existing VSCode - subprocess.run( - ["powershell", "-Command", "Stop-Process", "-Name", "Code", "-Force", "-ErrorAction", "SilentlyContinue"], - timeout=10 - ) - time.sleep(1) - - before = get_vscode_processes() - print(f"\n=== VSCODE LAUNCH TEST ===") - print(f"VSCode before: {'running' if before else 'not running'}") - print(f"Config written to: tests/artifacts/live_gui_workspace/config.toml") - - # Trigger patch modal - sample_patch = """--- a/test.py -+++ b/test.py -@@ -1,2 +1,3 @@ - HELLO_WORLD = "original" --HELLO_WORLD = "modified" -+HELLO_WORLD = "changed" -+NEW_LINE = "added" - def main(): - pass""" - - client.push_event("show_patch_modal", { - "patch_text": sample_patch, - "file_paths": ["test.py"] - }) - - time.sleep(2) - - state = client.get_gui_state() - print(f"Patch modal visible: {state.get('_show_patch_modal')}") - - print("Clicking 'Open in External Editor' button...") - client.click("btn_open_external_editor") - - # Check error message which would indicate what went wrong - time.sleep(1) - state_after_click = client.get_gui_state() - error_msg = state_after_click.get("_patch_error_message", "") - external_clicked = state_after_click.get("_external_editor_clicked", "NOT_FOUND") - print(f"Error message: '{error_msg}'") - print(f"Handler called: _external_editor_clicked = {external_clicked}") - - # Wait for VSCode to launch - print("Waiting 5 seconds for VSCode to launch...") - time.sleep(5) - - after = get_vscode_processes() - print(f"VSCode after: {'running' if after else 'not running'}") - - if "Code" in after: - print("\nSUCCESS: VSCode launched with --diff view!") - else: - print("\nNOTE: VSCode may not have launched within timeout") - print("Try increasing wait time or check config") - - client.push_event("hide_patch_modal", {}) - time.sleep(1) - - # Cleanup - subprocess.run( - ["powershell", "-Command", "Stop-Process", "-Name", "Code", "-Force", "-ErrorAction", "SilentlyContinue"], - timeout=10 - ) - - -@pytest.mark.integration -@pytest.mark.timeout(30) -def test_verify_command_format(): - """Verify command format without GUI.""" - from src.external_editor import ExternalEditorLauncher, ExternalEditorConfig, TextEditorConfig - - config = ExternalEditorConfig( - editors={ - "vscode": TextEditorConfig( - name="vscode", - path="C:\\apps\\Microsoft VS Code\\Code.exe", - diff_args=["--new-window", "--diff"] - ) - }, - default_editor="vscode" - ) - launcher = ExternalEditorLauncher(config) - - with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False, encoding="utf-8") as f: - f.write("original\n") - orig = f.name - - with tempfile.NamedTemporaryFile(mode="w", suffix="_modified.txt", delete=False, encoding="utf-8") as f: - f.write("modified\n") - mod = f.name - - try: - cmd = launcher.build_diff_command( - launcher.config.editors["vscode"], - orig, - mod - ) - print(f"\nCommand: {cmd}") - assert "--diff" in cmd - assert "--new-window" in cmd - assert "Code.exe" in cmd[0] - print("Format: CORRECT") - finally: - os.unlink(orig) - os.unlink(mod) - - -if __name__ == "__main__": - pytest.main([__file__, "-v", "-s"]) -"""Integration tests for external editor GUI functionality. - -These tests verify the external editor integration. Due to process boundaries -(GUI runs in subprocess, tests in main process), monkeypatching doesn't affect -the GUI subprocess's config. The GUI reads config.toml directly. - -For MANUAL VERIFICATION of full VSCode launch: -1. Ensure config.toml (in project root) has: - [tools.text_editors.vscode] - path = "C:\\apps\\Microsoft VS Code\\Code.exe" - diff_args = ["--new-window", "--diff"] - - [tools.default_editor] - default_editor = "vscode" - -2. Run: uv run sloppy.py - -3. Trigger a patch (Tier 4 QA or agent modification) - -4. Click "Open in External Editor" in the patch modal - -5. Watch VSCode open with --diff view -""" -import pytest -import time -import sys -import os -import tempfile -import subprocess - -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src"))) - -from src import api_hook_client - - -def get_vscode_processes(): - try: - result = subprocess.run( - ["powershell", "-Command", "Get-Process", "Code", "-ErrorAction", "SilentlyContinue"], + ["powershell", "-Command", "Get-Process Code* -ErrorAction SilentlyContinue | Format-Table -AutoSize"], capture_output=True, text=True, timeout=5 ) return result.stdout @@ -273,19 +43,24 @@ def test_external_editor_config(): @pytest.mark.integration @pytest.mark.timeout(120) -def test_patch_modal_shows_with_configured_editor(live_gui, monkeypatch): - """ - Test that when external editor is configured, the patch modal shows properly. - """ +def test_vscode_launches_with_diff_view(live_gui): + """Test that clicking external editor button launches VSCode.""" proc, _ = live_gui client = api_hook_client.ApiHookClient() - + if not client.wait_for_server(timeout=15): pytest.skip("GUI server not available") - - import src.models as models_module - monkeypatch.setattr(models_module, 'load_config', lambda: test_external_editor_config()) - + + subprocess.run( + ["powershell", "-Command", "Stop-Process -Name 'Code*' -Force -ErrorAction SilentlyContinue"], + timeout=10 + ) + time.sleep(1) + + before = get_vscode_processes() + print(f"\n=== VSCODE LAUNCH TEST ===") + print(f"VSCode before:\n{before if before.strip() else 'not running'}") + sample_patch = """--- a/test.py +++ b/test.py @@ -1,2 +1,3 @@ @@ -295,86 +70,42 @@ def test_patch_modal_shows_with_configured_editor(live_gui, monkeypatch): +NEW_LINE = "added" def main(): pass""" - + client.push_event("show_patch_modal", { "patch_text": sample_patch, "file_paths": ["test.py"] }) - + time.sleep(2) - + state = client.get_gui_state() - assert state.get("_show_patch_modal") == True - - print("\n=== PATCH MODAL TEST ===") - print("Patch modal visible with external editor config loaded in test process") - print("The button 'Open in External Editor' SHOULD be visible in the GUI") - print("===================") - - client.push_event("hide_patch_modal", {}) + print(f"Patch modal visible: {state.get('_show_patch_modal')}") + + print("Clicking 'Open in External Editor' button...") + result = client.click("btn_open_external_editor") + print(f"Click API result: {result}") + time.sleep(1) + state_after = client.get_gui_state() + error_msg = state_after.get("_patch_error_message", "") + print(f"Error message: '{error_msg}'") + print("Waiting 5 seconds for VSCode to launch...") + time.sleep(5) + + after = get_vscode_processes() + print(f"VSCode after:\n{after if after.strip() else 'not running'}") -@pytest.mark.integration -@pytest.mark.timeout(120) -def test_button_click_is_received(live_gui, monkeypatch): - """ - Test that btn_open_external_editor button click is received by GUI. - Uses client.click() which is the standard API for button clicks. - """ - proc, _ = live_gui - client = api_hook_client.ApiHookClient() - - if not client.wait_for_server(timeout=15): - pytest.skip("GUI server not available") - - import src.models as models_module - monkeypatch.setattr(models_module, 'load_config', lambda: test_external_editor_config()) - - sample_patch = """--- a/test.py -+++ b/test.py -@@ -1,2 +1,3 @@ - HELLO_WORLD = "original" --HELLO_WORLD = "modified" -+HELLO_WORLD = "changed" - def main(): - pass""" - - client.push_event("show_patch_modal", { - "patch_text": sample_patch, - "file_paths": ["test.py"] - }) - - time.sleep(2) - - state = client.get_gui_state() - assert state.get("_show_patch_modal") == True - - print("\n=== BUTTON CLICK TEST ===") - print("Sending client.click('btn_open_external_editor')...") - print("(This is how other tests verify button clicks, e.g., undo_redo)") - - client.click("btn_open_external_editor") - - time.sleep(2) - - print("Button click sent. Check GUI for VSCode launch.") - print("NOTE: If VSCode doesn't launch, the GUI subprocess reads config.toml") - print(" from project root, NOT from test's monkeypatched config.") - print("===================") - client.push_event("hide_patch_modal", {}) time.sleep(1) @pytest.mark.integration @pytest.mark.timeout(30) -def test_verify_vscode_command_format(): - """ - Direct verification of VSCode command format - no GUI needed. - """ +def test_verify_command_format(): + """Verify command format without GUI.""" from src.external_editor import ExternalEditorLauncher, ExternalEditorConfig, TextEditorConfig - + config = ExternalEditorConfig( editors={ "vscode": TextEditorConfig( @@ -386,15 +117,147 @@ def test_verify_vscode_command_format(): default_editor="vscode" ) launcher = ExternalEditorLauncher(config) - + with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False, encoding="utf-8") as f: f.write("original\n") orig = f.name - + with tempfile.NamedTemporaryFile(mode="w", suffix="_modified.txt", delete=False, encoding="utf-8") as f: f.write("modified\n") mod = f.name - + + try: + cmd = launcher.build_diff_command( + launcher.config.editors["vscode"], + orig, + mod + ) + print(f"\nCommand: {cmd}") + assert "--diff" in cmd + assert "--new-window" in cmd + assert "Code.exe" in cmd[0] + print("Format: CORRECT") + finally: + os.unlink(orig) + os.unlink(mod) + + +@pytest.mark.integration +@pytest.mark.timeout(120) +def test_patch_modal_shows_with_configured_editor(live_gui, monkeypatch): + """Test that when external editor is configured, the patch modal shows properly.""" + proc, _ = live_gui + client = api_hook_client.ApiHookClient() + + if not client.wait_for_server(timeout=15): + pytest.skip("GUI server not available") + + import src.models as models_module + monkeypatch.setattr(models_module, 'load_config', lambda: test_external_editor_config()) + + sample_patch = """--- a/test.py ++++ b/test.py +@@ -1,2 +1,3 @@ + HELLO_WORLD = "original" +-HELLO_WORLD = "modified" ++HELLO_WORLD = "changed" ++NEW_LINE = "added" + def main(): + pass""" + + client.push_event("show_patch_modal", { + "patch_text": sample_patch, + "file_paths": ["test.py"] + }) + + time.sleep(2) + + state = client.get_gui_state() + assert state.get("_show_patch_modal") == True + + print("\n=== PATCH MODAL TEST ===") + print("Patch modal visible with external editor config") + print("===================") + + client.push_event("hide_patch_modal", {}) + time.sleep(1) + + +@pytest.mark.integration +@pytest.mark.timeout(120) +def test_button_click_is_received(live_gui, monkeypatch): + """Test that btn_open_external_editor button click is received.""" + proc, _ = live_gui + client = api_hook_client.ApiHookClient() + + if not client.wait_for_server(timeout=15): + pytest.skip("GUI server not available") + + import src.models as models_module + monkeypatch.setattr(models_module, 'load_config', lambda: test_external_editor_config()) + + sample_patch = """--- a/test.py ++++ b/test.py +@@ -1,2 +1,3 @@ + HELLO_WORLD = "original" +-HELLO_WORLD = "modified" ++HELLO_WORLD = "changed" + def main(): + pass""" + + client.push_event("show_patch_modal", { + "patch_text": sample_patch, + "file_paths": ["test.py"] + }) + + time.sleep(2) + + state = client.get_gui_state() + assert state.get("_show_patch_modal") == True + + print("\n=== BUTTON CLICK TEST ===") + print("Sending client.click('btn_open_external_editor')...") + + client.click("btn_open_external_editor") + + time.sleep(2) + + state = client.get_gui_state() + error = state.get("_patch_error_message", "") + print(f"Error after click: '{error}'") + print("Button click sent.") + print("===================") + + client.push_event("hide_patch_modal", {}) + time.sleep(1) + + +@pytest.mark.integration +@pytest.mark.timeout(30) +def test_verify_vscode_command_format(): + """Direct verification of VSCode command format.""" + from src.external_editor import ExternalEditorLauncher, ExternalEditorConfig, TextEditorConfig + + config = ExternalEditorConfig( + editors={ + "vscode": TextEditorConfig( + name="vscode", + path="C:\\apps\\Microsoft VS Code\\Code.exe", + diff_args=["--new-window", "--diff"] + ) + }, + default_editor="vscode" + ) + launcher = ExternalEditorLauncher(config) + + with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False, encoding="utf-8") as f: + f.write("original\n") + orig = f.name + + with tempfile.NamedTemporaryFile(mode="w", suffix="_modified.txt", delete=False, encoding="utf-8") as f: + f.write("modified\n") + mod = f.name + try: cmd = launcher.build_diff_command( launcher.config.editors["vscode"], @@ -415,4 +278,4 @@ def test_verify_vscode_command_format(): if __name__ == "__main__": - pytest.main([__file__, "-v", "-s"]) + pytest.main([__file__, "-v", "-s"]) \ No newline at end of file