scopedvr-map-editor/tests/test_assets_panel.gd
slyphax 491a47d3f1 Editor-Bibliothek auf das Military-Desert-Pack umgestellt
- 423 Objekte in 19 deutschen Kategorien (Panzer, Radfahrzeuge, Flugzeuge, Gebaeude,
  Zelte, Tuerme, Mauern, Sperren, Kisten, Technik, Felsen, Boden, Decals ...);
  Gruppen "Bauwerke" und "Kisten & Barrieren" samt Test-Asset-Namenslisten entfernt
- LibraryBrowser: Gruppen/Namen aus dem Manifest (kategorie, name_de), unbekannte
  Kategorien bekommen automatisch eine eigene Gruppe
- Thumbs neu, library/ synchronisiert (inkl. neuer Kollision)
- Neue Identitaetsprobe: Kollision ALLER Objekte Editor = Client = Server
- Tests auf Pack-Objekte umgestellt

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-25 16:31:28 +01:00

360 lines
16 KiB
GDScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 # src/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://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://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")