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>
125 lines
5.9 KiB
Python
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
|