Newer
Older
navi-1 / tests / clients / test_input_box.py
"""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)