Le sélecteur de champ apparaît maintenant sur tout nouveau widget d'un élément de jeu lié à un objet

Le tour précédent exigeait que CHAQUE widget règle sa propre "Donnée
liée" (_data_definition_id) pour voir apparaître le sélecteur de champ -
mais un élément de jeu ("Mail card"...) créé avec un "Objet lié" (voir
element_types.html, bound_definition_id) a précisément pour but d'éviter
ce réglage widget par widget : ses {{champ}} sont censés venir de CET
objet, fourni plus tard par le Répéteur qui l'utilisera comme modèle de
ligne. D'où le bug remonté : un nouveau Titre/Texte posé dans un tel
élément de jeu n'affichait jamais le sélecteur.

Ajoute get_element_type_by_template_screen(slug, screen_id), pour
retrouver depuis l'éditeur d'un écran-modèle l'entrée du catalogue (et
donc l'objet lié) dont il est la recette. screen_edit.py le calcule pour
le panneau de propriétés et le passe à controls_with_values(), qui
l'utilise comme repli pour le champ "Contenu" SEULEMENT si ce widget n'a
pas déjà sa propre "Donnée liée" réglée (priorité conservée au réglage le
plus spécifique).

Ajoute tests/test_element_type_bound_field_picker.py (apparition sans
réglage supplémentaire, absence sans objet lié, priorité à la "Donnée
liée" du widget si réglée).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-08-25 13:38:10 +02:00
co-authored by Claude Sonnet 5
parent 7e80ff952a
commit 9c9e3c5379
5 changed files with 121 additions and 6 deletions
@@ -0,0 +1,76 @@
"""Sélecteur de champ du panneau de propriétés ("+ Ajouter" à côté de
"Contenu", voir insertFieldAtCursor() dans screen_edit.html) : doit
apparaître dès qu'un objet est disponible dans le contexte, pas seulement
quand le widget a lui-même une "Donnée liée" réglée."""
import re
import screens
def _create_email_object(client, slug):
resp = client.post(f"/game/{slug}/objects/new", data={
"object_name": "Email", "field_name[]": ["sujet"], "field_type[]": ["texte"],
"field_relation[]": [""], "field_required[]": ["0"], "field_min[]": [""], "field_max[]": [""],
}, follow_redirects=False)
return int(resp.headers["Location"].rstrip("/").split("/")[-1])
def test_new_widget_inside_a_bound_element_type_gets_the_field_picker_without_extra_setup(client, game):
"""Régression : une "Mail card" (élément de jeu créé avec "Objet lié" =
Email) doit proposer le sélecteur de champ sur tout nouveau Titre/Texte
posé dans son écran-modèle, sans qu'il faille en plus régler "Lier à un
objet de données" sur CE widget précis — l'objet lié à l'élément de jeu
suffit."""
def_id = _create_email_object(client, game)
resp = client.post(f"/game/{game}/element-types", data={
"name": "Mail card", "icon": "📧", "bound_definition_id": str(def_id),
}, follow_redirects=False)
assert resp.status_code == 302
et = next(t for t in screens.list_element_types(game) if t["name"] == "Mail card")
template_screen_id = et["template_screen_id"]
resp = client.post(f"/game/{game}/screens/{template_screen_id}/elements/add", data={"widget": "titre"}, follow_redirects=False)
element_id = int(re.search(r"selected=(\d+)", resp.headers["Location"]).group(1))
html = client.get(f"/game/{game}/screens/{template_screen_id}/edit?selected={element_id}").data.decode()
assert 'id="fieldPicker-content"' in html
assert '<option value="sujet">' in html
def test_field_picker_absent_when_element_type_has_no_bound_object(client, game):
resp = client.post(f"/game/{game}/element-types", data={"name": "Carte simple", "icon": "🃏"}, follow_redirects=False)
assert resp.status_code == 302
et = next(t for t in screens.list_element_types(game) if t["name"] == "Carte simple")
template_screen_id = et["template_screen_id"]
resp = client.post(f"/game/{game}/screens/{template_screen_id}/elements/add", data={"widget": "titre"}, follow_redirects=False)
element_id = int(re.search(r"selected=(\d+)", resp.headers["Location"]).group(1))
html = client.get(f"/game/{game}/screens/{template_screen_id}/edit?selected={element_id}").data.decode()
assert 'id="fieldPicker-content"' not in html
def test_widget_own_data_binding_still_takes_priority_over_element_type_bound_object(client, game):
"""Non-régression : si le widget a SA PROPRE "Donnée liée" réglée (sur
un objet différent de celui de l'élément de jeu), c'est elle qui doit
fournir les champs du sélecteur, pas l'objet lié de l'élément de jeu."""
email_id = _create_email_object(client, game)
resp = client.post(f"/game/{game}/objects/new", data={
"object_name": "Partie", "field_name[]": ["score"], "field_type[]": ["nombre_entier"],
"field_relation[]": [""], "field_required[]": ["0"], "field_min[]": [""], "field_max[]": [""],
}, follow_redirects=False)
partie_id = int(resp.headers["Location"].rstrip("/").split("/")[-1])
resp = client.post(f"/game/{game}/element-types", data={
"name": "Mail card 2", "icon": "📧", "bound_definition_id": str(email_id),
}, follow_redirects=False)
et = next(t for t in screens.list_element_types(game) if t["name"] == "Mail card 2")
template_screen_id = et["template_screen_id"]
resp = client.post(f"/game/{game}/screens/{template_screen_id}/elements/add", data={"widget": "titre"}, follow_redirects=False)
element_id = int(re.search(r"selected=(\d+)", resp.headers["Location"]).group(1))
client.post(f"/game/{game}/elements/{element_id}/save", data={"ctrl_data_definition_id": str(partie_id)})
html = client.get(f"/game/{game}/screens/{template_screen_id}/edit?selected={element_id}").data.decode()
assert '<option value="score">' in html
assert '<option value="sujet">' not in html