219 lines
9.3 KiB
GDScript
219 lines
9.3 KiB
GDScript
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")
|
||
|
||
|
||
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(158)
|
||
_check(tile != null and tile.custom_minimum_size.y == 158, "Mittel: Bibliotheks-Kachel 126 → 158")
|
||
var thumb := _find_thumb_rect(120)
|
||
_check(thumb != null and thumb.custom_minimum_size.x == 120, "Mittel: Thumbnail 96 → 120")
|
||
_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")
|