first commit
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
"""Tests des points "Confort" (section 3 de la fiche de cadrage) :
|
||||
3.1 interactions au survol, 3.2 séquences temporisées, 3.3 surbrillance
|
||||
générique dynamique, 3.4 overlay/modale réutilisable, 3.5 verrouillage
|
||||
d'un élément après décision."""
|
||||
import json
|
||||
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 _add_element(client, slug, screen_id, widget):
|
||||
resp = client.post(f"/game/{slug}/screens/{screen_id}/elements/add", data={"widget": widget}, follow_redirects=False)
|
||||
return int(re.search(r"selected=(\d+)", resp.headers["Location"]).group(1))
|
||||
|
||||
|
||||
# ---------- 3.1 — Interactions au survol ----------
|
||||
|
||||
def test_hover_text_control_saved_and_rendered(client, game):
|
||||
screen_id = _create_screen(client, game)
|
||||
el_id = _add_element(client, game, screen_id, "titre")
|
||||
client.post(f"/game/{game}/elements/{el_id}/save", data={
|
||||
"ctrl_content": "Mathilde Dubois", "ctrl_survol_texte": "mathilde.d@forgebase.fr",
|
||||
})
|
||||
resp = client.get(f"/game/{game}/play")
|
||||
html = resp.data.decode()
|
||||
assert 'data-hover-text="mathilde.d@forgebase.fr"' in html
|
||||
|
||||
|
||||
def test_hover_text_absent_by_default_no_regression(client, game):
|
||||
"""Sans réglage de survol, aucun attribut data-hover-text ne doit
|
||||
apparaître — aucune régression sur les éléments existants."""
|
||||
screen_id = _create_screen(client, game)
|
||||
_add_element(client, game, screen_id, "titre")
|
||||
resp = client.get(f"/game/{game}/play")
|
||||
html = resp.data.decode()
|
||||
# (le mot "data-hover-text" apparaît dans un commentaire JS du moteur
|
||||
# lui-même — on vérifie donc l'absence de l'ATTRIBUT réellement posé sur
|
||||
# un élément, pas la simple présence de la chaîne dans la page)
|
||||
assert 'data-hover-text="' not in html
|
||||
|
||||
|
||||
def test_play_page_exposes_hover_binding_runtime(client, game):
|
||||
resp = client.get(f"/game/{game}/play")
|
||||
html = resp.data.decode()
|
||||
assert "bindHoverTexts" in html
|
||||
|
||||
|
||||
# ---------- 3.2 — Séquences temporisées ----------
|
||||
|
||||
def test_attendre_action_node_persists_delay(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": "attendre", "data_value": "5"}),
|
||||
content_type="application/json",
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
node = resp.get_json()
|
||||
assert node["action_type"] == "attendre"
|
||||
assert node["data_value"] == "5"
|
||||
|
||||
|
||||
def test_play_page_exposes_wait_action_runtime(client, game):
|
||||
resp = client.get(f"/game/{game}/play")
|
||||
html = resp.data.decode()
|
||||
assert "'attendre'" in html
|
||||
assert "setTimeout" in html
|
||||
|
||||
|
||||
# ---------- 3.3 — Surbrillance générique dynamique ----------
|
||||
|
||||
def test_surbrillance_action_node_persists(client, game):
|
||||
screen_id = _create_screen(client, game)
|
||||
el_id = _add_element(client, game, screen_id, "bouton")
|
||||
resp = client.post(
|
||||
f"/game/{game}/screens/{screen_id}/flow/nodes/add",
|
||||
data=json.dumps({
|
||||
"node_type": "action", "action_type": "modifier_element",
|
||||
"target_element_id": el_id, "element_property": "surbrillance", "element_value": "on",
|
||||
}),
|
||||
content_type="application/json",
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
node = resp.get_json()
|
||||
assert node["element_property"] == "surbrillance"
|
||||
assert node["element_value"] == "on"
|
||||
|
||||
|
||||
def test_play_page_exposes_highlight_runtime_and_css(client, game):
|
||||
resp = client.get(f"/game/{game}/play")
|
||||
html = resp.data.decode()
|
||||
assert "forgeHighlight" in html
|
||||
assert "'surbrillance'" in html
|
||||
|
||||
|
||||
# ---------- 3.4 — Overlay / modale réutilisable ----------
|
||||
|
||||
def test_overlay_widget_renders_fullscreen_fixed_box(client, game):
|
||||
screen_id = _create_screen(client, game)
|
||||
overlay_id = _add_element(client, game, screen_id, "superposition")
|
||||
client.post(f"/game/{game}/elements/{overlay_id}/save", data={"ctrl_couleur_boite": "#222222", "ctrl_arrondi": "20"})
|
||||
client.post(f"/game/{game}/elements/{overlay_id}/children/add", data={"widget": "titre"})
|
||||
|
||||
resp = client.get(f"/game/{game}/play")
|
||||
html = resp.data.decode()
|
||||
idx = html.find(f'data-element-id="{overlay_id}"')
|
||||
assert idx != -1
|
||||
# Le tag qui porte data-element-id est celui rendu par _render_overlay :
|
||||
# on regarde tout son contenu de balise ouvrante (jusqu'au ">" suivant)
|
||||
# ainsi que ce qui suit immédiatement (la boîte centrée à l'intérieur).
|
||||
tag_start = html.rfind("<div", 0, idx)
|
||||
snippet = html[tag_start:idx + 400]
|
||||
assert "position:fixed" in snippet
|
||||
assert "forgeOverlayBox" in snippet
|
||||
|
||||
|
||||
def test_overlay_starts_hidden_by_default(client, game):
|
||||
"""Régression : une Superposition fraîchement posée ne doit PAS couvrir
|
||||
tout l'écran dès sa création (position:fixed + inset:0 la ferait sinon
|
||||
intercepter tous les clics de l'écran) — elle démarre masquée, à ouvrir
|
||||
explicitement via une action."""
|
||||
screen_id = _create_screen(client, game)
|
||||
overlay_id = _add_element(client, game, screen_id, "superposition")
|
||||
resp = client.get(f"/game/{game}/play")
|
||||
html = resp.data.decode()
|
||||
idx = html.find(f'data-element-id="{overlay_id}"')
|
||||
assert idx != -1
|
||||
tag_start = html.rfind("<div", 0, idx)
|
||||
snippet = html[tag_start:idx]
|
||||
assert "display:none" in snippet
|
||||
|
||||
|
||||
def test_overlay_can_be_hidden_and_shown_like_any_element(client, game):
|
||||
"""La fermeture manuelle de l'overlay réutilise l'action existante
|
||||
"Modifier un élément → Visibilité" — pas de mécanisme dédié."""
|
||||
screen_id = _create_screen(client, game)
|
||||
overlay_id = _add_element(client, game, screen_id, "superposition")
|
||||
resp = client.post(
|
||||
f"/game/{game}/screens/{screen_id}/flow/nodes/add",
|
||||
data=json.dumps({
|
||||
"node_type": "action", "action_type": "modifier_element",
|
||||
"target_element_id": overlay_id, "element_property": "visibilite", "element_value": "masque",
|
||||
}),
|
||||
content_type="application/json",
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
node = resp.get_json()
|
||||
assert node["element_property"] == "visibilite"
|
||||
assert node["target_element_id"] == overlay_id
|
||||
|
||||
|
||||
# ---------- 3.5 — Verrouillage d'un élément après décision ----------
|
||||
|
||||
def test_desactive_action_node_persists(client, game):
|
||||
screen_id = _create_screen(client, game)
|
||||
el_id = _add_element(client, game, screen_id, "bouton")
|
||||
resp = client.post(
|
||||
f"/game/{game}/screens/{screen_id}/flow/nodes/add",
|
||||
data=json.dumps({
|
||||
"node_type": "action", "action_type": "modifier_element",
|
||||
"target_element_id": el_id, "element_property": "desactive", "element_value": "on",
|
||||
}),
|
||||
content_type="application/json",
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
node = resp.get_json()
|
||||
assert node["element_property"] == "desactive"
|
||||
assert node["element_value"] == "on"
|
||||
|
||||
|
||||
def test_play_page_exposes_lock_runtime_and_css(client, game):
|
||||
resp = client.get(f"/game/{game}/play")
|
||||
html = resp.data.decode()
|
||||
assert "forgeDisabled" in html
|
||||
assert "pointer-events:none" in html
|
||||
assert "'desactive'" in html
|
||||
Reference in New Issue
Block a user