"""Tests de l'éditeur de flow à nœuds (déclencheur / condition / action).""" import json import re import screens from conftest import play_js_bundle def _create_screen(client, slug, name="Accueil"): resp = client.post(f"/game/{slug}/screens/new", data={"name": name}, follow_redirects=False) return int(re.search(r"/screens/(\d+)/edit", resp.headers["Location"]).group(1)) def test_flow_node_and_edge_crud(client, game): screen_id = _create_screen(client, game) element_id = screens.add_scene_object(game, screen_id) resp = client.post( f"/game/{game}/screens/{screen_id}/flow/nodes/add", data=json.dumps({"node_type": "trigger", "trigger_element_id": element_id, "trigger_event": "clic"}), content_type="application/json", ) assert resp.status_code == 200 trigger_node = resp.get_json() assert trigger_node["node_type"] == "trigger" resp = client.post( f"/game/{game}/screens/{screen_id}/flow/nodes/add", data=json.dumps({"node_type": "action", "action_type": "ecran_suivant"}), content_type="application/json", ) action_node = resp.get_json() resp = client.post( f"/game/{game}/screens/{screen_id}/flow/edges/add", data=json.dumps({"from_node_id": trigger_node["id"], "from_port": "out", "to_node_id": action_node["id"]}), content_type="application/json", ) assert resp.status_code == 200 html = client.get(f"/game/{game}/screens/{screen_id}/edit").data.decode() assert str(trigger_node["id"]) in html def test_multiple_actions_from_one_trigger(client, game): """Régression : un déclencheur doit pouvoir avoir PLUSIEURS arêtes sortantes (plusieurs actions exécutées au même clic).""" screen_id = _create_screen(client, game) element_id = screens.add_scene_object(game, screen_id) resp = client.post( f"/game/{game}/screens/{screen_id}/flow/nodes/add", data=json.dumps({"node_type": "trigger", "trigger_element_id": element_id, "trigger_event": "clic"}), content_type="application/json", ) trigger_node = resp.get_json() action_ids = [] for action_type in ("ecran_suivant", "rien"): resp = client.post( f"/game/{game}/screens/{screen_id}/flow/nodes/add", data=json.dumps({"node_type": "action", "action_type": action_type}), content_type="application/json", ) action_ids.append(resp.get_json()["id"]) for aid in action_ids: resp = client.post( f"/game/{game}/screens/{screen_id}/flow/edges/add", data=json.dumps({"from_node_id": trigger_node["id"], "from_port": "out", "to_node_id": aid}), content_type="application/json", ) assert resp.status_code == 200 edges = screens.list_flow_edges(game, screen_id) from_trigger = [e for e in edges if e["from_node_id"] == trigger_node["id"]] assert len(from_trigger) == 2 def test_play_page_exposes_row_bindings_helpers(client, game): """Le runtime jouable doit embarquer la logique côté client capable de résoudre les {{champ}} de la ligne ouverte (voir README, section "Cliquer une ligne de Répéteur") — vérifie que le JS livré au joueur contient bien les briques attendues, sans dépendre d'un navigateur.""" js = play_js_bundle() assert "applyOpenRowBindings" in js assert "lastClickedRowId" in js assert "'ouvrir_ligne'" in js def test_affichage_trigger_does_not_require_element(client, game): """Régression 1.2 : le nouveau déclencheur "affichage" doit pouvoir être créé SANS élément cible (il concerne l'écran entier), contrairement au déclencheur "clic" — voir screens/flow/constants.py.""" screen_id = _create_screen(client, game) resp = client.post( f"/game/{game}/screens/{screen_id}/flow/nodes/add", data=json.dumps({"node_type": "trigger", "trigger_event": "affichage"}), content_type="application/json", ) assert resp.status_code == 200 node = resp.get_json() assert node["trigger_event"] == "affichage" assert node["trigger_element_id"] is None def test_play_page_exposes_screen_show_trigger_helper(client, game): """Le runtime jouable doit exécuter les nœuds "affichage" à chaque fois qu'un écran est montré (premier affichage, retour en arrière, données modifiées) — voir README, section correspondante.""" js = play_js_bundle() assert "runScreenShowTriggers" in js assert "'affichage'" in js