108 lines
3.0 KiB
Python
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"]
|