File size: 2,668 Bytes
1ea8154
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
"""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


@pytest.fixture()
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