Audit complet de mise en forme — Liste à puces/numérotée (4e élément)

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>
This commit is contained in:
william
2026-09-26 15:13:14 +02:00
co-authored by Claude Sonnet 5
parent 8f879f6321
commit 4db1269348
17 changed files with 1080 additions and 34 deletions
+239
View File
@@ -161,6 +161,18 @@ def test_titre_box_style_attributes_render_via_shared_box_style(tmp_support_slug
assert "opacity:0.9;" in html
def test_titre_content_align_renders_justify_content_when_set(tmp_support_slug_cleanup: Any) -> None:
# Retour utilisateur du 26/09/2026 : "je peux augmenter la hauteur
# d'un conteneur mais pas l'alignement vertical à l'intérieur".
slug, page_id = _new_support(tmp_support_slug_cleanup)
element_id = doc_engine.add_document_element(slug, "titre", page_id=page_id)
doc_engine.update_document_element_attributes(
slug, element_id, {"content": "X", "height": "300px", "content_align": "center"}
)
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
assert "justify-content:center;" in html
def test_get_document_element_returns_none_for_missing_id(tmp_support_slug_cleanup: Any) -> None:
slug, _page_id = _new_support(tmp_support_slug_cleanup)
assert doc_engine.get_document_element(slug, 999) is None
@@ -356,6 +368,211 @@ def test_liste_puces_with_no_items_renders_an_empty_list(tmp_support_slug_cleanu
assert html == f'<ul class="docList" data-element-id="{element_id}" data-kind="liste_puces"></ul>'
def test_liste_default_attributes_include_all_new_formatting_fields(tmp_support_slug_cleanup: Any) -> None:
"""Audit du 26/09/2026 (réglages manquants — colonne "Liste à puces/
numérotée") : chaque nouveau champ doit exister avec une valeur
"neutre" par défaut, sauf item_padding (retour utilisateur explicite :
"il faut un padding de base par élément de liste car y en a pas
aujourd'hui" — comportement historique inchangé sinon, voir
_render_list)."""
slug, page_id = _new_support(tmp_support_slug_cleanup)
element_id = doc_engine.add_document_element(slug, "liste_puces", page_id=page_id)
element = doc_engine.get_document_element(slug, element_id)
assert element is not None
attrs = element["attributes"]
assert attrs["bold"] is False
assert attrs["italic"] is False
assert attrs["underline"] is False
assert attrs["font_family"] == ""
assert attrs["font_size"] == ""
assert attrs["line_height"] == ""
assert attrs["text_color"] == ""
assert attrs["list_style_type"] == ""
assert attrs["list_style_position"] == "outside"
assert attrs["marker_color"] == ""
assert attrs["marker_size"] == ""
assert attrs["svg_markup"] == ""
assert attrs["item_padding"] == "6px"
assert attrs["item_spacing"] == ""
assert attrs["border"] == doc_engine_box_default_border()
html = doc_engine.render_document([element])
assert html == (
f'<ul class="docList" data-element-id="{element_id}" data-kind="liste_puces" '
f'style="--doc-item-padding:6px;">'
f"<li>Premier élément</li><li>Deuxième élément</li></ul>"
)
def test_liste_typography_attributes_render_when_set(tmp_support_slug_cleanup: Any) -> None:
slug, page_id = _new_support(tmp_support_slug_cleanup)
element_id = doc_engine.add_document_element(slug, "liste_puces", page_id=page_id)
doc_engine.update_document_element_attributes(
slug,
element_id,
{
"items": ["X"],
"bold": True,
"italic": True,
"underline": True,
"font_family": "Georgia, serif",
"font_size": "18px",
"line_height": "1.8",
"text_color": "#ff0000",
},
)
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
assert "font-weight:700;" in html
assert "font-style:italic;" in html
assert "text-decoration:underline;" in html
assert "font-family:Georgia, serif;" in html
assert "font-size:18px;" in html
assert "line-height:1.8;" in html
assert "color:#ff0000;" in html
def test_liste_puces_marker_style_type_is_validated_per_kind(tmp_support_slug_cleanup: Any) -> None:
slug, page_id = _new_support(tmp_support_slug_cleanup)
element_id = doc_engine.add_document_element(slug, "liste_puces", page_id=page_id)
doc_engine.update_document_element_attributes(slug, element_id, {"items": ["X"], "list_style_type": "square"})
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
assert "list-style-type:square;" in html
# Une valeur propre aux listes NUMÉROTÉES est ignorée pour une liste à puces.
doc_engine.update_document_element_attributes(slug, element_id, {"items": ["X"], "list_style_type": "decimal"})
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
assert "list-style-type" not in html
def test_liste_numerotee_marker_style_type_is_validated_per_kind(tmp_support_slug_cleanup: Any) -> None:
slug, page_id = _new_support(tmp_support_slug_cleanup)
element_id = doc_engine.add_document_element(slug, "liste_numerotee", page_id=page_id)
doc_engine.update_document_element_attributes(slug, element_id, {"items": ["X"], "list_style_type": "upper-roman"})
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
assert "list-style-type:upper-roman;" in html
# Une valeur propre aux listes à puces est ignorée pour une liste numérotée.
doc_engine.update_document_element_attributes(slug, element_id, {"items": ["X"], "list_style_type": "square"})
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
assert "list-style-type" not in html
def test_liste_style_position_inside_renders(tmp_support_slug_cleanup: Any) -> None:
slug, page_id = _new_support(tmp_support_slug_cleanup)
element_id = doc_engine.add_document_element(slug, "liste_puces", page_id=page_id)
doc_engine.update_document_element_attributes(slug, element_id, {"items": ["X"], "list_style_position": "inside"})
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
assert "list-style-position:inside;" in html
def test_liste_removes_reserved_indent_when_no_visible_outside_bullet(tmp_support_slug_cleanup: Any) -> None:
# Bug réel constaté le 26/09/2026 : "si j'enlève les puces ou que les
# puces se mettent à l'intérieur, il reste un espace devant la
# liste, cet espace doit être supprimé" — le padding-left:1.4em par
# défaut (document-editor.css, .docList) réserve la place d'une
# puce EXTÉRIEURE, qui n'a plus lieu d'être dans ces deux cas.
slug, page_id = _new_support(tmp_support_slug_cleanup)
element_id = doc_engine.add_document_element(slug, "liste_puces", page_id=page_id)
doc_engine.update_document_element_attributes(slug, element_id, {"items": ["X"], "list_style_type": "none"})
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
assert "padding-left:0;" in html
doc_engine.update_document_element_attributes(slug, element_id, {"items": ["X"], "list_style_position": "inside"})
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
assert "padding-left:0;" in html
# Ni l'un ni l'autre réglé : la puce par défaut (disc) reste
# extérieure, la réservation d'espace reste nécessaire — jamais
# supprimée sans raison.
doc_engine.update_document_element_attributes(slug, element_id, {"items": ["X"]})
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
assert "padding-left:0;" not in html
def test_liste_marker_color_and_size_render_as_custom_properties(tmp_support_slug_cleanup: Any) -> None:
slug, page_id = _new_support(tmp_support_slug_cleanup)
element_id = doc_engine.add_document_element(slug, "liste_puces", page_id=page_id)
doc_engine.update_document_element_attributes(
slug, element_id, {"items": ["X"], "marker_color": "#00ff00", "marker_size": "20px"}
)
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
assert "--doc-marker-color:#00ff00;" in html
assert "--doc-marker-size:20px;" in html
def test_liste_puces_custom_bullet_image_renders_as_data_uri(tmp_support_slug_cleanup: Any) -> None:
slug, page_id = _new_support(tmp_support_slug_cleanup)
element_id = doc_engine.add_document_element(slug, "liste_puces", page_id=page_id)
doc_engine.update_document_element_attributes(
slug, element_id, {"items": ["X"], "svg_markup": "<svg><circle cx='1' cy='1' r='1'/></svg>"}
)
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
assert 'list-style-image:url("data:image/svg+xml,' in html
def test_liste_numerotee_ignores_custom_bullet_image(tmp_support_slug_cleanup: Any) -> None:
slug, page_id = _new_support(tmp_support_slug_cleanup)
element_id = doc_engine.add_document_element(slug, "liste_numerotee", page_id=page_id)
doc_engine.update_document_element_attributes(
slug, element_id, {"items": ["X"], "svg_markup": "<svg><circle cx='1' cy='1' r='1'/></svg>"}
)
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
assert "list-style-image" not in html
def test_liste_item_padding_and_spacing_render_as_custom_properties(tmp_support_slug_cleanup: Any) -> None:
slug, page_id = _new_support(tmp_support_slug_cleanup)
element_id = doc_engine.add_document_element(slug, "liste_puces", page_id=page_id)
doc_engine.update_document_element_attributes(
slug, element_id, {"items": ["X"], "item_padding": "10px", "item_spacing": "12px"}
)
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
assert "--doc-item-padding:10px;" in html
assert "--doc-item-spacing:12px;" in html
def test_liste_box_style_attributes_render_via_shared_box_style(tmp_support_slug_cleanup: Any) -> None:
slug, page_id = _new_support(tmp_support_slug_cleanup)
element_id = doc_engine.add_document_element(slug, "liste_puces", page_id=page_id)
border = doc_engine_box_default_border()
border["bottom"] = {"style": "dashed", "width": "3px", "color": "#123456"}
doc_engine.update_document_element_attributes(
slug,
element_id,
{
"items": ["X"],
"padding": "12px",
"margin": "5px",
"background_color": "#fafafa",
"border_radius": "4px",
"align_self": "center",
"border": border,
"box_shadow": "0 4px 12px rgba(0,0,0,.2)",
"opacity": "0.9",
},
)
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
assert "padding:12px;" in html
assert "margin:5px;" in html
assert "background-color:#fafafa;" in html
assert "border-radius:4px;" in html
assert "align-self:center;" in html
assert "border-bottom:3px dashed #123456;" in html
assert "box-shadow:0 4px 12px rgba(0,0,0,.2);" in html
assert "opacity:0.9;" in html
def test_liste_content_align_renders_justify_content_when_set(tmp_support_slug_cleanup: Any) -> None:
slug, page_id = _new_support(tmp_support_slug_cleanup)
element_id = doc_engine.add_document_element(slug, "liste_puces", page_id=page_id)
doc_engine.update_document_element_attributes(
slug, element_id, {"items": ["X"], "height": "300px", "content_align": "bottom"}
)
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
assert "justify-content:flex-end;" in html
def test_badge_default_attributes_and_render(tmp_support_slug_cleanup: Any) -> None:
slug, page_id = _new_support(tmp_support_slug_cleanup)
element_id = doc_engine.add_document_element(slug, "badge", page_id=page_id)
@@ -671,6 +888,28 @@ def test_image_box_style_targets_the_figure_when_a_caption_is_present(tmp_suppor
assert html.count("align-self") == 1
def test_image_content_align_renders_on_the_figure_when_a_caption_is_present(tmp_support_slug_cleanup: Any) -> None:
# Retour utilisateur du 26/09/2026 : "je peux augmenter la hauteur
# d'un conteneur mais pas l'alignement vertical à l'intérieur" —
# n'a de sens QUE sur la figure (conteneur flex-colonne à plusieurs
# enfants réels : image + légende), jamais sur l'<img> seul.
slug, page_id = _new_support(tmp_support_slug_cleanup)
element_id = doc_engine.add_document_element(slug, "image", page_id=page_id)
doc_engine.update_document_element_attributes(
slug,
element_id,
{
"src": "https://exemple.test/a.png",
"alt": "",
"caption": "Légende",
"height": "400px",
"content_align": "center",
},
)
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
assert '<figure class="docImageFigure" style="height:400px; justify-content:center;">' in html
def test_image_box_style_targets_the_link_wrapper_when_no_caption(tmp_support_slug_cleanup: Any) -> None:
slug, page_id = _new_support(tmp_support_slug_cleanup)
element_id = doc_engine.add_document_element(slug, "image", page_id=page_id)