diff --git a/screens/rendering/style_string.py b/screens/rendering/style_string.py index df479aa8..75d9d8ad 100644 --- a/screens/rendering/style_string.py +++ b/screens/rendering/style_string.py @@ -38,6 +38,16 @@ def _style_string(el, extra_base="", parent_flex_direction=None, widget_key=None nonlocal has_size_override if v in (None, ""): return False + if isinstance(v, (dict, list)): + # Ne devrait jamais arriver (chaque contrôle composite — ex. + # "shadow", voir c_shadow.py — est censé combiner ses sous- + # valeurs en une seule chaîne CSS avant d'être stocké), mais + # sert de filet de sécurité : un dict/une liste écrits tels + # quels ici donneraient une valeur CSS invalide (repr Python) + # qui cassait TOUT le style de l'élément (bug déjà corrigé côté + # écriture — voir default_style_for_widget.py — pour des + # éléments déjà créés avant ce correctif). + return False if k == "transform" and not is_child: # Un élément posé directement sur un écran a déjà ce "transform" # posé sur son cadre .canvasElementInner / .playElementInner diff --git a/screens/widgets/default_style_for_widget.py b/screens/widgets/default_style_for_widget.py index 2b2d04a5..abe9b686 100644 --- a/screens/widgets/default_style_for_widget.py +++ b/screens/widgets/default_style_for_widget.py @@ -73,6 +73,16 @@ def default_style_and_attributes(widget_key): # type de réglage (voir size_override_controls.py) — jamais une # vraie valeur par défaut à figer à la création. continue + if ctype == "shadow": + # "default" est un dict composite (voir c_shadow.py), pas une + # valeur CSS directement écrivable — sans cette exclusion, il + # était écrit TEL QUEL (repr Python d'un dict) dans le style de + # TOUT élément fraîchement créé, une valeur CSS invalide qui + # cassait le "box-shadow" de tout élément neuf. Comme pour + # "color" ci-dessus, la valeur par défaut n'est qu'une + # suggestion affichée dans le panneau, pas un réglage neutre à + # figer à la création (le neutre CSS est "pas d'ombre"). + continue if is_native_class_widget and control["key"] in _ZERO_DEFAULT_RESET_KEYS: continue target = control.get("target") diff --git a/tests/test_shadow_controls.py b/tests/test_shadow_controls.py index 253952a8..6055a818 100644 --- a/tests/test_shadow_controls.py +++ b/tests/test_shadow_controls.py @@ -14,6 +14,52 @@ def _create_element(client, slug, widget="conteneur"): return screen_id, element_id +def test_freshly_created_element_has_no_shadow_baked_in(client, game): + """Régression : le réglage par défaut du contrôle "shadow" est un dict + composite (voir c_shadow.py), pas une valeur CSS directement écrivable + — sans une exclusion dédiée dans default_style_for_widget.py, il était + écrit TEL QUEL (repr Python d'un dict) dans le style de TOUT élément + fraîchement créé, une valeur CSS invalide qui cassait "box-shadow" (et + donnait l'impression que "les propriétés ne sont pas prises en compte") + sur chaque nouvel élément, quel que soit son widget.""" + _screen_id, element_id = _create_element(client, game) + el = screens.get_element(game, element_id) + assert "box-shadow" not in el["style"] + + html = client.get(f"/game/{game}/runtime-payload").get_json() + rendered_html = None + for s in html["screens"]: + for e in s["elements"]: + if e["id"] == element_id: + rendered_html = e["rendered_html"] + assert rendered_html is not None + assert "{" not in rendered_html.split("style=")[1].split(">")[0] + + +def test_already_corrupted_style_does_not_break_the_whole_style_attribute(client, game): + """Filet de sécurité (voir style_string.py) : un élément déjà touché par + le bug ci-dessus avant son correctif (un dict resté tel quel dans son + style stocké) doit continuer à s'afficher — sans lui, une seule valeur + invalide au milieu de l'attribut style pouvait tout casser.""" + import json + + import db + + _screen_id, element_id = _create_element(client, game, "texte") + el = screens.get_element(game, element_id) + corrupted_style = dict(el["style"]) + corrupted_style["box-shadow"] = {"x": 0, "y": 4, "blur": 12, "spread": 0, "color": "#000000", "opacity": 0.4} + conn = db.connect(game) + conn.execute("UPDATE _screen_elements SET style = ? WHERE id = ?", (json.dumps(corrupted_style), element_id)) + conn.commit() + conn.close() + + html = client.get(f"/game/{game}/screens/{_screen_id}/edit").data.decode() + idx = html.find(f'id="elt-{element_id}"') + style_attr = html[max(0, idx - 400):idx] + assert "{" not in style_attr.split('style="')[-1] + + def test_shadow_controls_appear_in_the_properties_panel(client, game): screen_id, element_id = _create_element(client, game) html = client.get(f"/game/{game}/screens/{screen_id}/edit?selected={element_id}").data.decode()