diff --git a/tests/conftest.py b/tests/conftest.py index 4157d86..2180493 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -213,6 +213,15 @@ def live_gui() -> Generator[tuple[subprocess.Popen, str], None, None]: 'paths': { 'logs_dir': str((temp_workspace / "logs").absolute()), 'scripts_dir': str((temp_workspace / "scripts" / "generated").absolute()) + }, + 'tools': { + 'text_editors': { + 'vscode': { + 'path': 'C:\\apps\\Microsoft VS Code\\Code.exe', + 'diff_args': ['--new-window', '--diff'] + } + }, + 'default_editor': {'default_editor': 'vscode'} } } import tomli_w diff --git a/tests/test_external_editor_gui.py b/tests/test_external_editor_gui.py index 5084615..8b9977e 100644 --- a/tests/test_external_editor_gui.py +++ b/tests/test_external_editor_gui.py @@ -1,5 +1,213 @@ """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 +""" +import pytest +import time +import sys +import os +import tempfile +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 + + +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.