Corrige la comparaison booléenne : "Oui"/"Non" n'était pas reconnu comme valeur vraie/fausse
Bug remonté avec une "Mail card" (icône enveloppe fermée si is_opened
est à "Non", ouverte si "Oui") : les DEUX variantes s'affichaient (ou
aucune), selon la ligne.
Cause : _compare() (filter_repeater_rows.py, utilisé par la condition de
visibilité, le Répéteur et Donnée liée) ne reconnaissait "1"/"true"/"vrai"
comme valeur vraie pour un champ booléen — jamais "oui", pourtant le SEUL
vocabulaire que l'app affiche elle-même pour ce type de champ partout
ailleurs (voir data_list.html : "Oui" si vrai sinon "Non"). Une valeur de
comparaison fixe tapée "Oui" retombait donc silencieusement à "faux",
et comme l'opérateur et le champ étaient par ailleurs corrects, ça
donnait l'impression que la condition "ne voyait" rien : sur la ligne où
is_opened=faux, les DEUX cartes ("égal à Oui" et "égal à Non", toutes
deux évaluées comme "égal à faux") s'affichaient ensemble ; sur la ligne
où is_opened=vrai, aucune des deux.
Fix : "oui" ajouté à l'ensemble des valeurs reconnues comme vraies,
côté Python (_compare) ET côté JS (compareValues() dans play.html, qui
doit rester alignée — utilisée par les nœuds Condition de la Logique de
la scène), cette dernière au passage rendue insensible à la casse comme
son équivalent Python (elle ne l'était pas du tout).
Ajoute un test de régression dédié à ce cas précis.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
a28cc5881e
commit
baa3da1035
@@ -60,7 +60,14 @@ def _compare(actual, operator, expected, field_type):
|
|||||||
else:
|
else:
|
||||||
truthy = bool(actual)
|
truthy = bool(actual)
|
||||||
a = 1 if truthy else 0
|
a = 1 if truthy else 0
|
||||||
e = 1 if str(expected).strip().lower() in ("1", "true", "vrai") else 0
|
# "oui"/"non" est le vocabulaire affiché partout ailleurs dans
|
||||||
|
# l'app pour un champ booléen (voir data_list.html, "Oui" si vrai
|
||||||
|
# sinon "Non") — une valeur de comparaison fixe tapée "Oui" doit
|
||||||
|
# donc être reconnue comme vraie ici aussi, pas seulement "1"/
|
||||||
|
# "true"/"vrai" (sans quoi "Oui" retombait silencieusement à faux,
|
||||||
|
# et un widget conditionné sur "est égal à Oui" ne s'affichait
|
||||||
|
# jamais quand il aurait dû).
|
||||||
|
e = 1 if str(expected).strip().lower() in ("1", "true", "vrai", "oui") else 0
|
||||||
return a != e if operator == "different" else a == e
|
return a != e if operator == "different" else a == e
|
||||||
try:
|
try:
|
||||||
an, en = float(actual), float(expected)
|
an, en = float(actual), float(expected)
|
||||||
|
|||||||
+7
-1
@@ -557,7 +557,13 @@
|
|||||||
function compareValues(actual, operator, expected, fieldType) {
|
function compareValues(actual, operator, expected, fieldType) {
|
||||||
if (fieldType === 'booleen') {
|
if (fieldType === 'booleen') {
|
||||||
const a = actual ? 1 : 0;
|
const a = actual ? 1 : 0;
|
||||||
const e = (expected === '1' || expected === 'true' || expected === true) ? 1 : 0;
|
// "oui"/"non" (voir data_list.html) est le vocabulaire affiché
|
||||||
|
// partout ailleurs pour un champ booléen — une valeur de comparaison
|
||||||
|
// fixe tapée "Oui" doit donc être reconnue vraie ici aussi, pas
|
||||||
|
// seulement "1"/"true" (et insensible à la casse, aligné avec
|
||||||
|
// _compare() côté Python, voir filter_repeater_rows.py).
|
||||||
|
const expectedStr = String(expected).trim().toLowerCase();
|
||||||
|
const e = (expected === true || ['1', 'true', 'vrai', 'oui'].includes(expectedStr)) ? 1 : 0;
|
||||||
return operator === 'different' ? a !== e : a === e;
|
return operator === 'different' ? a !== e : a === e;
|
||||||
}
|
}
|
||||||
const an = parseFloat(actual), en = parseFloat(expected);
|
const an = parseFloat(actual), en = parseFloat(expected);
|
||||||
|
|||||||
@@ -79,6 +79,41 @@ def test_objet_mode_uses_most_recent_row_outside_a_repeater(client, game):
|
|||||||
assert "display:none" not in _rendered_html(client, game)
|
assert "display:none" not in _rendered_html(client, game)
|
||||||
|
|
||||||
|
|
||||||
|
def test_objet_mode_recognizes_oui_non_as_boolean_literal(client, game):
|
||||||
|
"""Régression : le sélecteur "Valeur fixe" du panneau de propriétés
|
||||||
|
n'offre pas "vrai"/"faux" pour un champ booléen — "Oui"/"Non" est le
|
||||||
|
seul vocabulaire visible dans toute l'app (voir data_list.html, qui
|
||||||
|
affiche "Oui"/"Non" pour ce type de champ). Une condition réglée
|
||||||
|
"est égal à Oui" doit donc reconnaître "Oui" comme vrai, pas seulement
|
||||||
|
"1"/"true"/"vrai" — sinon elle ne s'affichait jamais quand elle aurait
|
||||||
|
dû (repérée avec une "Mail card" affichant les DEUX variantes, ouverte
|
||||||
|
ET fermée, au lieu d'une seule)."""
|
||||||
|
screen_id = _create_screen(client, game)
|
||||||
|
closed_id = _add_element(client, game, screen_id, "icone")
|
||||||
|
resp = client.post(f"/game/{game}/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/{game}/objects/{def_id}/data/new", data={"is_opened": "on"})
|
||||||
|
|
||||||
|
client.post(f"/game/{game}/elements/{closed_id}/save", data={
|
||||||
|
"ctrl_visibilite_cond_mode": "objet", "ctrl_visibilite_cond_definition_id": str(def_id),
|
||||||
|
"ctrl_visibilite_cond_champ": "is_opened", "ctrl_visibilite_cond_operateur": "egal", "ctrl_visibilite_cond_valeur": "Non",
|
||||||
|
})
|
||||||
|
assert "display:none" in _rendered_html(client, game)
|
||||||
|
|
||||||
|
open_id = _add_element(client, game, screen_id, "icone")
|
||||||
|
client.post(f"/game/{game}/elements/{open_id}/save", data={
|
||||||
|
"ctrl_visibilite_cond_mode": "objet", "ctrl_visibilite_cond_definition_id": str(def_id),
|
||||||
|
"ctrl_visibilite_cond_champ": "is_opened", "ctrl_visibilite_cond_operateur": "egal", "ctrl_visibilite_cond_valeur": "Oui",
|
||||||
|
})
|
||||||
|
payload = client.get(f"/game/{game}/runtime-payload").get_json()
|
||||||
|
els = {el["id"]: el["rendered_html"] for el in payload["screens"][0]["elements"]}
|
||||||
|
assert "display:none" in els[closed_id]
|
||||||
|
assert "display:none" not in els[open_id]
|
||||||
|
|
||||||
|
|
||||||
def test_visible_special_render_widget_carries_refresh_marker(client, game):
|
def test_visible_special_render_widget_carries_refresh_marker(client, game):
|
||||||
"""Régression : une icône (special_render, pas le chemin générique)
|
"""Régression : une icône (special_render, pas le chemin générique)
|
||||||
actuellement VISIBLE sous condition doit quand même porter le
|
actuellement VISIBLE sous condition doit quand même porter le
|
||||||
|
|||||||
Reference in New Issue
Block a user