Implémente toutes les options manquantes identifiées pour l'élément Bouton : typographie (gras/italique/transformation/police/taille/ espacement des lettres/couleur), icône SVG optionnelle avec position (avant/après le texte) et taille réglables — au-delà de l'Étiquette, qui n'a qu'une icône fixe —, tous les attributs de boîte partagés déjà créés pour Titre/Paragraphe/Image, et un effet visuel universel au survol/clic. Deux ajouts transversaux demandés pendant le test : un réglage "les 4 côtés de la bordure à la fois" dans le module de boîte partagé (bénéficie automatiquement à tous les éléments qui l'utilisent), et le glisser-déposer d'un élément du canevas vers une autre page via le panneau Pages (le mécanisme serveur existait déjà pour la pagination automatique, seule l'interaction manuelle manquait). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
929 lines
43 KiB
Python
929 lines
43 KiB
Python
"""CRUD des éléments d'un support de formation (document_engine/elements/)
|
|
— sans Flask, directement contre le support.db créé par db.create_support.
|
|
Couvre : CRUD complet, rangées (parent_id), réinsertion (move_document_element
|
|
vers une nouvelle rangée/position), suppression en cascade d'une rangée avec
|
|
enfants.
|
|
|
|
Chaque élément appartient désormais à une page (voir document_engine/
|
|
pages/) — _new_support renvoie (slug, page_id) de la première page créée
|
|
par défaut, réutilisée par tous les tests de ce fichier."""
|
|
|
|
from typing import Any
|
|
|
|
import db
|
|
import document_engine as doc_engine
|
|
from document_engine.rendering.box_style import default_border as doc_engine_box_default_border
|
|
|
|
|
|
def _new_support(tmp_support_slug_cleanup: Any, name: str = "Support de test") -> tuple[str, int]:
|
|
# db.create_support ne crée plus aucune page par défaut (retour
|
|
# utilisateur du 26/09/2026, voir document_engine/pages/pages.md) —
|
|
# ce helper en ajoute une explicitement, puisque tous les tests de ce
|
|
# fichier portent sur des éléments qui doivent bien appartenir à une
|
|
# page existante.
|
|
slug = db.create_support(name, owner_folder="90")
|
|
tmp_support_slug_cleanup(slug)
|
|
page_id = doc_engine.add_document_page(slug)
|
|
return slug, page_id
|
|
|
|
|
|
def test_add_and_get_document_element_uses_default_attributes(tmp_support_slug_cleanup: Any) -> None:
|
|
slug, page_id = _new_support(tmp_support_slug_cleanup)
|
|
element_id = doc_engine.add_document_element(slug, "titre", page_id=page_id)
|
|
element = doc_engine.get_document_element(slug, element_id)
|
|
assert element is not None
|
|
assert element["kind"] == "titre"
|
|
assert element["page_id"] == page_id
|
|
assert element["parent_id"] is None
|
|
assert element["attributes"]["content"] == "Nouveau titre"
|
|
|
|
|
|
def test_titre_default_attributes_include_all_new_formatting_fields(tmp_support_slug_cleanup: Any) -> None:
|
|
"""Audit du 26/09/2026 (réglages manquants) — chaque nouveau champ
|
|
doit exister avec une valeur "neutre" par défaut (comportement
|
|
historique inchangé, voir _render_text)."""
|
|
slug, page_id = _new_support(tmp_support_slug_cleanup)
|
|
element_id = doc_engine.add_document_element(slug, "titre", page_id=page_id)
|
|
element = doc_engine.get_document_element(slug, element_id)
|
|
assert element is not None
|
|
attrs = element["attributes"]
|
|
assert attrs["strikethrough"] is False
|
|
assert attrs["font_family"] == ""
|
|
assert attrs["font_size"] == ""
|
|
assert attrs["line_height"] == ""
|
|
assert attrs["letter_spacing"] == ""
|
|
assert attrs["text_transform"] == "none"
|
|
assert attrs["text_shadow"] == ""
|
|
assert attrs["padding"] == ""
|
|
assert attrs["margin"] == ""
|
|
assert attrs["background_color"] == ""
|
|
assert attrs["border_radius"] == ""
|
|
assert attrs["align_self"] == "stretch"
|
|
assert attrs["height"] == ""
|
|
assert attrs["min_height"] == ""
|
|
assert attrs["max_height"] == ""
|
|
assert attrs["min_width"] == ""
|
|
assert attrs["box_shadow"] == ""
|
|
assert attrs["opacity"] == ""
|
|
assert attrs["border"] == doc_engine_box_default_border()
|
|
|
|
html = doc_engine.render_document([element])
|
|
assert html == (
|
|
f'<div class="docText" data-element-id="{element_id}" data-kind="titre" '
|
|
f'style="font-size:clamp(1.6rem,4vw,2rem); font-weight:800; line-height:1.15; '
|
|
f'font-style:normal; text-decoration:none; text-align:left; color:var(--forge-text);">'
|
|
f"Nouveau titre</div>"
|
|
)
|
|
|
|
|
|
def test_titre_strikethrough_combines_with_underline(tmp_support_slug_cleanup: Any) -> None:
|
|
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", "underline": True, "strikethrough": True}
|
|
)
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert "text-decoration:underline line-through;" in html
|
|
|
|
|
|
def test_titre_font_size_and_line_height_override_the_style_preset(tmp_support_slug_cleanup: Any) -> None:
|
|
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", "style": "titre1", "font_size": "40px", "line_height": "1"}
|
|
)
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert "font-size:40px;" in html
|
|
assert "line-height:1;" in html
|
|
assert "clamp(1.6rem" not in html
|
|
|
|
|
|
def test_titre_font_family_letter_spacing_and_text_shadow_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, "titre", page_id=page_id)
|
|
doc_engine.update_document_element_attributes(
|
|
slug,
|
|
element_id,
|
|
{
|
|
"content": "X",
|
|
"font_family": "Georgia, serif",
|
|
"letter_spacing": "0.05em",
|
|
"text_shadow": "1px 1px 2px rgba(0,0,0,.3)",
|
|
},
|
|
)
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert "font-family:Georgia, serif;" in html
|
|
assert "letter-spacing:0.05em;" in html
|
|
assert "text-shadow:1px 1px 2px rgba(0,0,0,.3);" in html
|
|
|
|
|
|
def test_titre_text_transform_uppercase_renders_and_none_does_not(tmp_support_slug_cleanup: Any) -> None:
|
|
slug, page_id = _new_support(tmp_support_slug_cleanup)
|
|
element_id = doc_engine.add_document_element(slug, "titre", page_id=page_id)
|
|
html_default = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert "text-transform" not in html_default
|
|
|
|
doc_engine.update_document_element_attributes(slug, element_id, {"content": "X", "text_transform": "uppercase"})
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert "text-transform:uppercase;" in html
|
|
|
|
|
|
def test_titre_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, "titre", 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,
|
|
{
|
|
"content": "X",
|
|
"padding": "10px",
|
|
"margin": "5px",
|
|
"background_color": "#fafafa",
|
|
"border_radius": "4px",
|
|
"align_self": "center",
|
|
"border": border,
|
|
"height": "300px",
|
|
"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:10px;" 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 "height:300px;" in html
|
|
assert "box-shadow:0 4px 12px rgba(0,0,0,.2);" in html
|
|
assert "opacity:0.9;" 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
|
|
|
|
|
|
def test_list_document_elements_groups_row_children_together(tmp_support_slug_cleanup: Any) -> None:
|
|
slug, page_id = _new_support(tmp_support_slug_cleanup)
|
|
row_id = doc_engine.add_document_element(slug, "row", page_id=page_id)
|
|
top_titre_id = doc_engine.add_document_element(slug, "titre", page_id=page_id)
|
|
child_a_id = doc_engine.add_document_element(slug, "paragraphe", page_id=page_id, parent_id=row_id)
|
|
child_b_id = doc_engine.add_document_element(slug, "image", page_id=page_id, parent_id=row_id)
|
|
|
|
elements = doc_engine.list_document_elements(slug, page_id)
|
|
ids_in_order = [el["id"] for el in elements]
|
|
|
|
# Les deux top-niveau (row_id, top_titre_id) groupés avant les deux
|
|
# enfants de la rangée (child_a_id, child_b_id) — tri (parent_id, order_index).
|
|
assert ids_in_order.index(row_id) < ids_in_order.index(child_a_id)
|
|
assert ids_in_order.index(top_titre_id) < ids_in_order.index(child_a_id)
|
|
assert ids_in_order.index(child_a_id) < ids_in_order.index(child_b_id)
|
|
|
|
|
|
def test_list_document_elements_only_returns_the_given_page(tmp_support_slug_cleanup: Any) -> None:
|
|
slug, page1_id = _new_support(tmp_support_slug_cleanup)
|
|
page2_id = doc_engine.add_document_page(slug)
|
|
el1_id = doc_engine.add_document_element(slug, "titre", page_id=page1_id)
|
|
el2_id = doc_engine.add_document_element(slug, "titre", page_id=page2_id)
|
|
|
|
assert [el["id"] for el in doc_engine.list_document_elements(slug, page1_id)] == [el1_id]
|
|
assert [el["id"] for el in doc_engine.list_document_elements(slug, page2_id)] == [el2_id]
|
|
|
|
|
|
def test_update_document_element_attributes_replaces_whole_json(tmp_support_slug_cleanup: Any) -> None:
|
|
slug, page_id = _new_support(tmp_support_slug_cleanup)
|
|
element_id = doc_engine.add_document_element(slug, "paragraphe", page_id=page_id)
|
|
doc_engine.update_document_element_attributes(slug, element_id, {"content": "Texte modifié", "style": "legende"})
|
|
element = doc_engine.get_document_element(slug, element_id)
|
|
assert element is not None
|
|
assert element["attributes"] == {"content": "Texte modifié", "style": "legende"}
|
|
|
|
|
|
def test_move_document_element_reorders_within_same_parent(tmp_support_slug_cleanup: Any) -> None:
|
|
slug, page_id = _new_support(tmp_support_slug_cleanup)
|
|
first_id = doc_engine.add_document_element(slug, "titre", page_id=page_id)
|
|
second_id = doc_engine.add_document_element(slug, "paragraphe", page_id=page_id)
|
|
third_id = doc_engine.add_document_element(slug, "image", page_id=page_id)
|
|
|
|
doc_engine.move_document_element(slug, third_id, None, 0)
|
|
|
|
elements = doc_engine.list_document_elements(slug, page_id)
|
|
top_level = [el for el in elements if el["parent_id"] is None]
|
|
ordered_ids = [el["id"] for el in sorted(top_level, key=lambda el: el["order_index"])]
|
|
assert ordered_ids == [third_id, first_id, second_id]
|
|
|
|
|
|
def test_move_document_element_reparents_into_a_row(tmp_support_slug_cleanup: Any) -> None:
|
|
slug, page_id = _new_support(tmp_support_slug_cleanup)
|
|
row_id = doc_engine.add_document_element(slug, "row", page_id=page_id)
|
|
lone_titre_id = doc_engine.add_document_element(slug, "titre", page_id=page_id)
|
|
|
|
doc_engine.move_document_element(slug, lone_titre_id, row_id, 0)
|
|
|
|
moved = doc_engine.get_document_element(slug, lone_titre_id)
|
|
assert moved is not None
|
|
assert moved["parent_id"] == row_id
|
|
assert moved["order_index"] == 0
|
|
|
|
|
|
def test_delete_document_element_cascades_to_row_children(tmp_support_slug_cleanup: Any) -> None:
|
|
slug, page_id = _new_support(tmp_support_slug_cleanup)
|
|
row_id = doc_engine.add_document_element(slug, "row", page_id=page_id)
|
|
child_id = doc_engine.add_document_element(slug, "titre", page_id=page_id, parent_id=row_id)
|
|
|
|
doc_engine.delete_document_element(slug, row_id)
|
|
|
|
assert doc_engine.get_document_element(slug, row_id) is None
|
|
assert doc_engine.get_document_element(slug, child_id) is None
|
|
|
|
|
|
def test_delete_document_element_on_missing_id_is_a_noop(tmp_support_slug_cleanup: Any) -> None:
|
|
slug, _page_id = _new_support(tmp_support_slug_cleanup)
|
|
doc_engine.delete_document_element(slug, 999)
|
|
|
|
|
|
def test_render_document_includes_row_children(tmp_support_slug_cleanup: Any) -> None:
|
|
slug, page_id = _new_support(tmp_support_slug_cleanup)
|
|
row_id = doc_engine.add_document_element(slug, "row", page_id=page_id)
|
|
titre_id = doc_engine.add_document_element(slug, "titre", page_id=page_id, parent_id=row_id)
|
|
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
|
|
assert 'data-kind="row"' in html
|
|
assert 'data-kind="titre"' in html
|
|
assert f'data-element-id="{titre_id}"' in html
|
|
|
|
|
|
def test_move_document_element_to_page_transfers_a_top_level_element(tmp_support_slug_cleanup: Any) -> None:
|
|
slug, page1_id = _new_support(tmp_support_slug_cleanup)
|
|
page2_id = doc_engine.add_document_page(slug)
|
|
element_id = doc_engine.add_document_element(slug, "titre", page_id=page1_id)
|
|
|
|
doc_engine.move_document_element_to_page(slug, element_id, page2_id)
|
|
|
|
moved = doc_engine.get_document_element(slug, element_id)
|
|
assert moved is not None
|
|
assert moved["page_id"] == page2_id
|
|
assert moved["parent_id"] is None
|
|
assert [el["id"] for el in doc_engine.list_document_elements(slug, page1_id)] == []
|
|
assert [el["id"] for el in doc_engine.list_document_elements(slug, page2_id)] == [element_id]
|
|
|
|
|
|
def test_move_document_element_to_page_cascades_row_children(tmp_support_slug_cleanup: Any) -> None:
|
|
slug, page1_id = _new_support(tmp_support_slug_cleanup)
|
|
page2_id = doc_engine.add_document_page(slug)
|
|
row_id = doc_engine.add_document_element(slug, "row", page_id=page1_id)
|
|
child_id = doc_engine.add_document_element(slug, "titre", page_id=page1_id, parent_id=row_id)
|
|
|
|
doc_engine.move_document_element_to_page(slug, row_id, page2_id)
|
|
|
|
child = doc_engine.get_document_element(slug, child_id)
|
|
assert child is not None
|
|
assert child["page_id"] == page2_id
|
|
assert child["parent_id"] == row_id
|
|
page2_ids = {el["id"] for el in doc_engine.list_document_elements(slug, page2_id)}
|
|
assert page2_ids == {row_id, child_id}
|
|
|
|
|
|
def test_move_document_element_to_page_renumbers_remaining_siblings(tmp_support_slug_cleanup: Any) -> None:
|
|
slug, page1_id = _new_support(tmp_support_slug_cleanup)
|
|
page2_id = doc_engine.add_document_page(slug)
|
|
first_id = doc_engine.add_document_element(slug, "titre", page_id=page1_id)
|
|
second_id = doc_engine.add_document_element(slug, "paragraphe", page_id=page1_id)
|
|
third_id = doc_engine.add_document_element(slug, "image", page_id=page1_id)
|
|
|
|
doc_engine.move_document_element_to_page(slug, second_id, page2_id)
|
|
|
|
remaining = doc_engine.list_document_elements(slug, page1_id)
|
|
remaining_ordered = sorted(remaining, key=lambda el: el["order_index"])
|
|
assert [el["id"] for el in remaining_ordered] == [first_id, third_id]
|
|
assert [el["order_index"] for el in remaining_ordered] == [0, 1]
|
|
|
|
|
|
def test_move_document_element_to_page_on_missing_id_is_a_noop(tmp_support_slug_cleanup: Any) -> None:
|
|
slug, page1_id = _new_support(tmp_support_slug_cleanup)
|
|
page2_id = doc_engine.add_document_page(slug)
|
|
doc_engine.move_document_element_to_page(slug, 999, page2_id)
|
|
assert doc_engine.list_document_elements(slug, page1_id) == []
|
|
assert doc_engine.list_document_elements(slug, page2_id) == []
|
|
|
|
|
|
def test_liste_puces_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, "liste_puces", page_id=page_id)
|
|
element = doc_engine.get_document_element(slug, element_id)
|
|
assert element is not None
|
|
assert element["attributes"]["items"] == ["Premier élément", "Deuxième élément"]
|
|
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert html.startswith("<ul")
|
|
assert 'data-kind="liste_puces"' in html
|
|
assert "<li>Premier élément</li>" in html
|
|
assert "<li>Deuxième élément</li>" in html
|
|
|
|
|
|
def test_liste_numerotee_renders_an_ordered_list(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": ["Un", "Deux", "Trois"]})
|
|
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert html.startswith("<ol")
|
|
assert 'data-kind="liste_numerotee"' in html
|
|
assert "<li>Un</li><li>Deux</li><li>Trois</li>" in html
|
|
|
|
|
|
def test_liste_puces_escapes_item_content(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": ["<script>alert(1)</script>"]})
|
|
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert "<script>" not in html
|
|
assert "<script>" in html
|
|
|
|
|
|
def test_liste_puces_with_no_items_renders_an_empty_list(tmp_support_slug_cleanup: Any) -> None:
|
|
slug, page_id = _new_support(tmp_support_slug_cleanup)
|
|
doc_engine.add_document_element(slug, "liste_puces", page_id=page_id)
|
|
element_id = doc_engine.list_document_elements(slug, page_id)[0]["id"]
|
|
doc_engine.update_document_element_attributes(slug, element_id, {"items": []})
|
|
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert html == f'<ul class="docList" data-element-id="{element_id}" data-kind="liste_puces"></ul>'
|
|
|
|
|
|
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)
|
|
element = doc_engine.get_document_element(slug, element_id)
|
|
assert element is not None
|
|
assert element["attributes"] == {
|
|
"content": "Étiquette",
|
|
"svg_markup": "",
|
|
"width": "",
|
|
"border_radius": "",
|
|
"bold": False,
|
|
"uppercase": False,
|
|
}
|
|
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert html == f'<div class="docBadge" data-element-id="{element_id}" data-kind="badge">Étiquette</div>'
|
|
|
|
|
|
def test_badge_escapes_content(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)
|
|
doc_engine.update_document_element_attributes(slug, element_id, {"content": "<script>alert(1)</script>"})
|
|
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert "<script>" not in html
|
|
assert "<script>" in html
|
|
|
|
|
|
def test_badge_with_svg_markup_renders_a_sanitized_icon(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)
|
|
doc_engine.update_document_element_attributes(
|
|
slug,
|
|
element_id,
|
|
{
|
|
"content": "Module obligatoire",
|
|
"svg_markup": '<svg onload="alert(1)"><circle cx="1" cy="1" r="1"/><script>alert(2)</script></svg>',
|
|
},
|
|
)
|
|
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert '<span class="docBadgeIcon">' in html
|
|
assert "<circle" in html
|
|
assert "onload" not in html
|
|
assert "<script>" not in html
|
|
|
|
|
|
def test_badge_without_svg_markup_renders_no_icon_span(tmp_support_slug_cleanup: Any) -> None:
|
|
slug, page_id = _new_support(tmp_support_slug_cleanup)
|
|
doc_engine.add_document_element(slug, "badge", page_id=page_id)
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert "docBadgeIcon" not in html
|
|
|
|
|
|
def test_badge_with_width_also_sets_align_self(tmp_support_slug_cleanup: Any) -> None:
|
|
"""Fixer une largeur doit TOUJOURS sortir du comportement par défaut
|
|
(pleine largeur, étirement flex) — les deux vont toujours ensemble,
|
|
voir render_document_element._render_badge."""
|
|
slug, page_id = _new_support(tmp_support_slug_cleanup)
|
|
element_id = doc_engine.add_document_element(slug, "badge", page_id=page_id)
|
|
doc_engine.update_document_element_attributes(slug, element_id, {"content": "X", "width": "fit-content"})
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert "align-self:flex-start;" in html
|
|
assert "width:fit-content;" in html
|
|
|
|
|
|
def test_badge_border_radius_bold_and_uppercase_render_inline_style(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)
|
|
doc_engine.update_document_element_attributes(
|
|
slug, element_id, {"content": "X", "border_radius": "999px", "bold": True, "uppercase": True}
|
|
)
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert "border-radius:999px;" in html
|
|
assert "font-weight:800;" in html
|
|
assert "text-transform:uppercase;" in html
|
|
|
|
|
|
def test_carte_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, "carte", page_id=page_id)
|
|
element = doc_engine.get_document_element(slug, element_id)
|
|
assert element is not None
|
|
assert element["attributes"] == {
|
|
"label": "A",
|
|
"title": "Titre de la carte",
|
|
"description": "Description de la carte.",
|
|
}
|
|
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert html == (
|
|
f'<div class="docCard" data-element-id="{element_id}" data-kind="carte">'
|
|
f'<div class="docCardLabel">A</div>'
|
|
f'<div class="docCardTitle">Titre de la carte</div>'
|
|
f'<div class="docCardDescription">Description de la carte.</div>'
|
|
f"</div>"
|
|
)
|
|
|
|
|
|
def test_carte_escapes_all_fields(tmp_support_slug_cleanup: Any) -> None:
|
|
slug, page_id = _new_support(tmp_support_slug_cleanup)
|
|
element_id = doc_engine.add_document_element(slug, "carte", page_id=page_id)
|
|
doc_engine.update_document_element_attributes(
|
|
slug, element_id, {"label": "<b>", "title": "<i>", "description": "<script>alert(1)</script>"}
|
|
)
|
|
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert "<script>" not in html
|
|
assert "<b>" not in html
|
|
assert "<i>" not in html
|
|
|
|
|
|
def test_image_default_attributes_include_empty_svg_markup(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)
|
|
element = doc_engine.get_document_element(slug, element_id)
|
|
assert element is not None
|
|
attrs = element["attributes"]
|
|
assert attrs["src"] == ""
|
|
assert attrs["alt"] == ""
|
|
assert attrs["svg_markup"] == ""
|
|
# Cadre FIXE par défaut (retour utilisateur du 26/09/2026 : "ce cadre
|
|
# ne devrait pas changer de taille en fonction de la taille de
|
|
# l'image mais être fixe et contraindre l'image dedans") — une photo
|
|
# importée est TOUJOURS rognée pour remplir ce cadre par défaut ;
|
|
# "Taille réelle" (object_fit="") reste un choix explicite possible
|
|
# via le panneau Propriétés, jamais le défaut d'un nouvel élément.
|
|
assert attrs["object_fit"] == "cover"
|
|
assert attrs["height"] == "220px"
|
|
assert attrs["aspect_ratio"] == ""
|
|
assert attrs["filter_preset"] == ""
|
|
assert attrs["click_behavior"] == ""
|
|
assert attrs["link_url"] == ""
|
|
assert attrs["lazy_load"] is False
|
|
assert attrs["caption"] == ""
|
|
assert attrs["border"] == doc_engine_box_default_border()
|
|
|
|
|
|
def test_image_with_svg_markup_takes_priority_over_src(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)
|
|
doc_engine.update_document_element_attributes(
|
|
slug,
|
|
element_id,
|
|
{"src": "https://exemple.test/photo.png", "alt": "", "svg_markup": '<svg><circle cx="5" cy="5" r="3"/></svg>'},
|
|
)
|
|
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert "<img" not in html
|
|
assert f'<div class="docImage" data-element-id="{element_id}" data-kind="image">' in html
|
|
assert '<circle cx="5" cy="5" r="3">' in html or '<circle cx="5" cy="5" r="3"/>' in html
|
|
|
|
|
|
def test_image_svg_markup_strips_script_tag(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)
|
|
malicious = '<svg onload="alert(1)"><script>alert(2)</script><circle cx="1" cy="1" r="1" onclick="alert(3)"/></svg>'
|
|
doc_engine.update_document_element_attributes(slug, element_id, {"src": "", "alt": "", "svg_markup": malicious})
|
|
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert "<script>" not in html
|
|
assert "onload" not in html
|
|
assert "onclick" not in html
|
|
assert "alert(" not in html
|
|
|
|
|
|
def test_image_object_fit_renders_only_known_values(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)
|
|
doc_engine.update_document_element_attributes(
|
|
slug, element_id, {"src": "https://exemple.test/a.png", "alt": "", "object_fit": "cover"}
|
|
)
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert "object-fit:cover;" in html
|
|
|
|
doc_engine.update_document_element_attributes(
|
|
slug, element_id, {"src": "https://exemple.test/a.png", "alt": "", "object_fit": "n-importe-quoi"}
|
|
)
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert "object-fit" not in html
|
|
|
|
|
|
def test_image_aspect_ratio_renders_and_is_escaped(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)
|
|
doc_engine.update_document_element_attributes(
|
|
slug, element_id, {"src": "https://exemple.test/a.png", "alt": "", "aspect_ratio": "16 / 9"}
|
|
)
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert "aspect-ratio:16 / 9;" in html
|
|
|
|
|
|
def test_image_filter_preset_maps_to_a_real_css_filter(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)
|
|
doc_engine.update_document_element_attributes(
|
|
slug, element_id, {"src": "https://exemple.test/a.png", "alt": "", "filter_preset": "grayscale"}
|
|
)
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert "filter:grayscale(1);" in html
|
|
|
|
|
|
def test_image_lazy_load_adds_the_loading_attribute(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)
|
|
doc_engine.update_document_element_attributes(
|
|
slug, element_id, {"src": "https://exemple.test/a.png", "alt": "", "lazy_load": True}
|
|
)
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert 'loading="lazy"' in html
|
|
|
|
|
|
def test_image_caption_wraps_in_a_figure(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)
|
|
doc_engine.update_document_element_attributes(
|
|
slug, element_id, {"src": "https://exemple.test/a.png", "alt": "", "caption": "Une légende <script>x</script>"}
|
|
)
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert '<figure class="docImageFigure">' in html
|
|
assert '<figcaption class="docImageCaption">' in html
|
|
assert "<script>" not in html
|
|
assert "<script>" in html
|
|
|
|
|
|
def test_image_click_behavior_link_wraps_in_an_anchor(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)
|
|
doc_engine.update_document_element_attributes(
|
|
slug,
|
|
element_id,
|
|
{
|
|
"src": "https://exemple.test/a.png",
|
|
"alt": "",
|
|
"click_behavior": "link",
|
|
"link_url": "https://exemple.test/cible",
|
|
},
|
|
)
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert (
|
|
'<a class="docImageLink" href="https://exemple.test/cible" target="_blank" rel="noopener noreferrer">' in html
|
|
)
|
|
|
|
|
|
def test_image_click_behavior_link_without_url_does_not_wrap(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)
|
|
doc_engine.update_document_element_attributes(
|
|
slug, element_id, {"src": "https://exemple.test/a.png", "alt": "", "click_behavior": "link", "link_url": ""}
|
|
)
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert "docImageLink" not in html
|
|
|
|
|
|
def test_image_click_behavior_lightbox_wraps_in_a_trigger_div(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)
|
|
doc_engine.update_document_element_attributes(
|
|
slug, element_id, {"src": "https://exemple.test/a.png", "alt": "", "click_behavior": "lightbox"}
|
|
)
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert '<div class="docImageLightboxTrigger">' in html
|
|
|
|
|
|
def test_image_box_style_attributes_render_and_placeholder_gets_them_too(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)
|
|
border = doc_engine_box_default_border()
|
|
border["left"] = {"style": "dotted", "width": "2px", "color": "#00ff00"}
|
|
doc_engine.update_document_element_attributes(
|
|
slug,
|
|
element_id,
|
|
{
|
|
"src": "",
|
|
"alt": "",
|
|
"padding": "8px",
|
|
"border_radius": "20px",
|
|
"width": "300px",
|
|
"border": border,
|
|
},
|
|
)
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert "docImagePlaceholder" in html
|
|
assert "padding:8px;" in html
|
|
assert "border-radius:20px;" in html
|
|
assert "width:300px;" in html
|
|
assert "border-left:2px dotted #00ff00;" in html
|
|
|
|
|
|
def test_image_box_style_targets_the_figure_when_a_caption_is_present(tmp_support_slug_cleanup: Any) -> None:
|
|
# Bug réel constaté le 26/09/2026 : "la position de bloc ne fonctionne
|
|
# pas sur l'image" — align-self (et le reste de render_box_style) doit
|
|
# se poser sur l'élément RÉELLEMENT top-niveau (l'enfant direct du
|
|
# flex-column de la page), jamais sur l'<img> interne quand une
|
|
# légende l'enveloppe : sans quoi align-self n'a aucun effet visuel.
|
|
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",
|
|
"align_self": "center",
|
|
"width": "300px",
|
|
},
|
|
)
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert '<figure class="docImageFigure" style="width:300px; align-self:center;">' in html
|
|
# align-self ne doit apparaître qu'UNE fois (sur la figure), jamais
|
|
# aussi sur l'<img> interne (ce qui n'aurait aucun effet CSS).
|
|
assert html.count("align-self") == 1
|
|
|
|
|
|
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)
|
|
doc_engine.update_document_element_attributes(
|
|
slug,
|
|
element_id,
|
|
{
|
|
"src": "https://exemple.test/a.png",
|
|
"alt": "",
|
|
"click_behavior": "link",
|
|
"link_url": "https://exemple.test/cible",
|
|
"align_self": "center",
|
|
},
|
|
)
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert (
|
|
'<a class="docImageLink" href="https://exemple.test/cible" target="_blank" rel="noopener noreferrer" '
|
|
'style="align-self:center;">' in html
|
|
)
|
|
|
|
|
|
def test_bouton_default_attributes_include_all_new_formatting_fields(tmp_support_slug_cleanup: Any) -> None:
|
|
"""Audit du 26/09/2026 (réglages manquants — colonne "Bouton") : chaque
|
|
nouveau champ doit exister avec une valeur "neutre" par défaut
|
|
(comportement historique inchangé, voir _render_button)."""
|
|
slug, page_id = _new_support(tmp_support_slug_cleanup)
|
|
element_id = doc_engine.add_document_element(slug, "bouton", page_id=page_id)
|
|
element = doc_engine.get_document_element(slug, element_id)
|
|
assert element is not None
|
|
attrs = element["attributes"]
|
|
assert attrs["label"] == "Bouton"
|
|
assert attrs["target"] == ""
|
|
assert attrs["attachment_stored_name"] == ""
|
|
assert attrs["attachment_filename"] == ""
|
|
assert attrs["bold"] is False
|
|
assert attrs["italic"] is False
|
|
assert attrs["text_transform"] == "none"
|
|
assert attrs["font_family"] == ""
|
|
assert attrs["font_size"] == ""
|
|
assert attrs["letter_spacing"] == ""
|
|
assert attrs["text_color"] == ""
|
|
assert attrs["svg_markup"] == ""
|
|
assert attrs["icon_position"] == "before"
|
|
assert attrs["icon_size"] == ""
|
|
assert attrs["padding"] == ""
|
|
assert attrs["align_self"] == "stretch"
|
|
assert attrs["border"] == doc_engine_box_default_border()
|
|
|
|
html = doc_engine.render_document([element])
|
|
assert html == (
|
|
f'<button type="button" class="docButton" data-element-id="{element_id}" '
|
|
f'data-kind="bouton"><span class="docButtonLabel">Bouton</span></button>'
|
|
)
|
|
|
|
|
|
def test_bouton_bold_and_italic_render_without_lowering_default_weight(tmp_support_slug_cleanup: Any) -> None:
|
|
slug, page_id = _new_support(tmp_support_slug_cleanup)
|
|
element_id = doc_engine.add_document_element(slug, "bouton", page_id=page_id)
|
|
doc_engine.update_document_element_attributes(slug, element_id, {"label": "X", "bold": True, "italic": True})
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert "font-weight:800;" in html
|
|
assert "font-style:italic;" in html
|
|
|
|
|
|
def test_bouton_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, "bouton", page_id=page_id)
|
|
doc_engine.update_document_element_attributes(
|
|
slug,
|
|
element_id,
|
|
{
|
|
"label": "X",
|
|
"text_transform": "uppercase",
|
|
"font_family": "Georgia, serif",
|
|
"font_size": "18px",
|
|
"letter_spacing": "0.05em",
|
|
"text_color": "#ffffff",
|
|
},
|
|
)
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert "text-transform:uppercase;" in html
|
|
assert "font-family:Georgia, serif;" in html
|
|
assert "font-size:18px;" in html
|
|
assert "letter-spacing:0.05em;" in html
|
|
assert "color:#ffffff;" in html
|
|
|
|
|
|
def test_bouton_icon_renders_before_by_default_and_after_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, "bouton", page_id=page_id)
|
|
svg = '<svg><circle cx="1" cy="1" r="1"/></svg>'
|
|
doc_engine.update_document_element_attributes(slug, element_id, {"label": "X", "svg_markup": svg})
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert html.index("docButtonIcon") < html.index("docButtonLabel")
|
|
|
|
doc_engine.update_document_element_attributes(
|
|
slug, element_id, {"label": "X", "svg_markup": svg, "icon_position": "after"}
|
|
)
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert html.index("docButtonLabel") < html.index("docButtonIcon")
|
|
|
|
|
|
def test_bouton_icon_size_sets_explicit_width_and_height(tmp_support_slug_cleanup: Any) -> None:
|
|
slug, page_id = _new_support(tmp_support_slug_cleanup)
|
|
element_id = doc_engine.add_document_element(slug, "bouton", page_id=page_id)
|
|
doc_engine.update_document_element_attributes(
|
|
slug, element_id, {"label": "X", "svg_markup": "<svg></svg>", "icon_size": "24px"}
|
|
)
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert 'style="width:24px; height:24px;"' in html
|
|
|
|
|
|
def test_bouton_svg_markup_is_sanitized(tmp_support_slug_cleanup: Any) -> None:
|
|
slug, page_id = _new_support(tmp_support_slug_cleanup)
|
|
element_id = doc_engine.add_document_element(slug, "bouton", page_id=page_id)
|
|
malicious = '<svg onload="alert(1)"><script>alert(2)</script></svg>'
|
|
doc_engine.update_document_element_attributes(slug, element_id, {"label": "X", "svg_markup": malicious})
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert "<script>" not in html
|
|
assert "onload" not in html
|
|
|
|
|
|
def test_bouton_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, "bouton", 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,
|
|
{
|
|
"label": "X",
|
|
"padding": "12px 20px",
|
|
"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 20px;" 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_replace_document_content_rebuilds_pages_and_elements_from_seed(tmp_support_slug_cleanup: Any) -> None:
|
|
slug, page_id = _new_support(tmp_support_slug_cleanup)
|
|
doc_engine.add_document_element(slug, "titre", page_id=page_id)
|
|
|
|
seed_pages: list[dict[str, Any]] = [
|
|
{
|
|
"vertical_align": "center",
|
|
"blocks": [
|
|
{"kind": "titre", "attributes": {"content": "Page 1 du modèle"}},
|
|
{
|
|
"kind": "row",
|
|
"attributes": {"gap": 20},
|
|
"children": [
|
|
{"kind": "paragraphe", "attributes": {"content": "Colonne gauche"}},
|
|
{"kind": "paragraphe", "attributes": {"content": "Colonne droite"}},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{"blocks": [{"kind": "liste_puces", "attributes": {"items": ["Un", "Deux"]}}]},
|
|
]
|
|
doc_engine.replace_document_content(slug, seed_pages)
|
|
|
|
pages = doc_engine.list_document_pages(slug)
|
|
assert len(pages) == 2
|
|
assert pages[0]["vertical_align"] == "center"
|
|
assert pages[1]["vertical_align"] == "top"
|
|
|
|
page1_elements = doc_engine.list_document_elements(slug, pages[0]["id"])
|
|
titre = next(el for el in page1_elements if el["kind"] == "titre")
|
|
assert titre["attributes"]["content"] == "Page 1 du modèle"
|
|
row = next(el for el in page1_elements if el["kind"] == "row")
|
|
children = [el for el in page1_elements if el["parent_id"] == row["id"]]
|
|
assert len(children) == 2
|
|
assert {c["attributes"]["content"] for c in children} == {"Colonne gauche", "Colonne droite"}
|
|
|
|
page2_elements = doc_engine.list_document_elements(slug, pages[1]["id"])
|
|
assert page2_elements[0]["kind"] == "liste_puces"
|
|
assert page2_elements[0]["attributes"]["items"] == ["Un", "Deux"]
|
|
|
|
|
|
def test_replace_document_content_merges_attributes_onto_defaults(tmp_support_slug_cleanup: Any) -> None:
|
|
slug, _page_id = _new_support(tmp_support_slug_cleanup)
|
|
doc_engine.replace_document_content(
|
|
slug, [{"blocks": [{"kind": "titre", "attributes": {"content": "Seulement le contenu"}}]}]
|
|
)
|
|
page_id = doc_engine.list_document_pages(slug)[0]["id"]
|
|
element = doc_engine.list_document_elements(slug, page_id)[0]
|
|
assert element["attributes"]["content"] == "Seulement le contenu"
|
|
assert element["attributes"]["style"] == "titre1"
|
|
assert element["attributes"]["align"] == "left"
|
|
|
|
|
|
def test_bouton_with_attachment_filename_renders_data_attribute(tmp_support_slug_cleanup: Any) -> None:
|
|
slug, page_id = _new_support(tmp_support_slug_cleanup)
|
|
element_id = doc_engine.add_document_element(slug, "bouton", page_id=page_id)
|
|
doc_engine.update_document_element_attributes(
|
|
slug,
|
|
element_id,
|
|
{
|
|
"label": "Télécharger",
|
|
"target": "",
|
|
"attachment_stored_name": "abc123.pdf",
|
|
"attachment_filename": "fiche-consignes.pdf",
|
|
},
|
|
)
|
|
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert 'data-attachment-filename="fiche-consignes.pdf"' in html
|
|
|
|
|
|
def test_paragraphe_default_max_width_is_empty(tmp_support_slug_cleanup: Any) -> None:
|
|
slug, page_id = _new_support(tmp_support_slug_cleanup)
|
|
element_id = doc_engine.add_document_element(slug, "paragraphe", page_id=page_id)
|
|
element = doc_engine.get_document_element(slug, element_id)
|
|
assert element is not None
|
|
assert element["attributes"]["max_width"] == ""
|
|
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert "max-width" not in html
|
|
|
|
|
|
def test_paragraphe_with_max_width_adds_it_to_the_inline_style(tmp_support_slug_cleanup: Any) -> None:
|
|
slug, page_id = _new_support(tmp_support_slug_cleanup)
|
|
element_id = doc_engine.add_document_element(slug, "paragraphe", page_id=page_id)
|
|
doc_engine.update_document_element_attributes(
|
|
slug, element_id, {"content": "Texte", "style": "paragraphe", "max_width": "60ch"}
|
|
)
|
|
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert "max-width:60ch;" in html
|
|
|
|
|
|
def test_paragraphe_max_width_is_escaped(tmp_support_slug_cleanup: Any) -> None:
|
|
slug, page_id = _new_support(tmp_support_slug_cleanup)
|
|
element_id = doc_engine.add_document_element(slug, "paragraphe", page_id=page_id)
|
|
doc_engine.update_document_element_attributes(
|
|
slug, element_id, {"content": "Texte", "style": "paragraphe", "max_width": '"><script>alert(1)</script>'}
|
|
)
|
|
|
|
html = doc_engine.render_document(doc_engine.list_document_elements(slug, page_id))
|
|
assert "<script>" not in html
|