Bug remonté avec deux icônes (enveloppe fermée / ouverte) posées
directement sur l'écran, chacune conditionnée sur is_opened : après avoir
ouvert le mail (une action "Modifier une donnée"), les DEUX icônes
restaient affichées en même temps au lieu que l'ouverte remplace la fermée.
Cause : refreshRuntimeData() (play.html) ne réévalue, après une action,
que les éléments dont le HTML porte un marqueur ("visibilityGated"/
"repeaterItem"/"jaugeBar"). render_element_html() posait bien ce marqueur
quand un élément sous condition est actuellement visible - mais seulement
sur le chemin de rendu GÉNÉRIQUE (texte, titre, conteneur...), jamais sur
les 9 widgets "special_render" (Icône, Tableau, Superposition, Onglets,
Case à cocher, Liste déroulante, Groupe de champs, Répéteur, Jauge) - un
élément CACHÉ portait toujours son marqueur (via son placeholder), mais un
élément VISIBLE de ce type non. Résultat : l'icône "fermée", visible au
premier chargement, ne portait aucun marqueur et restait donc figée dans
son état d'origine après toute action suivante, pendant que l'icône
"ouverte" (cachée au départ, donc marquée) se mettait, elle, correctement
à jour - d'où les deux affichées ensemble.
Fix : les 9 branches special_render passent maintenant, elles aussi, par
_mark() comme le chemin générique.
Ajoute un test de régression dédié (icône visible sous condition = doit
porter le marqueur).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
147 lines
7.4 KiB
Python
147 lines
7.4 KiB
Python
"""Tests de la "condition de visibilité" (panneau de propriétés, tout
|
|
widget) : masque un élément en mode jouable selon une variable globale ou
|
|
le champ d'un objet de données. Jamais appliquée dans l'éditeur."""
|
|
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 _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 test_variable_mode_hides_and_shows_element(client, game):
|
|
screen_id = _create_screen(client, game)
|
|
element_id = _add_element(client, game, screen_id)
|
|
client.post(f"/game/{game}/variables/new", data={"name": "porte_ouverte", "type": "booleen", "default_value": "0"})
|
|
client.post(f"/game/{game}/elements/{element_id}/save", data={
|
|
"ctrl_visibilite_cond_mode": "variable",
|
|
"ctrl_visibilite_cond_variable": "porte_ouverte",
|
|
"ctrl_visibilite_cond_operateur": "egal",
|
|
"ctrl_visibilite_cond_valeur": "1",
|
|
})
|
|
|
|
html = _rendered_html(client, game)
|
|
assert "display:none" in html
|
|
|
|
db.update_global_variable_value(game, "porte_ouverte", "1")
|
|
html = _rendered_html(client, game)
|
|
assert "display:none" not in html
|
|
|
|
|
|
def test_visibility_condition_never_hides_in_the_editor(client, game):
|
|
screen_id = _create_screen(client, game)
|
|
element_id = _add_element(client, game, screen_id)
|
|
client.post(f"/game/{game}/variables/new", data={"name": "porte_ouverte", "type": "booleen", "default_value": "0"})
|
|
client.post(f"/game/{game}/elements/{element_id}/save", data={
|
|
"ctrl_visibilite_cond_mode": "variable",
|
|
"ctrl_visibilite_cond_variable": "porte_ouverte",
|
|
"ctrl_visibilite_cond_operateur": "egal",
|
|
"ctrl_visibilite_cond_valeur": "1",
|
|
})
|
|
html = client.get(f"/game/{game}/screens/{screen_id}/edit").data.decode()
|
|
assert 'class="visibilityGated"' not in html
|
|
assert f'id="el-{element_id}"' in html
|
|
|
|
|
|
def test_objet_mode_uses_most_recent_row_outside_a_repeater(client, game):
|
|
screen_id = _create_screen(client, game)
|
|
element_id = _add_element(client, game, screen_id)
|
|
resp = client.post(f"/game/{game}/objects/new", data={
|
|
"object_name": "Partie", "field_name[]": ["score"], "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/{game}/objects/{def_id}/data/new", data={"score": "3"})
|
|
|
|
client.post(f"/game/{game}/elements/{element_id}/save", data={
|
|
"ctrl_visibilite_cond_mode": "objet",
|
|
"ctrl_visibilite_cond_definition_id": str(def_id),
|
|
"ctrl_visibilite_cond_champ": "score",
|
|
"ctrl_visibilite_cond_operateur": "superieur",
|
|
"ctrl_visibilite_cond_valeur": "10",
|
|
})
|
|
assert "display:none" in _rendered_html(client, game)
|
|
|
|
client.post(f"/game/{game}/objects/{def_id}/data/1/edit", data={"score": "20"})
|
|
assert "display:none" not in _rendered_html(client, game)
|
|
|
|
|
|
def test_visible_special_render_widget_carries_refresh_marker(client, game):
|
|
"""Régression : une icône (special_render, pas le chemin générique)
|
|
actuellement VISIBLE sous condition doit quand même porter le
|
|
marqueur "visibilityGated" — sinon refreshRuntimeData() (play.html) ne
|
|
sait jamais qu'il faut la réévaluer après une action "Modifier une
|
|
donnée/variable", et elle reste figée dans son état précédent. Cas
|
|
concret remonté : une icône "enveloppe fermée" (visible tant que
|
|
is_opened=0) reste affichée EN MÊME TEMPS que l'icône "enveloppe
|
|
ouverte" (qui, elle, se met à jour normalement puisqu'un élément
|
|
CACHÉ porte toujours son marqueur via son placeholder)."""
|
|
screen_id = _create_screen(client, game)
|
|
element_id = _add_element(client, game, screen_id, "icone")
|
|
client.post(f"/game/{game}/variables/new", data={"name": "porte_ouverte", "type": "booleen", "default_value": "0"})
|
|
client.post(f"/game/{game}/elements/{element_id}/save", data={
|
|
"ctrl_visibilite_cond_mode": "variable",
|
|
"ctrl_visibilite_cond_variable": "porte_ouverte",
|
|
"ctrl_visibilite_cond_operateur": "egal",
|
|
"ctrl_visibilite_cond_valeur": "0",
|
|
})
|
|
html = _rendered_html(client, game)
|
|
assert "display:none" not in html
|
|
assert "visibilityGated" in html
|
|
|
|
|
|
def test_objet_mode_inside_a_repeater_evaluates_each_row_independently(client, game):
|
|
"""Régression : une "Mail card" (élément de jeu réutilisable posé dans
|
|
un Répéteur d'e-mails) doit afficher l'icône "ouvert"/"fermé" qui
|
|
correspond à SA PROPRE ligne — pas toujours la même ligne (la plus
|
|
récente de l'objet) pour tous les exemplaires du Répéteur."""
|
|
resp = client.post(f"/game/{game}/objects/new", data={
|
|
"object_name": "Email", "field_name[]": ["sujet", "is_opened"], "field_type[]": ["texte", "booleen"],
|
|
"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={"sujet": "Fermé"})
|
|
client.post(f"/game/{game}/objects/{def_id}/data/new", data={"sujet": "Ouvert", "is_opened": "on"})
|
|
|
|
resp = client.post(f"/game/{game}/element-types", data={"name": "Mail card", "icon": "📧"}, follow_redirects=False)
|
|
et = next(t for t in screens.list_element_types(game) if t["name"] == "Mail card")
|
|
template_screen_id = et["template_screen_id"]
|
|
|
|
closed_icon_id = _add_element(client, game, template_screen_id, "icone")
|
|
client.post(f"/game/{game}/elements/{closed_icon_id}/save", data={
|
|
"ctrl_visibilite_cond_mode": "objet", "ctrl_visibilite_cond_definition_id": str(def_id),
|
|
"ctrl_visibilite_cond_champ": "is_opened", "ctrl_visibilite_cond_operateur": "egal", "ctrl_visibilite_cond_valeur": "0",
|
|
})
|
|
open_icon_id = _add_element(client, game, template_screen_id, "icone")
|
|
client.post(f"/game/{game}/elements/{open_icon_id}/save", data={
|
|
"ctrl_visibilite_cond_mode": "objet", "ctrl_visibilite_cond_definition_id": str(def_id),
|
|
"ctrl_visibilite_cond_champ": "is_opened", "ctrl_visibilite_cond_operateur": "egal", "ctrl_visibilite_cond_valeur": "1",
|
|
})
|
|
|
|
screen_id = _create_screen(client, game, "Boîte de réception")
|
|
repeater_id = _add_element(client, game, screen_id, "repeteur_donnees")
|
|
client.post(f"/game/{game}/elements/{repeater_id}/save", data={
|
|
"ctrl_definition_id": str(def_id), "ctrl_template_type_id": str(et["id"]),
|
|
})
|
|
|
|
html = _rendered_html(client, game)
|
|
items = html.split('class="repeaterItem"')
|
|
assert len(items) == 3
|
|
row_closed, row_open = items[1], items[2]
|
|
assert row_closed.count('class="visibilityGated"') == 1
|
|
assert row_open.count('class="visibilityGated"') == 1
|
|
assert row_closed != row_open
|