"""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_removed_from_properties_panel(client, game): """Le réglage "Survol" a été retiré du panneau de propriétés (voir universal_controls.py) : survoler un élément est conceptuellement un déclencheur de la Logique de la scène, pas une propriété statique — reconstruit là-bas avec les déclencheurs "survol"/"fin_survol" (voir plus bas). Poster ctrl_survol_texte ne doit donc plus avoir d'effet (le mécanisme data-hover-text/bindHoverTexts sous-jacent reste en place, il n'est simplement plus réglable depuis ce panneau).""" 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="' not 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 def test_survol_trigger_node_persists(client, game): screen_id = _create_screen(client, game) el_id = _add_element(client, game, screen_id, "titre") resp = client.post( f"/game/{game}/screens/{screen_id}/flow/nodes/add", data=json.dumps({"node_type": "trigger", "trigger_element_id": el_id, "trigger_event": "survol"}), content_type="application/json", ) assert resp.status_code == 200 node = resp.get_json() assert node["trigger_event"] == "survol" assert node["trigger_element_id"] == el_id def test_play_page_exposes_hover_trigger_runtime(client, game): """bindHoverTriggers() (mouseenter/mouseleave -> runFlowFrom) doit être exposé et appelé, exactement comme bindClicks() pour "Au clic".""" resp = client.get(f"/game/{game}/play") html = resp.data.decode() assert "bindHoverTriggers" in html assert "'survol'" in html assert "'fin_survol'" in html def test_modifier_element_contenu_action_persists(client, game): """"Modifier un élément → Contenu" : c'est ce qui permet de reconstruire l'ancien "texte affiché au survol" (et bien d'autres usages) à la main dans la Logique de la scène, en le combinant avec un déclencheur "Au survol" posé sur un AUTRE élément.""" screen_id = _create_screen(client, game) source_id = _add_element(client, game, screen_id, "titre") target_id = _add_element(client, game, screen_id, "texte") 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": target_id, "element_property": "contenu", "element_value": "Survol actif !", }), content_type="application/json", ) assert resp.status_code == 200 node = resp.get_json() assert node["element_property"] == "contenu" assert node["element_value"] == "Survol actif !" assert node["target_element_id"] == target_id # source_id n'est utilisé que pour documenter le scénario (le # déclencheur "Au survol" se poserait dessus) — non exercé ici, déjà # couvert par test_survol_trigger_node_persists. assert source_id != target_id def test_play_page_exposes_contenu_property_runtime(client, game): resp = client.get(f"/game/{game}/play") html = resp.data.decode() assert "'contenu'" 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("