extends SceneTree ## test_ui_layout (Karte t_eaccbfed, U1 UI-Fundament): prüft das Unity-artige ## Layout, das AUSSCHLIESSLICH aus der Registry (core/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://core/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://core/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 == 6, "rechts: genau 6 Tabs (Inspektor, Map, Terrain, Malen, Bauen, Server)") _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://core/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)