Implémente toutes les options manquantes identifiées pour les listes :
typographie complète, style/position/couleur/taille de puce (validés
selon le kind), puce personnalisée en SVG pour les listes à puces,
padding uniforme par élément (nouveau, il n'y en avait aucun), espacement
entre éléments réglable, et tous les attributs de boîte partagés sur la
liste entière. Bordure/fond/padding par élément individuel et sous-listes
imbriquées volontairement différés (portée actée avec l'utilisateur
avant implémentation : transformeraient le stockage des éléments en
objets structurés, chantier bien plus lourd).
Trois ajouts transversaux bénéficiant à plusieurs éléments : sections
"Contenu"/"Conteneur" dans tous les panneaux de propriétés, alignement
vertical du contenu dans son bloc (Titre/Paragraphe/Liste/Image
légendée), et une option pour retirer un thème appliqué ("Aucun modèle"
dans la modale, avec une nouvelle fonction db.remove_document_theme).
Quatre bugs réels trouvés et corrigés en chaîne pendant la validation
avec le thème "Sécurité incendie" : un badge de thème s'affichait
au-dessus du texte au lieu d'à côté ; le correctif a d'abord fait
disparaître les puces/numéros natifs de TOUTES les listes (bug plus
grave que celui corrigé) ; puis un marqueur natif redondant apparaissait
à côté du badge du thème ; puis une règle CSS site-large de spécificité
supérieure empêchait silencieusement ce dernier correctif. Chaque étape
vérifiée par navigateur automatisé sur un support jetable.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
97 lines
3.8 KiB
Python
97 lines
3.8 KiB
Python
"""Attributs de "boîte" partagés par plusieurs kinds de contenu (voir
|
|
document_engine/rendering/box_style.py) — testés une seule fois ici,
|
|
indépendamment de chaque kind qui les utilise (voir aussi
|
|
test_document_elements.py pour leur usage réel via _render_text)."""
|
|
|
|
from document_engine.rendering.box_style import BORDER_SIDES, default_border, render_box_style, render_content_align
|
|
|
|
|
|
def test_default_border_has_all_four_sides_set_to_none() -> None:
|
|
border = default_border()
|
|
assert set(border.keys()) == set(BORDER_SIDES)
|
|
for side in BORDER_SIDES:
|
|
assert border[side]["style"] == "none"
|
|
|
|
|
|
def test_default_border_returns_a_fresh_dict_each_call() -> None:
|
|
"""Jamais un littéral partagé muté par référence entre deux éléments
|
|
(même précaution que DEFAULT_QUIZ_CONFIG côté labels)."""
|
|
a = default_border()
|
|
b = default_border()
|
|
a["top"]["style"] = "solid"
|
|
assert b["top"]["style"] == "none"
|
|
|
|
|
|
def test_render_box_style_returns_empty_string_for_all_defaults() -> None:
|
|
assert render_box_style({}) == ""
|
|
assert render_box_style({"border": default_border(), "align_self": "stretch"}) == ""
|
|
|
|
|
|
def test_render_box_style_includes_padding_margin_background_and_radius() -> None:
|
|
style = render_box_style(
|
|
{"padding": "12px", "margin": "0 auto", "background_color": "#eee", "border_radius": "8px"}
|
|
)
|
|
assert "padding:12px;" in style
|
|
assert "margin:0 auto;" in style
|
|
assert "background-color:#eee;" in style
|
|
assert "border-radius:8px;" in style
|
|
|
|
|
|
def test_render_box_style_includes_sizing_shadow_and_opacity() -> None:
|
|
style = render_box_style(
|
|
{
|
|
"height": "200px",
|
|
"min_height": "100px",
|
|
"max_height": "400px",
|
|
"min_width": "50px",
|
|
"box_shadow": "0 4px 12px rgba(0,0,0,.2)",
|
|
"opacity": "0.8",
|
|
}
|
|
)
|
|
assert "height:200px;" in style
|
|
assert "min-height:100px;" in style
|
|
assert "max-height:400px;" in style
|
|
assert "min-width:50px;" in style
|
|
assert "box-shadow:0 4px 12px rgba(0,0,0,.2);" in style
|
|
assert "opacity:0.8;" in style
|
|
|
|
|
|
def test_render_box_style_includes_align_self_only_when_not_stretch() -> None:
|
|
assert "align-self" not in render_box_style({"align_self": "stretch"})
|
|
assert "align-self:center;" in render_box_style({"align_self": "center"})
|
|
|
|
|
|
def test_render_box_style_renders_only_sides_with_a_non_none_style() -> None:
|
|
border = default_border()
|
|
border["top"] = {"style": "solid", "width": "2px", "color": "#ff0000"}
|
|
style = render_box_style({"border": border})
|
|
assert "border-top:2px solid #ff0000;" in style
|
|
assert "border-right" not in style
|
|
assert "border-bottom" not in style
|
|
assert "border-left" not in style
|
|
|
|
|
|
def test_render_box_style_escapes_malicious_values() -> None:
|
|
border = default_border()
|
|
border["top"] = {"style": "solid", "width": "1px", "color": '"><script>alert(1)</script>'}
|
|
style = render_box_style({"padding": '"><script>alert(2)</script>', "border": border})
|
|
assert "<script>" not in style
|
|
|
|
|
|
def test_render_content_align_is_empty_for_default_top() -> None:
|
|
# Retour utilisateur du 26/09/2026 : "je peux augmenter la hauteur
|
|
# d'un conteneur mais pas l'alignement vertical à l'intérieur" —
|
|
# "top" est le comportement historique (contenu en haut), jamais un
|
|
# style ajouté pour ne rien changer par défaut.
|
|
assert render_content_align({}) == ""
|
|
assert render_content_align({"content_align": "top"}) == ""
|
|
|
|
|
|
def test_render_content_align_renders_center_and_bottom() -> None:
|
|
assert render_content_align({"content_align": "center"}) == "justify-content:center;"
|
|
assert render_content_align({"content_align": "bottom"}) == "justify-content:flex-end;"
|
|
|
|
|
|
def test_render_content_align_ignores_unknown_values() -> None:
|
|
assert render_content_align({"content_align": "n-importe-quoi"}) == ""
|