Files
Forge-Engine/tests/test_jauge.py
T
williamandwilliam 3f4ebc4527
Build and deploy / build-and-push (push) Successful in 17s
Build and deploy / deploy (push) Successful in 10s
first commit
2026-08-21 16:23:49 +02:00

108 lines
5.8 KiB
Python

"""Tests du widget Jauge (2.1 : binding live d'une donnée vers un style)."""
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_partie_object(client, slug, field_name="reputation", value="50"):
resp = client.post(f"/game/{slug}/objects/new", data={
"object_name": "Partie", "field_name[]": [field_name], "field_type[]": ["texte"],
"field_relation[]": [""], "field_required[]": ["0"],
}, follow_redirects=False)
def_id = int(resp.headers["Location"].rstrip("/").split("/")[-1])
client.post(f"/game/{slug}/objects/{def_id}/data/new", data={field_name: value})
return def_id
def test_gauge_width_reflects_field_value_without_extra_action(client, game):
"""Régression 2.1 : la largeur de la jauge doit refléter automatiquement
la valeur du champ suivi, sans qu'aucune action "Modifier un élément"
n'ait été posée dans le graphe de logique."""
def_id = _create_partie_object(client, game, "reputation", "50")
screen_id = _create_screen(client, game)
resp = client.post(f"/game/{game}/screens/{screen_id}/elements/add", data={"widget": "jauge"}, follow_redirects=False)
jauge_id = int(re.search(r"selected=(\d+)", resp.headers["Location"]).group(1))
client.post(f"/game/{game}/elements/{jauge_id}/save", data={
"ctrl_definition_id": str(def_id), "ctrl_champ": "reputation", "ctrl_min": "0", "ctrl_max": "100",
})
html = client.get(f"/game/{game}/screens/{screen_id}/edit").data.decode()
assert "width:50.0%" in html
def test_gauge_updates_after_data_change_with_no_extra_wiring(client, game):
"""Régression 2.1 : quand la donnée change (ex: une action "Modifier une
donnée" exécutée en jeu), la jauge doit refléter la NOUVELLE valeur au
prochain rendu — sans binding manuel à recâbler."""
def_id = _create_partie_object(client, game, "reputation", "50")
screen_id = _create_screen(client, game)
resp = client.post(f"/game/{game}/screens/{screen_id}/elements/add", data={"widget": "jauge"}, follow_redirects=False)
jauge_id = int(re.search(r"selected=(\d+)", resp.headers["Location"]).group(1))
client.post(f"/game/{game}/elements/{jauge_id}/save", data={
"ctrl_definition_id": str(def_id), "ctrl_champ": "reputation", "ctrl_min": "0", "ctrl_max": "100",
})
# Simule une action "Modifier une donnée" qui change la valeur en base.
client.post(f"/game/{game}/objects/{def_id}/data/new", data={"reputation": "90"})
html = client.get(f"/game/{game}/screens/{screen_id}/edit").data.decode()
assert "width:90.0%" in html
def test_gauge_color_gradient_shifts_between_low_and_high(client, game):
"""Régression 2.1 : la couleur de remplissage doit se rapprocher de la
couleur "maxi" quand la valeur est haute, pas rester figée."""
def_id = _create_partie_object(client, game, "reputation", "100")
screen_id = _create_screen(client, game)
resp = client.post(f"/game/{game}/screens/{screen_id}/elements/add", data={"widget": "jauge"}, follow_redirects=False)
jauge_id = int(re.search(r"selected=(\d+)", resp.headers["Location"]).group(1))
client.post(f"/game/{game}/elements/{jauge_id}/save", data={
"ctrl_definition_id": str(def_id), "ctrl_champ": "reputation", "ctrl_min": "0", "ctrl_max": "100",
"ctrl_couleur_basse": "#ff0000", "ctrl_couleur_haute": "#00ff00",
})
html = client.get(f"/game/{game}/screens/{screen_id}/edit").data.decode()
assert "background-color:#00ff00" in html # valeur au max -> couleur "haute" pure
def test_gauge_value_clamped_visually_when_out_of_range(client, game):
"""Une valeur au-delà du maxi réglé (avant que 2.2 n'empêche ça à la
source) ne doit pas casser l'affichage — la jauge doit rester à 100%,
pas déborder."""
def_id = _create_partie_object(client, game, "reputation", "500")
screen_id = _create_screen(client, game)
resp = client.post(f"/game/{game}/screens/{screen_id}/elements/add", data={"widget": "jauge"}, follow_redirects=False)
jauge_id = int(re.search(r"selected=(\d+)", resp.headers["Location"]).group(1))
client.post(f"/game/{game}/elements/{jauge_id}/save", data={
"ctrl_definition_id": str(def_id), "ctrl_champ": "reputation", "ctrl_min": "0", "ctrl_max": "100",
})
html = client.get(f"/game/{game}/screens/{screen_id}/edit").data.decode()
assert "width:100.0%" in html
# ---------- Régression : clics multipliés après un rafraîchissement de
# données (bouton +10/-10 relié à une jauge) ----------
def test_play_page_click_binding_guards_against_duplicate_listeners(client, game):
"""Bug remonté par l'utilisateur : après plusieurs clics sur un bouton
relié à une action "Modifier une donnée" (ex: +10 points), l'effet
devenait instable (parfois -20, 0, +100 au lieu de ±10 par clic).
Cause : bindClicks() est rappelée après CHAQUE refreshRuntimeData() (donc
après chaque donnée modifiée), et un bouton ordinaire (ni Répéteur, ni
Jauge) garde le MÊME nœud DOM d'un rafraîchissement à l'autre — sans
garde-fou, addEventListener('click') s'empile à chaque rafraîchissement,
et un seul clic finit par déclencher l'action plusieurs fois d'affilée.
On vérifie ici la présence du garde-fou (clickBound) qui empêche ce
ré-attachement — un vrai test de non-régression comportementale (clics
répétés via un navigateur réel) a été exécuté manuellement pour valider
le correctif : 5 clics sur +10 -> exactement +50, jamais plus."""
resp = client.get(f"/game/{game}/play")
html = resp.data.decode()
assert "clickBound" in html
assert "if (elDiv.dataset.clickBound) return;" in html