Spaces:
Running on Zero
Running on Zero
| """The two-phase turn: play_turn_text must return dialogue without painting, | |
| play_turn_images must complete the view afterwards — including from a fresh Engine | |
| (the ZeroGPU cross-worker contract via the state file).""" | |
| from __future__ import annotations | |
| import pytest | |
| from visualnovel import engine as engine_mod | |
| from visualnovel.engine import Engine | |
| from visualnovel.schemas import SetupForm | |
| def isolated_state_file(monkeypatch, tmp_path): | |
| monkeypatch.setattr(engine_mod, "_STATE_FILE", tmp_path / "vn_game_state.json") | |
| def _started_engine() -> Engine: | |
| eng = Engine() | |
| eng.start(SetupForm(theme="school", tone="romantic", seed=7)) | |
| return eng | |
| def test_turn_text_then_images(isolated_state_file): | |
| eng = _started_engine() | |
| v1 = eng.play_turn_text("hello there") | |
| assert v1.dialogue | |
| assert v1.backdrop_url is None | |
| assert v1.present == [] | |
| v2 = eng.play_turn_images() | |
| assert v2.backdrop_url | |
| assert v2.present | |
| assert v2.dialogue == v1.dialogue | |
| def test_turn_images_survives_worker_restart(isolated_state_file): | |
| eng = _started_engine() | |
| v1 = eng.play_turn_text("hello there") | |
| # ZeroGPU may dispatch the second call to a fresh worker: a new Engine must | |
| # recover the pending turn from the state file. | |
| fresh = Engine() | |
| v2 = fresh.play_turn_images() | |
| assert v2.backdrop_url | |
| assert v2.dialogue == v1.dialogue | |
| def test_composed_play_turn_still_full(isolated_state_file): | |
| eng = _started_engine() | |
| v = eng.play_turn("hello there") | |
| assert v.dialogue | |
| assert v.backdrop_url | |
| assert v.present | |
| def test_resume_roundtrip(isolated_state_file): | |
| eng = _started_engine() | |
| v1 = eng.play_turn("hello there") | |
| fresh = Engine() | |
| v = fresh.resume() | |
| assert v is not None | |
| assert v.turn_index == v1.turn_index | |
| assert v.dialogue == v1.dialogue | |
| assert v.backdrop_url | |
| # journal recap: the recent exchanges travel with display names, not ids | |
| assert v.history | |
| assert v.history[-1].dialogue == v1.dialogue | |
| assert v.history[-1].speaker == "Hana" # mock first character's name, not "hana" | |
| def test_resume_without_state_returns_none(isolated_state_file): | |
| assert Engine().resume() is None | |
| def test_session_info(isolated_state_file, monkeypatch): | |
| from visualnovel import engine as engine_mod | |
| assert engine_mod.session_info() == {"exists": False} | |
| eng = _started_engine() | |
| eng.play_turn("hello there") | |
| info = engine_mod.session_info() | |
| assert info["exists"] is True | |
| assert info["turn_index"] == eng.state.turn_index | |
| assert info["place"] == eng.state.scene.place | |
| assert info["ended"] is False | |