Corrige le contenu de "Donnée liée" figé après un changement de variable/donnée

Bug rapporté : changer une variable globale utilisée comme valeur de
comparaison d'une "Donnée liée" (Texte/Titre, dans une boîte de dialogue
posée comme élément de jeu réutilisable) recalculait bien, côté SERVEUR,
la bonne ligne à afficher — mais le contenu affiché en jeu restait figé
sur son ancienne ligne tant que la page n'était pas complètement
rechargée.

Cause : templates/play.html ne régénère, après une action "Modifier une
variable"/"Modifier une donnée", QUE les éléments dont le rendered_html
porte un marqueur connu (voir refreshRuntimeData()/hasMarker() —
"repeaterItem", "jaugeBar", "visibilityGated"). Un Texte/Titre "Donnée
liée" n'en portait AUCUN : jamais identifié comme "dépendant de la
donnée", donc jamais régénéré, même si le nouveau HTML était déjà prêt
côté serveur à chaque rendu.

Correctif : nouveau marqueur "dataBound" (render_element_html.py), posé
dès que _data_definition_id est réglé, reconnu par hasMarker(). Un
exemplaire d'élément de jeu (ex. la boîte de dialogue posée sur une
scène) porte ce marqueur EN PROFONDEUR dans son propre rendered_html dès
qu'un de ses descendants internes en a un — il se retrouve donc bien
régénéré dans son ensemble, sans changement supplémentaire nécessaire.

Deux nouveaux tests, confirmés en échec sur l'ancien code (même scénario
que le rapport : reproduit avec objet "dialog" + variable "dialog_order"
+ élément de jeu réutilisable) puis au vert avec le correctif. 155 tests
au vert au total.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-08-28 09:47:12 +02:00
co-authored by Claude Sonnet 5
parent 8cffbeac68
commit 124f250d2b
3 changed files with 129 additions and 4 deletions
+100
View File
@@ -0,0 +1,100 @@
"""Régression : changer une variable globale (ou une donnée) pendant la
partie recalculait bien, côté SERVEUR, quelle ligne "Donnée liée" (Texte/
Titre) doit afficher — mais templates/play.html ne régénère, après une
telle action, que les éléments dont le rendered_html porte un marqueur
connu ("repeaterItem"/"jaugeBar"/"visibilityGated" — voir
refreshRuntimeData()/hasMarker()). Un Texte/Titre "Donnée liée" n'en
portait AUCUN : son contenu restait donc figé sur son ancienne ligne
jusqu'au prochain rechargement complet de page, même si le bon contenu
était déjà prêt côté serveur — "la variable change mais pas le contenu de
la modale". Corrigé en ajoutant un marqueur "dataBound" (render_element_html.py),
reconnu par hasMarker()."""
import re
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 test_data_bound_element_carries_the_refresh_marker(client, game):
resp = client.post(f"/game/{game}/objects/new", data={
"object_name": "Niveau", "field_name[]": ["numero", "spiker"],
"field_type[]": ["nombre_entier", "texte"],
"field_relation[]": ["", ""], "field_required[]": ["0", "0"],
"field_min[]": ["", ""], "field_max[]": ["", ""],
}, follow_redirects=False)
def_id = int(resp.headers["Location"].rstrip("/").split("/")[-1])
client.post(f"/game/{game}/objects/{def_id}/data/new", data={"numero": "1", "spiker": "un"})
client.post(f"/game/{game}/objects/{def_id}/data/new", data={"numero": "2", "spiker": "deux"})
client.post(f"/game/{game}/variables/new", data={"name": "n", "type": "nombre_entier", "default_value": "1"})
screen_id = _create_screen(client, game)
resp = client.post(f"/game/{game}/screens/{screen_id}/elements/add", data={"widget": "texte"}, follow_redirects=False)
el_id = int(re.search(r"selected=(\d+)", resp.headers["Location"]).group(1))
client.post(f"/game/{game}/elements/{el_id}/save", data={
"ctrl_content": "{{spiker}}",
"ctrl_data_definition_id": str(def_id),
"ctrl_data_filtres_champ[]": ["numero"],
"ctrl_data_filtres_operateur[]": ["egal"],
"ctrl_data_filtres_valeur[]": ["{{$n}}"],
})
payload = client.get(f"/game/{game}/runtime-payload").get_json()
el = payload["screens"][0]["elements"][0]
assert "dataBound" in el["rendered_html"]
assert "un" in el["rendered_html"]
# Change la variable -> le rendu recalculé (déjà correct côté serveur
# avant ce correctif) doit refléter la nouvelle ligne.
import db
variable = db.get_global_variable(game, "n")
db.update_global_variable(game, variable["id"], "nombre_entier", "2")
payload2 = client.get(f"/game/{game}/runtime-payload").get_json()
el2 = payload2["screens"][0]["elements"][0]
assert "deux" in el2["rendered_html"]
assert "dataBound" in el2["rendered_html"]
def test_element_type_instance_bubbles_up_the_data_bound_marker(client, game):
"""Même vérification, mais pour un Texte "Donnée liée" posé DANS un
élément de jeu réutilisable : le marqueur porté par l'élément interne
doit se retrouver dans le rendered_html de l'EXEMPLAIRE (le seul élément
réellement présent dans la liste plate de la scène qui l'accueille —
voir list_elements.py, les descendants d'un exemplaire sont exclus)."""
resp = client.post(f"/game/{game}/objects/new", data={
"object_name": "dialog", "field_name[]": ["order", "spiker"],
"field_type[]": ["nombre_entier", "texte"],
"field_relation[]": ["", ""], "field_required[]": ["0", "0"],
"field_min[]": ["", ""], "field_max[]": ["", ""],
}, follow_redirects=False)
def_id = int(resp.headers["Location"].rstrip("/").split("/")[-1])
client.post(f"/game/{game}/objects/{def_id}/data/new", data={"order": "1", "spiker": "Un"})
client.post(f"/game/{game}/objects/{def_id}/data/new", data={"order": "2", "spiker": "Deux"})
client.post(f"/game/{game}/variables/new", data={"name": "dialog_order", "type": "nombre_entier", "default_value": "1"})
client.post(f"/game/{game}/element-types", data={"name": "dialog"}, follow_redirects=False)
et = next(t for t in screens.list_element_types(game) if t["name"] == "dialog")
resp = client.post(f"/game/{game}/screens/{et['template_screen_id']}/elements/add", data={"widget": "superposition"}, follow_redirects=False)
overlay_id = int(re.search(r"selected=(\d+)", resp.headers["Location"]).group(1))
resp = client.post(f"/game/{game}/elements/{overlay_id}/children/add", data={"widget": "texte"}, follow_redirects=False)
spiker_id = int(re.search(r"selected=(\d+)", resp.headers["Location"]).group(1))
client.post(f"/game/{game}/elements/{spiker_id}/save", data={
"ctrl_content": "{{spiker}}",
"ctrl_data_definition_id": str(def_id),
"ctrl_data_filtres_champ[]": ["order"],
"ctrl_data_filtres_operateur[]": ["egal"],
"ctrl_data_filtres_valeur[]": ["{{$dialog_order}}"],
})
screen_id = _create_screen(client, game, "Scene")
client.post(f"/game/{game}/screens/{screen_id}/elements/add",
data={"widget": "__catalogue__", "element_type_id": et["id"]}, follow_redirects=False)
payload = client.get(f"/game/{game}/runtime-payload").get_json()
instance = payload["screens"][0]["elements"][0]
assert "dataBound" in instance["rendered_html"]
assert "Un" in instance["rendered_html"]