tests: cover the basic UI

Drive the line-based UI in-process: input() answers come from a list
and the output is captured. The tests walk the main menu through
creating, playing, querying, deleting, importing and exporting
characters, plus bad menu input and the Ctrl+C handler.

The play loop runs on a fake clock that advances a minute per call and
stops when time.sleep has been called a set number of times, so it
levels up and saves on schedule without waiting.

The test helpers move to tests/helpers.py, which now also holds the
input() feeder.
This commit is contained in:
Dash committed 2026-10-17 19:46:05 +02:00
1 parent 083dbe01c2
commit f8c370cc2e
4 files changed
+501 -18

No files matched your search

+63
View File
@@ -0,0 +1,63 @@
"""Characters, saves and prompts shared by the UI and CLI tests."""
from collections.abc import Iterable
from pathlib import Path
import pytest
from test_pqw import played
from pqcli import pqw
from pqcli.game_data import load_game_data
from pqcli.mechanic import Player
from pqcli.roster import Roster
DATA = load_game_data()
def hero(name: str = "Exportée", ticks: int = 300) -> Player:
"""A character that has played for a while."""
player = played(ticks).player
player.name = name
return player
def save_path(config: Path) -> Path:
"""Where pqcli keeps its roster with XDG_CONFIG_HOME set to config."""
return config / "pqcli" / "save.dat"
def write_roster(config: Path, *players: Player) -> Path:
path = save_path(config)
Roster(path, list(players)).save()
return path
def read_names(config: Path) -> list[str]:
return [player.name for player in Roster.load(save_path(config)).players]
def write_pqw(path: Path, player: Player, *, online: bool = False) -> Path:
"""Write player as a .pqw; online adds fields the import warns about."""
game = pqw.export_game(player, DATA)
if online:
game["online"] = {"realm": "Alpaquil", "passkey": 1}
path.write_text(pqw.encode(game), encoding="ascii")
return path
def feed(monkeypatch: pytest.MonkeyPatch, answers: Iterable[str]) -> None:
"""Answer input() prompts in order; running out is a test failure.
Prompts are echoed so captured output reads like a session.
"""
it = iter(answers)
def fake_input(prompt: str = "") -> str:
try:
answer = next(it)
except StopIteration:
raise AssertionError(f"unexpected prompt {prompt!r}") from None
print(f"{prompt}{answer}")
return answer
monkeypatch.setattr("builtins.input", fake_input)