- "qui parle" n'est plus réservé au personnage : N'IMPORTE QUEL objet de scène nommé (personnage, décor, fond — voir "ℹ️ Informations", screens/rendering/scene_object_names.py, ex-personnage_names.py) peut parler dans un dialogue. - Confirmé/documenté : aucune limite au nombre de répliques par colonne (bouton "+ Réplique" reste toujours disponible). - Bulle = header (menu déroulant du "qui parle") + body (le texte), centrée dans sa colonne à 70% de largeur, alignée verticalement et reliée à la suivante par un trait — remplace l'ancien alignement gauche/droite façon chat. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
146 lines
7.2 KiB
Python
146 lines
7.2 KiB
Python
"""Widget d'interface "🖥️ Interface -> 💬 Boîte de dialogue" (voir
|
||
screens/rendering/dialogue_box_style.py, screens/scenes/add_scene_object.py
|
||
kind="dialogue_box") : affiche automatiquement, en jeu, la réplique en
|
||
cours d'une quête déclenchée par une règle de collision "quete" (voir
|
||
static/js/play/dialogue-box-controller.js) — header/body/footer
|
||
stylisables. Et le nom d'un personnage ("ℹ️ Informations",
|
||
screens/scenes/set_scene_object_name.py), utilisé comme "qui parle" dans
|
||
l'éditeur de dialogue de quête (screens/rendering/scene_object_names.py)."""
|
||
import db
|
||
import screens
|
||
|
||
|
||
def _create_jeu2d_game(tmp_game_slug_cleanup, name="pytest_dialogue_box"):
|
||
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
|
||
|
||
|
||
def test_add_dialogue_box_has_sensible_default_size(tmp_game_slug_cleanup):
|
||
slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup)
|
||
obj_id = screens.add_scene_object(slug, screen_id, kind="dialogue_box")
|
||
obj = screens.get_scene_object(slug, obj_id)
|
||
assert obj["kind"] == "dialogue_box"
|
||
assert obj["width"] == 320
|
||
assert obj["height"] == 150
|
||
|
||
|
||
def test_dialogue_box_default_style(tmp_game_slug_cleanup):
|
||
slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup)
|
||
obj_id = screens.add_scene_object(slug, screen_id, kind="dialogue_box")
|
||
obj = screens.get_scene_object(slug, obj_id)
|
||
style = screens.resolve_dialogue_box_style(obj)
|
||
assert style == screens.DEFAULT_DIALOGUE_BOX_STYLE
|
||
|
||
|
||
def test_render_dialogue_box_has_header_body_footer(tmp_game_slug_cleanup):
|
||
slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup)
|
||
obj_id = screens.add_scene_object(slug, screen_id, kind="dialogue_box")
|
||
obj = screens.get_scene_object(slug, obj_id)
|
||
html = screens.render_scene_object(obj)
|
||
assert "dialogueBoxHeader" in html
|
||
assert "dialogueBoxBody" in html
|
||
assert "dialogueBoxFooter" in html
|
||
assert f'data-object-id="{obj_id}"' in html
|
||
|
||
|
||
def test_set_scene_object_dialogue_box_style_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="dialogue_box")
|
||
style = screens.set_scene_object_dialogue_box_style(slug, obj_id, {
|
||
"font_family": "Georgia", "font_size": 999, "font_weight": "bold",
|
||
"text_color": "#ff0000", "header_bg": "#000000", "unknown_key": "ignored",
|
||
})
|
||
assert style["font_family"] == "Georgia"
|
||
assert style["font_size"] == 48 # bornée au maximum autorisé
|
||
assert style["font_weight"] == "bold"
|
||
assert style["text_color"] == "#ff0000"
|
||
assert style["header_bg"] == "#000000"
|
||
obj = screens.get_scene_object(slug, obj_id)
|
||
assert screens.resolve_dialogue_box_style(obj) == style
|
||
|
||
|
||
def test_dialogue_box_route_persists_style(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="dialogue_box")
|
||
resp = client.post(
|
||
f"/game/{slug}/scene-objects/{obj_id}/dialogue-box-style",
|
||
data='{"style": {"header_bg": "#123456"}}', content_type="application/json",
|
||
)
|
||
assert resp.get_json()["style"]["header_bg"] == "#123456"
|
||
|
||
|
||
def test_dialogue_box_route_404s_for_missing_object(client, game):
|
||
resp = client.post(f"/game/{game}/scene-objects/999999/dialogue-box-style", data='{"style": {}}', content_type="application/json")
|
||
assert resp.status_code == 404
|
||
|
||
|
||
def test_dialogue_box_excluded_from_collision_rules_payload(tmp_game_slug_cleanup):
|
||
slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup)
|
||
screens.add_scene_object(slug, screen_id, kind="dialogue_box")
|
||
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"] == "dialogue_box")
|
||
assert "collision_rules" not in box
|
||
assert "collision" not in box
|
||
assert box["dialogue_box_style"] == screens.DEFAULT_DIALOGUE_BOX_STYLE
|
||
|
||
|
||
def test_full_game_payload_exposes_quests_game_wide(tmp_game_slug_cleanup):
|
||
slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup)
|
||
db.create_quest(slug, "Trouver la clé")
|
||
payload = screens.full_game_payload(slug)
|
||
assert [q["title"] for q in payload["quests"]] == ["Trouver la clé"]
|
||
|
||
|
||
# ---------- Nom d'un personnage ("ℹ️ Informations") ----------
|
||
|
||
def test_scene_object_name_is_none_by_default(tmp_game_slug_cleanup):
|
||
slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup)
|
||
obj_id = screens.add_scene_object(slug, screen_id, kind="personnage")
|
||
obj = screens.get_scene_object(slug, obj_id)
|
||
assert obj["name"] is None
|
||
|
||
|
||
def test_set_scene_object_name_persists_and_trims(tmp_game_slug_cleanup):
|
||
slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup)
|
||
obj_id = screens.add_scene_object(slug, screen_id, kind="personnage")
|
||
screens.set_scene_object_name(slug, obj_id, " Le Sorcier ")
|
||
assert screens.get_scene_object(slug, obj_id)["name"] == "Le Sorcier"
|
||
|
||
|
||
def test_set_scene_object_name_blank_clears_it(tmp_game_slug_cleanup):
|
||
slug, screen_id = _create_jeu2d_game(tmp_game_slug_cleanup)
|
||
obj_id = screens.add_scene_object(slug, screen_id, kind="personnage")
|
||
screens.set_scene_object_name(slug, obj_id, "Le Sorcier")
|
||
screens.set_scene_object_name(slug, obj_id, " ")
|
||
assert screens.get_scene_object(slug, obj_id)["name"] is None
|
||
|
||
|
||
def test_scene_object_name_route_persists(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="personnage")
|
||
resp = client.post(f"/game/{slug}/scene-objects/{obj_id}/name", data='{"name": "Le Sorcier"}', content_type="application/json")
|
||
assert resp.status_code == 200
|
||
assert screens.get_scene_object(slug, obj_id)["name"] == "Le Sorcier"
|
||
|
||
|
||
def test_list_named_scene_object_names_is_alphabetical_deduped_and_includes_any_kind(tmp_game_slug_cleanup):
|
||
""""il peut sélectionner le nom du personnage qui parle" -> étendu à
|
||
N'IMPORTE QUEL objet de scène nommé (pas seulement un personnage),
|
||
voir screens/rendering/scene_object_names.py."""
|
||
slug = tmp_game_slug_cleanup(db.create_game("pytest_scene_object_names"))
|
||
scene_a = screens.create_screen(slug, "Scène A", kind="jeu_2d")
|
||
scene_b = screens.create_screen(slug, "Scène B", kind="jeu_2d")
|
||
zed_id = screens.add_scene_object(slug, scene_a, kind="personnage")
|
||
screens.set_scene_object_name(slug, zed_id, "Zed le forgeron")
|
||
anna_id = screens.add_scene_object(slug, scene_b, kind="personnage")
|
||
screens.set_scene_object_name(slug, anna_id, "Anna")
|
||
unnamed_id = screens.add_scene_object(slug, scene_a, kind="personnage") # sans nom, ignoré
|
||
decor_id = screens.add_scene_object(slug, scene_a, kind="decor") # nommé -> INCLUS (pas réservé au personnage)
|
||
screens.set_scene_object_name(slug, decor_id, "Un coffre")
|
||
dialogue_box_id = screens.add_scene_object(slug, scene_a, kind="dialogue_box") # widget d'UI, jamais un "qui parle"
|
||
screens.set_scene_object_name(slug, dialogue_box_id, "Boîte nommée par erreur")
|
||
|
||
assert screens.list_named_scene_object_names(slug) == ["Anna", "Un coffre", "Zed le forgeron"]
|