Pagination automatique : le contenu qui déborde part sur une nouvelle page
Retour utilisateur du 23/09/2026 : "si il n'y a plus de place sur la page il faut automatiquement créer une autre page [et y] coller le contenu et amener l'utilisateur sur la page" — remplace le comportement précédent (overflow:hidden, contenu clipsé, à gérer manuellement). - Nouvelle capacité serveur : document_engine.move_document_element_to_page (+ route POST /document/<slug>/elements/<id>/move-to-page) déplace un élément (et ses enfants de rangée en cascade) vers une AUTRE page — jusqu'ici move_document_element ne gérait que le réordonnancement DANS la même page. - Client : forgeDocCheckPageOverflow, appelée à la fin de CHAQUE forgeDocRefreshCanvas (point d'entrée unique après toute mutation) : mesure le débordement réel (scrollHeight vs clientHeight), trouve le premier élément top-niveau qui dépasse le bas de la page (getBoundingClientRect, tient compte du zoom), déplace cet élément et tout ce qui le suit vers une page neuve, puis y bascule l'utilisateur. Jamais déclenché sur une page mini-jeu (toujours seule sur sa page, aucun débordement pertinent à corriger). Vérifié par un test jsdom dédié (géométrie simulée via getBoundingClientRect/scrollHeight/clientHeight, jsdom n'ayant pas de vrai moteur de mise en page) : ordre des déplacements, page inchangée si le contenu tient, page mini-jeu jamais scindée. 6 nouveaux tests Python (document_engine + route). 711/711 tests passent. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
0e8c6efb06
commit
c6e173589f
@@ -126,3 +126,57 @@ def test_render_document_includes_row_children(tmp_support_slug_cleanup: Any) ->
|
||||
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) == []
|
||||
|
||||
@@ -264,6 +264,35 @@ def test_document_element_move_and_delete(client: FlaskClient, support: str) ->
|
||||
assert delete_resp.get_json()["ok"] is True
|
||||
|
||||
|
||||
def test_document_element_move_to_page_transfers_the_element(client: FlaskClient, support: str) -> None:
|
||||
"""Retour utilisateur du 23/09/2026 : pagination automatique — un
|
||||
élément qui déborde d'une page est déplacé vers une autre, ici la
|
||||
route qui porte ce déplacement (jamais document_element_move, qui
|
||||
ne fait que réordonner DANS la même page)."""
|
||||
page1_id = _page_id(support)
|
||||
page2_id = document_engine.add_document_page(support)
|
||||
element_id = client.post(
|
||||
f"/document/{support}/elements/add", data={"kind": "titre", "page_id": page1_id}
|
||||
).get_json()["id"]
|
||||
|
||||
resp = client.post(f"/document/{support}/elements/{element_id}/move-to-page", json={"target_page_id": page2_id})
|
||||
assert resp.status_code == 200
|
||||
assert resp.get_json()["ok"] is True
|
||||
moved = document_engine.get_document_element(support, element_id)
|
||||
assert moved is not None
|
||||
assert moved["page_id"] == page2_id
|
||||
|
||||
|
||||
def test_document_element_move_to_page_rejects_an_unknown_target(client: FlaskClient, support: str) -> None:
|
||||
page_id = _page_id(support)
|
||||
element_id = client.post(
|
||||
f"/document/{support}/elements/add", data={"kind": "titre", "page_id": page_id}
|
||||
).get_json()["id"]
|
||||
|
||||
resp = client.post(f"/document/{support}/elements/{element_id}/move-to-page", json={"target_page_id": 999})
|
||||
assert resp.status_code == 404
|
||||
|
||||
|
||||
def test_restricted_user_can_have_one_game_and_one_support_at_once(
|
||||
user_client: FlaskClient, tmp_game_slug_cleanup: Any, tmp_support_slug_cleanup: Any
|
||||
) -> None:
|
||||
|
||||
Reference in New Issue
Block a user