"""Tests for the sessions picker screen and the /sessions switch path."""
from __future__ import annotations
from pathlib import Path
import pytest
from textual.widgets import ListView
from clients.terminal.tui.screens.sessions_picker import NEW_SESSION, SessionsPickerScreen
from clients.terminal.tui.tui_app import NaviCodeTui
@pytest.fixture(autouse=True)
def tmp_state_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
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": "new-sess", "profile_id": "navi_code"},
)
monkeypatch.setattr(
api_module, "get_session", lambda sid: {"session_id": sid, "profile_id": "navi_code", "messages": []}
)
monkeypatch.setattr(api_module, "get_profile_model", lambda pid: "configured-model")
def _sessions() -> list[dict]:
return [
{"session_id": "navi-aaaa1111", "profile_id": "navi_code", "name": "alpha", "created_at": "2026-01-01"},
{"session_id": "dev-bbbb2222", "profile_id": "developer", "name": "other-profile", "created_at": "2026-01-02"},
{"session_id": "navi-cccc3333", "profile_id": "navi_code", "preview": "beta chat", "created_at": "2026-01-03"},
]
def _fake_bridge(monkeypatch: pytest.MonkeyPatch) -> None:
import clients.terminal.tui.tui_app as tui_app_module
class FakeBridge:
def __init__(self, app, session_id, cwd=None) -> None:
self.client = None
async def start(self) -> None:
pass
async def stop(self) -> None:
pass
monkeypatch.setattr(tui_app_module, "WsBridge", FakeBridge)
@pytest.mark.anyio
async def test_picker_lists_only_navi_code_sessions(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr("clients.terminal.api.list_sessions", _sessions)
_fake_bridge(monkeypatch)
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
pilot.app.push_screen(SessionsPickerScreen(current_session_id="navi-aaaa1111"))
await pilot.pause()
screen = pilot.app.screen
assert isinstance(screen, SessionsPickerScreen)
assert [s["session_id"] for s in screen._filtered] == ["navi-aaaa1111", "navi-cccc3333"]
@pytest.mark.anyio
async def test_picker_marks_current_session(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr("clients.terminal.api.list_sessions", _sessions)
_fake_bridge(monkeypatch)
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
pilot.app.push_screen(SessionsPickerScreen(current_session_id="navi-cccc3333"))
await pilot.pause()
# current session is the second real entry -> list index 2 (after New row + first session)
assert pilot.app.screen.query_one("#sessions-list", ListView).index == 2
@pytest.mark.anyio
async def test_picker_new_session_row_dismisses_sentinel(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr("clients.terminal.api.list_sessions", _sessions)
_fake_bridge(monkeypatch)
result: list = []
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
pilot.app.push_screen(SessionsPickerScreen(), callback=lambda sid: result.append(sid))
await pilot.pause()
# Highlight is on the "New session" row (index 0) by default.
await pilot.press("enter")
await pilot.pause()
assert result == [NEW_SESSION]
@pytest.mark.anyio
async def test_picker_selects_existing_session(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr("clients.terminal.api.list_sessions", _sessions)
_fake_bridge(monkeypatch)
result: list = []
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
pilot.app.push_screen(SessionsPickerScreen(), callback=lambda sid: result.append(sid))
await pilot.pause()
await pilot.press("down") # onto first real session (navi-aaaa1111)
await pilot.press("enter")
await pilot.pause()
assert result == ["navi-aaaa1111"]
@pytest.mark.anyio
async def test_picker_escape_dismisses_none(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr("clients.terminal.api.list_sessions", _sessions)
_fake_bridge(monkeypatch)
result: list = []
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
pilot.app.push_screen(SessionsPickerScreen(), callback=lambda sid: result.append(sid))
await pilot.pause()
await pilot.press("escape")
await pilot.pause()
assert result == [None]
@pytest.mark.anyio
async def test_picker_filter_narrows(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr("clients.terminal.api.list_sessions", _sessions)
_fake_bridge(monkeypatch)
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
pilot.app.push_screen(SessionsPickerScreen())
await pilot.pause()
screen = pilot.app.screen
from textual.widgets import Input
screen.query_one("#sessions-input", Input).value = "beta"
await pilot.pause()
assert [s["session_id"] for s in screen._filtered] == ["navi-cccc3333"]
@pytest.mark.anyio
async def test_picker_api_error_dismisses_none(monkeypatch: pytest.MonkeyPatch) -> None:
def boom() -> list[dict]:
raise RuntimeError("server down")
monkeypatch.setattr("clients.terminal.api.list_sessions", boom)
_fake_bridge(monkeypatch)
result: list = []
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
pilot.app.push_screen(SessionsPickerScreen(), callback=lambda sid: result.append(sid))
await pilot.pause()
assert result == [None]
@pytest.mark.anyio
async def test_sessions_command_switches_to_selected(monkeypatch: pytest.MonkeyPatch) -> None:
"""The /sessions command opens the picker and a selection fully switches."""
monkeypatch.setattr("clients.terminal.api.list_sessions", _sessions)
_fake_bridge(monkeypatch)
attached: list[str] = []
orig_attach = NaviCodeTui.attach_session
async def spy_attach(self, session_id):
attached.append(session_id)
return await orig_attach(self, session_id)
monkeypatch.setattr(NaviCodeTui, "attach_session", spy_attach)
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
pilot.app._run_command("/sessions")
await pilot.pause()
await pilot.press("down") # first real session
await pilot.press("enter")
await pilot.pause()
assert "navi-aaaa1111" in attached
@pytest.mark.anyio
async def test_sessions_command_new_session_creates(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr("clients.terminal.api.list_sessions", _sessions)
_fake_bridge(monkeypatch)
created: list = []
monkeypatch.setattr(
"clients.terminal.api.create_session",
lambda profile_id=None: created.append(profile_id) or {"session_id": "fresh-sess", "profile_id": "navi_code"},
)
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
startup_creates = len(created)
pilot.app._run_command("/sessions")
await pilot.pause()
await pilot.press("enter") # "New session" row is highlighted by default
await pilot.pause()
# The picker triggered one more create_session and switched onto it.
assert len(created) == startup_creates + 1
assert pilot.app._ctx.session_id == "fresh-sess"