"""Step definitions for tui_slash_command_overlay_coverage.feature. These steps target uncovered lines in slash_command_overlay.py: - Line 16: _FallbackStatic.__init__ sets self._text = "" - Line 19: _FallbackStatic.update sets self._text = text - Lines 31-32: set_commands filtering logic (query vs empty query) - Line 34: lines = [f"/{query}"] - Lines 35-36: loop appending filtered commands with descriptions - Lines 37-38: "(no commands)" fallback when nothing matches - Line 39: self.update("\n".join(lines)) """ import importlib from unittest.mock import patch from behave import given, then, when import cleveragents.tui.widgets.slash_command_overlay as _sco_mod from cleveragents.tui.slash_catalog import SlashCommandSpec from cleveragents.tui.widgets.slash_command_overlay import _load_static_base def _make_specs(names_csv: str) -> list[SlashCommandSpec]: """Build a list of SlashCommandSpec from a CSV of command names.""" return [ SlashCommandSpec( command=c.strip(), group="Test", description=f"Desc for {c.strip()}" ) for c in names_csv.split(",") if c.strip() ] # --------------------------------------------------------------------------- # Background — force FallbackStatic so tests never need a live Textual app # --------------------------------------------------------------------------- @given("the slash command overlay module is imported") def step_module_imported(context): """Reload the module with textual import patched out to force fallback.""" with patch("importlib.import_module", side_effect=ImportError("no textual")): importlib.reload(_sco_mod) # Re-bind to module-level names after reload context._sco_overlay_cls = _sco_mod.SlashCommandOverlay assert context._sco_overlay_cls is not None assert _load_static_base is not None def _restore(): importlib.reload(_sco_mod) context.add_cleanup(_restore) # --------------------------------------------------------------------------- # Instantiation (line 16) # --------------------------------------------------------------------------- @when("I create a SlashCommandOverlay instance") def step_create_overlay(context): """Instantiate SlashCommandOverlay, hitting _FallbackStatic.__init__.""" context.overlay = context._sco_overlay_cls() @then("the overlay internal text should be empty") def step_verify_empty_text(context): """Verify _text was initialised to empty string (line 16).""" assert context.overlay._text == "" # --------------------------------------------------------------------------- # update method (line 19) # --------------------------------------------------------------------------- @given("I have a SlashCommandOverlay instance") def step_have_overlay(context): """Create and store an overlay instance for subsequent steps.""" context.overlay = context._sco_overlay_cls() @when('I call update with "{text}"') def step_call_update(context, text): """Call update on the overlay, hitting _FallbackStatic.update (line 19).""" context.overlay.update(text) @then('the overlay internal text should be "{expected}"') def step_verify_text(context, expected): """Verify _text matches the expected value.""" assert context.overlay._text == expected, ( f"Expected '{expected}', got '{context.overlay._text}'" ) # --------------------------------------------------------------------------- # set_commands with query (lines 31-36, 39) # --------------------------------------------------------------------------- @when('I call set_commands with query "{query}" and commands "{commands_csv}"') def step_call_set_commands(context, query, commands_csv): """Call set_commands with a query and a CSV list of commands (as SlashCommandSpec).""" specs = _make_specs(commands_csv) context.overlay.set_commands(query, specs) @when('I call set_commands with empty query and commands "{commands_csv}"') def step_call_set_commands_empty_query(context, commands_csv): """Call set_commands with an empty query and a CSV list of commands (as SlashCommandSpec).""" specs = _make_specs(commands_csv) context.overlay.set_commands("", specs) @then('the overlay text should contain "{substring}"') def step_text_contains(context, substring): """Verify the overlay text contains the expected substring.""" assert substring in context.overlay._text, ( f"Expected '{substring}' in:\n{context.overlay._text}" ) @then('the overlay text should not contain "{substring}"') def step_text_not_contains(context, substring): """Verify the overlay text does NOT contain the substring.""" assert substring not in context.overlay._text, ( f"Did not expect '{substring}' in:\n{context.overlay._text}" ) # --------------------------------------------------------------------------- # set_commands with empty query (line 32 else branch) # --------------------------------------------------------------------------- # Reuses the same step definitions above with query "" # --------------------------------------------------------------------------- # set_commands no matches (lines 37-38) # --------------------------------------------------------------------------- # Reuses step_call_set_commands; the "zzz" query triggers the no-commands path # --------------------------------------------------------------------------- # set_commands truncation to 12 entries (line 35 [:12]) # --------------------------------------------------------------------------- @when( 'I call set_commands with query "" and {count:d} commands prefixed with "{prefix}"' ) def step_call_set_commands_many(context, count, prefix): """Call set_commands with a large number of commands (as SlashCommandSpec).""" specs = [ SlashCommandSpec(command=f"{prefix}{i}", group="Test", description=f"Desc {i}") for i in range(count) ] context.overlay.set_commands("", specs) @then("the overlay text should have exactly {expected:d} lines") def step_verify_line_count(context, expected): """Verify the number of lines in overlay text.""" lines = context.overlay._text.split("\n") assert len(lines) == expected, ( f"Expected {expected} lines, got {len(lines)}: {lines}" ) # --------------------------------------------------------------------------- # Force fallback static base (lines 16, 19 via except path) # --------------------------------------------------------------------------- @when("I force the fallback static base to load") def step_force_fallback(context): """Patch importlib.import_module to raise, forcing the fallback path.""" with patch("importlib.import_module", side_effect=ImportError("no textual")): fallback_cls = _load_static_base() context.fallback_cls = fallback_cls context.fallback_instance = fallback_cls() @then("the fallback class should be usable as a standalone object") def step_verify_fallback_class(context): """Verify the fallback instance has the expected _text attribute.""" assert hasattr(context.fallback_instance, "_text") assert context.fallback_instance._text == "" @then("calling update on the fallback instance should store the text") def step_verify_fallback_update(context): """Verify update stores text on the fallback instance (line 19).""" context.fallback_instance.update("test content") assert context.fallback_instance._text == "test content"