"""Tests for the /terminals picker modal."""
from __future__ import annotations
from pathlib import Path
import pytest
from clients.terminal.tui.screens.terminals_picker import TerminalsPickerScreen
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
import clients.terminal.tui.settings as settings_module
original = config.settings.state_dir
config.settings.state_dir = tmp_path
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
async def fake_create_session(profile_id=None):
return {"session_id": "test-session", "profile_id": "navi_code"}
async def fake_get_session(sid):
return {"session_id": sid, "profile_id": "navi_code"}
async def fake_list_sessions():
return []
async def fake_get_profile_model(pid):
return "configured-model"
async def fake_list_terminals(session_id):
return []
monkeypatch.setattr(api_module, "create_session", fake_create_session)
monkeypatch.setattr(api_module, "get_session", fake_get_session)
monkeypatch.setattr(api_module, "list_sessions", fake_list_sessions)
monkeypatch.setattr(api_module, "get_profile_model", fake_get_profile_model)
monkeypatch.setattr(api_module, "list_terminals", fake_list_terminals)
def _terminals() -> list[dict]:
return [
{"name": "dev", "description": "dev server", "status": "busy", "pid": 11, "uptime_seconds": 5},
{"name": "watch", "description": "test watcher", "status": "idle", "pid": 22, "uptime_seconds": 60},
]
@pytest.mark.anyio
async def test_picker_lists_terminals(monkeypatch: pytest.MonkeyPatch) -> None:
async def fake_list(sid):
return _terminals()
monkeypatch.setattr("clients.terminal.api.list_terminals", fake_list)
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
pilot.app.push_screen(TerminalsPickerScreen("test-session"))
await pilot.pause()
screen = pilot.app.screen
assert isinstance(screen, TerminalsPickerScreen)
assert screen._order == ["dev", "watch"]
@pytest.mark.anyio
async def test_picker_close_on_delete(monkeypatch: pytest.MonkeyPatch) -> None:
async def fake_list(sid):
return _terminals()[:1]
closed: list[tuple] = []
async def fake_close(session_id, name):
closed.append((session_id, name))
return {"session_id": session_id, "terminal_name": name, "closed": True}
monkeypatch.setattr("clients.terminal.api.list_terminals", fake_list)
monkeypatch.setattr("clients.terminal.api.close_terminal", fake_close)
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
pilot.app.push_screen(TerminalsPickerScreen("test-session"))
await pilot.pause()
await pilot.press("delete")
await pilot.pause()
assert closed == [("test-session", "dev")]
# The closed terminal drops out of the list.
assert "dev" not in pilot.app.screen._order
@pytest.mark.anyio
async def test_picker_escape_cancels(monkeypatch: pytest.MonkeyPatch) -> None:
async def fake_list(sid):
return _terminals()[:1]
monkeypatch.setattr("clients.terminal.api.list_terminals", fake_list)
result: list = []
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
pilot.app.push_screen(TerminalsPickerScreen("test-session"), callback=lambda v: result.append(v))
await pilot.pause()
await pilot.press("escape")
await pilot.pause()
assert result == [None]
@pytest.mark.anyio
async def test_picker_live_refresh_from_model(monkeypatch: pytest.MonkeyPatch) -> None:
async def fake_list(sid):
return []
monkeypatch.setattr("clients.terminal.api.list_terminals", fake_list)
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
pilot.app.push_screen(TerminalsPickerScreen("test-session"))
await pilot.pause()
screen = pilot.app.screen
assert isinstance(screen, TerminalsPickerScreen)
assert screen._order == []
# Simulate a terminal_opened event feeding the model, then live-refresh.
screen.refresh_from_model({
"dev": {"description": "server", "pid": 1, "status": "busy", "closed": False},
"gone": {"description": "old", "pid": 2, "status": "idle", "closed": True},
})
assert screen._order == ["dev"] # closed ones filtered out
assert "dev" in screen._terminals
@pytest.mark.anyio
async def test_picker_empty_state(monkeypatch: pytest.MonkeyPatch) -> None:
async def fake_list(sid):
return []
monkeypatch.setattr("clients.terminal.api.list_terminals", fake_list)
async with NaviCodeTui(new_session=True).run_test() as pilot:
await pilot.pause()
pilot.app.push_screen(TerminalsPickerScreen("test-session"))
await pilot.pause()
screen = pilot.app.screen
assert isinstance(screen, TerminalsPickerScreen)
assert screen._order == []
# Delete on an empty list does not raise / does not call close.
await pilot.press("delete")
await pilot.pause()
# Still mounted, nothing closed (no crash).
assert isinstance(pilot.app.screen, TerminalsPickerScreen)