"""Tests for slash-command hints and Tab completion in the input box."""
from __future__ import annotations
from pathlib import Path
import pytest
from clients.terminal.tui.tui_app import NaviCodeTui
@pytest.fixture(autouse=True)
def tmp_state_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
"""Override the state dir so tests never touch ~/.navi_code."""
from clients.terminal import config
original = config.settings.state_dir
config.settings.state_dir = tmp_path
import clients.terminal.tui.settings as settings_module
settings_module._tui_settings = None
yield tmp_path
config.settings.state_dir = original
settings_module._tui_settings = None
@pytest.fixture(autouse=True)
def mock_tui_api(monkeypatch: pytest.MonkeyPatch) -> None:
import clients.terminal.api as api_module
monkeypatch.setattr(
api_module,
"create_session",
lambda profile_id=None: {"session_id": "test-session", "profile_id": "navi_code"},
)
monkeypatch.setattr(
api_module, "get_session", lambda sid: {"session_id": sid, "profile_id": "navi_code"}
)
monkeypatch.setattr(api_module, "list_sessions", lambda: [])
monkeypatch.setattr(api_module, "get_profile_model", lambda pid: "configured-model")
async def _set_text(pilot, text: str) -> None:
"""Set the prompt text and let the TUI react to the change message."""
input_box = pilot.app.query_one("InputBox")
input_box._input.text = text
await pilot.pause()
@pytest.mark.anyio
async def test_hints_hidden_without_slash() -> None:
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
await _set_text(pilot, "hello there")
hints = pilot.app.query_one("CommandHints")
assert hints.display is False
@pytest.mark.anyio
async def test_hints_shown_for_slash_prefix() -> None:
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
await _set_text(pilot, "/s")
hints = pilot.app.query_one("CommandHints")
assert hints.display is True
names = {c.meta.name for c in hints._matches}
assert "sessions" in names and "switch" in names
@pytest.mark.anyio
async def test_hints_show_all_for_bare_slash() -> None:
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
await _set_text(pilot, "/")
hints = pilot.app.query_one("CommandHints")
assert hints.display is True
assert len(hints._matches) >= 1
@pytest.mark.anyio
async def test_hints_hidden_once_whitespace_typed() -> None:
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
await _set_text(pilot, "/switch abc")
hints = pilot.app.query_one("CommandHints")
assert hints.display is False
@pytest.mark.anyio
async def test_hints_hidden_when_no_match() -> None:
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
await _set_text(pilot, "/zzzzzz")
hints = pilot.app.query_one("CommandHints")
assert hints.display is False
@pytest.mark.anyio
async def test_tab_completes_command_name() -> None:
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
input_box = pilot.app.query_one("InputBox")
await _set_text(pilot, "/sw")
await pilot.press("tab")
await pilot.pause()
assert input_box._input.text == "/switch "
@pytest.mark.anyio
async def test_tab_does_not_complete_without_slash() -> None:
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
input_box = pilot.app.query_one("InputBox")
await _set_text(pilot, "sw")
await pilot.press("tab")
await pilot.pause()
# No slash -> completion not applied; text unchanged (or default tab behavior).
assert input_box._input.text == "sw"
@pytest.mark.anyio
async def test_tab_does_not_complete_when_args_present() -> None:
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
input_box = pilot.app.query_one("InputBox")
await _set_text(pilot, "/switch abc")
await pilot.press("tab")
await pilot.pause()
assert input_box._input.text == "/switch abc"
@pytest.mark.anyio
async def test_enter_runs_slash_command_not_sent_to_agent() -> None:
"""A slash command on Enter is dispatched as a command, not a user message."""
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
chat = pilot.app.query_one("ChatPanel")
before = len(chat._model.items)
await _set_text(pilot, "/help")
await pilot.press("enter")
await pilot.pause()
# /help emits a status entry into the chat model — no user_message added.
kinds = [i.kind for i in chat._model.items[before:]]
assert "user_message" not in kinds
assert any(k in ("status", "error") for k in kinds)
@pytest.mark.anyio
async def test_down_arrow_moves_highlight() -> None:
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
await _set_text(pilot, "/t") # matches: thinking, themes
hints = pilot.app.query_one("CommandHints")
assert hints.current_match().meta.name == "thinking"
await pilot.press("down")
await pilot.pause()
assert hints.current_match().meta.name == "themes"
await pilot.press("up")
await pilot.pause()
assert hints.current_match().meta.name == "thinking"
@pytest.mark.anyio
async def test_arrow_keys_wrap_around() -> None:
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
await _set_text(pilot, "/t")
hints = pilot.app.query_one("CommandHints")
# Up from the top wraps to the bottom.
await pilot.press("up")
await pilot.pause()
assert hints.current_match().meta.name == "themes"
@pytest.mark.anyio
async def test_arrows_no_op_when_hints_hidden() -> None:
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
input_box = pilot.app.query_one("InputBox")
await _set_text(pilot, "hello")
await pilot.press("down")
await pilot.press("up")
await pilot.pause()
assert input_box._input.text == "hello"
@pytest.mark.anyio
async def test_enter_runs_highlighted_command(monkeypatch: pytest.MonkeyPatch) -> None:
"""Enter while hints are open runs the highlighted command, not the typed prefix."""
invoked: list[tuple[str, str]] = []
async def spy(self, cmd, args: str) -> None:
invoked.append((cmd.meta.name, args))
monkeypatch.setattr(NaviCodeTui, "_command_worker", spy)
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
await _set_text(pilot, "/t") # matches: thinking (top), themes
await pilot.press("down") # highlight themes
await pilot.press("enter")
await pilot.pause()
assert invoked == [("themes", "")]
@pytest.mark.anyio
async def test_enter_without_highlight_runs_top_match(monkeypatch: pytest.MonkeyPatch) -> None:
invoked: list[tuple[str, str]] = []
async def spy(self, cmd, args: str) -> None:
invoked.append((cmd.meta.name, args))
monkeypatch.setattr(NaviCodeTui, "_command_worker", spy)
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
await _set_text(pilot, "/h") # only "help" matches
await pilot.press("enter")
await pilot.pause()
assert invoked == [("help", "")]