Files
Forge-Engine/tests/test_flow_clicked_row.py
T
williamandClaude Sonnet 5 b094342097 Ajoute "Ligne cliquée (Répéteur)" comme cible pour une Condition/action de la Logique de la scène
Problème remonté : un déclencheur "Au clic" posé sur un Répéteur exécute
le MÊME graphe pour n'importe quelle ligne cliquée - or une Condition
("Si is_opened est égal à Non") ou une action "Modifier une donnée" ne
pouvaient viser qu'une ligne FIXE, choisie à la création du nœud dans
l'éditeur. Impossible donc de dire "modifie le champ DE LA LIGNE QUE JE
VIENS DE CLIQUER", puisque cette ligne n'est justement jamais connue à
l'avance.

Nouvelle valeur sentinelle CLICKED_ROW_ID (-1, screens/flow/constants.py,
ne collisionne jamais avec un vrai id de ligne) proposée en tête de TOUTE
liste déroulante "Ligne concernée" (clause principale et clauses
supplémentaires d'un nœud Condition, cible d'une action "Modifier une
donnée") : "🖱️ Ligne cliquée (Répéteur)".

Résolution au moment de l'exécution, pas à la création du nœud :
- Condition (évaluée côté client) : readFieldValue() (play.html) résout
  -1 en window.lastClickedRowId, déjà capturé par bindClicks() au clic sur
  une ligne de Répéteur (déjà utilisé par "Ouvrir la ligne cliquée").
- Action "Modifier une donnée" (exécutée côté serveur) : le client envoie
  clicked_row_id dans le corps de la requête POST ; flow_node_run_data.py
  ne s'en sert que si le nœud vise justement CLICKED_ROW_ID, sinon la
  ligne fixe stockée sur le nœud reste utilisée normalement.

Ajoute tests/test_flow_clicked_row.py (ligne cliquée seule modifiée,
absence de clic = no-op plutôt que plantage, non-régression d'une cible
fixe, présence de l'option dans l'éditeur).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-25 17:19:41 +02:00

111 lines
4.6 KiB
Python

"""CLICKED_ROW_ID (-1, voir screens/flow/constants.py) : une action
"Modifier une donnée" (ou un nœud Condition) peut viser "la ligne de
Répéteur sur laquelle on vient de cliquer" plutôt qu'une ligne fixe -
indispensable quand le déclencheur "Au clic" est posé sur un Répéteur,
puisque chaque ligne exécute le même graphe et la ligne cliquée n'est
jamais connue à l'avance dans l'éditeur."""
import json
import re
import db
import screens
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_email_object(client, slug):
resp = client.post(f"/game/{slug}/objects/new", data={
"object_name": "Email", "field_name[]": ["is_opened"], "field_type[]": ["booleen"],
"field_relation[]": [""], "field_required[]": ["0"], "field_min[]": [""], "field_max[]": [""],
}, follow_redirects=False)
def_id = int(resp.headers["Location"].rstrip("/").split("/")[-1])
client.post(f"/game/{slug}/objects/{def_id}/data/new", data={})
client.post(f"/game/{slug}/objects/{def_id}/data/new", data={})
return def_id
def test_modifier_donnee_targeting_clicked_row_updates_only_that_row(client, game):
def_id = _create_email_object(client, game)
screen_id = _create_screen(client, game)
resp = client.post(
f"/game/{game}/screens/{screen_id}/flow/nodes/add",
data=json.dumps({
"node_type": "action", "action_type": "modifier_donnee",
"target_definition_id": def_id, "target_row_id": -1, "target_field": "is_opened",
"data_operation": "definir_bool_vrai", "data_value": "",
}),
content_type="application/json",
)
node = resp.get_json()
assert node["target_row_id"] == -1
resp = client.post(
f"/game/{game}/flow/nodes/{node['id']}/run-data",
data=json.dumps({"clicked_row_id": 2}),
content_type="application/json",
)
assert resp.get_json() == {"ok": True}
definition = db.get_definition(game, def_id)
row1 = db.get_row(game, definition, 1)
row2 = db.get_row(game, definition, 2)
assert not row1["is_opened"]
assert row2["is_opened"]
def test_modifier_donnee_targeting_clicked_row_without_a_click_is_a_noop(client, game):
"""Pas de ligne cliquée transmise (ex. déclenché autrement qu'un vrai
clic sur une ligne de Répéteur) -> ne doit rien modifier, pas planter."""
def_id = _create_email_object(client, game)
screen_id = _create_screen(client, game)
resp = client.post(
f"/game/{game}/screens/{screen_id}/flow/nodes/add",
data=json.dumps({
"node_type": "action", "action_type": "modifier_donnee",
"target_definition_id": def_id, "target_row_id": -1, "target_field": "is_opened",
"data_operation": "definir_bool_vrai", "data_value": "",
}),
content_type="application/json",
)
node = resp.get_json()
resp = client.post(f"/game/{game}/flow/nodes/{node['id']}/run-data", data=json.dumps({}), content_type="application/json")
assert resp.get_json() == {"ok": False}
definition = db.get_definition(game, def_id)
assert not db.get_row(game, definition, 1)["is_opened"]
assert not db.get_row(game, definition, 2)["is_opened"]
def test_ordinary_fixed_row_target_is_unaffected(client, game):
"""Non-régression : une action visant une ligne FIXE (pas -1) continue
d'ignorer tout clicked_row_id transmis."""
def_id = _create_email_object(client, game)
screen_id = _create_screen(client, game)
resp = client.post(
f"/game/{game}/screens/{screen_id}/flow/nodes/add",
data=json.dumps({
"node_type": "action", "action_type": "modifier_donnee",
"target_definition_id": def_id, "target_row_id": 1, "target_field": "is_opened",
"data_operation": "definir_bool_vrai", "data_value": "",
}),
content_type="application/json",
)
node = resp.get_json()
client.post(f"/game/{game}/flow/nodes/{node['id']}/run-data", data=json.dumps({"clicked_row_id": 2}), content_type="application/json")
definition = db.get_definition(game, def_id)
assert db.get_row(game, definition, 1)["is_opened"]
assert not db.get_row(game, definition, 2)["is_opened"]
def test_clicked_row_option_appears_in_the_flow_node_editor(client, game):
_create_email_object(client, game)
screen_id = _create_screen(client, game)
html = client.get(f"/game/{game}/screens/{screen_id}/edit").data.decode()
assert "FLOW_CLICKED_ROW_ID" in html
assert "Ligne cliquée" in html