Fix éditeur de scène 2D : décalage visuel des objets + crash Timeline
Build and deploy / test-python (push) Successful in 1m40s
Build and deploy / test-js (push) Successful in 7s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped

Deux bugs remontés en test manuel sur l'éditeur de scène (Phase A,
commit f0070fa) :

1. Décalage visuel à l'ajout d'un objet de scène (personnage/décor) :
   render_scene_object.py positionne son <img> en absolu (left/top/
   width/height en px), pensé pour être un enfant DIRECT de
   .playScreen.playScene en mode jouable. Dans scene_edit.html, la même
   balise est nichée dans .canvasElementInner, lui-même déjà positionné
   par .canvasElement — l'image se repositionnait donc EN PLUS depuis
   #canvas (son ancêtre positionné le plus proche), en double du
   décalage déjà appliqué par le conteneur. Corrigé par une règle CSS
   scoped à l'éditeur (.canvasElementInner > img) qui neutralise le
   positionnement propre de l'image et la fait simplement remplir son
   conteneur.

2. "FOREIGN KEY constraint failed" à l'ajout d'un clip de Timeline sur
   un objet de scène : _animation_clips.element_id portait une VRAIE
   contrainte FK vers _screen_elements depuis la création de la table.
   animation-timeline.js est réutilisé TEL QUEL entre les deux éditeurs
   (voir le plan "Fondations d'une plateforme multi-éditeurs") et
   n'opère aucune distinction — pour un jeu jeu_2d, element_id désigne
   en réalité un id de _scene_objects, absent de _screen_elements, d'où
   l'échec de l'INSERT sous PRAGMA foreign_keys=ON. ensure_animation_schema
   reconstruit maintenant la table (une fois, migration automatique à la
   volée comme le reste du schéma) sans cette contrainte FK — même
   patron que trigger_element_id/cond_element_a dans ensure_flow_schema.py.
   Comme la suppression en cascade reposait jusqu'ici sur cette FK, un
   nettoyage manuel des clips a été ajouté à la suppression d'un élément
   (delete_element.py) et d'un objet de scène (delete_scene_object.py).

Nouveaux tests (tests/test_scene_edit_view.py, +4 cas) : ajout d'un
clip de Timeline sur un objet de scène via la route, nettoyage des
clips à la suppression d'un objet de scène et d'un élément DOM. Suite
complète : 312 tests passent (aucune régression).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-08-31 10:22:58 +02:00
co-authored by Claude Sonnet 5
parent f0070faced
commit 43abfc9b93
5 changed files with 115 additions and 20 deletions
+39
View File
@@ -131,3 +131,42 @@ def test_screen_edit_js_bundle_contains_scene_flow_helpers():
js = screen_edit_js_bundle()
assert "FLOW_TARGETS_OBJECTS" in js
assert "toggleFlowObjectPropertyValue" in js
def test_animation_clip_add_route_accepts_a_scene_object(client, tmp_game_slug_cleanup):
"""Bug remonté par l'utilisateur : _animation_clips.element_id portait
une VRAIE contrainte FK vers _screen_elements — poser un clip de
Timeline sur un objet de scène (_scene_objects, un id space distinct)
faisait échouer l'insertion avec "FOREIGN KEY constraint failed"."""
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
obj_id = screens.add_scene_object(slug, screen_id)
resp = client.post(
f"/game/{slug}/screens/{screen_id}/animations/clips/add",
data=json.dumps({"element_id": obj_id, "kind": "animate_css", "animate_name": "fadeIn"}),
content_type="application/json",
)
assert resp.status_code == 200
clip = resp.get_json()
assert clip["element_id"] == obj_id
def test_deleting_scene_object_cleans_up_its_animation_clips(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
obj_id = screens.add_scene_object(slug, screen_id)
screens.add_animation_clip(slug, screen_id, obj_id, kind="animate_css", animate_name="fadeIn")
screens.delete_scene_object(slug, obj_id)
assert screens.list_animation_clips(slug, screen_id) == []
def test_deleting_element_cleans_up_its_animation_clips(client, game):
resp = client.post(f"/game/{game}/screens/new", data={"name": "Accueil"}, follow_redirects=False)
screen_id = int(re.search(r"/screens/(\d+)/edit", resp.headers["Location"]).group(1))
el_resp = client.post(
f"/game/{game}/screens/{screen_id}/elements/add",
data={"widget": "texte"},
follow_redirects=False,
)
element_id = int(re.search(r"selected=(\d+)", el_resp.headers["Location"]).group(1))
screens.add_animation_clip(game, screen_id, element_id, kind="animate_css", animate_name="fadeIn")
screens.delete_element(game, element_id)
assert screens.list_animation_clips(game, screen_id) == []