Files
Forge-Engine/tests/test_shadow_controls.py
T
williamandClaude Sonnet 5 94f1414506 Corrige un bug important : le style de TOUT élément neuf était cassé depuis l'ajout de l'Ombre
Bug remonté ("toutes les propriétés ne sont pas prises en compte") : la
vraie cause n'avait rien à voir avec les éléments de jeu — le contrôle
"Ombre" (voir le tour précédent) a une valeur par défaut composite (un
dict Python, {"x":0,"y":4,...}, pas une chaîne CSS). default_style_for_
widget.py, qui fige les réglages "dont la valeur par défaut a un effet
visuel voulu dès la création" pour chaque widget neuf, n'excluait pas ce
nouveau type de contrôle — il écrivait donc ce dict TEL QUEL (repr Python)
dans le style de CHAQUE élément fraîchement créé depuis ce commit, quel
que soit son widget. Une valeur CSS invalide au milieu du style pouvait
donner l'impression que "plein de propriétés" ne s'appliquaient plus.

Deux correctifs :
1. default_style_for_widget.py exclut maintenant "shadow" du gel à la
   création (même raisonnement déjà appliqué à "color" juste au-dessus :
   la valeur par défaut n'est qu'une suggestion affichée dans le panneau,
   pas un réglage neutre à figer - le neutre CSS est "pas d'ombre").
2. style_string.py ignore désormais toute valeur non scalaire (dict/liste)
   au moment de construire l'attribut style - filet de sécurité pour les
   éléments déjà créés AVANT ce correctif, qui portent encore ce dict figé
   en base et continueraient sinon à s'afficher cassés.

Ajoute deux tests de régression dans test_shadow_controls.py (aucune ombre
au premier rendu d'un élément neuf ; un élément déjà corrompu avant ce
correctif continue de s'afficher normalement).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-25 15:54:06 +02:00

103 lines
5.0 KiB
Python

"""Ombre portée (panneau de propriétés, tout widget) : 6 sous-réglages
(décalage X/Y, flou, étendue, couleur, opacité) combinés en une seule
valeur CSS box-shadow."""
import re
import screens
def _create_element(client, slug, widget="conteneur"):
resp = client.post(f"/game/{slug}/screens/new", data={"name": "Accueil"}, follow_redirects=False)
screen_id = int(re.search(r"/screens/(\d+)/edit", resp.headers["Location"]).group(1))
resp = client.post(f"/game/{slug}/screens/{screen_id}/elements/add", data={"widget": widget}, follow_redirects=False)
element_id = int(re.search(r"selected=(\d+)", resp.headers["Location"]).group(1))
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()
assert "ctrl_box-shadow_x" in html
assert "ctrl_box-shadow_color" in html
def test_saving_a_shadow_composes_box_shadow_css(client, game):
screen_id, element_id = _create_element(client, game)
client.post(f"/game/{game}/elements/{element_id}/save", data={
"ctrl_box-shadow_x": "2", "ctrl_box-shadow_y": "6", "ctrl_box-shadow_blur": "20",
"ctrl_box-shadow_spread": "3", "ctrl_box-shadow_color": "#ff0000", "ctrl_box-shadow_opacity": "0.5",
})
el = screens.get_element(game, element_id)
assert el["style"]["box-shadow"] == "2px 6px 20px 3px rgba(255, 0, 0, 0.5)"
def test_shadow_value_round_trips_back_into_the_form(client, game):
screen_id, element_id = _create_element(client, game)
client.post(f"/game/{game}/elements/{element_id}/save", data={
"ctrl_box-shadow_x": "2", "ctrl_box-shadow_y": "6", "ctrl_box-shadow_blur": "20",
"ctrl_box-shadow_spread": "3", "ctrl_box-shadow_color": "#ff0000", "ctrl_box-shadow_opacity": "0.5",
})
html = client.get(f"/game/{game}/screens/{screen_id}/edit?selected={element_id}").data.decode()
assert 'name="ctrl_box-shadow_x" value="2.0"' in html or 'name="ctrl_box-shadow_x" value="2"' in html
assert 'name="ctrl_box-shadow_color" value="#ff0000"' in html
def test_all_zero_offsets_remove_the_shadow(client, game):
screen_id, element_id = _create_element(client, game)
client.post(f"/game/{game}/elements/{element_id}/save", data={
"ctrl_box-shadow_x": "2", "ctrl_box-shadow_y": "6", "ctrl_box-shadow_blur": "20",
"ctrl_box-shadow_spread": "3", "ctrl_box-shadow_color": "#ff0000", "ctrl_box-shadow_opacity": "0.5",
})
client.post(f"/game/{game}/elements/{element_id}/save", data={
"ctrl_box-shadow_x": "0", "ctrl_box-shadow_y": "0", "ctrl_box-shadow_blur": "0",
"ctrl_box-shadow_spread": "0", "ctrl_box-shadow_color": "#ff0000", "ctrl_box-shadow_opacity": "0.5",
})
el = screens.get_element(game, element_id)
assert "box-shadow" not in el["style"]