Files
clide/tests/harnesses/controller_harness.py
T
jpmschweitzerandClaude Opus 4.5 c5f8b2e615 Add Clide project structure and initial implementation
Set up the TUI IDE wrapper for Claude Code CLI with:
- Core app structure using Textual framework
- Panel architecture (sidebar, workspace, claude, context)
- Theme system with 22 built-in themes (Summer Night default)
- Pydantic models for configuration and data
- Makefile for development commands
- Project documentation and specs

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 21:04:44 +01:00

98 lines
3.1 KiB
Python

"""Controller isolation test harness for Clide."""
from typing import TYPE_CHECKING, Any, Optional
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: Optional[type] = 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: Optional[type] = 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