"""Un nœud Condition du graphe de logique peut désormais tester une VARIABLE GLOBALE, pas seulement un champ d'objet (voir ensure_flow_schema.py : cond_source/cond_variable/cond_variable_chemin). Évalué côté CLIENT (templates/play.html, evaluateConditionClause) contre gameData.variables — exposé par full_game_payload.py, vérifié ici via /runtime-payload. La logique JS elle-même (résolution de chemin, lecture de gameData.variables, repli sur le comportement historique) a été vérifiée manuellement avec Node.js (fonctions extraites de play.html) : variable scalaire, objet avec chemin chaîné, tableau par index, variable introuvable (ne matche rien plutôt que planter), booléen "Oui", et clause sans "source" (comportement legacy, champ d'objet) — les 7 scénarios passent.""" import json import re 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_condition_node_stores_variable_source(client, game): client.post(f"/game/{game}/variables/new", data={"name": "score", "type": "nombre_entier", "default_value": "5"}) screen_id = _create_screen(client, game) resp = client.post( f"/game/{game}/screens/{screen_id}/flow/nodes/add", data=json.dumps({ "node_type": "condition", "cond_source": "variable", "cond_variable": "score", "cond_variable_chemin": "", "cond_operator": "superieur_egal", "cond_value": "3", }), content_type="application/json", ) assert resp.status_code == 200 node = resp.get_json() assert node["cond_source"] == "variable" assert node["cond_variable"] == "score" assert node["cond_operator"] == "superieur_egal" # Aucun champ d'objet renseigné pour une clause "variable". assert node["cond_definition_id"] is None assert node["cond_field"] is None def test_condition_node_with_combined_variable_and_object_clauses(client, game): client.post(f"/game/{game}/variables/new", data={"name": "score", "type": "nombre_entier", "default_value": "5"}) resp = client.post(f"/game/{game}/objects/new", data={ "object_name": "Niveau", "field_name[]": ["numero"], "field_type[]": ["nombre_entier"], "field_relation[]": [""], "field_required[]": ["0"], "field_min[]": [""], "field_max[]": [""], }, follow_redirects=False) def_id = int(resp.headers["Location"].rstrip("/").split("/")[-1]) screen_id = _create_screen(client, game) clauses = [ {"source": "variable", "variable": "score", "operator": "superieur_egal", "value": "3"}, {"source": "objet", "definition_id": def_id, "row_id": None, "field": "numero", "field_type": "nombre_entier", "operator": "egal", "value": "1"}, ] resp = client.post( f"/game/{game}/screens/{screen_id}/flow/nodes/add", data=json.dumps({ "node_type": "condition", "cond_clauses": json.dumps(clauses), "cond_combinator": "ou", }), content_type="application/json", ) assert resp.status_code == 200 node = resp.get_json() stored = json.loads(node["cond_clauses"]) assert stored[0]["source"] == "variable" assert stored[1]["source"] == "objet" assert node["cond_combinator"] == "ou" def test_runtime_payload_exposes_global_variables_for_condition_evaluation(client, game): client.post(f"/game/{game}/variables/new", data={"name": "score", "type": "nombre_entier", "default_value": "5"}) client.post(f"/game/{game}/variables/new", data={"name": "perso", "type": "objet", "default_value": json.dumps({"nom": "Alice"})}) payload = client.get(f"/game/{game}/runtime-payload").get_json() assert payload["variables"]["score"] == {"value": "5", "type": "nombre_entier"} assert payload["variables"]["perso"]["type"] == "objet" assert json.loads(payload["variables"]["perso"]["value"]) == {"nom": "Alice"} def test_screen_edit_panel_exposes_variable_source_condition_ui(client, game): client.post(f"/game/{game}/variables/new", data={"name": "score", "type": "nombre_entier", "default_value": "5"}) screen_id = _create_screen(client, game) html = client.get(f"/game/{game}/screens/{screen_id}/edit").get_data(as_text=True) assert 'id="nf-cond-source"' in html assert 'id="nf-cond-variable-fields"' in html assert "onFlowConditionSourceChange" in html assert "onFlowClauseSourceChange" in html def test_condition_builder_still_available_without_any_object_defined(client, game): """Régression : le panneau de condition était entièrement masqué quand le jeu n'avait AUCUN objet ("Aucun objet défini..."), rendant impossible de créer une condition sur une variable dans ce cas. Seule la partie "Champ d'un objet" doit rester masquée ; "Variable globale" doit rester utilisable.""" client.post(f"/game/{game}/variables/new", data={"name": "score", "type": "nombre_entier", "default_value": "5"}) screen_id = _create_screen(client, game) html = client.get(f"/game/{game}/screens/{screen_id}/edit").get_data(as_text=True) assert 'id="nf-cond-source"' in html assert 'id="nf-cond-variable-fields"' in html