Files
snotes/tests/test_tui.py
schmeeve 0758095046 CLI
2026-09-26 22:06:28 -07:00

108 lines
3.0 KiB
Python

"""Headless smoke tests for the snotes TUI (Textual pilot)."""
from __future__ import annotations
import importlib.util
import sys
from pathlib import Path
import pytest
textual = pytest.importorskip("textual") # noqa: F841
def _load_cli():
path = Path(__file__).parent.parent / "cli" / "snotes.py"
spec = importlib.util.spec_from_file_location("snotes_cli", path)
mod = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = mod
spec.loader.exec_module(mod)
return mod
cli = _load_cli()
NOTES = [
{"id": "a1", "title": "Shopping list", "updated_at": "2026-09-26 09:00:00", "tags": ["home"]},
{
"id": "b2",
"title": "Journal for 2026-09-26",
"updated_at": "2026-09-25 22:00:00",
"tags": ["journal"],
},
]
BODIES = {
"a1": "# Shopping list\n\n- milk\n- eggs\n",
"b2": "# Journal for 2026-09-26\n\nA fine day.\n",
}
class FakeApi:
def __init__(self):
self.cfg = cli.Config(url="http://test", user="schmeeve")
self.deleted: list[str] = []
def recent(self, limit: int = 30) -> list[dict]:
return NOTES[:limit]
def search(self, q: str) -> list[dict]:
return [n for n in NOTES if q.lower() in n["title"].lower()]
def get_note(self, note_id: str) -> dict:
return {"id": note_id, "title": "t", "body": BODIES[note_id]}
def create(self, body: str) -> dict: # pragma: no cover - not exercised
return {"id": "new", "title": body}
def update(self, note_id: str, body: str) -> dict: # pragma: no cover
return {"id": note_id}
def delete(self, note_id: str) -> None:
self.deleted.append(note_id)
async def test_tui_boots_with_recent_notes():
app = cli.make_app(FakeApi(), None)
async with app.run_test() as pilot:
await pilot.pause()
from textual.widgets import ListView
lv = app.query_one("#notes", ListView)
assert len(lv.children) == 2
assert app.title == "snotes"
async def test_tui_initial_query_filters():
app = cli.make_app(FakeApi(), "journal")
async with app.run_test() as pilot:
await pilot.pause()
await pilot.pause(0.4) # debounce
from textual.widgets import Input, ListView
assert app.query_one("#search", Input).value == "journal"
assert len(app.query_one("#notes", ListView).children) == 1
async def test_tui_preview_shows_note_body():
app = cli.make_app(FakeApi(), None)
async with app.run_test() as pilot:
await pilot.pause()
from textual.widgets import Markdown
preview = app.query_one("#preview", Markdown)
await pilot.pause(0.2)
assert "Shopping list" in preview.source or "milk" in preview.source
async def test_tui_delete_flow():
fake = FakeApi()
app = cli.make_app(fake, None)
async with app.run_test() as pilot:
await pilot.pause()
await pilot.press("d")
await pilot.pause()
await pilot.press("y")
await pilot.pause(0.3)
assert fake.deleted == ["a1"]