"""Shared TUI mock-Textual infrastructure helpers for step-definition files.""" from __future__ import annotations import importlib import shutil import sys import tempfile from pathlib import Path from types import ModuleType from typing import Any, cast from unittest.mock import MagicMock _MOCK_TEXTUAL_KEYS = [ "textual", "textual.app", "textual.containers", "textual.widgets", ] def _build_mock_textual(): """Build mock textual modules that satisfy the app import gate.""" mock_textual = ModuleType("textual") mock_textual_app = ModuleType("textual.app") mock_textual_containers = ModuleType("textual.containers") mock_textual_widgets = ModuleType("textual.widgets") class MockApp: """Minimal App stand-in for the Textual base class.""" def __init__(self, *args, **kwargs): self._widgets = {} def query_one(self, selector, widget_type=None): if selector in self._widgets: return self._widgets[selector] if widget_type is not None: widget = widget_type(id=selector.lstrip("#")) self._widgets[selector] = widget return widget return MagicMock() class MockVertical: def __init__(self, *args, **kwargs): pass def __enter__(self): return self def __exit__(self, *args): pass class MockHeader: def __init__(self, *args, **kwargs): pass class MockFooter: def __init__(self, *args, **kwargs): pass class MockStatic: def __init__(self, *args, **kwargs): self._text = "" self.display = False self._classes: set[str] = set() def update(self, text): self._text = text def add_class(self, name): self._classes.add(name) def remove_class(self, name): self._classes.discard(name) def has_class(self, name): return name in self._classes class MockInput: """Minimal Input stand-in for the Textual base class.""" value = "" def __init__(self, *args, **kwargs): self.value = "" self._classes: set[str] = set() def add_class(self, name): self._classes.add(name) def remove_class(self, name): self._classes.discard(name) def has_class(self, name): return name in self._classes cast(Any, mock_textual_app).App = MockApp cast(Any, mock_textual_containers).Vertical = MockVertical cast(Any, mock_textual_widgets).Header = MockHeader cast(Any, mock_textual_widgets).Footer = MockFooter cast(Any, mock_textual_widgets).Static = MockStatic cast(Any, mock_textual_widgets).Input = MockInput return { "textual": mock_textual, "textual.app": mock_textual_app, "textual.containers": mock_textual_containers, "textual.widgets": mock_textual_widgets, } def _install_mock_textual(context): """Inject mock textual into sys.modules and reload the app module.""" mocks = _build_mock_textual() context._tui_saved_modules = {} for key in _MOCK_TEXTUAL_KEYS: context._tui_saved_modules[key] = sys.modules.pop(key, None) for key, mod in mocks.items(): sys.modules[key] = mod # Reload widget modules so they pick up the mock Static/Input base class import cleveragents.tui.widgets.help_panel_overlay as hp_mod import cleveragents.tui.widgets.persona_bar as pb_mod import cleveragents.tui.widgets.prompt as prompt_mod import cleveragents.tui.widgets.reference_picker as rp_mod import cleveragents.tui.widgets.slash_command_overlay as sco_mod importlib.reload(hp_mod) importlib.reload(pb_mod) importlib.reload(prompt_mod) importlib.reload(rp_mod) importlib.reload(sco_mod) import cleveragents.tui.app as app_mod importlib.reload(app_mod) context._tui_app_mod = app_mod context._tui_mock_static = mocks["textual.widgets"].Static def _restore_modules(context): """Restore original sys.modules and reload the app module.""" for key, val in getattr(context, "_tui_saved_modules", {}).items(): if val is None: sys.modules.pop(key, None) else: sys.modules[key] = val # Reload widget modules so they pick up the real Static/Input base class again import cleveragents.tui.widgets.help_panel_overlay as hp_mod import cleveragents.tui.widgets.persona_bar as pb_mod import cleveragents.tui.widgets.prompt as prompt_mod import cleveragents.tui.widgets.reference_picker as rp_mod import cleveragents.tui.widgets.slash_command_overlay as sco_mod importlib.reload(hp_mod) importlib.reload(pb_mod) importlib.reload(prompt_mod) importlib.reload(rp_mod) importlib.reload(sco_mod) import cleveragents.tui.app as app_mod importlib.reload(app_mod) def _make_persona_state(context): """Create a real PersonaState backed by a temp directory.""" from cleveragents.tui.persona.registry import PersonaRegistry from cleveragents.tui.persona.state import PersonaState tmp = tempfile.mkdtemp() context._tui_tmpdir = tmp registry = PersonaRegistry(config_dir=Path(tmp)) registry.ensure_default() return PersonaState(registry=registry) def _cleanup_tmpdir(context): tmp = getattr(context, "_tui_tmpdir", None) if tmp: shutil.rmtree(tmp, ignore_errors=True) class _FakeCommandRouter: """Test command router that returns predictable responses.""" def handle(self, raw, *, session_id): return f"handled:{raw}"