Files
Forge-Engine/tests/test_sound.py
T
williamandClaude Sonnet 5 76331f9285
Build and deploy / test-python (push) Successful in 1m27s
Build and deploy / test-js (push) Successful in 6s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Phase 6 : son (musique de fond par écran + action "Jouer un son")
Ajoute deux mécanismes distincts, à la demande de l'utilisateur qui a
précisé qu'un son doit pouvoir être attaché à une SCÈNE (pas seulement
joué ponctuellement par une action de flow, comme prévu initialement) :

- Musique de fond par écran (nouveau champ background_music_url sur
  _screens, ALTER TABLE nullable) : réglée dans le panneau gauche de
  l'éditeur (URL ou envoi de fichier, réutilise la route d'upload
  générique existante), enregistrée en AJAX au même patron que le format
  d'aperçu (screen_set_aspect.py). Démarrée en boucle à l'affichage de
  l'écran et arrêtée au changement d'écran (runScreenBackgroundMusic(),
  appelée depuis showScreen() dans static/js/play/screens.js) — un seul
  Audio actif à la fois, jamais cumulé avec une musique restée d'un écran
  précédent.

- Action de flow "jouer_son" (aux côtés des actions existantes) : effet
  sonore ponctuel, non bouclé, déclenchable sur n'importe quel nœud
  Déclencheur. Réutilise data_value (déjà un champ texte générique sur
  le nœud action, comme pour "attendre") plutôt qu'une nouvelle colonne
  dédiée — fire-and-forget côté client (runActionNode), ne bloque jamais
  la suite du graphe.

Les deux réutilisent le même mécanisme d'upload de fichier déjà en place
ailleurs dans l'éditeur (ex. source d'une vidéo), sans nouvelle route.

Ceci complète les 6 phases du plan d'extension du moteur (état par
joueur, hasard/maths, clavier/minuteur, ajout de ligne, position/
collision, son) : Forge Engine peut désormais couvrir des jeux bien
au-delà du narratif/puzzle/quiz (action, arcade, jeux à contrainte de
temps).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-30 17:56:49 +02:00

97 lines
3.6 KiB
Python

"""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 conftest import play_js_bundle, screen_edit_js_bundle
import screens
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_flow_node(client, slug, screen_id, **fields):
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, game):
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 = 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, game):
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 = screens.get_screen(game, screen_id)
assert screen["background_music_url"] is None
def test_play_payload_exposes_background_music_per_screen(client, game):
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, game):
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 = 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, game):
js = play_js_bundle()
assert "runScreenBackgroundMusic" in js
assert "'jouer_son'" in js
assert "new Audio" in js
def test_screen_edit_exposes_sound_editors(client, game):
screen_id = _create_screen(client, game)
html = client.get(f"/game/{game}/screens/{screen_id}/edit").get_data(as_text=True)
assert "screen-bg-music-url" in html
assert "nf-action-sound-fields" in html
assert "nf-action-sound-url" in html
js = screen_edit_js_bundle()
assert "jouer_son" in js
assert "bindScreenBackgroundMusicField" in js