Ajoute "Variable globale" au sélecteur "Valeur fixe / Donnée d'un autre objet"
Le sélecteur de valeur de comparaison (tout contrôle "..._valeur" : filtre
du Répéteur, Donnée liée, condition de visibilité) proposait déjà une
valeur fixe ou le champ d'un AUTRE objet - manquait la possibilité de
comparer à une variable globale (voir db/global_vars/), qui change elle
aussi en cours de partie mais n'est rattachée à aucun objet précis.
Nouvelle syntaxe interne "{{$nom_variable}}" (le "$" la distingue sans
ambiguïté de "{{Objet.champ}}", qui attend toujours un point) :
_resolve_filter_value (filter_repeater_rows.py) va lire sa valeur actuelle
via db.get_global_variable, comme "{{Objet.champ}}" le fait déjà pour un
champ d'objet. Le panneau de propriétés gagne un troisième mode
"Variable globale" à côté de "Valeur fixe"/"Donnée d'un autre objet",
avec la liste déroulante des variables existantes.
Corrige au passage un bug latent découvert en testant bout en bout : un
Répéteur SANS modèle de ligne (texte brut avec {{champ}}) plantait en
mode jouable avec TypeError - render_repeater.py substitue lui aussi
directement les {{champ}} du ctx dans ce cas (repli), et ce ctx porte
aussi _forge_play_mode (un booléen, voir render_element_html.py) depuis
l'ajout de la condition de visibilité - déjà corrigé pour le chemin
générique (apply_ctx.py) mais pas pour ce chemin séparé.
Le sélecteur de champ pour insérer {{champ}} dans "Contenu" (demandé dans
le même message) existe déjà depuis un tour précédent (voir
insertFieldAtCursor()) - vérifié toujours fonctionnel.
Ajoute tests/test_filter_value_global_variable.py.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
6894c5fc95
commit
7d48445443
@@ -0,0 +1,95 @@
|
||||
"""Le sélecteur "Valeur fixe / Donnée d'un autre objet" (tout contrôle
|
||||
"..._valeur", voir screen_edit.html) propose désormais aussi "Variable
|
||||
globale" — syntaxe interne "{{$nom_variable}}" (voir _VAR_REF_PATTERN,
|
||||
filter_repeater_rows.py), au même titre que "{{Objet.champ}}" pour un
|
||||
objet."""
|
||||
import re
|
||||
|
||||
import db
|
||||
import screens
|
||||
|
||||
|
||||
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 _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": "3"})
|
||||
client.post(f"/game/{slug}/objects/{def_id}/data/new", data={"numero": "5"})
|
||||
return def_id
|
||||
|
||||
|
||||
def test_repeater_filter_resolves_a_global_variable_reference(client, game):
|
||||
client.post(f"/game/{game}/variables/new", data={"name": "niveau_actuel", "type": "nombre_entier", "default_value": "3"})
|
||||
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))
|
||||
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": "{{$niveau_actuel}}",
|
||||
})
|
||||
|
||||
payload = client.get(f"/game/{game}/runtime-payload").get_json()
|
||||
html = payload["screens"][0]["elements"][0]["rendered_html"]
|
||||
assert html.count("repeaterItem") == 1
|
||||
|
||||
db.update_global_variable_value(game, "niveau_actuel", "5")
|
||||
payload = client.get(f"/game/{game}/runtime-payload").get_json()
|
||||
html = payload["screens"][0]["elements"][0]["rendered_html"]
|
||||
assert html.count("repeaterItem") == 1
|
||||
|
||||
|
||||
def test_deleted_variable_reference_matches_nothing_rather_than_crashing(client, game):
|
||||
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))
|
||||
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": "{{$inexistante}}",
|
||||
})
|
||||
payload = client.get(f"/game/{game}/runtime-payload").get_json()
|
||||
html = payload["screens"][0]["elements"][0]["rendered_html"]
|
||||
assert html.count("repeaterItem") == 0
|
||||
|
||||
|
||||
def test_variable_picker_appears_in_the_properties_panel(client, game):
|
||||
client.post(f"/game/{game}/variables/new", data={"name": "niveau_actuel", "type": "nombre_entier", "default_value": "3"})
|
||||
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))
|
||||
client.post(f"/game/{game}/elements/{rep_id}/save", data={"ctrl_definition_id": str(def_id)})
|
||||
|
||||
html = client.get(f"/game/{game}/screens/{screen_id}/edit?selected={rep_id}").data.decode()
|
||||
assert "filterValueVarSel" in html
|
||||
assert '<option value="niveau_actuel">niveau_actuel</option>' in html
|
||||
|
||||
|
||||
def test_repeater_without_template_still_renders_in_play_mode(client, game):
|
||||
"""Non-régression : un Répéteur sans modèle de ligne (texte brut avec
|
||||
{{champ}}) plantait en mode jouable avec TypeError: replace() argument
|
||||
2 must be str, not bool - le ctx porte aussi _forge_play_mode (voir
|
||||
render_element_html.py), un booléen jamais destiné à une substitution
|
||||
{{champ}}."""
|
||||
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))
|
||||
client.post(f"/game/{game}/elements/{rep_id}/save", data={"ctrl_definition_id": str(def_id), "ctrl_content": "Numéro : {{numero}}"})
|
||||
|
||||
resp = client.get(f"/game/{game}/play")
|
||||
assert resp.status_code == 200
|
||||
assert b"Num\xc3\xa9ro : 3" in resp.data
|
||||
Reference in New Issue
Block a user