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:
co-authored by
Claude Sonnet 5
parent
8f879f6321
commit
4db1269348
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user