scopedvr-map-editor/tests/test_theme.gd

203 lines
7.8 KiB
GDScript

extends SceneTree
## Editor-Test (headless): Theme-Konsistenz — Karte t_2336c58e (U7 Feinschliff).
## Ausführen: godot --headless --path . -s res://tests/test_theme.gd
## Exit 0 = alles grün.
##
## Was hier geprüft wird:
## 1. Theme-Ressource: main nutzt theme/editor_theme.tres (EINE Farbquelle),
## Theme-Schrift = UIScale.font_size().
## 2. Theme-Inhalt: Hover/Fokus/Disabled (Button), Tab-Hover, LineEdit,
## Splitter-Anfassen (grabber_area), Scrollbars, Tooltip + die Farb-Token
## (accent, status_ok/error, muted, field, title, warning).
## 3. KEINE hartkodierten Farben im Code: Suche nach "Color(" in
## src/ui/panels/*.gd, src/ui/main.gd, src/ui/inspector_panel.gd.
## 4. Alle Knöpfe (Button/MenuButton/CheckBox) haben einen Tooltip.
## 5. Typografie-Raster: Überschrift > Body > Nebentext, alles über UIScale.
## 6. Fenstertitel zeigt ungespeicherte Änderungen an (U7 Kleinigkeit).
const UIScale := preload("res://src/ui/ui_scale.gd")
var failures := 0
var checks := 0
var main: Control
func _initialize() -> void:
_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:
if failures == 0:
print("ALL OK: %d checks" % checks)
quit(0)
else:
print("FAILED: %d/%d checks" % [failures, checks])
quit(1)
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)
# Zwei Frames: _ready + Layout laufen lassen (auch deferred UI-Arbeit).
await process_frame
await process_frame
_test_theme_set()
_test_theme_content()
_test_no_hardcoded_colors()
_test_tooltips()
_test_typography()
_test_window_title_dirty()
_finish()
## 1) main hängt genau die Theme-Datei aus dem Projekt an (eine Quelle).
func _test_theme_set() -> void:
print("Testgruppe: Theme-Ressource gesetzt")
_check(main.theme != null, "main.theme gesetzt")
if main.theme == null:
return
var theme_file: Theme = load("res://theme/editor_theme.tres")
_check(main.theme == theme_file,
"main nutzt theme/editor_theme.tres (eine Farbquelle)")
_check(int(main.theme.default_font_size) == UIScale.font_size(),
"Theme-Schrift = UIScale.font_size() (%d)" % UIScale.font_size())
## 2) Die U7-Theme-Bausteine sind alle da (Hover/Fokus/Disabled, Splitter,
## Scrollbars, Tooltip, Farb-Token).
func _test_theme_content() -> void:
print("Testgruppe: Theme-Inhalt (Hover/Fokus/Disabled/Splitter/Farben)")
var th: Theme = main.theme
_check(th.has_stylebox("disabled", "Button"), "Button: Disabled-Style (ausgegraut)")
_check(th.has_stylebox("hover", "Button"), "Button: Hover-Style")
_check(th.has_stylebox("pressed", "Button"), "Button: Aktiv-Style")
_check(th.has_stylebox("focus", "Button"), "Button: Fokus-Rahmen")
_check(th.has_color("font_disabled_color", "Button"),
"Button: Disabled-Schriftfarbe (sichtbar ausgegraut)")
_check(th.has_color("font_placeholder_color", "LineEdit"),
"LineEdit: Platzhalter-Farbe")
_check(th.has_stylebox("focus", "LineEdit"), "LineEdit: Fokus-Rahmen")
_check(th.has_stylebox("tab_hovered", "TabContainer"), "Tabs: Hover-Zustand")
_check(th.has_stylebox("grabber_area", "HSplitContainer"),
"Splitter: Anfassen-Fläche (HSplit)")
_check(th.has_stylebox("grabber_area", "VSplitContainer"),
"Splitter: Anfassen-Fläche (VSplit)")
_check(th.has_stylebox("grabber_area_highlight", "HSplitContainer"),
"Splitter: Anfassen-Hervorhebung (HSplit)")
_check(th.has_stylebox("grabber", "VScrollBar"), "ScrollBar: Griff (V)")
_check(th.has_stylebox("grabber", "HScrollBar"), "ScrollBar: Griff (H)")
_check(th.has_stylebox("panel", "TooltipPanel"), "Tooltip: Panel-Style")
_check(th.has_color("accent", "Editor"), "Farb-Token: accent")
_check(th.has_color("status_ok", "Editor"), "Farb-Token: status_ok")
_check(th.has_color("status_error", "Editor"), "Farb-Token: status_error")
_check(th.has_color("muted", "Editor"), "Farb-Token: muted")
_check(th.has_color("field", "Editor"), "Farb-Token: field")
_check(th.has_color("title", "Editor"), "Farb-Token: title")
_check(th.has_color("warning", "Editor"), "Farb-Token: warning")
## 3) Keine Farbwerte mehr im Code — alles kommt aus dem Theme.
## Scan: src/ui/panels/*.gd (verbindlich) + main.gd + inspector_panel.gd.
## Achtung: nur "Color(" (Konstruktor) zählt — theme_color()/-> Color: sind
## Schlüsselwörter, keine Farbwerte.
func _test_no_hardcoded_colors() -> void:
print("Testgruppe: keine hartkodierten Farben im Code")
var dateien: Array = []
var dir := DirAccess.open("res://src/ui/panels")
if dir != null:
dir.list_dir_begin()
var fname := dir.get_next()
while not fname.is_empty():
if fname.ends_with(".gd"):
dateien.append("res://src/ui/panels/" + fname)
fname = dir.get_next()
dateien.append("res://src/ui/main.gd")
dateien.append("res://src/ui/inspector_panel.gd")
_check(dateien.size() > 5, "Farb-Scan: %d Skripte gefunden" % dateien.size())
var farb_dateien := 0
for pfad in dateien:
var text := FileAccess.get_file_as_string(pfad)
if text.contains("Color("):
farb_dateien += 1
print(" hartkodierte Farbe in: %s" % pfad)
_check(farb_dateien == 0,
"kein 'Color(' in den Panel-Skripten/main.gd/Inspektor")
## 4) Jeder klickbare Knopf (Button/MenuButton/CheckBox/CheckButton) im
## gebauten UI trägt einen Tooltip — Werkzeugname/Tastenkürzel inklusive.
func _test_tooltips() -> void:
print("Testgruppe: alle Knöpfe haben Tooltips")
var ohne := 0
var mit := 0
var stack: Array = [main]
while not stack.is_empty():
var n: Node = stack.pop_back()
if n is Button or n is MenuButton:
var str_text := str((n as Control).tooltip_text)
if str_text.strip_edges().is_empty():
ohne += 1
var ort := str(n.get_parent().name)
var elter := n.get_parent()
while elter != null and elter != main:
elter = elter.get_parent()
if elter != null:
ort = str(elter.name) + "/" + ort
print(" ohne Tooltip: %s (%s) text='%s' pfad=%s" % [
n.name, n.get_class(),
str((n as Control).get("text")), ort])
else:
mit += 1
for c in n.get_children():
stack.append(c)
_check(mit > 20, "Knöpfe mit Tooltip gefunden (%d)" % mit)
_check(ohne == 0, "kein Knopf ohne Tooltip (%d ohne)" % ohne)
## 5) Genau drei Typografie-Ebenen über UIScale: Überschrift > Body > Nebentext.
func _test_typography() -> void:
print("Testgruppe: Typografie-Raster über UIScale")
_check(UIScale.heading_font_size() > UIScale.font_size(),
"Überschrift (%d) größer als Body (%d)" % [
UIScale.heading_font_size(), UIScale.font_size()])
_check(UIScale.font_size() > UIScale.small_font_size(),
"Body (%d) größer als Nebentext (%d)" % [
UIScale.font_size(), UIScale.small_font_size()])
# Stufen-Skalierung wandert mit (Groß-Stufe, ohne UI-Rebuild zu brauchen):
UIScale.set_step_index(2)
_check(UIScale.heading_font_size() == UIScale.px(UIScale.HEADING_FONT_SIZE),
"Überschrift skaliert mit der UI-Stufe (Groß)")
_check(UIScale.small_font_size() == UIScale.px(UIScale.SMALL_FONT_SIZE),
"Nebentext skaliert mit der UI-Stufe (Groß)")
UIScale.set_step_index(0) # zurück auf Klein (wie der Test-Start)
## 6) Fenstertitel nennt ungespeicherte Änderungen (U7-Kleinigkeit).
func _test_window_title_dirty() -> void:
print("Testgruppe: Fenstertitel bei ungespeicherten Änderungen")
main.dirty = true
main.call("_refresh_file_label")
var titel_dirty := str(root.title)
_check(titel_dirty.contains("ungespeichert"),
"Titel bei dirty nennt „ungespeichert“ (%s)" % titel_dirty)
main.dirty = false
main.call("_refresh_file_label")
var titel_sauber := str(root.title)
_check(not titel_sauber.contains("ungespeichert"),
"Titel ohne dirty bleibt sauber (%s)" % titel_sauber)