Files
Forge-Engine/tests/test_onglets_widget.py
T
williamandClaude Sonnet 5 ea9f91fc19 Éditeur de scène : Logique/Timeline en onglets plein écran, réglages d'écran déplacés dans le panneau Éléments
Remplace l'ancien panneau du bas rétractable/redimensionnable à la souris
(Logique de la scène, Timeline d'animation) par un système d'onglets au
centre de l'éditeur - Écran / Logique de la scène / Timeline d'animation
- un seul visible à la fois, occupant systématiquement tout l'espace
disponible (switchBuilderTab() dans screen_edit.html). Changer d'onglet
équivaut à "fermer" celui qu'on quitte ; plus besoin d'une poignée de
redimensionnement séparée puisque l'onglet actif prend déjà toute la
place. Supprime au passage tout l'ancien mécanisme (toggleFlowPanel/
toggleAnimPanel, poignées flowResizeHandle/animResizeHandle, classe CSS
.flowPanel) devenu inutile.

Déplace aussi le renommage de l'écran, le bouton "Jouer depuis le début"
et le choix du format d'aperçu (Portrait/Paysage/Carré) - jusqu'ici
au-dessus du canevas - dans le panneau flottant "Éléments" (celui qui
porte déjà ce nom, à gauche) : des réglages qu'on touche rarement une
fois l'écran en construction, qui n'ont plus besoin de rester en
permanence visibles au-dessus de la zone de travail.

Corrige au passage un bug découvert pendant ce tour : sur la page "Nouvel
objet" (2 colonnes), le bouton "+ Ajouter un champ" avait disparu -
placé APRÈS la zone de liste à défilement (flex-grow:1) dans la colonne
de droite, un flex-grow imprévisible dans ce contexte le poussait hors de
vue. Déplacé avant la liste (statique, toujours visible), pattern déjà
éprouvé ailleurs sur cette même page.

Deux tests mis à jour pour refléter intentionnellement la nouvelle
structure : la présence de "animTabPanel" (au lieu de l'ancien
"animPanel"), et un marqueur plus précis pour distinguer le bloc de
propriétés "Onglets" d'une simple occurrence du même texte dans une liste
déroulante de la Logique de la scène (qui apparaît désormais plus tôt
dans le document, cet éditeur de flow étant maintenant un onglet du
centre plutôt qu'un panneau tout en bas de page).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-25 11:27:50 +02:00

125 lines
5.9 KiB
Python

"""Tests du widget Onglets — ajouter/renommer/supprimer un onglet, contenu
par onglet (un vrai conteneur enfant, voir screens/elements/add_tab.py).
À ne pas confondre avec tests/test_onglets.py, qui teste l'action de flux
existante "activer_onglet" (2.3, montrer un élément et masquer ses frères) —
un mécanisme manuel plus bas niveau, toujours disponible séparément, que ce
widget ne remplace pas."""
import re
def _create_screen(client, slug, name="Accueil"):
resp = client.post(f"/game/{slug}/screens/new", data={"name": name}, follow_redirects=False)
return int(re.search(r"/screens/(\d+)/edit", resp.headers["Location"]).group(1))
def _create_onglets(client, slug, screen_id):
resp = client.post(f"/game/{slug}/screens/{screen_id}/elements/add", data={"widget": "onglets"}, follow_redirects=False)
return int(re.search(r"selected=(\d+)", resp.headers["Location"]).group(1))
def _tab_rename_ids(html):
"""Ids des formulaires de renommage DANS le bloc "Onglets" du panneau de
propriétés — pas le premier /elements/<id>/rename de la page (le champ
"Nommer" générique de l'élément actuellement sélectionné), ni une
occurrence de "Onglets (" dans une liste déroulante d'éléments de la
Logique de la scène (ex. "<option>Onglets (elt-1)</option>"), qui existe
aussi sur la page — seul l'en-tête du groupe de réglages ("<summary>🗂️
Onglets (") repère sans ambiguïté le bloc de propriétés lui-même."""
block = html.split(">🗂️ Onglets (", 1)[-1]
return [int(x) for x in re.findall(r"/elements/(\d+)/rename", block)]
def test_new_tabs_widget_has_no_tabs_yet(client, game):
screen_id = _create_screen(client, game)
_create_onglets(client, game, screen_id)
html = client.get(f"/game/{game}/play").data.decode()
assert "forgeTabBtn" not in html
assert "ajoute au moins un onglet" in html
def test_add_tab_creates_a_named_child_container(client, game):
screen_id = _create_screen(client, game)
onglets_id = _create_onglets(client, game, screen_id)
resp = client.post(f"/game/{game}/elements/{onglets_id}/tabs/add", follow_redirects=False)
assert resp.status_code == 302
assert f"selected={onglets_id}" in resp.headers["Location"] # reste sur le widget Onglets, pas le nouvel onglet
html = client.get(f"/game/{game}/screens/{screen_id}/edit").data.decode()
assert ">Onglet 1<" in html # nom par défaut, affiché tel quel comme libellé de l'onglet
def test_second_tab_is_named_onglet_2_and_starts_hidden(client, game):
screen_id = _create_screen(client, game)
onglets_id = _create_onglets(client, game, screen_id)
client.post(f"/game/{game}/elements/{onglets_id}/tabs/add")
client.post(f"/game/{game}/elements/{onglets_id}/tabs/add")
html = client.get(f"/game/{game}/screens/{screen_id}/edit").data.decode()
assert ">Onglet 1<" in html
assert ">Onglet 2<" in html
# Seul le premier onglet est visible par défaut (le second est en display:none).
assert 'style="display:none;"' in html
def test_renaming_a_tab_changes_the_visible_label(client, game):
screen_id = _create_screen(client, game)
onglets_id = _create_onglets(client, game, screen_id)
client.post(f"/game/{game}/elements/{onglets_id}/tabs/add")
html = client.get(f"/game/{game}/screens/{screen_id}/edit?selected={onglets_id}").data.decode()
tab_id = _tab_rename_ids(html)[0]
client.post(f"/game/{game}/elements/{tab_id}/rename", data={"name": "Statistiques"})
html = client.get(f"/game/{game}/screens/{screen_id}/edit").data.decode()
assert ">Statistiques<" in html
assert ">Onglet 1<" not in html
def test_deleting_a_tab_removes_it_and_reselects_the_tabs_widget(client, game):
screen_id = _create_screen(client, game)
onglets_id = _create_onglets(client, game, screen_id)
client.post(f"/game/{game}/elements/{onglets_id}/tabs/add")
client.post(f"/game/{game}/elements/{onglets_id}/tabs/add", follow_redirects=False)
html = client.get(f"/game/{game}/screens/{screen_id}/edit?selected={onglets_id}").data.decode()
tab_ids = _tab_rename_ids(html)
assert len(tab_ids) == 2
resp = client.post(f"/game/{game}/elements/{tab_ids[1]}/delete", follow_redirects=False)
assert resp.status_code == 302
assert f"selected={onglets_id}" in resp.headers["Location"]
html = client.get(f"/game/{game}/screens/{screen_id}/edit").data.decode()
assert html.count("forgeShowTab(this)") == 1
def test_tab_content_holds_a_real_container_children_can_be_added_to(client, game):
"""Régression : chaque onglet doit se comporter comme un vrai conteneur
— on doit pouvoir y poser un Répéteur/Conteneur/etc. via "Ajouter DANS
ce conteneur", exactement comme pour n'importe quel autre conteneur."""
screen_id = _create_screen(client, game)
onglets_id = _create_onglets(client, game, screen_id)
client.post(f"/game/{game}/elements/{onglets_id}/tabs/add")
html = client.get(f"/game/{game}/screens/{screen_id}/edit?selected={onglets_id}").data.decode()
tab_id = _tab_rename_ids(html)[0]
resp = client.post(f"/game/{game}/elements/{tab_id}/children/add", data={"widget": "titre"}, follow_redirects=False)
assert resp.status_code == 302
child_id = int(re.search(r"selected=(\d+)", resp.headers["Location"]).group(1))
client.post(f"/game/{game}/elements/{child_id}/save", data={"ctrl_content": "Bienvenue dans cet onglet"})
html = client.get(f"/game/{game}/screens/{screen_id}/edit").data.decode()
assert "Bienvenue dans cet onglet" in html
def test_deleting_the_tabs_widget_cascades_to_its_tabs(client, game):
screen_id = _create_screen(client, game)
onglets_id = _create_onglets(client, game, screen_id)
client.post(f"/game/{game}/elements/{onglets_id}/tabs/add")
client.post(f"/game/{game}/elements/{onglets_id}/delete")
html = client.get(f"/game/{game}/screens/{screen_id}/edit").data.decode()
assert "forgeTabBtn" not in html
assert ">Onglet 1<" not in html