253 lines
10 KiB
GDScript
253 lines
10 KiB
GDScript
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://core/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://core/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")
|
||
# Das alte „Auswahl“-Feld ist weg (25.10.): der Inspektor ist die einzige Anzeige.
|
||
_check(main.get("sel_label") == null, "T1 kein altes Auswahl-Feld mehr (Tab Auswahl entfernt)")
|
||
|
||
|
||
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)
|