"""Son — Phase 6 du plan d'extension du moteur : musique de fond par écran (en boucle, suit le cycle de vie de l'écran) et action ponctuelle "Jouer un son" (effet sonore, non bouclé). Comme le reste du graphe de logique côté client, la lecture audio RÉELLE n'est pas testable sans navigateur (voir play_js_bundle() — on vérifie que la logique existe dans le JS livré et que les données persistent, pas qu'elles jouent un son).""" import json import re from typing import Any from conftest import play_js_bundle # type: ignore[import-not-found] # resolu par pytest, pas mypy from flask.testing import FlaskClient import screens from tests.conftest import not_none def _create_screen(client: FlaskClient, slug: str, name: str = "Accueil") -> Any: resp = client.post(f"/game/{slug}/screens/new", data={"name": name}, follow_redirects=False) return int(not_none(re.search(r"/screens/(\d+)/edit", resp.headers["Location"])).group(1)) def _add_flow_node(client: FlaskClient, slug: str, screen_id: int, **fields: Any) -> Any: resp = client.post( f"/game/{slug}/screens/{screen_id}/flow/nodes/add", data=json.dumps(fields), content_type="application/json", ) assert resp.status_code == 200 return resp.get_json()["id"] def test_screen_background_music_persists(client: FlaskClient, game: str) -> None: screen_id = _create_screen(client, game) resp = client.post( f"/game/{game}/screens/{screen_id}/background-music", data=json.dumps({"url": "https://example.com/song.mp3"}), content_type="application/json", ) assert resp.status_code == 200 screen = not_none(screens.get_screen(game, screen_id)) assert screen["background_music_url"] == "https://example.com/song.mp3" def test_screen_background_music_empty_string_clears_it(client: FlaskClient, game: str) -> None: screen_id = _create_screen(client, game) client.post( f"/game/{game}/screens/{screen_id}/background-music", data=json.dumps({"url": "https://example.com/song.mp3"}), content_type="application/json", ) client.post( f"/game/{game}/screens/{screen_id}/background-music", data=json.dumps({"url": ""}), content_type="application/json", ) screen = not_none(screens.get_screen(game, screen_id)) assert screen["background_music_url"] is None def test_play_payload_exposes_background_music_per_screen(client: FlaskClient, game: str) -> None: screen_id = _create_screen(client, game) client.post( f"/game/{game}/screens/{screen_id}/background-music", data=json.dumps({"url": "https://example.com/song.mp3"}), content_type="application/json", ) html = client.get(f"/game/{game}/runtime-payload").get_json() screen_payload = next(s for s in html["screens"] if s["id"] == screen_id) assert screen_payload["background_music_url"] == "https://example.com/song.mp3" def test_sound_action_node_persists_url(client: FlaskClient, game: str) -> None: screen_id = _create_screen(client, game) node_id = _add_flow_node( client, game, screen_id, node_type="action", action_type="jouer_son", data_value="https://example.com/effect.wav", ) node = not_none(screens.get_flow_node(game, node_id)) assert node["action_type"] == "jouer_son" assert node["data_value"] == "https://example.com/effect.wav" def test_play_page_exposes_sound_runtime(client: FlaskClient, game: str) -> None: js = play_js_bundle() assert "runScreenBackgroundMusic" in js assert "'jouer_son'" in js assert "new Audio" in js def test_scene_editor_exposes_sound_action(client: FlaskClient, game: str) -> None: """L'UI de réglage de la musique de fond par écran (screen-bg-music-url) et le formulaire générique de nœud d'action (nf-action-sound-fields) n'existaient que dans l'éditeur document, retiré — l'action "Jouer un son" reste disponible pour jeu_2d via l'assistant "+ Action" de l'onglet Collision (voir screens/scenes/flow_palette.py) ; le réglage de musique de fond par écran (screen_set_background_music.py) reste fonctionnel côté backend/API (voir les tests ci-dessus), même sans UI dédiée dans le nouvel éditeur.""" screen_id = _create_screen(client, game) html = client.get(f"/game/{game}/screens/{screen_id}/edit").get_data(as_text=True) assert "jouer_son" in html