"""Mock-mode end-to-end test of the branching state machine, driving the same handler functions Gradio wires to the UI. Run: STORY_MOCK=1 python test_flow.py """ import os assert os.environ.get("STORY_MOCK") == "1", "run with STORY_MOCK=1" import app # noqa: E402 def unpack(result): state = result[0] return state def current(state): return state["nodes"][state["path"]] def test_full_flow(): # Start: opening node with title + choice state = unpack( app.start_story( "Mia", "3-6", "en", "Hop", "little rabbit", "curious", "a small white rabbit", "Sage", "old tortoise", "wise", "an old green tortoise", "Didn't want to share toys today.", ) ) node = current(state) assert state["path"] == "" assert node["title"], "opening must have a title" assert node["choice"], "opening must have a choice" assert state["images"].get("") is not None, "opening must have an illustration" # Choose B twice -> mock yields gentle_setback at depth 2 (MOCK-B path) state = unpack(app.choose(state, "B")) assert state["path"] == "B" assert current(state)["choice"], "depth 1 must still have a choice" state = unpack(app.choose(state, "B")) assert state["path"] == "BB" node = current(state) assert node["isEnding"] and node["endingTone"] == "gentle_setback", f"expected setback, got {node['endingTone']}" # Rewind to the choice point: path back to 'B', option B disabled state = unpack(app.back_to_choice(state)) assert state["path"] == "B" assert state["disabled_option"] == "B" # Replay with A -> sibling BB is a setback => replay flag forces warm state = unpack(app.choose(state, "A")) node = current(state) assert state["path"] == "BA" assert node["isEnding"] and node["endingTone"] == "warm", f"replay must end warm, got {node['endingTone']}" assert node["moral"], "warm ending must carry a moral" # Branch reuse: returning to an explored path must not regenerate nodes_before = dict(state["nodes"]) state = unpack(app.back_to_choice(state)) state = unpack(app.choose(state, "A")) # BA again — cached assert state["nodes"] is not nodes_before or len(state["nodes"]) == len(nodes_before) assert state["path"] == "BA" # Story markdown sanity md = app._story_markdown(state) assert "🌙" in md or len(md) > 50 print("✅ full branching flow passed:", sorted(state["nodes"].keys())) def test_render_shapes(): state = unpack( app.start_story( "Mia", "3-6", "de", # German: TTS must degrade gracefully (None) "Hop", "rabbit", "curious", "white rabbit", "", "", "", "", "A small quarrel happened.", ) ) rendered = app._render(state) assert len(rendered) == 12, f"render tuple arity changed: {len(rendered)}" audio = rendered[5] assert audio is None or isinstance(audio, tuple) print("✅ render shapes ok (German TTS degraded gracefully)") if __name__ == "__main__": test_full_flow() test_render_shapes() print("🎉 mock e2e: PASS")