"""Variables globales de type "objet"/"tableau" (db/constants.py) : la valeur est du JSON, lisible via un chemin ".champ"/"[index]" chaînable (_resolve_variable_path, screens/rendering/filter_repeater_rows.py) — aussi bien dans un filtre de Répéteur ({{$var.chemin}}) que dans une Condition de visibilité en mode "variable" (contrôle séparé "visibilite_cond_variable_chemin").""" import json import re import db 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 _add_element(client, slug, screen_id, widget="texte"): resp = client.post(f"/game/{slug}/screens/{screen_id}/elements/add", data={"widget": widget}, follow_redirects=False) return int(re.search(r"selected=(\d+)", resp.headers["Location"]).group(1)) def _rendered_html(client, slug): payload = client.get(f"/game/{slug}/runtime-payload").get_json() return payload["screens"][0]["elements"][0]["rendered_html"] def _create_niveau_object(client, slug): resp = client.post(f"/game/{slug}/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]) client.post(f"/game/{slug}/objects/{def_id}/data/new", data={"numero": "12"}) client.post(f"/game/{slug}/objects/{def_id}/data/new", data={"numero": "99"}) return def_id def test_create_and_update_validate_json_with_safe_fallback(client, game): client.post(f"/game/{game}/variables/new", data={"name": "perso", "type": "objet", "default_value": "not json"}) variable = db.get_global_variable(game, "perso") assert variable["value"] == "{}" # JSON invalide -> défaut sûr, pas la chaîne brute client.post(f"/game/{game}/variables/new", data={"name": "scores", "type": "tableau", "default_value": ""}) variable = db.get_global_variable(game, "scores") assert variable["value"] == "[]" # vide -> défaut sûr propre au type db.update_global_variable(game, db.get_global_variable(game, "perso")["id"], "objet", json.dumps({"nom": "Alice"})) assert json.loads(db.get_global_variable(game, "perso")["value"]) == {"nom": "Alice"} def test_repeater_filter_reads_a_field_inside_an_object_variable(client, game): client.post(f"/game/{game}/variables/new", data={ "name": "perso", "type": "objet", "default_value": json.dumps({"nom": "Alice", "arme": {"degats": 12}}), }) def_id = _create_niveau_object(client, game) screen_id = _create_screen(client, game) resp = client.post(f"/game/{game}/screens/{screen_id}/elements/add", data={"widget": "repeteur_donnees"}, follow_redirects=False) rep_id = int(re.search(r"selected=(\d+)", resp.headers["Location"]).group(1)) # {{$perso.arme.degats}} (chemin chaîné) doit résoudre à 12, et donc ne # garder que la ligne "numero = 12". client.post(f"/game/{game}/elements/{rep_id}/save", data={ "ctrl_definition_id": str(def_id), "ctrl_filtre_champ": "numero", "ctrl_filtre_operateur": "egal", "ctrl_filtre_valeur": "{{$perso.arme.degats}}", }) html = _rendered_html(client, game) assert html.count("repeaterItem") == 1 assert 'data-row-id="1"' in html assert 'data-row-id="2"' not in html def test_visibility_condition_reads_an_array_element_by_index(client, game): client.post(f"/game/{game}/variables/new", data={ "name": "scores", "type": "tableau", "default_value": json.dumps([10, 20, 30]), }) screen_id = _create_screen(client, game) element_id = _add_element(client, game, screen_id) # scores[1] == 20 -> visible. client.post(f"/game/{game}/elements/{element_id}/save", data={ "ctrl_visibilite_cond_mode": "variable", "ctrl_visibilite_cond_variable": "scores", "ctrl_visibilite_cond_variable_chemin": "[1]", "ctrl_visibilite_cond_operateur": "egal", "ctrl_visibilite_cond_valeur": "20", }) html = _rendered_html(client, game) assert "display:none" not in html # scores[0] == 10, pas 20 -> masqué. client.post(f"/game/{game}/elements/{element_id}/save", data={ "ctrl_visibilite_cond_mode": "variable", "ctrl_visibilite_cond_variable": "scores", "ctrl_visibilite_cond_variable_chemin": "[0]", "ctrl_visibilite_cond_operateur": "egal", "ctrl_visibilite_cond_valeur": "20", }) html = _rendered_html(client, game) assert "display:none" in html def test_invalid_or_missing_path_never_crashes(client, game): client.post(f"/game/{game}/variables/new", data={ "name": "perso", "type": "objet", "default_value": json.dumps({"nom": "Alice"}), }) screen_id = _create_screen(client, game) element_id = _add_element(client, game, screen_id) client.post(f"/game/{game}/elements/{element_id}/save", data={ "ctrl_visibilite_cond_mode": "variable", "ctrl_visibilite_cond_variable": "perso", "ctrl_visibilite_cond_variable_chemin": ".champ_inexistant", "ctrl_visibilite_cond_operateur": "egal", "ctrl_visibilite_cond_valeur": "peu importe", }) resp = client.get(f"/game/{game}/runtime-payload") assert resp.status_code == 200 assert "display:none" in resp.get_json()["screens"][0]["elements"][0]["rendered_html"]