Add an Onglets (Tabs) widget
New widget where each tab is a real "conteneur" element posed as a child (see screens/elements/add_tab.py) — this reuses everything that already exists for a normal container (adding a Répéteur/Conteneur/etc. inside via "Ajouter DANS ce conteneur", renaming to change the tab's visible label, deleting via the standard trash icon) instead of inventing a separate storage format for tabs. The widget's own properties panel gets a dedicated "Onglets" section to add a tab, rename one, jump to its content, or delete it. Rendering (render_onglets.py) builds a tab bar + one panel per tab, switched client-side (forgeShowTab, in both screen_edit.html and play.html) with only one panel visible at a time. Distinct from the existing "activer_onglet" flow action (2.3, manual show-one/hide-siblings) — that stays available for custom show/hide wiring; this widget is the turnkey version with tab management built in. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
2b2619d1c4
commit
17fa4cf087
@@ -0,0 +1,121 @@
|
||||
"""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, qui est le
|
||||
champ "Nommer" générique de l'élément actuellement sélectionné (pas
|
||||
forcément un onglet, ex. l'élément Onglets 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("forgeTabBtn") == 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
|
||||
Reference in New Issue
Block a user