Audit complet de mise en forme — Image (2e élément du tableau)

Implémente toutes les options manquantes identifiées pour l'élément
Image : dimensionnement/ratio/object-fit, filtres CSS, upload de
fichier (en plus de l'URL), lien/plein écran au clic, chargement
différé, légende, et tous les attributs de boîte partagés déjà créés
pour Titre/Paragraphe (padding/margin/fond/bordure/ombre/opacité/
position du bloc).

Système de pages : un support peut désormais avoir 0 page (un nouveau
support démarre vide), suppression de toutes les pages en un clic, et
la pagination automatique insère intelligemment la nouvelle page juste
après celle qui déborde plutôt qu'en toute fin de liste.

Bugs réels trouvés et corrigés en cours de route : le style de bloc
(dont align-self) ciblait l'élément interne au lieu de son enveloppe
(légende/lien) ; une image à sa taille native pouvait déclencher une
pagination infinie ; upload/mise à jour d'attribut ne déclenchaient
jamais le contrôle de débordement.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-09-26 12:06:44 +02:00
co-authored by Claude Sonnet 5
parent 6c7675fad0
commit 9ad50c58b8
27 changed files with 1299 additions and 174 deletions
+79
View File
@@ -39,6 +39,19 @@ def test_document_edit_renders_directly_no_intermediate_page(client: FlaskClient
assert resp.status_code == 200
def test_document_edit_works_on_a_support_with_zero_pages(client: FlaskClient, support: str) -> None:
# Retour utilisateur du 26/09/2026 : "l'éditeur ne dois plus etre
# obliger d'avoir une page active ou créer, il peut etre ouvert sans
# aucune page" — un support neuf (ou vidé via "Supprimer toutes les
# pages") n'a plus de page active, l'éditeur ne doit pas planter.
client.post(f"/document/{support}/pages/delete-all")
resp = client.get(f"/document/{support}/edit")
assert resp.status_code == 200
html = resp.get_data(as_text=True)
assert "activePageId: null" in html
assert "pages: []" in html
def test_document_element_add_returns_rendered_html(client: FlaskClient, support: str) -> None:
resp = client.post(f"/document/{support}/elements/add", data={"kind": "titre", "page_id": _page_id(support)})
assert resp.status_code == 200
@@ -417,6 +430,72 @@ def test_download_attachment_404s_when_nothing_was_uploaded(client: FlaskClient,
assert resp.status_code == 404
def _add_image(client: FlaskClient, support: str) -> int:
resp = client.post(f"/document/{support}/elements/add", data={"kind": "image", "page_id": _page_id(support)})
return int(resp.get_json()["id"])
def test_upload_image_sets_src_and_clears_svg_markup(client: FlaskClient, support: str) -> None:
element_id = _add_image(client, support)
document_engine.update_document_element_attributes(support, element_id, {"svg_markup": "<svg></svg>"})
resp = client.post(
f"/document/{support}/elements/{element_id}/upload-image",
data={"file": (io.BytesIO(b"fake png bytes"), "photo.png")},
content_type="multipart/form-data",
)
assert resp.status_code == 200
payload = resp.get_json()
assert payload["ok"] is True
assert payload["attributes"]["svg_markup"] == ""
src = payload["attributes"]["src"]
assert f"/document/{support}/uploads/" in src
assert f'src="{src}"' in payload["rendered_html"]
def test_upload_image_rejects_a_non_image_element(client: FlaskClient, support: str) -> None:
element_id = _add_bouton(client, support)
resp = client.post(
f"/document/{support}/elements/{element_id}/upload-image",
data={"file": (io.BytesIO(b"peu importe"), "x.png")},
content_type="multipart/form-data",
)
assert resp.status_code == 400
def test_upload_image_rejects_a_missing_file(client: FlaskClient, support: str) -> None:
element_id = _add_image(client, support)
resp = client.post(f"/document/{support}/elements/{element_id}/upload-image", data={})
assert resp.status_code == 400
def test_upload_image_rejects_a_disallowed_extension(client: FlaskClient, support: str) -> None:
element_id = _add_image(client, support)
resp = client.post(
f"/document/{support}/elements/{element_id}/upload-image",
data={"file": (io.BytesIO(b"#!/bin/sh"), "script.sh")},
content_type="multipart/form-data",
)
assert resp.status_code == 400
def test_uploaded_file_serves_the_stored_image(client: FlaskClient, support: str) -> None:
element_id = _add_image(client, support)
resp = client.post(
f"/document/{support}/elements/{element_id}/upload-image",
data={"file": (io.BytesIO(b"fake png bytes"), "photo.png")},
content_type="multipart/form-data",
)
src = resp.get_json()["attributes"]["src"]
resp = client.get(src)
assert resp.status_code == 200
assert resp.data == b"fake png bytes"
def test_uploaded_file_404s_for_an_unknown_filename(client: FlaskClient, support: str) -> None:
resp = client.get(f"/document/{support}/uploads/inconnu.png")
assert resp.status_code == 404
def test_document_edit_has_no_theme_link_by_default(client: FlaskClient, support: str) -> None:
resp = client.get(f"/document/{support}/edit")
assert "document/themes/securite-incendie.css" not in resp.get_data(as_text=True)