Files
clide/legacy/tests/harnesses/controller_harness.py
T
jpmschweitzerandClaude Opus 4.7 a355751437 move python clide to legacy/
Clide is being rebuilt as a Flutter desktop app. The Python Textual
implementation moves wholesale into legacy/ rather than being deleted:
its pane model, panel set, git skills, and panel communication design
are real thought that should remain readable next to the new code
while the rebuild finds its shape. Git's rename tracking preserves
history, so `git log -- legacy/` still works.

The Flutter rebuild lives at the repo root alongside a Go sidecar
(the architecture claudian was heading toward, which folds into
clide as a core component rather than a separate plugin project).
Bootstrap of the new shape lands in subsequent commits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 20:30:51 +02:00

98 lines
3.1 KiB
Python

"""Controller isolation test harness for Clide."""
from typing import TYPE_CHECKING, Any
from unittest.mock import AsyncMock, MagicMock
from textual.message import Message
if TYPE_CHECKING:
from clide.controllers.base import BaseController
class MockApp:
"""Minimal mock of a Textual App for controller testing."""
def __init__(self) -> None:
self.messages: list[Message] = []
self.post_message = MagicMock(side_effect=self._capture_message)
def _capture_message(self, message: Message) -> None:
self.messages.append(message)
def get_messages(self, message_type: type | None = None) -> list[Message]:
"""Get captured messages, optionally filtered by type."""
if message_type is None:
return self.messages.copy()
return [m for m in self.messages if isinstance(m, message_type)]
def clear_messages(self) -> None:
"""Clear captured messages."""
self.messages.clear()
class ControllerHarness:
"""Test harness for isolated controller testing.
Provides a mock app environment for testing controllers without
the full Textual application overhead.
Usage:
harness = ControllerHarness()
controller = GitController(harness.mock_app)
await controller.initialize()
await controller.refresh_status()
messages = harness.get_messages()
"""
def __init__(self) -> None:
self._mock_app = MockApp()
self._controllers: list[BaseController] = []
self._mocks: dict[str, Any] = {}
@property
def mock_app(self) -> MockApp:
"""Get the mock app for controller injection."""
return self._mock_app
def register_controller(self, controller: "BaseController") -> None:
"""Register a controller for lifecycle management."""
self._controllers.append(controller)
async def initialize_all(self) -> None:
"""Initialize all registered controllers."""
for controller in self._controllers:
await controller.initialize()
async def shutdown_all(self) -> None:
"""Shutdown all registered controllers."""
for controller in self._controllers:
await controller.shutdown()
def get_messages(self, message_type: type | None = None) -> list[Message]:
"""Get messages posted to the mock app."""
return self._mock_app.get_messages(message_type)
def clear_messages(self) -> None:
"""Clear all captured messages."""
self._mock_app.clear_messages()
def add_mock(self, name: str, mock: Any) -> None:
"""Add a named mock for dependency injection.
Args:
name: Identifier for the mock
mock: Mock object or AsyncMock
"""
self._mocks[name] = mock
def get_mock(self, name: str) -> Any:
"""Retrieve a named mock."""
return self._mocks.get(name)
def create_async_mock(self, return_value: Any = None) -> AsyncMock:
"""Create an AsyncMock with optional return value."""
mock = AsyncMock()
if return_value is not None:
mock.return_value = return_value
return mock