264 lines
10 KiB
GDScript
264 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: Klemmung 0,05–20 + Marker-Rescale (echtes Modell) + Zurücksetzen.
|
||
## 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) + crate_wood (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": "crate_wood", "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
|
||
|
||
_t1_einzelauswahl()
|
||
_t2_tippen()
|
||
_t3_ziehen()
|
||
_t4_mehrfach_drehung()
|
||
_t5_mixed_angleichen()
|
||
_t6_groesse()
|
||
_t7_auf_boden()
|
||
_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: props"), "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 crate_wood 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("Barrel"), "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 „—“")
|
||
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 and absf(x1 - 3.0) < 0.0001,
|
||
"T5 Tippen gleicht beide X auf 3.0 an")
|
||
_check(_undo_steps_to_clear() == 1, "T5 Angleichen = GENAU EIN Undo-Schritt")
|
||
|
||
|
||
func _t6_groesse() -> void:
|
||
_select(0)
|
||
var w = insp.widget("scale")
|
||
w.edit.text = "100"
|
||
w.edit.text_submitted.emit("100")
|
||
_check(absf(float(_sel_obj(0).get("scale", 1.0)) - 20.0) < 0.0001,
|
||
"T6 Größe auf 100 geklemmt auf 20")
|
||
var marker = main.view_builder.object_markers[0]
|
||
var mi: MeshInstance3D = marker.get_child(0) as MeshInstance3D
|
||
if glb_ok:
|
||
_check(mi != null and mi.scale.length() > 19.9, "T6 Marker-Mesh skaliert live (echtes Modell)")
|
||
_check(float(marker.get_meta("half_height", 0.0)) > 0.0
|
||
and absf(float(marker.get_meta("pick_radius", 0.0))) > 0.0,
|
||
"T6 Pick-Metas nach Rescale gesetzt")
|
||
_check(_undo_steps_to_clear() == 1, "T6 Größen-Tippen = GENAU EIN Undo-Schritt")
|
||
# Zurücksetzen-Knopf (Undo hat die Auswahl geleert → neu anwählen).
|
||
_select(0)
|
||
w = insp.widget("scale")
|
||
w.edit.text = "3"
|
||
w.edit.text_submitted.emit("3")
|
||
_check(absf(float(_sel_obj(0).get("scale", 1.0)) - 3.0) < 0.0001, "T6 Größe 3.0 gesetzt")
|
||
insp.get("_reset_btn").pressed.emit()
|
||
_check(absf(float(_sel_obj(0).get("scale", 1.0)) - 1.0) < 0.0001,
|
||
"T6 Zurücksetzen stellt 1.0 her")
|
||
# (Vorher: Tippen 3.0 = 1 Schritt, Zurücksetzen = 1 Schritt → EIN Undo
|
||
# holt das Zurücksetzen zurück.)
|
||
_check(main.history.can_undo(), "T6 Zurücksetzen erzeugt Undo-Eintrag")
|
||
main.call("_do_undo")
|
||
_check(absf(float(_sel_obj(0).get("scale", 1.0)) - 3.0) < 0.0001,
|
||
"T6 EIN Undo-Schritt holt 3.0 zurück (kein Frame-/Tasten-Splitting)")
|
||
_undo_steps_to_clear()
|
||
# Platzhalter-Pfad (crate_wood, scale 2.0 → 1.0)
|
||
main.interaction.call("select_object", 1)
|
||
await process_frame
|
||
var w2 = insp.widget("scale")
|
||
w2.edit.text = "1"
|
||
w2.edit.text_submitted.emit("1")
|
||
_check(absf(float(_sel_obj(1).get("scale", 1.0)) - 1.0) < 0.0001,
|
||
"T6 Platzhalter-Größe geändert")
|
||
var marker2 = main.view_builder.object_markers[1]
|
||
var mi2: MeshInstance3D = marker2.get_child(0) as MeshInstance3D
|
||
_check(mi2 != null and absf(mi2.scale.x - 0.5) < 0.01,
|
||
"T6 Platzhalter-Mesh relativ reskaliert (2.0 → 1.0 = Faktor 0.5)")
|
||
_undo_steps_to_clear()
|
||
|
||
|
||
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)
|