scopedvr-map-editor/tests/test_ui_layout.gd
slyphax 3f0d0f3f33 Map-Editor: UI aufgeräumt, Auswahl-Sync, Drag&Drop, Bauen (Wände bündig, Geschosshöhe, große Dächer)
- Tabs Auswahl/Prüfen/Test-Start/Einstellungen/Symmetrie weg; Spawn-Zonen, Test-Start, Symmetrie im Map-Tab; Einstellungen-Fenster (Gitter, Höhenlinien, Symmetrie-Modus, UI-Größe, Raster-Snap, Gizmo-Schritt, Karte prüfen)
- Bauen-Tab nur Bauteile; Objekt-Werkzeuge entfernt
- Klick in der Map-Inhalt-Liste wählt in der Szene aus (SELECT_MULTI feuert nur multi_selected)
- Objekt-Kachel per Ziehen in die Szene setzen
- Wände bündig in der Zelle, gleiche Geschosshöhe mit/ohne Decke, Dächer/Giebelwände 4 m und 6 m

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 20:32:32 +01:00

209 lines
No EOL
9.6 KiB
GDScript

extends SceneTree
## test_ui_layout (Karte t_eaccbfed, U1 UI-Fundament): prüft das Unity-artige
## Layout, das AUSSCHLIESSLICH aus der Registry (src/ui/panels/registry.gd)
## gebaut wird.
##
## Geprüft wird:
## 1. Alle Registry-Bereiche existieren GENAU EINMAL im Baum (kein Doppel,
## kein Verwaister) und als Tab mit dem Registry-Titel erreichbar sind.
## 2. Die Tab-Gruppen stimmen: rechts = 5 Tabs, links = 1, unten = 2.
## 3. Assets-Bereich unten vorhanden (Objekt-Bibliothek mit Suche + Kacheln).
## 4. Theme gesetzt (theme/editor_theme.tres als einzige Farbquelle).
## 5. Keine doppelten Node-Namen im UI-Subbaum.
## 6. main.gd hält die bekannten Werkzeug-/Test-Referenzen (Vertrag).
const UIScaleStepDefault := 0 # (Lesbarkeit; Stufen sind nicht Teil dieser Suite)
var _fails := 0
var _oks := 0
func _check(cond: bool, what: String) -> void:
if cond:
_oks += 1
print(" ok: ", what)
else:
_fails += 1
print(" FAIL: ", what)
func _count_named(node: Node, wanted: String) -> int:
var n := 1 if str(node.name) == wanted and not (node is BaseButton) else 0
for c in node.get_children():
n += _count_named(c, wanted)
return n
func _init() -> void:
print("Testgruppe: Layout aus der Registry")
var scene: PackedScene = load("res://scenes/main.tscn")
var main: Control = scene.instantiate()
root.add_child(main)
await process_frame
await process_frame
await process_frame
# --- 1b. GRÖSSEN-GEGENPROBE (Operator-Nachbesserung 20.10.) ----------------
# Sly-Befund „Was ist das? Damit kann man nicht mehr arbeiten!" — Ursache war
# reine FLÄCHE, nicht Aufbau: die Werkzeugleiste erbte von EditorPanel
# (VBoxContainer) und wurde dadurch selbst senkrecht → ~11 Knöpfe untereinander,
# das 3D-Fenster auf einen Briefmarken-Streifen gedrückt. Aufbau-Tests sagen
# dazu nichts — deshalb hier echte Größen.
# WICHTIG (Lektion 20.10.): im headless-Testlauf ist das Wurzelfenster nur
# 64 px hoch — Prozentwerte gegen main.size.y sind deshalb Unsinn. Geprüft
# wird ABSOLUT, und zwar die Größe, die den Fehler ausmachte.
# main ist als Control typisiert — Skript-Eigenschaften über get() lesen
# (sonst Parse-Fehler „Invalid access to property"). Namen mit probe_*,
# damit sie nicht mit Variablen der übrigen Suite kollidieren.
var probe_top: Control = main.get("ui_top_bar")
_check(probe_top != null, "Größenprobe: Top-Bar vorhanden")
if probe_top != null:
_check(probe_top.size.y <= 80.0,
"Leiste ist EINE Zeile (Höhe %.0f px, Grenze 80)" % probe_top.size.y)
var probe_center: Control = main.find_child("MainSplit", true, false)
_check(probe_center != null, "Größenprobe: Mittelbereich (MainSplit) vorhanden")
if probe_center != null:
_check(probe_center.size.y >= 120.0,
"Mittelbereich hat echte Höhe (%.0f px, min 120)" % probe_center.size.y)
# --- 1. Registry-Tabelle ist die Autorität --------------------------------
var registry_script: GDScript = load("res://src/ui/panels/registry.gd")
var ids: Array = []
for entry in registry_script.PANELS:
ids.append(str(entry["id"]))
_check(ids.size() == registry_script.PANELS.size(), "Registry: IDs eindeutig (keine Duplikate in der Tabelle)")
_check(not ids.is_empty(), "Registry: mindestens ein Bereich angemeldet")
var embedded_ids: Array = []
for entry in registry_script.PANELS:
if str(entry["gruppe"]) in ["map", "dialog"]:
embedded_ids.append(str(entry["id"]))
# --- 2. Jeder Bereich GENAU EINMAL im Baum, als Tab mit Titel --------------
var tabs_by_name: Dictionary = {} # gruppe → TabContainer
var stack: Array = [main]
var all_controls: Array = []
while not stack.is_empty():
var n: Node = stack.pop_back()
if n is TabContainer:
all_controls.append(n)
stack.append_array(n.get_children())
for tab in all_controls:
for c in (tab as TabContainer).get_children():
var cid := str(c.name)
if cid in ids:
_check(tabs_by_name.get(cid) == null, "Bereich '%s' GENAU EINMAL im Baum" % cid)
tabs_by_name[cid] = c
# Tab-Titel = Registry-Titel (erreichbar/erkennbar)
var title: String = ""
for entry in registry_script.PANELS:
if str(entry["id"]) == cid:
title = str(entry["titel"])
var idx: int = c.get_index()
_check(tab.get_tab_title(idx) == title,
"Tab '%s' trägt Registry-Titel '%s'" % [cid, title])
for wanted in ids:
if wanted in embedded_ids:
# Map-Abschnitte und Einstellungen-Fenster sind keine Tabs (25.10.):
# GENAU EINMAL im Baum, Registry-Name = Node-Name.
var treffer := _count_named(main, wanted)
_check(treffer == 1, "Bereich '%s' (eingebettet) GENAU EINMAL im Baum (%d)" % [wanted, treffer])
continue
_check(tabs_by_name.get(wanted) != null, "Bereich '%s' aus der Registry im Baum" % wanted)
# --- 3. Tab-Gruppen --------------------------------------------------------
var right: TabContainer = main.get("ui_right_panel")
var left: TabContainer = main.get("ui_left_panel")
var bottom: Node = main.get("ui_dock_bottom")
_check(right != null and right is TabContainer, "rechte Spalte ist ein TabContainer")
_check(left != null and left is TabContainer, "linke Spalte ist ein TabContainer (U2-vorbereitet)")
_check(bottom != null and bottom is VSplitContainer, "unten: VSplit (größenverstellbar)")
if right != null:
# Registry-getrieben (T3, Karte t_aaff5dcc): die erwartete Tab-Anzahl
# kommt aus der Registry-Tabelle (gruppe "rechts") statt hart kodiert —
# neue Panels melden sich dort an und der Check bleibt wahr.
var expected_right := 0
for entry in registry_script.PANELS:
if str(entry["gruppe"]) == "rechts":
expected_right += 1
_check(expected_right == 5, "rechts: genau 5 Tabs (Inspektor, Map, Terrain, Malen, Bauen)")
_check(right.get_child_count() == expected_right,
"rechts: %d Tabs aus der Registry — hat %d" % [expected_right, right.get_child_count()])
_check(right.get_tab_count() == right.get_child_count(),
"rechts: alle Bereiche als Tab in der Leiste erreichbar (Titel je Bereich)")
for i in right.get_tab_count():
_check(not str(right.get_tab_title(i)).is_empty(),
"rechts: Tab %d hat sichtbaren Titel" % i)
if left != null:
_check(left.get_child_count() == 1, "links: 1 Tab (Map-Inhalt) — hat %d" % left.get_child_count())
if bottom is VSplitContainer and bottom.get_child_count() == 2:
var bchild: Node = bottom.get_child(1) # PanelContainer mit BottomTabs
var btab: TabContainer = null
for gc in bchild.get_children():
if gc is TabContainer:
btab = gc
_check(btab != null, "unten: TabContainer für Assets gefunden")
if btab != null:
_check(btab.get_child_count() == 1, "unten: 1 Tab (Objekt-Bibliothek) — hat %d" % btab.get_child_count())
# --- 4. Assets-Bereich unten vollständig -----------------------------------
var assets: Control = tabs_by_name.get("assets")
_check(assets != null, "Objekt-Bibliothek (Assets) unten vorhanden")
if assets != null:
_check(assets.get("library_search") != null, "Bibliothek: Suchfeld vorhanden")
_check(assets.get("library_open_all_btn") != null and assets.get("library_close_all_btn") != null,
"Bibliothek: „Alle auf/zu“ vorhanden")
var lib_box: Node = assets.get("library_box")
_check(lib_box != null and lib_box.get_child_count() > 0, "Bibliothek: Kachel-/Gruppen-Liste gefüllt")
_check(tabs_by_name.get("werkzeuge") == null, "Tab „Objekt-Werkzeuge“ ist weg")
var settings: Control = main.find_child("einstellungen", true, false)
_check(settings != null and settings.get("raster_snap_check") != null, "Raster-Snap wohnt im Einstellungen-Fenster")
_check(settings != null and settings.get("grid_btn") != null and settings.get("contours_btn") != null
and settings.get("size_menu") != null and settings.get("sym_menu") != null,
"Einstellungen: Gitter, Höhenlinien, UI-Größe, Symmetrie-Modus")
# --- 5. Theme als einzige Farbquelle ----------------------------------------
var theme_res: Theme = main.get("theme")
_check(theme_res != null, "Theme gesetzt (main.theme)")
if theme_res != null:
_check(int(theme_res.default_font_size) >= 14, "Theme: Grundschrift ≥ 14 px (Lesbarkeits-Grundsatz)")
var theme_file: Theme = load("res://theme/editor_theme.tres")
_check(theme_res == theme_file, "Theme: main nutzt theme/editor_theme.tres (eine Quelle)")
# --- 6. Keine doppelten Node-Namen unter main -------------------------------
var names: Dictionary = {}
var dupes: Array = []
var stack2: Array = [main]
while not stack2.is_empty():
var n2: Node = stack2.pop_back()
var key := str(n2.name) + "#" + n2.get_class()
if key in names:
dupes.append(str(n2.name))
names[key] = true
stack2.append_array(n2.get_children())
_check(dupes.is_empty(), "keine doppelten Node-Namen im UI — %s" % str(dupes))
# --- 7. Koordinator-Vertrag (Werkzeuge/Tests greifen weiter auf main.*) ----
for ref_name in ["toolbar_mod", "status_mod", "selection_mod", "map_mod",
"terrain_mod", "check_mod", "tree_mod", "assets_mod",
"content_tree", "status_label", "prop_name", "library_search",
"terrain_radius_slider", "terrain_size_btn_medium"]:
_check(main.get(ref_name) != null, "main.%s vorhanden" % ref_name)
# --- 8. Lauf-Modus (T1) bleibt erreichbar: Toolbar-Aktion aus Registry ------
var tb: Control = main.get("toolbar_mod")
var found_test_btn := false
if tb != null:
for entry in registry_script.ACTIONS:
if str(entry["id"]) == "karte_testen":
var btn: Node = tb.find_child(str(entry["id"]), true, false)
found_test_btn = btn != null
_check(found_test_btn, "Lauf-Modus: „▶ Karte testen“ in der Toolbar (Registry-Aktion)")
print("CHECKS: %d FAILURES: %d" % [_oks + _fails, _fails])
if _fails > 0:
print("UI_LAYOUT_TEST_FAIL")
quit(1)
else:
print("ALL OK: %d checks" % (_oks + _fails))
quit(0)