progress.py setzt docs/progress-sync.md um (read_at in Serverzeit mit Uhrversatz, sent_at beim
Senden, force, Warteschlange je Buch, Neuanwendung nach dem Abgleich; dieselben Regeln wie
Ops.kt/ops.js). Der Reader speichert nur, was der Leser bewegt hat; wiederherstellen,
Umbruch und Sprünge schreiben nichts. Neuere Stellen anderer Geräte werden angeboten
("Auf X weitergelesen – springen?"), nie von allein angewendet; "w" springt zur weitesten
Stelle. Fortschritt als Textanteil in Zeichen statt 0.0.
Abgleich über GET /books/ (ohne Markierungs-Blobs), Bücher erst beim Öffnen geladen
(☁ in der Liste). `diora-tui login` meldet wie die Apps an, liest den vorhandenen Token,
leitet den Schlüssel aus dem Passwort ab und speichert beides geschützt (DPAPI).
DIORA_TUI_HOME legt alle Dateien in einen Ordner (platformdirs folgt unter Windows den
Umgebungsvariablen nicht). 35 Tests, tools/smoke_server.py gegen einen lokalen Server.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
68 lines
2.3 KiB
Python
68 lines
2.3 KiB
Python
"""The Textual app starts, lists a library, opens a book and leaves it again."""
|
|
import asyncio
|
|
|
|
import pytest
|
|
|
|
from diora_tui.app import DioraTuiApp
|
|
from diora_tui.reader_screen import ReaderScreen
|
|
|
|
|
|
@pytest.fixture
|
|
def store_fresh(tmp_path, monkeypatch):
|
|
from diora_tui import progress
|
|
|
|
st = progress.ProgressStore(tmp_path / "p.json")
|
|
monkeypatch.setattr(progress, "_default", st)
|
|
return st
|
|
|
|
|
|
def test_open_read_and_leave(epub, tmp_path):
|
|
lib = tmp_path / "lib"
|
|
lib.mkdir()
|
|
path = epub(["<h1>Anfang</h1>" + "".join(f"<p>Absatz {i} " + "wort " * 40 + "</p>" for i in range(60))])
|
|
path.rename(lib / path.name)
|
|
|
|
async def run():
|
|
app = DioraTuiApp(lib)
|
|
async with app.run_test(size=(100, 30)) as pilot:
|
|
await pilot.pause()
|
|
await pilot.press("enter")
|
|
for _ in range(50):
|
|
await pilot.pause(0.1)
|
|
if isinstance(app.screen, ReaderScreen):
|
|
break
|
|
assert isinstance(app.screen, ReaderScreen)
|
|
await pilot.pause(0.5)
|
|
await pilot.press("j", "j", "j", "n")
|
|
await pilot.press("escape")
|
|
await pilot.pause()
|
|
assert not isinstance(app.screen, ReaderScreen)
|
|
|
|
asyncio.run(run())
|
|
|
|
|
|
def test_server_books_are_listed_before_they_are_downloaded(tmp_path, store_fresh):
|
|
from textual.widgets import Label, ListView
|
|
|
|
from diora_tui import library_meta
|
|
|
|
lib = tmp_path / "lib"
|
|
lib.mkdir()
|
|
library_meta.set_remote({"s9": {"server_id": 9, "title": "Cloud-Buch", "author": "", "is_read": False, "uploaded_at": "2026-10-01"}})
|
|
store_fresh.apply_server("s9", {"position_anchor": "5:0.5", "scroll_fraction": 0.4, "read_at": 1_760_000_000_000})
|
|
|
|
async def run():
|
|
app = DioraTuiApp(lib)
|
|
async with app.run_test(size=(100, 30)) as pilot:
|
|
await pilot.pause()
|
|
items = app.screen.query_one(ListView).query(Label)
|
|
text = " | ".join(str(i.render()) for i in items)
|
|
assert "☁ Cloud-Buch" in text and "40 %" in text
|
|
await pilot.press("enter") # not signed in here: says so instead of failing
|
|
await pilot.pause()
|
|
assert not isinstance(app.screen, ReaderScreen)
|
|
|
|
try:
|
|
asyncio.run(run())
|
|
finally:
|
|
library_meta.set_remote({})
|