"""Ajout d'un objet de scène en AJAX (voir routes/scenes/scene_object_add.py, addSceneObject() dans static/js/scenes/scene-editor.js) : la réponse JSON (posée quand l'appelant envoie X-Requested-With: fetch) remplace le rechargement complet de page — sans ça, poser un objet "vidait" un instant l'éditeur de collision/quêtes (tout l'état JS repartait de zéro à chaque ajout).""" import io import struct import auth import screens def _png_bytes(width, height): ihdr = struct.pack(">II", width, height) + b"\x08\x02\x00\x00\x00" return b"\x89PNG\r\n\x1a\n" + b"\x00\x00\x00\x0d" + b"IHDR" + ihdr + b"\x00\x00\x00\x00" def _create_jeu2d_game(client, tmp_game_slug_cleanup, name="pytest_add_ajax"): resp = client.post("/games/new", data={"name": name}, follow_redirects=False) slug = tmp_game_slug_cleanup(resp.headers["Location"].rstrip("/").split("/")[-1]) screen_id = screens.create_screen(slug, "Scène 1", kind="jeu_2d") return slug, screen_id def _add_ajax(client, slug, screen_id, **data): return client.post( f"/game/{slug}/screens/{screen_id}/scene-objects/add", data=data, headers={"X-Requested-With": "fetch"}, ) def test_ajax_add_returns_json_instead_of_redirecting(client, tmp_game_slug_cleanup): slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup) resp = _add_ajax(client, slug, screen_id, kind="personnage") assert resp.status_code == 200 body = resp.get_json() assert body["kind"] == "personnage" assert "rendered_html" in body and body["rendered_html"] assert body["animations"] def test_ajax_add_with_drop_position_places_the_object_exactly_there(client, tmp_game_slug_cleanup): """Vrai glisser-déposer (demande explicite : "n'importe où sur la scène") — x/y postés remplacent le défaut 100/100.""" slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup) resp = _add_ajax(client, slug, screen_id, kind="decor", x="300", y="220") assert resp.status_code == 200 body = resp.get_json() assert body["x"] == 300 assert body["y"] == 220 def test_ajax_add_with_drop_position_near_the_edge_is_clamped_into_the_camera(client, tmp_game_slug_cleanup): """Un dépôt trop proche d'un bord ne doit jamais laisser l'objet déborder du cadre visible par la caméra une fois sa taille réelle connue (même esprit que ai/tools.py::_clamp_to_camera pour l'IA).""" slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup) resp = _add_ajax(client, slug, screen_id, kind="decor", x="999999", y="999999") assert resp.status_code == 200 body = resp.get_json() assert body["x"] <= 960 - body["width"] assert body["y"] <= 540 - body["height"] # ---------- Fond importé ("Mes assets") posé à sa taille RÉELLE — bug : # "je ne peux pas poser un objet hors du champ de la caméra quand # j'utilise un fond de scène importé" (sans ça, l'image retombait # toujours sur 128×128, trop petite pour agrandir le "monde" au-delà de # la caméra nominale 960×540) ---------- def test_ajax_add_fond_ignores_the_drop_position_and_stays_anchored_at_the_origin(client, tmp_game_slug_cleanup): """Bug corrigé ("décalage") : le vrai glisser-déposer envoie une position EXACTE même pour un fond — sans ce garde-fou, un fond déposé ailleurs qu'au coin haut-gauche restait décalé, laissant un bandeau de canevas vide visible entre le bord de la scène et l'image.""" slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup) resp = _add_ajax(client, slug, screen_id, kind="fond", x="450", y="300") assert resp.status_code == 200 body = resp.get_json() assert (body["x"], body["y"]) == (0, 0) def test_ajax_add_fond_from_user_asset_uses_the_real_image_dimensions(client, tmp_game_slug_cleanup): slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup) with client.session_transaction() as sess: user_id = sess["user_id"] asset_id, _filename = auth.save_user_asset( user_id, _png_bytes(1920, 1080), ".png", original_name="ciel.png", source="upload", scene_kind="fond", ) resp = _add_ajax(client, slug, screen_id, kind="fond", asset_id=str(asset_id)) assert resp.status_code == 200 body = resp.get_json() assert body["width"] == 1920 assert body["height"] == 1080 # ---------- Redimensionnement manuel d'un "fond" — la route /geometry # doit renvoyer le "monde" recalculé pour que scene-editor.js puisse # rafraîchir SCENE_WIDTH/SCENE_HEIGHT tout de suite (même bug que # ci-dessus, mais via le redimensionnement à la souris plutôt qu'un # import — sans ça, l'agrandissement n'était visible qu'après un # rechargement complet de la page) ---------- def test_geometry_route_returns_the_recomputed_world_size_for_an_enlarged_fond(client, tmp_game_slug_cleanup): slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup) resp = _add_ajax(client, slug, screen_id, kind="fond") obj_id = resp.get_json()["id"] geo = client.post( f"/game/{slug}/scene-objects/{obj_id}/geometry", json={"x": 0, "y": 0, "width": 2000, "height": 1200}, ) assert geo.status_code == 200 body = geo.get_json() assert body["world_width"] >= 2000 assert body["world_height"] >= 1200 def test_non_ajax_add_still_redirects(client, tmp_game_slug_cleanup): """Secours pour tout appelant non-JS (aucun connu, mais gratuit à garder) — comportement inchangé : redirection classique, sans le header AJAX.""" slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup) resp = client.post(f"/game/{slug}/screens/{screen_id}/scene-objects/add", data={"kind": "personnage"}, follow_redirects=False) assert resp.status_code == 302 assert "selected=" in resp.headers["Location"]