extends SceneTree ## Editor-Test (headless): UI-Skalierung — Karte t_297221b5. ## Ausführen: godot --headless --path . -s res://tests/test_ui_scale.gd ## Exit 0 = alles grün. ## ## Geprüft wird: ## 1. Stufen-API: 3 Stufen (Klein 1.0 / Mittel 1.25 / Groß 1.5), Labels/Keys. ## 2. Rechenhelfer: px/f/v2/font_size multiplizieren korrekt (inkl. Runden). ## 3. Config-Roundtrip: Stufe übersteht reload() (= Neustart) UND der ## [library]-Abschnitt (Klapp-Zustand, Karte t_59fc0f21) bleibt erhalten. ## 4. ECHTE Main-UI (scenes/main.tscn): Theme-Schrift, Knopf-/Kachel-/ ## Thumbnail-Mindestmaße skalieren bei jeder Stufe — sofort nach dem ## Rebuild (derselbe Pfad wie der Menü-Klick „UI-Größe“). ## 5. Rebuild erhält den Map-Zustand (doc-Objekt bleibt, Map-Eigenschaften ## werden wieder eingefüllt, Objektzahl unverändert). ## 6. Ungültige Menü-Indices und headless-Fenster-Aufrufe sind harmlos. ## ## Die Suite ist hermetisch: SCOPEDVR_UI_CFG zeigt auf eine Temp-Config, ## damit weder die Nutzer-Config noch andere Suite-Läufe gestört werden. const UIScale := preload("res://src/ui/ui_scale.gd") var failures := 0 var checks := 0 var main: Control func _initialize() -> void: # Hermetische Config (Temp-Pfad im user://-Bereich): OS.set_environment("SCOPEDVR_UI_CFG", "user://ui_scale_test.cfg") _run_all.call_deferred() func _check(cond: bool, label: String) -> void: checks += 1 if cond: print(" ok: %s" % label) else: failures += 1 print(" FAIL: %s" % label) func _finish() -> void: # Sauber hinterlassen: Test-Config löschen, Env-Umleitung aufheben. var dir := DirAccess.open("user://") if dir != null and dir.file_exists("ui_scale_test.cfg"): dir.remove("ui_scale_test.cfg") OS.set_environment("SCOPEDVR_UI_CFG", "") print("CHECKS: %d FAILURES: %d" % [checks, failures]) print("UI_SCALE_TEST_OK" if failures == 0 else "UI_SCALE_TEST_FAIL") quit(1 if failures > 0 else 0) func _run_all() -> void: UIScale.set_step_index(0) _test_steps_api() _test_math() _test_cfg_roundtrip() await _test_real_ui_scaling() await _test_state_survives_rebuild() _test_headless_safety() _finish() func _test_steps_api() -> void: print("Testgruppe: Stufen-API") _check(UIScale.step_count() == 3, "Genau 3 Stufen") _check(UIScale.step_label(0) == "Klein" and UIScale.step_label(1) == "Mittel" and UIScale.step_label(2) == "Groß", "Labels Klein/Mittel/Groß") UIScale.set_step_index(0) _check(is_equal_approx(UIScale.factor(), 1.0), "Klein = Faktor 1.0") UIScale.set_step_index(1) _check(is_equal_approx(UIScale.factor(), 1.25), "Mittel = Faktor 1.25") UIScale.set_step_index(2) _check(is_equal_approx(UIScale.factor(), 1.5), "Groß = Faktor 1.5") _check(UIScale.step_index_from_key("mittel") == 1, "Key 'mittel' → Index 1") _check(UIScale.step_index_from_key("quatsch") == 0, "Unbekannter Key → Klein (0)") _check(UIScale.step_index_from_key("") == 0, "Leerer Key → Klein (0)") func _test_math() -> void: print("Testgruppe: Rechenhelfer") UIScale.set_step_index(0) _check(UIScale.px(40) == 40 and UIScale.px(16) == 16, "Klein: px unverändert (1.0)") _check(UIScale.font_size() == 16, "Klein: Standardschrift 16 (wie Vorher)") UIScale.set_step_index(1) _check(UIScale.px(40) == 50, "Mittel: 40 px → 50 px") _check(UIScale.px(126) == 158, "Mittel: 126 px → 158 px (gerundet)") _check(UIScale.px(280) == 350, "Mittel: Panel 280 px → 350 px") _check(is_equal_approx(UIScale.f(-320.0), -400.0), "Mittel: split_offset -320 → -400") _check(UIScale.font_size() == 20, "Mittel: Standardschrift 20") var v := UIScale.v2(140, 40) _check(v.x == 175 and v.y == 50, "Mittel: v2(140,40) → (175,50)") UIScale.set_step_index(2) _check(UIScale.px(40) == 60 and UIScale.font_size() == 24, "Groß: 40 px → 60 px, Schrift 24") _check(UIScale.px(96) == 144, "Groß: Thumbnail 96 → 144 (px-Rechnung)") func _test_cfg_roundtrip() -> void: print("Testgruppe: Config-Roundtrip (Neustart-Festigkeit + [library]-Schutz)") UIScale.set_step_index(1) # schreibt [ui] groesse=mittel # [library]-Abschnitt anlegen (wie Karte t_59fc0f21 es tut): var cfg := ConfigFile.new() cfg.load(UIScale.cfg_path()) cfg.set_value("library", "gebaeude", true) cfg.save(UIScale.cfg_path()) # „Neustart“: Cache verwerfen, frisch aus der Datei lesen. UIScale.reload() _check(UIScale.step_index() == 1, "Stufe Mittel übersteht reload() (= Neustart)") var cfg2 := ConfigFile.new() cfg2.load(UIScale.cfg_path()) _check(bool(cfg2.get_value("library", "gebaeude", false)), "[library]-Abschnitt bleibt beim Schreiben erhalten") _check(str(cfg2.get_value("ui", "groesse", "")) == "mittel", "[ui] groesse=mittel steht in der Datei") func _test_real_ui_scaling() -> void: print("Testgruppe: Echte Main-UI skaliert (Build + Rebuild)") var scene: PackedScene = load("res://scenes/main.tscn") _check(scene != null, "scenes/main.tscn lädt") if scene == null: return UIScale.set_step_index(0) main = scene.instantiate() root.add_child(main) await process_frame await process_frame _check(main != null and main.undo_btn != null, "Main-UI gebaut") _check(int(main.theme.default_font_size) == 16, "Klein: Theme-Schrift 16") _check(main.undo_btn.custom_minimum_size == Vector2(140, 40), "Klein: Undo-Knopf 140×40") _check(main.redo_btn.custom_minimum_size == Vector2(150, 40), "Klein: Redo-Knopf 150×40") _check(main.size_menu != null and main.size_menu.text == "UI-Größe", "UI-Größe-Menü in der Top-Bar") var sp: PopupMenu = main.size_menu.get_popup() _check(sp.item_count == 3, "Menü hat 3 Einträge") _check(sp.is_item_checked(0), "Menü-Häkchen auf aktueller Stufe (Klein)") # Rebuild-Pfad (derselbe wie beim Menü-Klick) → Mittel: UIScale.set_step_index(1) main._rebuild_ui_for_scale() await process_frame await process_frame _check(is_instance_valid(main.undo_btn), "Rebuild: Knöpfe neu gebaut") _check(int(main.theme.default_font_size) == 20, "Mittel: Theme-Schrift 20 (sofort wirksam)") _check(main.undo_btn.custom_minimum_size == Vector2(175, 50), "Mittel: Undo-Knopf 175×50") _check(main.library_search.custom_minimum_size.y == 45, "Mittel: Suchfeld 36 → 45") var tile := _find_tile_button(235) _check(tile != null and tile.custom_minimum_size.y == 235, "Mittel: Bibliotheks-Kachel 188 → 235") var thumb := _find_thumb_rect(160) _check(thumb != null and thumb.custom_minimum_size.x == 160, "Mittel: Thumbnail 128 → 160") _check(main.size_menu.get_popup().is_item_checked(1), "Menü-Häkchen wandert auf Mittel (neues Popup)") # Groß: main._on_ui_size_selected(2) await process_frame await process_frame _check(int(main.theme.default_font_size) == 24, "Groß: Theme-Schrift 24 (per Menü-Handler)") _check(main.undo_btn.custom_minimum_size == Vector2(210, 60), "Groß: Undo-Knopf 210×60") _check(main.status_label != null and is_instance_valid(main.status_label), "Statuszeile existiert (Groß)") _check(main.content_tree != null and is_instance_valid(main.content_tree), "Inhalts-Baum existiert (Groß)") # Zurück auf Klein: wieder exakt die Vorher-Maße. main._on_ui_size_selected(0) await process_frame await process_frame _check(main.size_menu.get_popup().is_item_checked(0), "Menü-Häkchen wieder auf Klein") _check(int(main.theme.default_font_size) == 16, "Zurück auf Klein: Schrift wieder 16") _check(main.undo_btn.custom_minimum_size == Vector2(140, 40), "Zurück auf Klein: Undo-Knopf 140×40") func _find_tile_button(y: int) -> Button: # Bibliotheks-Kacheln: Buttons mit custom_minimum_size.y == Stufenhöhe # unterhalb der Haupt-VBox suchen — der Rebuild baut sie jedes Mal neu. var stack: Array = [main] while not stack.is_empty(): var node: Node = stack.pop_back() if node is Button and node.custom_minimum_size.y == y and node.custom_minimum_size.x == 0: return node for c in node.get_children(): stack.push_back(c) return null func _find_thumb_rect(x: int) -> TextureRect: var stack: Array = [main] while not stack.is_empty(): var node: Node = stack.pop_back() if node is TextureRect and node.custom_minimum_size.x == x: return node for c in node.get_children(): stack.push_back(c) return null func _test_state_survives_rebuild() -> void: print("Testgruppe: Rebuild erhält Map-Zustand") if main == null: return var count_before := SvmapDocument.object_count(main.doc) main.doc["info"]["name"] = "Skalierungs-Test" main._rebuild_ui_for_scale() await process_frame await process_frame _check(SvmapDocument.object_count(main.doc) == count_before, "Map-Dokument unverändert (Objektzahl)") _check(str(main.prop_name.text) == "Skalierungs-Test", "Map-Eigenschaften nach Rebuild wieder eingefüllt") _check(main.doc == main.history.doc, "Undo-History zeigt weiter auf dasselbe doc") _check(main.camera != null and is_instance_valid(main.camera), "3D-Kamera nach Rebuild vorhanden") _check(main.view_builder != null and is_instance_valid(main.view_builder), "Map-Ansicht nach Rebuild vorhanden") func _test_headless_safety() -> void: print("Testgruppe: Headless-Sicherheit") # Headless: Fenster-Aufrufe dürfen nicht krachen (Guards springen an). main._apply_window_state() main._save_window_state() _check(true, "Fenster-Aufrufe headless ohne Crash") # Ungültige Menü-Indices harmlos: main._on_ui_size_selected(-1) main._on_ui_size_selected(99) _check(true, "Ungültige Menü-Indices ohne Crash") _check(UIScale.step_index() == 0, "Stufe bleibt Klein nach ungültigen Aufrufen")