From a28cc5881e58a809d851f498fb7db46d927a1dbe Mon Sep 17 00:00:00 2001 From: william Date: Tue, 25 Aug 2026 12:31:50 +0200 Subject: [PATCH] =?UTF-8?q?Corrige=20la=20condition=20de=20visibilit=C3=A9?= =?UTF-8?q?=20:=20un=20widget=20"special=5Frender"=20visible=20ne=20se=20r?= =?UTF-8?q?afra=C3=AEchissait=20jamais=20en=20jeu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- screens/rendering/render_element_html.py | 27 ++++++++++++++++-------- tests/test_visibility_condition.py | 24 +++++++++++++++++++++ 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/screens/rendering/render_element_html.py b/screens/rendering/render_element_html.py index a569f8c0..240df2df 100644 --- a/screens/rendering/render_element_html.py +++ b/screens/rendering/render_element_html.py @@ -56,25 +56,34 @@ def render_element_html(el, slug=None, children_map=None, ctx=None, parent_flex_ bound_ctx = _resolve_bound_row_ctx(slug, attrs_raw) if bound_ctx: ctx = {**(ctx or {}), **bound_ctx} + # Chaque branche "special_render" passe elle aussi par _mark() : sans + # ça, un widget de ce genre (Icône, Tableau, Superposition...) actuellement + # VISIBLE sous condition ne portait aucun marqueur, et refreshRuntimeData() + # (play.html) ne savait donc jamais qu'il fallait le réévaluer après une + # action "Modifier une donnée/variable" — il restait figé dans son état + # de l'affichage précédent (ex: une icône "enveloppe fermée" restée + # visible après l'ouverture du mail, en même temps que "enveloppe + # ouverte" qui apparaît, elle, correctement puisqu'un élément CACHÉ + # porte toujours son marqueur via le placeholder ci-dessus). special = meta.get("special_render") if special == "table": - return _render_table(el, meta, slug, children_map, ctx, parent_flex_direction) + return _mark(_render_table(el, meta, slug, children_map, ctx, parent_flex_direction)) if special == "select_options": - return _render_select(el, meta, slug, children_map, ctx, parent_flex_direction) + return _mark(_render_select(el, meta, slug, children_map, ctx, parent_flex_direction)) if special == "fieldset_legend": - return _render_fieldset(el, meta, slug, children_map, ctx, parent_flex_direction) + return _mark(_render_fieldset(el, meta, slug, children_map, ctx, parent_flex_direction)) if special == "checkbox_label": - return _render_checkbox_or_radio(el, meta, slug, children_map, ctx, parent_flex_direction) + return _mark(_render_checkbox_or_radio(el, meta, slug, children_map, ctx, parent_flex_direction)) if special == "repeater": - return _render_repeater(el, meta, slug, children_map, ctx, parent_flex_direction) + return _mark(_render_repeater(el, meta, slug, children_map, ctx, parent_flex_direction)) if special == "jauge": - return _render_jauge(el, meta, slug, children_map, ctx, parent_flex_direction) + return _mark(_render_jauge(el, meta, slug, children_map, ctx, parent_flex_direction)) if special == "overlay": - return _render_overlay(el, meta, slug, children_map, ctx, parent_flex_direction) + return _mark(_render_overlay(el, meta, slug, children_map, ctx, parent_flex_direction)) if special == "onglets": - return _render_onglets(el, meta, slug, children_map, ctx, parent_flex_direction) + return _mark(_render_onglets(el, meta, slug, children_map, ctx, parent_flex_direction)) if special == "icone": - return _render_icone(el, meta, slug, children_map, ctx, parent_flex_direction) + return _mark(_render_icone(el, meta, slug, children_map, ctx, parent_flex_direction)) tag = meta.get("tag") if tag is None: # widget à balise dynamique (ex: "titre" -> h1/h2/h3) diff --git a/tests/test_visibility_condition.py b/tests/test_visibility_condition.py index 9b4cc325..7ed63bc4 100644 --- a/tests/test_visibility_condition.py +++ b/tests/test_visibility_condition.py @@ -79,6 +79,30 @@ def test_objet_mode_uses_most_recent_row_outside_a_repeater(client, game): 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