scopedvr-map-editor/tests/test_inspector.gd
slyphax 491a47d3f1 Editor-Bibliothek auf das Military-Desert-Pack umgestellt
- 423 Objekte in 19 deutschen Kategorien (Panzer, Radfahrzeuge, Flugzeuge, Gebaeude,
  Zelte, Tuerme, Mauern, Sperren, Kisten, Technik, Felsen, Boden, Decals ...);
  Gruppen "Bauwerke" und "Kisten & Barrieren" samt Test-Asset-Namenslisten entfernt
- LibraryBrowser: Gruppen/Namen aus dem Manifest (kategorie, name_de), unbekannte
  Kategorien bekommen automatisch eine eigene Gruppe
- Thumbs neu, library/ synchronisiert (inkl. neuer Kollision)
- Neue Identitaetsprobe: Kollision ALLER Objekte Editor = Client = Server
- Tests auf Pack-Objekte umgestellt

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-25 16:31:28 +01:00

254 lines
10 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

extends SceneTree
## Editor-Test (headless): Objekt-Inspektor — Karte t_bb0d11c9.
## Ausführen: godot --headless --path . --script res://tests/test_inspector.gd
## Exit 0 = alles grün.
##
## Instanziert die ECHTE Main-UI (wie test_gui_smoke) und prüft:
## T1 Kopf/Sub/Info füllen bei Einzel-Auswahl (Name, ID, Kategorie,
## „N von M“, Poly, Kollision, echtes Modell ja/nein).
## T2 Position TIPPEN → doc geändert + 3D-Marker live; EIN Undo-Schritt.
## T3 Position ZIEHEN (Probe-Drag wie Maus) → doc geändert; EIN Undo-Schritt.
## T4 Mehrfachauswahl: Drehung tippen → beide Objekte gleich; EIN Undo-Schritt.
## T5 Uneinheitliche Werte → „—“; Tippen gleicht ALLE an.
## T6 Größe FEST (t_e166e656): scale-Zeile + Reset-Knopf entfernt, Wert bleibt.
## T7 „Auf Boden setzen“ nutzt die Boden-Logik; EIN Undo-Schritt.
## T8 Keine Auswahl → Felder aus, Hinweis an.
var failures := 0
var checks := 0
var main
var insp
var glb_ok := false # GLB-Quelle verfügbar? (sonst wie test_glb_view umgebungsabhängig)
func _initialize() -> void:
_run_all.call_deferred()
func _check(cond: bool, label: String) -> void:
checks += 1
print("%s: %s" % ["ok " if cond else "FAIL", label])
if not cond:
failures += 1
func _undo_steps_to_clear() -> int:
## Wie viele Undo-Schritte nötig, bis die History leer ist (Baseline clear).
var n := 0
while main.history.can_undo() and n < 50:
main.call("_do_undo")
n += 1
return n
func _run_all() -> void:
var scene: PackedScene = load("res://scenes/main.tscn")
_check(scene != null, "scenes/main.tscn laedt")
if scene == null:
_finish()
return
main = scene.instantiate()
root.add_child(main)
await process_frame
await process_frame
insp = main.get("inspector")
_check(insp != null, "Inspektor ist in main eingebaut")
if insp == null:
_finish()
return
# Zwei Objekte ins Doc: barrel_001 (echtes GLB) + box_001 (Platzhalter).
(main.doc["objects"] as Array).append({
"id": "barrel_001", "pos": [4.0, 0.0, 4.0], "scale": 1.0, "rot_y_deg": 0.0})
(main.doc["objects"] as Array).append({
"id": "box_001", "pos": [-6.0, 0.0, 2.0], "scale": 2.0, "rot_y_deg": 10.0})
main.call("_after_structure_change")
main.history.clear()
await process_frame
glb_ok = load("res://src/viewport/glb_mesh_store.gd").mesh_fuer("barrel_001") != null
# await PFLICHT: T4/T5/T8 enthalten process_frame-Awaits — ohne await
# lief _finish() sofort durch und die Mehrfach-Checks liefen NIEMALS
# (Suite zeigte „ALL OK: 33 checks" und skippte still T4/T5/T8).
await _t1_einzelauswahl()
await _t2_tippen()
await _t3_ziehen()
await _t4_mehrfach_drehung()
await _t5_mixed_angleichen()
await _t6_groesse()
await _t7_auf_boden()
await _t8_leer()
_finish()
func _select(idx: int) -> void:
# select_object ist synchron; das Panel füllt im selben Aufruf (refresh).
main.interaction.call("select_object", idx)
func _sel_obj(idx: int) -> Dictionary:
return (main.doc["objects"] as Array)[idx]
func _t1_einzelauswahl() -> void:
_select(0)
var head: String = str(insp.get("_head").text)
var sub: String = str(insp.get("_sub").text)
var info: String = str(insp.get("_info").text)
_check(not head.is_empty() and head != "Objekt-Inspektor", "T1 Kopf zeigt Objekt-Namen")
_check(sub.contains("ID: barrel_001"), "T1 Sub zeigt ID")
_check(sub.contains("Kategorie: kisten"), "T1 Sub zeigt Kategorie")
_check(sub.contains("1 von 2 Objekten markiert"), "T1 Sub zeigt 'N von M markiert'")
_check(sub.contains("Poly: 184"), "T1 Sub zeigt Poly-Zahl")
_check(info.contains("Kollision:"), "T1 Info zeigt Kollisionsform")
# „Echtes Modell ja“ braucht die GLB-Quellen (Geschwister-Ordner) — fehlen
# sie im Workspace-Klon, zeigt das Panel korrekt „nein“ (wie test_glb_view
# umgebungsabhängig; der „nein“-Pfad wird über box_001 geprüft).
if glb_ok:
_check(info.contains("Echtes Modell: ja"), "T1 echtes Modell ja (barrel_001 mit GLB)")
else:
print("SKIP: T1 echtes-Modell-ja (keine GLB-Quelle im Workspace — Platzhalter-„nein“ ist geprüft)")
_check(str(insp.widget("pos_x").edit.text).contains("4.00"), "T1 pos_x zeigt 4.00")
_check(str(insp.widget("rot_y").edit.text).contains("0.0"), "T1 rot_y zeigt 0.0")
# Auswahl-Beweis per Klick im Viewport-Kontext: Panel ist sichtbar.
_check(insp.get("_grid").visible, "T1 Felder sichtbar bei Auswahl")
# Fix Vorbestand: das alte „Auswahl“-Feld füllt sich jetzt auch bei
# select_object (Vorher stand dort dauerhaft „Nichts ausgewählt.“).
_check(str(main.get("sel_label").text).contains("Fass"), "T1 altes Auswahl-Feld füllt bei 3D-Klick-Weg")
func _t2_tippen() -> void:
_select(0)
var w = insp.widget("pos_x")
w.edit.text = "12.5"
w.edit.text_submitted.emit("12.5")
var px: float = float(_sel_obj(0).get("pos", [0, 0, 0])[0])
_check(absf(px - 12.5) < 0.0001, "T2 Tippen ändert doc-Position auf 12.5")
var marker = main.view_builder.object_markers[0]
_check(marker != null and absf(marker.position.x - 12.5) < 0.0001,
"T2 3D-Marker folgt live (position.x = 12.5)")
_check(_undo_steps_to_clear() == 1, "T2 Tippen = GENAU EIN Undo-Schritt")
_check(absf(float(_sel_obj(0).get("pos", [0, 0, 0])[0]) - 4.0) < 0.0001,
"T2 Undo stellt 4.0 wieder her")
func _t3_ziehen() -> void:
_select(0)
var base: float = float(_sel_obj(0).get("pos", [0, 0, 0])[2])
var w = insp.widget("pos_z")
w.probe_drag(40.0) # 40 px × 0,05 m/px = +2,0 m
var now: float = float(_sel_obj(0).get("pos", [0, 0, 0])[2])
_check(absf(now - (base + 2.0)) < 0.0001, "T3 Ziehen am Label ändert Z um +2,0 m")
var marker = main.view_builder.object_markers[0]
_check(marker != null and absf(marker.position.z - now) < 0.0001,
"T3 3D-Marker folgt live beim Ziehen")
_check(_undo_steps_to_clear() == 1, "T3 Ziehen (Loslassen) = GENAU EIN Undo-Schritt")
func _t4_mehrfach_drehung() -> void:
main.interaction.call("select_many", [0, 1])
await process_frame
var w = insp.widget("rot_y")
w.edit.text = "45"
w.edit.text_submitted.emit("45")
var r0: float = float(_sel_obj(0).get("rot_y_deg", 0.0))
var r1: float = float(_sel_obj(1).get("rot_y_deg", 0.0))
_check(absf(r0 - 45.0) < 0.0001 and absf(r1 - 45.0) < 0.0001,
"T4 Mehrfachauswahl: beide Drehungen = 45°")
_check(_undo_steps_to_clear() == 1, "T4 Mehrfach-Änderung = GENAU EIN Undo-Schritt")
_check(absf(float(_sel_obj(0).get("rot_y_deg", 0.0))) < 0.0001
and absf(float(_sel_obj(1).get("rot_y_deg", 0.0)) - 10.0) < 0.0001,
"T4 Undo stellt 0°/10° wieder her")
func _t5_mixed_angleichen() -> void:
main.interaction.call("select_many", [0, 1])
await process_frame
var w = insp.widget("pos_x")
_check(str(w.edit.text) == "—", "T5 ungleiche X-Werte zeigen „—“")
# Karte t_bceb0965: Position bleibt RELATIV — der getippte Wert gilt für
# den Anker (erstes markiertes Objekt), die anderen wandern mit demselben
# Delta mit (Abstände bleiben). Vorher: x0=4.0, x1=-6.0 → Abstand 10.0.
var x0_vor: float = float(_sel_obj(0).get("pos", [0, 0, 0])[0])
var x1_vor: float = float(_sel_obj(1).get("pos", [0, 0, 0])[0])
w.edit.text = "3"
w.edit.text_submitted.emit("3")
var x0: float = float(_sel_obj(0).get("pos", [0, 0, 0])[0])
var x1: float = float(_sel_obj(1).get("pos", [0, 0, 0])[0])
_check(absf(x0 - 3.0) < 0.0001, "T5 Anker (Objekt 0) landet auf 3.0")
_check(absf((x1 - x0) - (x1_vor - x0_vor)) < 0.0001,
"T5 Relativ: Abstand zwischen beiden bleibt (%.1f m)" % absf(x1 - x0))
_check(_undo_steps_to_clear() == 1, "T5 Relativ-Move = GENAU EIN Undo-Schritt")
_check(absf(float(_sel_obj(0).get("pos", [0, 0, 0])[0]) - x0_vor) < 0.0001
and absf(float(_sel_obj(1).get("pos", [0, 0, 0])[0]) - x1_vor) < 0.0001,
"T5 Undo stellt beide Ausgangs-X wieder her")
# Drehung/Größe bleiben ABSOLUT bei Mehrfachauswahl (Karten-Auftrag):
main.interaction.call("select_many", [0, 1])
await process_frame
var wr = insp.widget("rot_y")
wr.edit.text = "45"
wr.edit.text_submitted.emit("45")
var r1: float = float(_sel_obj(1).get("rot_y_deg", 0.0))
_check(absf(r1 - 45.0) < 0.0001, "T5 Drehung bei Mehrfachauswahl = absolut (beide 45°)")
_undo_steps_to_clear()
func _t6_groesse() -> void:
# Karte t_e166e656: Scale ist FEST — die Zeile ist aus dem Inspektor
# entfernt (Sly-Wunsch „Realismus“). Kein Widget, kein Reset-Knopf, der
# gespeicherte Wert bleibt unangetastet.
_select(0)
_check(insp.widget("scale") == null, "T6 scale-Zeile ist ENTFERNT (kein Widget)")
_check(insp.get("_reset_btn") == null, "T6 Reset-Knopf entfernt")
var vor: float = float(_sel_obj(0).get("scale", 1.0))
_check(absf(vor - 1.0) < 0.0001, "T6 Objekt behält sein scale (1.0)")
# Ausgewählt lassen und prüfen: der Inspektor zeigt den lesenden Hinweis.
insp.call("refresh")
_check(insp.get("_ground_btn") != null and insp.get("_ground_btn").visible,
"T6 Boden-Knopf bleibt bedienbar (Größe-Sektion zeigt nur Hinweis)")
func _t7_auf_boden() -> void:
_select(0)
var w = insp.widget("pos_y")
w.edit.text = "5"
w.edit.text_submitted.emit("5")
_check(absf(float(_sel_obj(0).get("pos", [0, 0, 0])[1]) - 5.0) < 0.0001,
"T7 Höhe getippt: y = 5")
insp.get("_ground_btn").pressed.emit()
var floor_y: float = float(_sel_obj(0).get("pos", [0, 0, 0])[1])
var expected: float = SvmapDocument.terrain_height_at(main.doc, 4.0, 4.0)
_check(absf(floor_y - expected) < 0.01, "T7 'Auf Boden setzen' setzt auf Terrain-Höhe")
# Zwei Schritte in der History: Tippen (0→5) + Boden (5→Boden). Jedes EIN Schritt.
_check(main.history.can_undo(), "T7 Boden-Setzen hat Undo-Eintrag")
main.call("_do_undo")
_check(absf(float(_sel_obj(0).get("pos", [0, 0, 0])[1]) - 5.0) < 0.0001,
"T7 1. Undo holt das Boden-Setzen zurück (y = 5)")
main.call("_do_undo")
_check(absf(float(_sel_obj(0).get("pos", [0, 0, 0])[1]) - 0.0) < 0.0001,
"T7 2. Undo holt das Tippen zurück (y = 0, Ausgangswert)")
_check(not main.history.can_undo(), "T7 genau 2 Undo-Schritte, dann History leer")
# Y unter Boden tippen wird geklemmt (nichts begraben).
w.edit.text = "-3"
w.edit.text_submitted.emit("-3")
_check(absf(float(_sel_obj(0).get("pos", [0, 0, 0])[1]) - expected) < 0.01,
"T7 y unter dem Boden wird auf Terrain-Höhe geklemmt")
_undo_steps_to_clear()
func _t8_leer() -> void:
main.interaction.call("clear_selection")
await process_frame
_check(insp.get("_hint").visible, "T8 ohne Auswahl: Hinweis sichtbar")
_check(not insp.get("_grid").visible, "T8 ohne Auswahl: Felder aus")
_check(not insp.get("_ground_btn").visible, "T8 ohne Auswahl: Knöpfe aus")
func _finish() -> void:
if failures == 0:
print("ALL OK: %d checks" % checks)
quit(0)
else:
print("FAILED: %d/%d checks" % [failures, checks])
quit(1)