"""Smoke tests for the Navi Code TUI."""
from __future__ import annotations
from pathlib import Path
import pytest
from clients.terminal.tui.events import WsEvent
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.mark.anyio
async def test_tui_mounts_widgets() -> None:
"""The TUI app mounts chat, status, and input widgets."""
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
assert pilot.app.query_one("ChatPanel") is not None
assert pilot.app.query_one("StatusPanel") is not None
assert pilot.app.query_one("SessionsPanel") is not None
assert pilot.app.query_one("InputBox") is not None
@pytest.mark.anyio
async def test_user_message_appears_in_chat() -> None:
"""Typing a message and submitting adds it to the chat panel."""
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
input_box = pilot.app.query_one("InputBox")
input_box._input.value = "hello"
await pilot.press("enter")
await pilot.pause()
chat = pilot.app.query_one("ChatPanel")
assert any(
item.kind == "user_message" and item.content == "hello" for item in chat._model.items
)
@pytest.mark.anyio
async def test_input_text_is_rendered_with_visible_color() -> None:
"""Typed text in the input field is rendered with a visible foreground color."""
async with NaviCodeTui(new_session=True).run_test(size=(80, 24)) as pilot:
await pilot.pause()
input_box = pilot.app.query_one("InputBox")
input_box._input.focus()
await pilot.press("h", "i", " ", "n", "a", "v", "i")
await pilot.pause()
assert input_box._input.value == "hi navi"
strip = input_box._input.render_line(0)
segments = list(strip)
rendered_text = "".join(seg.text for seg in segments)
assert "hi navi" in rendered_text
# Ensure foreground and background are not identical so text is visible.
for seg in segments:
if "hi navi" in seg.text and seg.style:
assert seg.style.color != seg.style.bgcolor
@pytest.mark.anyio
async def test_ws_event_renders_in_chat() -> None:
"""A synthetic WebSocket stream_delta is added to the assistant response."""
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
chat = pilot.app.query_one("ChatPanel")
chat.handle_ws_event({"type": "stream_start"})
chat.handle_ws_event({"type": "stream_delta", "delta": "hi"})
await pilot.pause()
assert chat._model._current_assistant is not None
assert chat._model._current_assistant.content == "hi"
@pytest.mark.anyio
async def test_unknown_slash_command_shows_error() -> None:
"""Unknown slash command produces an error item in chat."""
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
input_box = pilot.app.query_one("InputBox")
input_box._input.value = "/notacommand"
await pilot.press("enter")
await pilot.pause()
chat = pilot.app.query_one("ChatPanel")
assert any(item.kind == "error" for item in chat._model.items)
@pytest.mark.anyio
async def test_chat_panel_clear_resets_conversation() -> None:
"""ChatPanel.clear() removes all items and current assistant state."""
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
chat = pilot.app.query_one("ChatPanel")
chat.handle_ws_event({"type": "stream_start"})
chat.handle_ws_event({"type": "stream_delta", "delta": "hi"})
await pilot.pause()
assert chat._model.items
chat.clear()
assert not chat._model.items
assert chat._model._current_assistant is None
@pytest.mark.anyio
async def test_status_panel_shows_backend_and_theme() -> None:
"""Status panel displays backend URL and current theme."""
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
status = pilot.app.query_one("StatusPanel")
backend_text = str(status._backend.render())
theme_text = str(status._theme.render())
assert "Backend:" in backend_text
assert "Theme: gnexus-dark" in theme_text
@pytest.mark.anyio
async def test_streaming_state_tracks_ws_events() -> None:
"""stream_start enters streaming mode; stream_end/stream_stopped/error leave it."""
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
app = pilot.app
input_box = app.query_one("InputBox")
app.on_ws_event(WsEvent({"type": "stream_start"}))
await pilot.pause()
assert app._streaming is True
assert "Esc to stop" in input_box._input.placeholder
app.on_ws_event(WsEvent({"type": "stream_delta", "delta": "hi"}))
await pilot.pause()
assert app._streaming is True
app.on_ws_event(WsEvent({"type": "stream_stopped"}))
await pilot.pause()
assert app._streaming is False
assert input_box._input.placeholder == "Ask anything..."
@pytest.mark.anyio
async def test_escape_stops_active_stream(monkeypatch: pytest.MonkeyPatch) -> None:
"""Pressing Esc while streaming calls api.stop_session for the current session."""
stopped: list[str] = []
async def fake_stop_session(session_id: str) -> dict:
stopped.append(session_id)
return {"ok": True}
import clients.terminal.tui.tui_app as tui_app_module
monkeypatch.setattr(tui_app_module.api, "stop_session", fake_stop_session)
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
app = pilot.app
# Simulate an active run for the resolved session.
app.on_ws_event(WsEvent({"type": "stream_start"}))
await pilot.pause()
assert app._streaming is True
current_session = app._ctx.session_id
assert current_session
await pilot.press("escape")
await pilot.pause(0.1)
assert stopped == [current_session]
@pytest.mark.anyio
async def test_stream_stopped_renders_status() -> None:
"""stream_stopped resets the assistant buffer and shows a status message."""
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
chat = pilot.app.query_one("ChatPanel")
chat.handle_ws_event({"type": "stream_start"})
chat.handle_ws_event({"type": "stream_delta", "delta": "partial"})
chat.handle_ws_event({"type": "stream_stopped"})
await pilot.pause()
assert chat._model._current_assistant is None
assert any(
item.kind == "status" and "stopped" in item.content.lower()
for item in chat._model.items
)
@pytest.mark.anyio
async def test_escape_does_nothing_when_not_streaming(monkeypatch: pytest.MonkeyPatch) -> None:
"""Esc without an active stream does not call stop_session."""
calls: list[str] = []
async def fake_stop_session(session_id: str) -> dict:
calls.append(session_id)
return {"ok": True}
import clients.terminal.tui.tui_app as tui_app_module
monkeypatch.setattr(tui_app_module.api, "stop_session", fake_stop_session)
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
await pilot.press("escape")
await pilot.pause(0.1)
assert calls == []