"""Step definitions for tui_app_coverage.feature targeting cleveragents.tui.app.""" from types import SimpleNamespace from behave import given, then, when from features.steps._tui_helpers import _submit_text from features.steps._tui_mock_helpers import ( _FakeCommandRouter, _cleanup_tmpdir, _install_mock_textual, _make_persona_state, _restore_modules, ) # --------------------------------------------------------------------------- # Background # --------------------------------------------------------------------------- @given("the TUI app module is imported with mocked Textual") def step_import_with_mock_textual(context): """Install mock Textual, reload app module, register cleanup.""" _install_mock_textual(context) context.add_cleanup(lambda: _restore_modules(context)) context.add_cleanup(lambda: _cleanup_tmpdir(context)) # --------------------------------------------------------------------------- # Module-level import gate (lines 31-38) # --------------------------------------------------------------------------- @then("the reloaded app module should have _TEXTUAL_AVAILABLE True") def step_textual_available_true(context): assert context._tui_app_mod._TEXTUAL_AVAILABLE is True @then("the reloaded app module should expose _TextualCleverAgentsTuiApp") def step_has_textual_class(context): assert hasattr(context._tui_app_mod, "_TextualCleverAgentsTuiApp") # --------------------------------------------------------------------------- # textual_available function (lines 43-45) # --------------------------------------------------------------------------- @then("textual_available should return True on the reloaded module") def step_textual_available_returns_true(context): assert context._tui_app_mod.textual_available() is True # --------------------------------------------------------------------------- # SessionView (lines 48-54) # --------------------------------------------------------------------------- @when('I create a SessionView with session_id "{sid}" and transcript entries') def step_create_session_view(context, sid): context._tui_session_view = context._tui_app_mod.SessionView( session_id=sid, transcript=["line1", "line2"] ) @then('the SessionView session_id should be "{sid}"') def step_session_view_id(context, sid): assert context._tui_session_view.session_id == sid @then("the SessionView transcript should contain {count:d} entries") def step_session_view_transcript_count(context, count): assert len(context._tui_session_view.transcript) == count # --------------------------------------------------------------------------- # _CommandRouter protocol (lines 56-61) # --------------------------------------------------------------------------- @when("I create a _CommandRouter implementation") def step_create_command_router_impl(context): class MyRouter: def handle(self, raw, *, session_id): return f"response:{raw}:{session_id}" context._tui_router_impl = MyRouter() @then("calling handle on the implementation should return a response") def step_call_handle(context): result = context._tui_router_impl.handle("test", session_id="s1") assert result == "response:test:s1" # --------------------------------------------------------------------------- # App instantiation (lines 91-100) # --------------------------------------------------------------------------- class _FakeCommandRouter: """Test command router that returns predictable responses.""" def handle(self, raw, *, session_id): return f"handled:{raw}" @given("a mock command router and persona state") def step_create_mock_deps(context): context._tui_cmd_router = _FakeCommandRouter() context._tui_persona_state = _make_persona_state(context) @when("I instantiate the Textual TUI app") def step_instantiate_app(context): AppClass = context._tui_app_mod._ResolvedTuiApp context._tui_app = AppClass( command_router=context._tui_cmd_router, persona_state=context._tui_persona_state, ) @then('the app should have a _session with session_id "{sid}"') def step_app_session_id(context, sid): assert context._tui_app._session.session_id == sid @then("the app should store the command router") def step_app_has_router(context): assert context._tui_app._command_router is context._tui_cmd_router @then("the app should store the persona state") def step_app_has_persona_state(context): assert context._tui_app._persona_state is context._tui_persona_state # --------------------------------------------------------------------------- # Class variables (lines 84-89) # --------------------------------------------------------------------------- @then('the app class should have CSS_PATH set to "{path}"') def step_css_path(context, path): assert path == context._tui_app.CSS_PATH @then("the app class should have {count:d} key bindings") def step_bindings_count(context, count): assert len(context._tui_app.BINDINGS) == count # --------------------------------------------------------------------------- # compose (lines 102-112) # --------------------------------------------------------------------------- @when("I call compose on the app") def step_call_compose(context): context._tui_compose_items = list(context._tui_app.compose()) @then("compose should yield at least {count:d} widgets") def step_compose_count(context, count): assert len(context._tui_compose_items) >= count # --------------------------------------------------------------------------- # on_mount (lines 114-121) # --------------------------------------------------------------------------- @when("I call on_mount on the app") def step_call_on_mount(context): context._tui_app.on_mount() @then("the persona bar should have content set") def step_persona_bar_has_content(context): from cleveragents.tui.widgets.persona_bar import PersonaBar bar = context._tui_app.query_one("#persona-bar", PersonaBar) assert hasattr(bar, "_text") assert bar._text # non-empty @then("the help panel should be hidden on mount") def step_help_panel_hidden_on_mount(context): from cleveragents.tui.widgets.help_panel_overlay import HelpPanelOverlay panel = context._tui_app.query_one("#help-panel", HelpPanelOverlay) assert panel.visible is False assert panel._text == "" @then("the reference picker should have suggestions initialised") def step_ref_picker_initialised(context): from cleveragents.tui.widgets.reference_picker import ReferencePickerOverlay picker = context._tui_app.query_one("#reference-picker", ReferencePickerOverlay) assert hasattr(picker, "_text") @then("the slash overlay should be hidden on mount") def step_slash_overlay_hidden_on_mount(context): from cleveragents.tui.widgets.slash_command_overlay import SlashCommandOverlay overlay = context._tui_app.query_one("#slash-overlay", SlashCommandOverlay) assert overlay.visible is False assert overlay._text == "" # --------------------------------------------------------------------------- # action_help (lines 123-125) # --------------------------------------------------------------------------- @when("I call action_help on the app") def step_call_action_help(context): context._tui_app.action_help() @when('I set the prompt text to "{text}"') def step_set_prompt_text(context, text): from cleveragents.tui.widgets.prompt import PromptInput prompt = context._tui_app.query_one("#prompt", PromptInput) prompt.value = text @then('the conversation widget should contain "{text}"') def step_conversation_contains(context, text): MockStatic = context._tui_mock_static conv = context._tui_app.query_one("#conversation", MockStatic) assert text in conv._text, f"Expected '{text}' in '{conv._text}'" @then('the help panel text should contain "{text}"') def step_help_panel_contains(context, text): from cleveragents.tui.widgets.help_panel_overlay import HelpPanelOverlay panel = context._tui_app.query_one("#help-panel", HelpPanelOverlay) assert text in panel._text, f"Expected '{text}' in '{panel._text}'" # --------------------------------------------------------------------------- # action_cycle_preset (lines 127-129) # --------------------------------------------------------------------------- @when("I call action_cycle_preset on the app") def step_call_action_cycle_preset(context): context._tui_app.action_cycle_preset() @then("the persona bar content should be refreshed") def step_persona_bar_refreshed(context): from cleveragents.tui.widgets.persona_bar import PersonaBar bar = context._tui_app.query_one("#persona-bar", PersonaBar) assert bar._text # non-empty after refresh # --------------------------------------------------------------------------- # _refresh_persona_bar (lines 131-142) # --------------------------------------------------------------------------- @when("I call _refresh_persona_bar on the app") def step_call_refresh_persona_bar(context): context._tui_app._refresh_persona_bar() @then("the persona bar should display the persona name and scope") def step_persona_bar_shows_name(context): from cleveragents.tui.widgets.persona_bar import PersonaBar bar = context._tui_app.query_one("#persona-bar", PersonaBar) assert "default" in bar._text assert "scope refs" in bar._text # --------------------------------------------------------------------------- # on_input_submitted: empty text (lines 144-150) # --------------------------------------------------------------------------- @when("I submit empty text to the app") def step_submit_empty(context): MockStatic = context._tui_mock_static conv = context._tui_app.query_one("#conversation", MockStatic) context._tui_conv_before = conv._text _submit_text(context, "") @then("the conversation widget should not be updated by input") def step_conv_not_updated(context): MockStatic = context._tui_mock_static conv = context._tui_app.query_one("#conversation", MockStatic) assert conv._text == context._tui_conv_before # --------------------------------------------------------------------------- # on_input_submitted: command mode (lines 152-167) # --------------------------------------------------------------------------- # --------------------------------------------------------------------------- # on_input_submitted: normal text with @ (lines 179-185) # --------------------------------------------------------------------------- @then("the reference picker should have been updated") def step_ref_picker_updated(context): from cleveragents.tui.widgets.reference_picker import ReferencePickerOverlay picker = context._tui_app.query_one("#reference-picker", ReferencePickerOverlay) assert hasattr(picker, "_text") assert picker._text # updated with suggestions # --------------------------------------------------------------------------- # action_escape cascade (issue #6450) # --------------------------------------------------------------------------- @when("I show the actor selection overlay") def step_show_actor_selection(context): from cleveragents.tui.widgets.actor_selection_overlay import ( ActorSelectionOverlay, ) overlay = context._tui_app.query_one("#actor-selection", ActorSelectionOverlay) overlay.show() @when('I show the help panel overlay for context "{context_name}"') def step_show_help_panel_context(context, context_name): from cleveragents.tui.widgets.help_panel_overlay import HelpPanelOverlay panel = context._tui_app.query_one("#help-panel", HelpPanelOverlay) panel.show_context(context_name) @when('I show the slash overlay for query "{query}"') def step_show_slash_overlay(context, query): from cleveragents.tui.widgets.slash_command_overlay import SlashCommandOverlay overlay = context._tui_app.query_one("#slash-overlay", SlashCommandOverlay) commands = [SimpleNamespace(command=query, description=f"/{query} command")] overlay.set_commands(query, commands) @when('I show the reference picker overlay for query "{query}"') def step_show_reference_picker(context, query): from cleveragents.tui.widgets.reference_picker import ReferencePickerOverlay picker = context._tui_app.query_one("#reference-picker", ReferencePickerOverlay) picker.set_suggestions(query, [query]) @when("I call action_escape on the app") def step_call_action_escape(context): context._tui_app.action_escape() @then("the actor selection overlay should be hidden") def step_actor_overlay_hidden(context): from cleveragents.tui.widgets.actor_selection_overlay import ( ActorSelectionOverlay, ) overlay = context._tui_app.query_one("#actor-selection", ActorSelectionOverlay) assert overlay.visible is False @then("the help panel should remain visible") def step_help_panel_visible(context): from cleveragents.tui.widgets.help_panel_overlay import HelpPanelOverlay panel = context._tui_app.query_one("#help-panel", HelpPanelOverlay) assert panel.visible is True assert panel._text @then("the slash overlay should remain visible") def step_slash_overlay_visible(context): from cleveragents.tui.widgets.slash_command_overlay import SlashCommandOverlay overlay = context._tui_app.query_one("#slash-overlay", SlashCommandOverlay) assert overlay.visible is True assert overlay._text @then("the slash overlay should be hidden") def step_slash_overlay_hidden(context): from cleveragents.tui.widgets.slash_command_overlay import SlashCommandOverlay overlay = context._tui_app.query_one("#slash-overlay", SlashCommandOverlay) assert overlay.visible is False assert overlay._text == "" @then("the reference picker overlay should remain visible") def step_reference_picker_visible(context): from cleveragents.tui.widgets.reference_picker import ReferencePickerOverlay picker = context._tui_app.query_one("#reference-picker", ReferencePickerOverlay) assert picker.visible is True assert picker._text @then("the reference picker overlay should be hidden") def step_reference_picker_hidden(context): from cleveragents.tui.widgets.reference_picker import ReferencePickerOverlay picker = context._tui_app.query_one("#reference-picker", ReferencePickerOverlay) assert picker.visible is False assert picker._text == "" @then('the prompt text should be "{text}"') def step_prompt_text_equals(context, text): from cleveragents.tui.widgets.prompt import PromptInput prompt = context._tui_app.query_one("#prompt", PromptInput) assert prompt.value == text # --------------------------------------------------------------------------- # CleverAgentsTuiApp alias (line 189) # --------------------------------------------------------------------------- @then("CleverAgentsTuiApp should be _TextualCleverAgentsTuiApp on the reloaded module") def step_alias_check(context): assert ( context._tui_app_mod.CleverAgentsTuiApp is context._tui_app_mod._ResolvedTuiApp ) assert ( context._tui_app_mod.CleverAgentsTuiApp.__name__ == "_TextualCleverAgentsTuiApp" ) # --------------------------------------------------------------------------- # THEME class variable (issue #4742) # --------------------------------------------------------------------------- @then('the app class should have THEME set to "{theme}"') def step_theme_class_var(context, theme: str) -> None: assert theme == context._tui_app.THEME, ( f"Expected THEME='{theme}', got '{context._tui_app.THEME}'" ) # --------------------------------------------------------------------------- # on_input_changed: live overlay update steps # --------------------------------------------------------------------------- def _trigger_input_changed(context, text: str) -> None: """Set prompt text and fire on_input_changed.""" from cleveragents.tui.widgets.prompt import PromptInput prompt = context._tui_app.query_one("#prompt", PromptInput) prompt.text = text event = SimpleNamespace() context._tui_app.on_input_changed(event) @when('I trigger on_input_changed with text "{text}"') def step_trigger_input_changed(context, text: str) -> None: _trigger_input_changed(context, text) @when("I trigger on_input_changed with empty text") def step_trigger_input_changed_empty_text(context) -> None: _trigger_input_changed(context, "") @then('the slash overlay should show only commands matching "{query}"') def step_slash_overlay_filtered(context, query: str) -> None: from cleveragents.tui.widgets.slash_command_overlay import SlashCommandOverlay overlay = context._tui_app.query_one("#slash-overlay", SlashCommandOverlay) assert overlay._text, "Slash overlay text should not be empty" # All displayed commands must start with the query for cmd in overlay._commands: assert cmd.command.startswith(query), ( f"Command '{cmd.command}' does not start with '{query}'" ) @then("the reference picker should be reset to empty") def step_ref_picker_reset(context) -> None: from cleveragents.tui.widgets.reference_picker import ReferencePickerOverlay picker = context._tui_app.query_one("#reference-picker", ReferencePickerOverlay) assert picker._text == "", ( f"Expected reference picker to be empty after reset, got: '{picker._text}'" ) @then("the slash overlay should be reset to all commands") def step_slash_overlay_all_commands(context) -> None: from cleveragents.tui.widgets.slash_command_overlay import SlashCommandOverlay overlay = context._tui_app.query_one("#slash-overlay", SlashCommandOverlay) # "reset to all commands" means the overlay returned to its default hidden state # (set_commands with empty query calls hide(), clearing _text and _commands) assert not overlay._visible, "Slash overlay should be hidden after reset" assert overlay._text == "", "Slash overlay text should be empty after reset"