360 lines
16 KiB
GDScript
360 lines
16 KiB
GDScript
extends SceneTree
|
||
## Editor-Test (headless): Assets-Bereich Unity-artig — Karte t_69687afc (U4).
|
||
## Ausführen: godot --headless --path . --script res://tests/test_assets_panel.gd
|
||
## Exit 0 = alles grün.
|
||
##
|
||
## Instanziert die ECHTE Main-UI (Muster tests/test_library_browser_ui.gd) und prüft:
|
||
## 1. Kategorien-Leiste: ein Chip je Bibliotheks-Gruppe (keine eigene Liste),
|
||
## Summe der Gruppen-Anzahlen = Gesamtzahl des Manifests, „Alle" = Gesamtzahl.
|
||
## 2. Suche findet NAME und ID (case-insensitive), Zähler „N von M sichtbar".
|
||
## 3. Kategorie-Chip filtert auf genau diese Gruppe (Zähler M = Gruppengröße).
|
||
## 4. Fehlendes Modell (glb-Eintrag, keine Quelle) → Kachel trägt die
|
||
## Markierung „Modell fehlt" + Tooltip-Hinweis; Primitive ohne glb nie.
|
||
## 5. Auswahl-Signal: Klick (Selection) löst object_selected GENAU EINMAL je
|
||
## NEUER Auswahl aus (Wiederholung derselben Kachel kein zweites Signal).
|
||
## 6. Hover vergrößert die ID-Schrift auf der Kachel.
|
||
## 7. Doppelklick-Aktion startet das Platzier-Werkzeug wie bisher und nochmal
|
||
## auf derselben Kachel bricht ab (alter An/Aus-Vertrag).
|
||
## 8. „Zuletzt benutzt" wird je Auswahl in user://scopedvr_editor.cfg
|
||
## gemerkt, überlebt Neu-Instanz und taucht als Chip + Ansicht auf.
|
||
|
||
const GLB_QUELLEN_SETTING := "scopedvr/editor/glb_quellen"
|
||
|
||
var failures := 0
|
||
var checks := 0
|
||
var main: Control
|
||
var assets # core/ui/panels/assets_panel.gd
|
||
var selected_count := 0
|
||
var activated_count := 0
|
||
|
||
|
||
func _initialize() -> void:
|
||
_run_all.call_deferred()
|
||
|
||
|
||
func _run_all() -> void:
|
||
_clear_library_cfg_section()
|
||
var scene: PackedScene = load("res://core/scenes/main.tscn")
|
||
_check(scene != null, "scenes/main.tscn laedt")
|
||
if scene == null:
|
||
_finish()
|
||
return
|
||
main = scene.instantiate()
|
||
root.add_child(main)
|
||
await process_frame
|
||
await process_frame
|
||
assets = main.assets_mod
|
||
_check(assets != null, "Assets-Panel über Registry angemeldet")
|
||
if assets == null:
|
||
_finish()
|
||
return
|
||
assets.object_selected.connect(_on_selected)
|
||
assets.object_activated.connect(_on_activated)
|
||
|
||
_test_klapp_zustaende_cfg_robus()
|
||
_test_kategorien()
|
||
_test_suche_und_zaehler()
|
||
_test_kategorie_filter()
|
||
_test_fehlendes_modell()
|
||
_test_auswahl_signal()
|
||
_test_hover()
|
||
_test_doppelklick()
|
||
await _test_zuletzt_persistenz()
|
||
|
||
_finish()
|
||
|
||
|
||
func _on_selected(_oid: String) -> void:
|
||
selected_count += 1
|
||
|
||
|
||
func _on_activated(_oid: String) -> void:
|
||
activated_count += 1
|
||
|
||
|
||
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 _check(cond: bool, label: String) -> void:
|
||
checks += 1
|
||
if cond:
|
||
print(" ok: %s" % label)
|
||
else:
|
||
failures += 1
|
||
print(" FAIL: %s" % label)
|
||
|
||
|
||
func _clear_library_cfg_section() -> void:
|
||
# Nur UNSEREN Abschnitt leeren — [test]/[update]/[fenster] bleiben.
|
||
var cfg := ConfigFile.new()
|
||
cfg.load("user://scopedvr_editor.cfg")
|
||
if cfg.has_section("library"):
|
||
cfg.erase_section("library")
|
||
cfg.save("user://scopedvr_editor.cfg")
|
||
|
||
|
||
# --------------------------------------- 0 Klapp-Zustände aus kaputter cfg ----
|
||
|
||
func _test_klapp_zustaende_cfg_robus() -> void:
|
||
print("Testgruppe: Klapp-Zustände laden sauber aus kaputter cfg (t_efb3d022)")
|
||
# Kaputte cfg: [library] enthält einen Array-Eintrag (zuletzt_benutzt) und
|
||
# einen korrupten Eintrag (String statt Bool) neben einem echten Bool.
|
||
var cfg := ConfigFile.new()
|
||
cfg.set_value("library", "zuletzt_benutzt", PackedStringArray(["box_002", "box_001"]))
|
||
cfg.set_value("library", "gruppen_kisten", true)
|
||
cfg.set_value("library", "gruppen_bauteile", "kaputt")
|
||
cfg.set_value("library", "gruppen_spawn", 42)
|
||
cfg.save("user://scopedvr_editor.cfg")
|
||
# Direkt aufrufen: darf KEINEN SCRIPT ERROR werfen (bool(<Array>) war der
|
||
# alte Crash) und muss die sauberen Bool-Zustände trotzdem liefern.
|
||
var states: Dictionary = assets._library_load_open_states()
|
||
_check(states.size() == 3, "3 Zustände geladen, Array-Eintrag übersprungen (ist %d)" % states.size())
|
||
_check(states.get("gruppen_kisten") == true, "echter Bool bleibt true")
|
||
_check(states.get("gruppen_bauteile") == true, "String-Eintrag auf Fallback true")
|
||
_check(states.get("gruppen_spawn") == true, "Int-Eintrag auf Fallback true")
|
||
# Und der Persistenz-Weg (save → load) liefert die Zustände unverändert:
|
||
assets._library_save_open_state("gruppen_kisten", false)
|
||
var states2: Dictionary = assets._library_load_open_states()
|
||
_check(states2.get("gruppen_kisten") == false, "save→load Rundtrip: false kommt zurück")
|
||
_check(states2.get("gruppen_bauteile") == true, "kaputter Eintrag nach Rundtrip weiter Fallback true")
|
||
# Aufräumen: nächste Testgruppen brauchen eine saubere cfg.
|
||
_clear_library_cfg_section()
|
||
|
||
|
||
# ------------------------------------------------------------- 1 Kategorien ----
|
||
|
||
func _test_kategorien() -> void:
|
||
print("Testgruppe: Kategorien-Leiste (aus der Bibliothek, Summe = Gesamtzahl)")
|
||
var lib: Dictionary = main.manifest.get("objects", {})
|
||
var groups := LibraryBrowser.groups_in_order(main.manifest)
|
||
var chip_keys: Array = assets._cat_buttons.keys()
|
||
_check(chip_keys.has("alle"), "Chip „Alle“ existiert")
|
||
_check(chip_keys.has("zuletzt"), "Chip „Zuletzt benutzt“ angelegt (noch versteckt)")
|
||
var alle_chip: Button = assets._cat_buttons.get("alle")
|
||
_check(int(alle_chip.get_meta("cat_count")) == lib.size(),
|
||
"„Alle“ zeigt Gesamtzahl (%d)" % int(alle_chip.get_meta("cat_count")))
|
||
var sum := 0
|
||
var group_keys := {}
|
||
for group in groups:
|
||
if (group["ids"] as Array).is_empty():
|
||
continue
|
||
group_keys[str(group["key"])] = true
|
||
var chip: Button = assets._cat_buttons.get(str(group["key"]))
|
||
_check(chip != null, "Chip für Gruppe „%s“ existiert" % str(group["label"]))
|
||
if chip == null:
|
||
continue
|
||
sum += int(chip.get_meta("cat_count"))
|
||
_check(chip.text.contains(str(group["label"])), "Chip-Text trägt Gruppenname (%s)" % chip.text)
|
||
_check(chip_keys.size() == group_keys.size() + 2, "keine erfundenen Kategorien (%d Chips für %d Gruppen + Alle/Zuletzt)" % [chip_keys.size(), group_keys.size()])
|
||
_check(sum == lib.size(), "Summe der Gruppen-Anzahlen = Gesamtzahl (%d = %d)" % [sum, lib.size()])
|
||
_check(alle_chip.button_pressed, "„Alle“ ist Start-Kategorie")
|
||
_check(not (assets._cat_buttons.get("zuletzt") as Button).visible, "Zuletzt-Chip startet versteckt (noch nichts benutzt)")
|
||
|
||
|
||
# ---------------------------------------------------- 2 Suche + Zähler ----
|
||
|
||
func _zaehler_n() -> int:
|
||
var text: String = assets.library_counter.text
|
||
var vor: String = text.split(" ")[0]
|
||
return int(vor)
|
||
|
||
|
||
func _zaehler_m() -> int:
|
||
var text: String = assets.library_counter.text
|
||
var teile: PackedStringArray = text.split(" ")
|
||
return int(teile[2])
|
||
|
||
|
||
func _test_suche_und_zaehler() -> void:
|
||
print("Testgruppe: Suche (Name + ID) und Zähler „N von M sichtbar“")
|
||
var lib_size := (main.manifest.get("objects", {}) as Dictionary).size()
|
||
# Namens-Suche (deutsch, case egal):
|
||
var erwartet_zelt := 0
|
||
for oid2 in (main.manifest.get("objects", {}) as Dictionary).keys():
|
||
var e2: Dictionary = (main.manifest["objects"] as Dictionary)[oid2]
|
||
if str(e2.get("name_de", "")).to_lower().contains("zelt") or str(oid2).contains("zelt"):
|
||
erwartet_zelt += 1
|
||
main.library_search.text = "ZELT"
|
||
assets._apply_library_filter("ZELT")
|
||
var pilze := 0
|
||
for oid in (main.manifest.get("objects", {}) as Dictionary).keys():
|
||
var btn := main.library_buttons.get(str(oid)) as Button
|
||
if btn != null and btn.visible:
|
||
pilze += 1
|
||
_check(pilze == erwartet_zelt and pilze >= 10, "„ZELT“ (Name, groß) findet %d Kacheln (ist %d)" % [erwartet_zelt, pilze])
|
||
_check(assets.library_counter.text == "%d von %d sichtbar" % [erwartet_zelt, lib_size],
|
||
"Zähler bei Suche: „%s“" % assets.library_counter.text)
|
||
# ID-Suche (Kurz-ID):
|
||
main.library_search.text = ""
|
||
assets._apply_library_filter("water_tower_004")
|
||
var wt := main.library_buttons.get("water_tower_004") as Button
|
||
_check(wt != null and wt.visible, "ID-Suche „water_tower_004“ findet den Wasserturm")
|
||
main.library_search.text = ""
|
||
assets._apply_library_filter("")
|
||
_check(assets.library_counter.text == "%d von %d sichtbar" % [assets._library_group_rows[0]["count"], lib_size],
|
||
"Zähler bei „Alle“ (1 Gruppe offen): „%s“" % assets.library_counter.text)
|
||
|
||
|
||
# -------------------------------------------------- 3 Kategorie-Filter ----
|
||
|
||
func _test_kategorie_filter() -> void:
|
||
print("Testgruppe: Kategorie-Chip filtert auf die Gruppe")
|
||
var zelte_n := 0
|
||
for oid2 in (main.manifest.get("objects", {}) as Dictionary).keys():
|
||
if str(((main.manifest["objects"] as Dictionary)[oid2] as Dictionary).get("kategorie", "")) == "zelte":
|
||
zelte_n += 1
|
||
assets._select_category("zelte")
|
||
var sichtbar := 0
|
||
for oid in (main.manifest.get("objects", {}) as Dictionary).keys():
|
||
var btn := main.library_buttons.get(str(oid)) as Button
|
||
if btn != null and btn.visible:
|
||
sichtbar += 1
|
||
_check(sichtbar == zelte_n and zelte_n > 0, "Kategorie „Zelte“: %d Kacheln sichtbar (ist %d)" % [zelte_n, sichtbar])
|
||
_check(assets.library_counter.text == "%d von %d sichtbar" % [zelte_n, zelte_n], "Zähler in Kategorie: „%s“" % assets.library_counter.text)
|
||
var pilze_chip: Button = assets._cat_buttons.get("zelte")
|
||
_check(pilze_chip.button_pressed, "Zelte-Chip gedrückt")
|
||
assets._select_category("alle")
|
||
_check((assets._cat_buttons.get("alle") as Button).button_pressed, "Zurück auf „Alle“")
|
||
|
||
|
||
# -------------------------------------------------- 4 Fehlendes Modell ----
|
||
|
||
func _test_fehlendes_modell() -> void:
|
||
print("Testgruppe: Fehlendes Modell wird markiert (nicht stillschweigend)")
|
||
# Hermetisch: GLB-Quellen auf ein garantiert leeres Lager umlenken →
|
||
# JEDES Katalog-Objekt mit glb-Eintrag gilt als fehlend, egal wie die
|
||
# Umgebung aussieht. (Leere Liste greift nicht — quellen() fällt auf
|
||
# DEFAULT_QUELLEN zurück.)
|
||
var alter_cache: Dictionary = GlbMeshStore._cache.duplicate()
|
||
GlbMeshStore._cache.clear()
|
||
ProjectSettings.set_setting(GLB_QUELLEN_SETTING, ["/nonexistent_glb_lager_u4/assets/objekte"])
|
||
var fehlt_oid := ""
|
||
for oid in (main.manifest.get("objects", {}) as Dictionary).keys():
|
||
var entry: Dictionary = (main.manifest.get("objects", {}) as Dictionary).get(oid, {})
|
||
if not str(entry.get("glb", "")).is_empty():
|
||
fehlt_oid = str(oid)
|
||
break
|
||
_check(not fehlt_oid.is_empty(), "Katalog hat glb-Objekte (Probe: %s)" % fehlt_oid)
|
||
if fehlt_oid.is_empty():
|
||
ProjectSettings.set_setting(GLB_QUELLEN_SETTING, GlbMeshStore.DEFAULT_QUELLEN)
|
||
return
|
||
_check(GlbMeshStore.fehlt(fehlt_oid), "GlbMeshStore.fehlt ohne Lager = true (%s)" % fehlt_oid)
|
||
_check(assets._tile_is_missing(fehlt_oid, main.manifest.get("objects", {}).get(fehlt_oid, {})),
|
||
"_tile_is_missing(%s) = true ohne Quellen" % fehlt_oid)
|
||
_check(not assets._tile_is_missing("box_001", main.manifest.get("objects", {}).get("box_001", {})),
|
||
"Primitive ohne glb-Eintrag gelten NIE als fehlend (box_001)")
|
||
var probe_grid := GridContainer.new()
|
||
root.add_child(probe_grid)
|
||
var tile: Button = assets._build_tile(fehlt_oid)
|
||
probe_grid.add_child(tile)
|
||
var warn: Label = null
|
||
var dim_thumb := false
|
||
var stack: Array = [tile]
|
||
while not stack.is_empty():
|
||
var node: Node = stack.pop_back()
|
||
if node is Label and (node as Label).text == "Modell fehlt":
|
||
warn = node
|
||
if node is TextureRect and (node as TextureRect).modulate.a < 0.9:
|
||
dim_thumb = true
|
||
for c in node.get_children():
|
||
stack.push_back(c)
|
||
_check(warn != null, "Kachel trägt Warn-Label „Modell fehlt“")
|
||
_check(dim_thumb, "Vorschaubild bei fehlendem Modell abgedunkelt")
|
||
_check(tile.tooltip_text.contains("MODELL FEHLT"), "Tooltip nennt das fehlende Modell")
|
||
probe_grid.queue_free()
|
||
ProjectSettings.set_setting(GLB_QUELLEN_SETTING, GlbMeshStore.DEFAULT_QUELLEN)
|
||
GlbMeshStore._cache = alter_cache
|
||
|
||
|
||
# ------------------------------------------------------- 5 Auswahl-Signal ----
|
||
|
||
func _test_auswahl_signal() -> void:
|
||
print("Testgruppe: Auswahl-Signal genau einmal je neuer Auswahl")
|
||
selected_count = 0
|
||
assets._select_tile("box_001")
|
||
_check(assets.selected_oid == "box_001", "selected_oid gesetzt")
|
||
_check(selected_count == 1, "1. Auswahl → genau 1 Signal (ist %d)" % selected_count)
|
||
assets._select_tile("box_001")
|
||
_check(selected_count == 1, "gleiche Kachel nochmal → KEIN zweites Signal (ist %d)" % selected_count)
|
||
assets._select_tile("box_002")
|
||
_check(selected_count == 2, "Wechsel → 2. Signal (ist %d)" % selected_count)
|
||
var sel_btn := main.library_buttons.get("box_002") as Button
|
||
_check(sel_btn != null and sel_btn.modulate.a > 0.95, "gewählte Kachel voll sichtbar")
|
||
var unsel_btn := main.library_buttons.get("box_001") as Button
|
||
_check(unsel_btn != null and unsel_btn.modulate.a < 0.95, "andere Kachel abgedimmt")
|
||
|
||
|
||
# --------------------------------------------------------------- 6 Hover ----
|
||
|
||
func _test_hover() -> void:
|
||
print("Testgruppe: Hover zeigt die ID größer")
|
||
var btn := main.library_buttons.get("box_002") as Button
|
||
var id_lbl_normal: int = assets._id_font_normal
|
||
assets._set_tile_hover("box_002", true)
|
||
var id_lbl: Label = assets._find_id_label(btn)
|
||
_check(id_lbl != null, "Kachel hat ID-Label")
|
||
if id_lbl != null:
|
||
var gross: int = id_lbl.get_theme_font_size("font_size")
|
||
assets._set_tile_hover("box_002", false)
|
||
var klein: int = id_lbl.get_theme_font_size("font_size")
|
||
_check(gross > klein, "Hover vergrößert die ID (%d > %d)" % [gross, klein])
|
||
_check(klein == id_lbl_normal, "ohne Hover wieder Normalgröße (%d)" % klein)
|
||
|
||
|
||
# --------------------------------------------------------- 7 Doppelklick ----
|
||
|
||
func _test_doppelklick() -> void:
|
||
print("Testgruppe: Doppelklick startet Werkzeug wie bisher, nochmal = aus")
|
||
activated_count = 0
|
||
assets._activate_tile("box_001")
|
||
_check(main.interaction.placing and main.interaction.place_oid == "box_001",
|
||
"Doppelklick: Platzier-Werkzeug läuft für box_001")
|
||
_check(activated_count == 1, "object_activated genau 1× (ist %d)" % activated_count)
|
||
assets._activate_tile("box_001")
|
||
_check(not main.interaction.placing, "nochmal dieselbe Kachel: Platzieren abgebrochen")
|
||
assets._activate_tile("box_002")
|
||
_check(main.interaction.placing and main.interaction.place_oid == "box_002",
|
||
"Wechsel per Doppelklick auf box_002")
|
||
main.interaction.cancel_placement()
|
||
_check(not main.interaction.placing, "aufgeräumt: kein Platzieren mehr aktiv")
|
||
|
||
|
||
# --------------------------------------------------- 8 Zuletzt benutzt ----
|
||
|
||
func _test_zuletzt_persistenz() -> void:
|
||
print("Testgruppe: „Zuletzt benutzt“ — cfg + Neu-Instanz")
|
||
var cfg := ConfigFile.new()
|
||
cfg.load("user://scopedvr_editor.cfg")
|
||
var lst: Array = cfg.get_value("library", "zuletzt_benutzt", [])
|
||
_check(lst.size() >= 2, "cfg [library] zuletzt_benutzt gefüllt (%d)" % lst.size())
|
||
_check(str(lst[0]) == "box_002", "neueste Benutzung zuerst (ist %s)" % str(lst[0]))
|
||
var chip := assets._cat_buttons.get("zuletzt") as Button
|
||
_check(chip != null and chip.visible, "Zuletzt-Chip jetzt sichtbar")
|
||
_check(chip.text.contains("2"), "Zuletzt-Chip zählt Einträge (%s)" % chip.text)
|
||
# Ansicht: Kategorie „zuletzt" zeigt nur die Gemerkten.
|
||
assets._select_category("zuletzt")
|
||
_check(assets._recent_box.visible, "Zuletzt-Ansicht sichtbar")
|
||
var tiles := 0
|
||
for oid in assets._recent_ids:
|
||
if assets._recent_tiles.has(str(oid)):
|
||
tiles += 1
|
||
_check(tiles == 2, "Zuletzt-Ansicht zeigt 2 Kacheln (ist %d)" % tiles)
|
||
# Neu-Instanz = „Neustart": Chip + Liste kommen zurück.
|
||
main.queue_free()
|
||
await process_frame
|
||
await process_frame
|
||
main = (load("res://core/scenes/main.tscn") as PackedScene).instantiate()
|
||
root.add_child(main)
|
||
await process_frame
|
||
await process_frame
|
||
assets = main.assets_mod
|
||
var chip2 := assets._cat_buttons.get("zuletzt") as Button
|
||
_check(chip2 != null and chip2.visible, "nach Neu-Instanz: Zuletzt-Chip sichtbar")
|
||
_check(assets._recent_ids.size() == 2, "nach Neu-Instanz: 2 gemerkte IDs (ist %d)" % assets._recent_ids.size())
|
||
_check(str(assets._recent_ids[0]) == "box_002", "Reihenfolge hält den Neustart")
|