Files
williamandClaude Sonnet 5 e03bea39c5 Ajoute icône/largeur/arrondi/gras/majuscules à l'étiquette (badge)
Nouveaux attributs par élément (svg_markup/width/border_radius/bold/
uppercase, tous vides ou False par défaut = comportement historique
inchangé), réglables depuis le panneau Propriétés — même esprit que
bold/align/color déjà présents sur titre/paragraphe. Fixer une largeur
implique toujours de sortir de l'étirement pleine largeur par défaut
(align-self:flex-start posé automatiquement avec elle).

Le kicker "Module obligatoire" du thème Sécurité Incendie s'en sert
maintenant (icône flamme, largeur au contenu, arrondi complet, gras,
majuscules) pour être conforme à la maquette d'origine — la puce
décorative CSS générique qui la remplaçait disparaît du thème.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-24 11:36:30 +02:00

120 lines
5.2 KiB
Python

"""Catalogue de thèmes visuels (document_engine/themes/) — conversion du
contenu-seed en éléments "à plat" pour l'aperçu (seed_blocks_to_elements),
et respect par CHAQUE thème du catalogue des règles structurelles du
moteur (ex. un mini-jeu seul sur sa page, voir routes/document/
document_element_add.py) — vérifié ici statiquement sur les données du
catalogue, sans passer par une route HTTP."""
from typing import Any
import document_engine as doc_engine
def test_get_document_theme_entry_returns_none_for_an_unknown_id() -> None:
assert doc_engine.get_document_theme_entry("n-importe-quoi") is None
def test_get_document_theme_entry_returns_the_matching_entry() -> None:
entry = doc_engine.get_document_theme_entry("securite-incendie")
assert entry is not None
assert entry["id"] == "securite-incendie"
assert entry["name"] == "Sécurité Incendie"
assert len(entry["seed_pages"]) > 0
def test_securite_incendie_title_page_is_vertically_centered() -> None:
"""Retour utilisateur du 24/09/2026 : la page de garde doit être
centrée verticalement, comme une page de titre — les pages suivantes
restent alignées en haut (comportement par défaut)."""
entry = doc_engine.get_document_theme_entry("securite-incendie")
assert entry is not None
assert entry["seed_pages"][0]["vertical_align"] == "center"
assert entry["seed_pages"][1].get("vertical_align", "top") == "top"
def test_securite_incendie_kicker_badge_uses_the_new_badge_properties() -> None:
"""Retour utilisateur du 24/09/2026 : l'étiquette "Module obligatoire"
doit avoir une icône, une largeur au contenu, un arrondi complet et
du gras/majuscules, comme dans la maquette d'origine."""
entry = doc_engine.get_document_theme_entry("securite-incendie")
assert entry is not None
kicker = entry["seed_pages"][0]["blocks"][0]
assert kicker["kind"] == "badge"
attrs = kicker["attributes"]
assert attrs["svg_markup"]
assert attrs["width"] == "fit-content"
assert attrs["border_radius"] == "999px"
assert attrs["bold"] is True
assert attrs["uppercase"] is True
def test_securite_incendie_intro_paragraph_has_a_max_width() -> None:
"""Retour utilisateur du 24/09/2026 : le sous-titre de la page de
garde doit rester aussi étroit que dans la maquette, pas étiré sur
toute la largeur de la page."""
entry = doc_engine.get_document_theme_entry("securite-incendie")
assert entry is not None
intro_paragraph = entry["seed_pages"][0]["blocks"][2]
assert intro_paragraph["kind"] == "paragraphe"
assert intro_paragraph["attributes"]["max_width"] == "60ch"
def test_seed_blocks_to_elements_assigns_unique_synthetic_ids() -> None:
blocks: list[dict[str, Any]] = [
{"kind": "titre", "attributes": {"content": "Titre"}},
{
"kind": "row",
"attributes": {"gap": 10},
"children": [
{"kind": "paragraphe", "attributes": {"content": "A"}},
{"kind": "paragraphe", "attributes": {"content": "B"}},
],
},
]
elements = doc_engine.seed_blocks_to_elements(blocks)
ids = [el["id"] for el in elements]
assert len(ids) == len(set(ids))
assert len(elements) == 4
row = next(el for el in elements if el["kind"] == "row")
children = [el for el in elements if el["parent_id"] == row["id"]]
assert len(children) == 2
def test_seed_blocks_to_elements_merges_onto_default_attributes() -> None:
elements = doc_engine.seed_blocks_to_elements([{"kind": "titre", "attributes": {"content": "Contenu seul"}}])
assert elements[0]["attributes"]["content"] == "Contenu seul"
assert elements[0]["attributes"]["style"] == "titre1"
def test_seed_blocks_to_elements_renders_without_error() -> None:
theme = doc_engine.get_document_theme_entry("securite-incendie")
assert theme is not None
elements = doc_engine.seed_blocks_to_elements(theme["seed_pages"][0]["blocks"])
html = doc_engine.render_document(elements)
assert "docText" in html
def test_every_theme_seed_page_has_a_valid_vertical_align() -> None:
for theme in doc_engine.DOCUMENT_THEMES:
for seed_page in theme["seed_pages"]:
align = seed_page.get("vertical_align", "top")
assert align in doc_engine.VERTICAL_ALIGNS, f"thème {theme['id']!r} : vertical_align invalide {align!r}"
def test_every_theme_seed_page_respects_the_minigame_exclusivity_rule() -> None:
"""Un mini-jeu doit toujours être SEUL sur sa page (règle appliquée
côté route pour un ajout manuel, voir routes/document/
document_element_add.py) — le contenu-seed d'un thème la respecte
dès sa conception puisque document_engine.replace_document_content
ne la revérifie pas elle-même (contenu fourni par le thème, pas par
l'utilisateur)."""
for theme in doc_engine.DOCUMENT_THEMES:
for seed_page in theme["seed_pages"]:
page_blocks = seed_page["blocks"]
minigame_blocks = [b for b in page_blocks if b["kind"] in doc_engine.MINIGAME_KINDS]
if minigame_blocks:
assert len(page_blocks) == 1, (
f"thème {theme['id']!r} : une page avec mini-jeu doit contenir uniquement ce mini-jeu"
)