"""Réglages "❓ Boîte à quiz" propres au quiz AUTONOME/plein écran (demande explicite : "étendre les fonctionnalités de la boîte à quiz pour les RH qui veulent juste faire un quiz") — voir screens/rendering/ quiz_box_config.py, screens/rendering/quiz_box_templates.py. DISTINCT de "🎨 Style" (dialogue_box_style.py, partagé avec dialogue_box/score_widget, voir test_dialogue_box.py) : ce fichier ne teste que plein écran/ minuteur/modèle.""" import db import screens def _create_jeu2d_game(tmp_game_slug_cleanup, name="pytest_quiz_config"): slug = tmp_game_slug_cleanup(db.create_game(name)) screen_id = screens.create_screen(slug, "Scène 1", kind="jeu_2d") return slug, screen_id # ---------- Sanitizer/résolution ---------- def test_default_quiz_box_config_has_no_fullscreen_no_timer_and_the_defaut_template(tmp_game_slug_cleanup): """"Aucun" minuteur/plein écran désactivé par défaut — demande explicite : jamais imposé, seulement au choix du créateur.""" slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") obj = screens.get_scene_object(slug, obj_id) config = screens.resolve_quiz_box_config(obj) assert config == screens.DEFAULT_QUIZ_BOX_CONFIG assert config["fullscreen"] is False assert config["timer_mode"] == "aucun" assert config["dialog_template"] == "defaut" assert config["page_template"] == "classique" def test_sanitize_quiz_box_config_clamps_timer_seconds(tmp_game_slug_cleanup): slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") config = screens.set_scene_object_quiz_config(slug, obj_id, {"timer_seconds": 999999}) assert config["timer_seconds"] == 600 config = screens.set_scene_object_quiz_config(slug, obj_id, {"timer_seconds": 0}) assert config["timer_seconds"] == 3 def test_sanitize_quiz_box_config_rejects_unknown_timer_mode_and_templates(tmp_game_slug_cleanup): slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") config = screens.set_scene_object_quiz_config(slug, obj_id, { "timer_mode": "toutes-les-secondes", "dialog_template": "arc-en-ciel", "page_template": "aquarelle", }) assert config["timer_mode"] == "aucun" assert config["dialog_template"] == "defaut" assert config["page_template"] == "classique" def test_sanitize_quiz_box_config_keeps_dialog_and_page_template_independent(tmp_game_slug_cleanup): """Demande explicite : les deux réglages sont conservés INDÉPENDAMMENT — un modèle "boîte" invalide ne doit jamais affecter le modèle "page" valide, et réciproquement ("verre" n'existe plus depuis la suppression des modèles "boîte" non-base, sert ici justement à vérifier qu'une valeur invalide retombe sur "defaut" SANS toucher au page_template valide à côté).""" slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") config = screens.set_scene_object_quiz_config(slug, obj_id, {"dialog_template": "verre", "page_template": "manga"}) assert config["dialog_template"] == "defaut" assert config["page_template"] == "manga" def test_sanitize_quiz_box_config_ignores_a_non_dict_input(tmp_game_slug_cleanup): slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") config = screens.set_scene_object_quiz_config(slug, obj_id, "pas-un-dict") assert config == screens.DEFAULT_QUIZ_BOX_CONFIG def test_set_scene_object_quiz_config_persists_and_sanitizes(tmp_game_slug_cleanup): slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") screens.set_scene_object_quiz_config(slug, obj_id, { "fullscreen": True, "timer_mode": "question", "timer_seconds": 15, "dialog_template": "defaut", "page_template": "manga", }) obj = screens.get_scene_object(slug, obj_id) config = screens.resolve_quiz_box_config(obj) assert config == { "fullscreen": True, "timer_mode": "question", "timer_seconds": 15, "dialog_template": "defaut", "page_template": "manga", } def test_set_scene_object_quiz_config_returns_none_for_unknown_object(tmp_game_slug_cleanup): slug, _ = _create_jeu2d_game(tmp_game_slug_cleanup) assert screens.set_scene_object_quiz_config(slug, 999999, {}) is None # ---------- Route ---------- def test_quiz_config_route_persists_config_and_returns_rendered_html(client, tmp_game_slug_cleanup): slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") resp = client.post( f"/game/{slug}/scene-objects/{obj_id}/quiz-config", data='{"config": {"fullscreen": false, "timer_mode": "quiz", "timer_seconds": 30, "dialog_template": "defaut"}}', content_type="application/json", ) assert resp.status_code == 200 body = resp.get_json() assert body["config"]["fullscreen"] is False assert body["config"]["dialog_template"] == "defaut" assert 'class="quizBoxWidget"' in body["rendered_html"] obj = screens.get_scene_object(slug, obj_id) assert screens.resolve_quiz_box_config(obj)["dialog_template"] == "defaut" def test_quiz_config_route_page_template_only_applies_when_fullscreen(client, tmp_game_slug_cleanup): slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") resp = client.post( f"/game/{slug}/scene-objects/{obj_id}/quiz-config", data='{"config": {"fullscreen": true, "page_template": "manga"}}', content_type="application/json", ) assert resp.status_code == 200 body = resp.get_json() assert body["config"]["page_template"] == "manga" assert "quizBoxWidget--manga" in body["rendered_html"] def test_quiz_config_route_404s_for_missing_object(client, game): resp = client.post(f"/game/{game}/scene-objects/999999/quiz-config", data='{"config": {}}', content_type="application/json") assert resp.status_code == 404 # ---------- Rendu — "contrat" de points d'ancrage (voir §3 du plan) : # chaque modèle doit exposer les mêmes data-quiz-role, quelle que soit sa # présentation, pour que le moteur JS partagé fonctionne quel que soit le # modèle choisi ---------- def test_render_quiz_box_defaut_is_unchanged_for_a_non_reconfigured_object(tmp_game_slug_cleanup): """Non-régression explicite : un quiz existant (jamais reconfiguré) doit rester visuellement identique à avant cette extension.""" slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") obj = screens.get_scene_object(slug, obj_id) html = screens.render_scene_object(obj) assert "quizBoxWidget--" not in html assert 'data-quiz-role="header"' in html assert 'data-quiz-role="question"' in html assert 'data-quiz-role="choices"' in html assert "Quête : Titre de la quête" in html assert html.count("quizBoxChoiceBtn") == 2 assert "quizBoxFooter" not in html def test_render_quiz_box_header_and_topbar_share_the_same_header_row(tmp_game_slug_cleanup): """Demande explicite : "le score doit être à l'intérieur de la boîte à quiz, pas à l'extérieur" — le score (topbar) et le titre (header) doivent être frères DANS le même bandeau ("quizBoxHeaderRow"), jamais deux bandes empilées de couleurs différentes.""" slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) for template in screens.QUIZ_BOX_DIALOG_TEMPLATES: obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") screens.set_scene_object_quiz_config(slug, obj_id, {"dialog_template": template}) obj = screens.get_scene_object(slug, obj_id) html = screens.render_scene_object(obj) row_start = html.index("quizBoxHeaderRow") row_end = html.index("quizBoxBody") # le bandeau se termine avant le corps assert row_start < html.index('data-quiz-role="header"', row_start) < row_end, template assert row_start < html.index('data-quiz-role="topbar"', row_start) < row_end, template def test_render_quiz_box_every_dialog_template_exposes_the_required_data_quiz_role_anchors(tmp_game_slug_cleanup): """Contrat commun (voir §3 du plan) — vérifié ici pour les modèles "boîte de dialogue" (fullscreen=False, voir le test jumeau pour les modèles "page de quiz").""" slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") for template in screens.QUIZ_BOX_DIALOG_TEMPLATES: screens.set_scene_object_quiz_config(slug, obj_id, {"dialog_template": template}) obj = screens.get_scene_object(slug, obj_id) html = screens.render_scene_object(obj) for role in ("header", "question", "choices", "topbar", "score", "timer"): assert f'data-quiz-role="{role}"' in html, f"modèle {template!r} : ancrage {role!r} manquant" assert f'data-quiz-template="{template}"' in html assert 'class="quizBoxWidget' in html def test_render_quiz_box_every_page_template_exposes_the_required_data_quiz_role_anchors(tmp_game_slug_cleanup): """Même contrat, pour les modèles "page de quiz" (fullscreen=True) — "topbar" n'est PAS exigé ici (voir le contrat assoupli dans screens/rendering/quiz_box_templates.py : un modèle "page" n'a pas besoin de regrouper score/minuteur à masquer, il n'existe QUE plein écran).""" slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") screens.set_scene_object_quiz_config(slug, obj_id, {"fullscreen": True}) for template in screens.QUIZ_BOX_PAGE_TEMPLATES: screens.set_scene_object_quiz_config(slug, obj_id, {"fullscreen": True, "page_template": template}) obj = screens.get_scene_object(slug, obj_id) html = screens.render_scene_object(obj) for role in ("header", "question", "choices", "score", "timer"): assert f'data-quiz-role="{role}"' in html, f"modèle {template!r} : ancrage {role!r} manquant" assert f'data-quiz-template="{template}"' in html assert 'class="quizBoxWidget' in html def test_render_quiz_box_reflects_fullscreen_and_timer_data_attributes(tmp_game_slug_cleanup): slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") screens.set_scene_object_quiz_config(slug, obj_id, {"fullscreen": True, "timer_mode": "question", "timer_seconds": 42}) obj = screens.get_scene_object(slug, obj_id) html = screens.render_scene_object(obj) assert 'data-fullscreen="1"' in html assert 'data-timer-mode="question"' in html assert 'data-timer-seconds="42"' in html def test_render_quiz_box_manga_shows_aucun_under_temps_when_no_timer_is_configured(tmp_game_slug_cleanup): """Demande explicite : "garde le temps mais si il n'y a pas de chrono marque en dessous de temps aucun" — la plaque "Temps" reste TOUJOURS visible (jamais masquée), avec "Aucun" en son absence.""" slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") screens.set_scene_object_quiz_config(slug, obj_id, {"fullscreen": True, "timer_mode": "aucun", "page_template": "manga"}) obj = screens.get_scene_object(slug, obj_id) html = screens.render_scene_object(obj) assert 'Aucun' in html def test_render_quiz_box_manga_leaves_the_timer_value_empty_when_a_timer_is_configured(tmp_game_slug_cleanup): """Un vrai minuteur est rempli par le moteur JS (compte à rebours), pas par le rendu serveur — jamais "Aucun" affiché en même temps qu'un minuteur réellement configuré.""" slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") screens.set_scene_object_quiz_config(slug, obj_id, {"fullscreen": True, "timer_mode": "question", "timer_seconds": 20, "page_template": "manga"}) obj = screens.get_scene_object(slug, obj_id) html = screens.render_scene_object(obj) assert '' in html def test_render_quiz_box_manga_has_a_progress_stepper_placeholder(tmp_game_slug_cleanup): """Demande explicite : "il n'y a pas le steper" — le HTML doit exposer un conteneur `data-manga-role="progress"` que le JS (forgeQuizMangaOnShow, static/js/play/quiz-box-templates.js) remplit au jeu (nombre de questions inconnu à la création de l'objet).""" slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") screens.set_scene_object_quiz_config(slug, obj_id, {"fullscreen": True, "page_template": "manga"}) obj = screens.get_scene_object(slug, obj_id) html = screens.render_scene_object(obj) assert '
' in html def test_render_quiz_box_manga_has_an_always_empty_result_zone_placeholder(tmp_game_slug_cleanup): """Demande explicite : "il faut aussi adapter le modele manga pour avoir sa page de résultat" — voir forgeQuizMangaOnQuizComplete, static/js/play/quiz-box-templates.js, qui remplit/affiche ce conteneur EN JEU seulement (toujours vide côté rendu serveur).""" slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") screens.set_scene_object_quiz_config(slug, obj_id, {"fullscreen": True, "page_template": "manga"}) obj = screens.get_scene_object(slug, obj_id) html = screens.render_scene_object(obj) assert '
' in html def test_render_quiz_box_manga_dialogue_applies_its_modifier_class_and_frame(tmp_game_slug_cleanup): """"Manga" (boîte de dialogue) — demande explicite : "il faut créer un modele de boite themé mangas qui va avec le mode de page mangas", validé via une maquette interactive. Reste une petite carte flottante (fullscreen=False), structure PARTAGÉE avec les autres modèles "boîte" (contrat déjà vérifié génériquement, voir test_render_quiz_box_every_dialog_template_exposes_the_required_data_quiz_role_anchors).""" slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") screens.set_scene_object_quiz_config(slug, obj_id, {"dialog_template": "manga_dialogue"}) obj = screens.get_scene_object(slug, obj_id) html = screens.render_scene_object(obj) assert 'class="quizBoxWidget quizBoxWidget--mangaDialogue"' in html assert '
' in html assert 'data-quiz-template="manga_dialogue"' in html def test_render_quiz_box_manga_dialogue_has_an_always_visible_footer_band(tmp_game_slug_cleanup): """Demande explicite : "le modele dois etre une copie conforme a la maquete au pixel pret" — un bandeau de pied de page sombre reste TOUJOURS visible (même principe que le modèle "page" "Classique"), même avant toute réponse (le bouton "Continuer →" du moteur partagé, lui, n'apparaît qu'après coup, posé par-dessus ce bandeau en CSS).""" slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") screens.set_scene_object_quiz_config(slug, obj_id, {"dialog_template": "manga_dialogue"}) obj = screens.get_scene_object(slug, obj_id) html = screens.render_scene_object(obj) assert '' in html # "Titre de la quête" seul (jamais "Quête : Titre de la quête") — le # préfixe "Quête : " est posé en CSS (::before), pas dans le HTML, # sinon il apparaîtrait EN DOUBLE dans l'aperçu éditeur. assert '>Titre de la quête<' in html assert '>Quête : Titre de la quête<' not in html def test_render_quiz_box_manga_dialogue_shows_a_disabled_continue_button_placeholder(tmp_game_slug_cleanup): """Demande explicite (répétée : "il est ou le bouton continuer !!!!!", puis "le bouton continuer est toujours visible mais activer seulement quand un choix est fait") — le vrai bouton, comme ce repère d'aperçu éditeur, est maintenant TOUJOURS présent dès la question (posé désactivé par forgeShowQuizBox, activé par forgeQuizBoxAnswer une fois répondu — voir static/js/play/dialogue-box-controller.js).""" slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") screens.set_scene_object_quiz_config(slug, obj_id, {"dialog_template": "manga_dialogue"}) obj = screens.get_scene_object(slug, obj_id) html = screens.render_scene_object(obj) assert '' in html def test_render_quiz_box_manga_dialogue_body_has_no_extra_wrapper_around_the_continue_button(tmp_game_slug_cleanup): """Demande explicite : "le contener qui contien les choix a les bord du bas qui ne sont pas pointue" — le coin dentelé du bas est un simple `background` (dégradé) sur .quizBoxBody, PAS un clip-path ni un décor séparé à part (deux essais précédents, tous deux abandonnés : l'un rognait, l'autre emprisonnait dans un contexte d'empilement, le bouton "Continuer →" du moteur JS partagé — voir static/style.css). Le HTML reste donc le plus simple possible : le bouton reste un DESCENDANT DIRECT de .quizBoxChoices, sans wrapper intermédiaire.""" slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") screens.set_scene_object_quiz_config(slug, obj_id, {"dialog_template": "manga_dialogue"}) obj = screens.get_scene_object(slug, obj_id) html = screens.render_scene_object(obj) assert "quizMangaDialogueBodyBg" not in html assert ( '
' '' '' '' '
' ) in html def test_render_quiz_box_classique_is_the_default_page_template(tmp_game_slug_cleanup): """"Classique" — modèle de PAGE DE QUIZ DE BASE, devient le réglage par défaut (au même titre que "defaut"/Classique côté "boîte de dialogue") : un créateur qui active juste "Plein écran" sans jamais toucher aux modèles obtient ce modèle sobre, jamais "manga".""" slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") screens.set_scene_object_quiz_config(slug, obj_id, {"fullscreen": True}) obj = screens.get_scene_object(slug, obj_id) config = screens.resolve_quiz_box_config(obj) assert config["page_template"] == "classique" html = screens.render_scene_object(obj) assert 'class="quizBoxWidget quizBoxWidget--classique"' in html def test_render_quiz_box_classique_uses_the_object_name_field_as_its_title(tmp_game_slug_cleanup): """Demande explicite : "aujourd'hui un quiz n'a pas de titre mais il y a un champs nom qui peut servir de titre pour le modele" — pas de nouveau champ, le champ "Nom" (déjà existant, "ℹ️ Informations") devient le titre affiché dans le bandeau de ce modèle.""" slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") screens.set_scene_object_name(slug, obj_id, "Formation Sécurité Incendie") screens.set_scene_object_quiz_config(slug, obj_id, {"fullscreen": True, "page_template": "classique"}) obj = screens.get_scene_object(slug, obj_id) html = screens.render_scene_object(obj) assert 'Formation Sécurité Incendie' in html def test_render_quiz_box_classique_falls_back_to_a_generic_title_without_a_name(tmp_game_slug_cleanup): slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") screens.set_scene_object_quiz_config(slug, obj_id, {"fullscreen": True, "page_template": "classique"}) obj = screens.get_scene_object(slug, obj_id) html = screens.render_scene_object(obj) assert 'Quiz' in html def test_render_quiz_box_classique_shows_aucun_under_temps_when_no_timer_is_configured(tmp_game_slug_cleanup): slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") screens.set_scene_object_quiz_config(slug, obj_id, {"fullscreen": True, "page_template": "classique", "timer_mode": "aucun"}) obj = screens.get_scene_object(slug, obj_id) html = screens.render_scene_object(obj) assert 'Aucun' in html def test_render_quiz_box_classique_timer_and_score_chips_share_the_exact_same_pill_container(tmp_game_slug_cleanup): """Demande explicite (répétée) : "le conteneur de temps + aucun doivent avoir la background mais pas le mot aucun" — UN SEUL fond (celui de ".classiqueChip", identique pour "Temps" et "Score", avec ou sans minuteur configuré) ; la pastille parasite que ".quizBoxTimerDisplay" pose d'ordinaire autour de sa propre valeur (voir static/style.css) est neutralisée pour ce modèle — un bug de classe partagée qui collait un second fond/coin arrondi UNIQUEMENT autour du mot "Aucun", jamais autour de "Score : 0".""" slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") screens.set_scene_object_quiz_config(slug, obj_id, {"fullscreen": True, "page_template": "classique", "timer_mode": "aucun"}) obj = screens.get_scene_object(slug, obj_id) html = screens.render_scene_object(obj) assert 'class="classiqueChip classiqueChip--timer"' in html assert 'class="classiqueChip classiqueChip--score"' in html assert 'class="classiqueChipValue quizBoxTimerDisplay"' in html def test_render_quiz_box_classique_has_an_always_empty_result_zone_placeholder(tmp_game_slug_cleanup): """Écran de résultat de fin de quiz (demande explicite : "elle affiche un conteneur qui reaffiche le contenu du quiz au lieu du resultat" — fonctionnalité désormais réellement implémentée, voir forgeQuizClassiqueOnQuizComplete/forgeQuizResultRestart, static/js/ play/quiz-box-templates.js et dialogue-box-controller.js) : le HTML expose toujours ce conteneur vide, rempli/affiché EN JEU seulement.""" slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") screens.set_scene_object_quiz_config(slug, obj_id, {"fullscreen": True, "page_template": "classique"}) obj = screens.get_scene_object(slug, obj_id) html = screens.render_scene_object(obj) assert '
' in html def test_render_quiz_box_classique_has_an_always_visible_footer_band(tmp_game_slug_cleanup): """Demande explicite : "il manque le footer foncé avec le bouton" — un bandeau de pied de page (couleur footer_bg du panneau "🎨 Style") reste TOUJOURS visible, même avant toute réponse (le bouton "Continuer →" du moteur partagé, lui, n'apparaît qu'après coup, posé par-dessus ce bandeau en CSS).""" slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") screens.set_scene_object_quiz_config(slug, obj_id, {"fullscreen": True, "page_template": "classique"}) obj = screens.get_scene_object(slug, obj_id) html = screens.render_scene_object(obj) assert '' in html def test_render_quiz_box_classique_has_a_progress_stepper_placeholder(tmp_game_slug_cleanup): slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") screens.set_scene_object_quiz_config(slug, obj_id, {"fullscreen": True, "page_template": "classique"}) obj = screens.get_scene_object(slug, obj_id) html = screens.render_scene_object(obj) assert '
' in html def test_render_quiz_box_classique_ships_an_always_empty_explain_placeholder(tmp_game_slug_cleanup): """Encart d'explication réservé pour une fonctionnalité FUTURE non encore implémentée (demande explicite : "tu peut le mettre dans le modele mais ne fonctionnera pas pour le moment") — présent dans le HTML mais toujours vide aujourd'hui, aucune donnée ne l'alimente.""" slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") screens.set_scene_object_quiz_config(slug, obj_id, {"fullscreen": True, "page_template": "classique"}) obj = screens.get_scene_object(slug, obj_id) html = screens.render_scene_object(obj) assert '
' in html def test_quiz_box_style_panel_colors_still_apply_regardless_of_template(tmp_game_slug_cleanup): """Le panneau "🎨 Style" (couleurs/police) continue de s'appliquer à TOUS les modèles — un modèle change la disposition/décoration, jamais une raison d'ignorer les couleurs déjà choisies par le créateur.""" slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") screens.set_scene_object_dialogue_box_style(slug, obj_id, {"text_color": "#123456"}) screens.set_scene_object_quiz_config(slug, obj_id, {"dialog_template": "defaut"}) obj = screens.get_scene_object(slug, obj_id) html = screens.render_scene_object(obj) assert "#123456" in html # ---------- Payload ---------- def test_full_game_payload_exposes_quiz_config_for_quiz_box(tmp_game_slug_cleanup): slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") screens.set_scene_object_quiz_config(slug, obj_id, {"fullscreen": True, "page_template": "manga"}) payload = screens.full_game_payload(slug) scene = next(s for s in payload["screens"] if s["id"] == screen_id) box = next(o for o in scene["elements"] if o["kind"] == "quiz_box") assert box["quiz_config"]["fullscreen"] is True assert box["quiz_config"]["page_template"] == "manga" def test_full_game_payload_exposes_any_quiz_box_fullscreen_per_screen(tmp_game_slug_cleanup): """Bug signalé : "il faut régler la caméra sur plein écran et désactiver ce réglage quand la boîte à quiz est plein écran" — ce drapeau permet à templates/play.html de rendre l'ÉCRAN lui-même en plein viewport (voir .playScene--quizFullscreen), pas seulement le widget.""" slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) payload = screens.full_game_payload(slug) scene = next(s for s in payload["screens"] if s["id"] == screen_id) assert scene["any_quiz_box_fullscreen"] is False obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") screens.set_scene_object_quiz_config(slug, obj_id, {"fullscreen": True}) payload = screens.full_game_payload(slug) scene = next(s for s in payload["screens"] if s["id"] == screen_id) assert scene["any_quiz_box_fullscreen"] is True def test_play_route_renders_the_screen_itself_fullscreen_when_a_quiz_box_is_fullscreen(client, tmp_game_slug_cleanup): slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) obj_id = screens.add_scene_object(slug, screen_id, kind="quiz_box") screens.set_scene_object_quiz_config(slug, obj_id, {"fullscreen": True}) html = client.get(f"/game/{slug}/play").get_data(as_text=True) assert "playScene--quizFullscreen" in html # Le réglage "Caméra (px)" (scene_width/scene_height) ne doit plus du # tout dimensionner l'écran dans ce cas. assert "width:960px" not in html def test_play_route_keeps_the_camera_size_without_a_fullscreen_quiz_box(client, tmp_game_slug_cleanup): slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup) screens.add_scene_object(slug, screen_id, kind="quiz_box") # pas plein écran html = client.get(f"/game/{slug}/play").get_data(as_text=True) # La règle CSS elle-même (toujours présente dans