extends SceneTree ## Editor-Test (headless): GUI-Smoke — Karte t_d3b93793. ## Ausführen: godot --headless --path . --script res://tests/test_gui_smoke.gd ## Exit 0 = alles grün. ## ## ANDERS als die übrigen Tests instanziert dieser Test die ECHTE Main-UI ## (scenes/main.tscn → src/ui/main.gd) und deckt damit Runtime-UI-Pfade ab, ## die die logik-only-Suite nie sieht (GUI-Start-Befund 15.10.: Terrain-Preset- ## Crash beim Panel-Bau + `command_pressed`-Spam bei jeder Taste — beides ## headless unsichtbar, weil das UI nie instanziert wurde). ## ## Was hier geprüft wird: ## 1. Panel-Bau überlebt _ready (kein abgebrochener _build_right_panel). ## 2. Terrain-Größen-Presets sind initial korrekt belegt (Mittel = 12 m) ## und klickbar (Klick stellt Radius-Slider + Label um). ## 3. Keyboard-Shortcut-Pfad (_unhandled_key_input) läuft ohne ## Property-Fehler und setzt Strg+Z wirklich in Undo um. var failures := 0 var checks := 0 var main: Control func _initialize() -> void: _run_all.call_deferred() 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_ui_built() _test_terrain_presets() _test_shortcut_undo() _test_terrain_hotkeys() _finish() 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) ## Abnahme 1: Der komplette UI-Bau (_ready → _build_ui → _build_right_panel) ## läuft durch — genau dort crashte der Terrain-Preset-Handler (main.gd:659). func _test_ui_built() -> void: print("Testgruppe: UI-Bau überlebt _ready") _check(main != null, "Main-Instanz existiert") _check(main.terrain_size_btn_small != null, "Preset-Button Klein gebaut") _check(main.terrain_size_btn_medium != null, "Preset-Button Mittel gebaut") _check(main.terrain_size_btn_large != null, "Preset-Button Groß gebaut") _check(main.terrain_radius_slider != null, "Radius-Slider gebaut") _check(main.undo_btn != null and main.redo_btn != null, "Undo/Redo-Buttons gebaut (Panel-Bau lief komplett durch)") ## Abnahme 2a: Initialzustand ohne Nebeneffekt-Callback (set_pressed_no_signal): ## Mittel aktiv, Slider passend 12 m. func _test_terrain_presets() -> void: print("Testgruppe: Terrain-Größen-Presets") _check(main.terrain_size_btn_medium.button_pressed, "Mittel ist initial aktiv") _check(is_equal_approx(float(main.terrain_radius_slider.value), 12.0), "Slider startet bei 12 m (passend zum aktiven Preset)") # Klick auf Groß: button_pressed=true feuert toggled → Handler muss # jetzt (mit existierendem Slider) Radius + Label setzen. main.terrain_size_btn_large.button_pressed = true _check(is_equal_approx(float(main.terrain_radius_slider.value), 32.0), "Klick auf Groß setzt Slider auf 32 m") _check(str(main.terrain_radius_label.text).contains("32"), "Pinsel-Label zeigt 32 m") _check(main.terrain_size_btn_medium.button_pressed == false, "ButtonGroup: Mittel beim Klick auf Groß deaktiviert") # Klick auf Klein (Position-Klick simuliert echten User-Klick). main.terrain_size_btn_small.button_pressed = true _check(is_equal_approx(float(main.terrain_radius_slider.value), 4.0), "Klick auf Klein setzt Slider auf 4 m") func _press_key(keycode: Key, ctrl: bool) -> InputEventKey: var ev := InputEventKey.new() ev.keycode = keycode ev.physical_keycode = keycode ev.pressed = true ev.ctrl_pressed = ctrl return ev ## Abnahme 2b + 3: Der Shortcut-Pfad muss ohne Property-Fehler durchlaufen ## (vorher crashte `event.command_pressed` bei JEDER Taste) und Strg+Z ## tatsächlich in ein Undo übersetzen. func _test_shortcut_undo() -> void: print("Testgruppe: Keyboard-Shortcuts (Strg+Z)") # Echte History-Aktion in die laufende Editor-History legen. var before: Dictionary = main.history.snapshot(["objects"]) (main.doc["objects"] as Array).append( {"id": "test_objekt", "pos": [10.0, 0.0, 10.0], "scale": 1.0, "rot_y_deg": 0.0}) main.history.commit("Test: Objekt platzieren", before, main.history.snapshot(["objects"])) _check((main.doc["objects"] as Array).size() == 1, "Test-Objekt platziert") # Karte 1 (t_c46d31c2) hat F belegt: F zentriert die Auswahl — ohne # Auswahl gibt es den freundlichen Hinweis (kein Crash, kein Wirken). main._unhandled_key_input(_press_key(KEY_F, false)) _check(str(main.status_label.text).contains("Nichts ausgewählt"), "F ohne Auswahl: freundlicher Hinweis, läuft sauber durch") main._unhandled_key_input(_press_key(KEY_Z, true)) _check((main.doc["objects"] as Array).is_empty(), "Strg+Z hat das Objekt entfernt (Undo wirkt)") _check(str(main.status_label.text).contains("Rückgängig gemacht"), "Statusleiste meldet das Undo") main._unhandled_key_input(_press_key(KEY_Y, true)) _check((main.doc["objects"] as Array).size() == 1, "Strg+Y stellt wieder her (Redo wirkt)") # Leere History + Strg+Z: freundliche Meldung statt Crash. # Erst das Redo-Ergebnis wieder weg-undoen, DANN ist die History leer. main._unhandled_key_input(_press_key(KEY_Z, true)) _check((main.doc["objects"] as Array).is_empty(), "Strg+Z nach Redo: History leer") main._unhandled_key_input(_press_key(KEY_Z, true)) _check(str(main.status_label.text).contains("Nichts zum Rückgängigmachen"), "Strg+Z bei leerer History: Statusmeldung, kein Absturz") ## Karte 2/3 (t_55257792): Terrain-Ziffern 1–4 + [ / ] am ECHTEN main — ## Buttons/Slider/Label müssen mitwandern, Ziffern außerhalb des Terrain- ## Werkzeugs dürfen NICHTS tun (Konfliktfreiheit). func _test_terrain_hotkeys() -> void: print("Testgruppe: Terrain-Pinsel per Tastatur (1–4, [ und ])") main._set_tool_mode("terrain") _check(str(main.get("tool_mode")) == "terrain", "Terrain-Modus aktiv") # Ziffer 2 = Senken: Modus-Button + Pinsel-Modus wandern mit. main._unhandled_key_input(_press_key(KEY_2, false)) _check(main.terrain_mode_lower.button_pressed, "Ziffer 2: Button „Senken“ aktiv") _check(int(main.terrain_tool.get("brush_mode")) == TerrainField.Mode.LOWER, "Ziffer 2: Pinsel-Modus = Senken") # Ziffer 4 = Malen: Palette sichtbar, Modus wirklich PAINT (alter # set_brush_mode-Clamp hätte das still zu Glätten degradiert). main._unhandled_key_input(_press_key(KEY_4, false)) _check(int(main.terrain_tool.get("brush_mode")) == TerrainField.Mode.PAINT, "Ziffer 4: Pinsel-Modus = Malen (Clamp-Fix)") _check(main.terrain_palette.visible, "Ziffer 4: Material-Palette sichtbar") # Ziffer 1 zurück: Palette verschwindet wieder. main._unhandled_key_input(_press_key(KEY_1, false)) _check(int(main.terrain_tool.get("brush_mode")) == TerrainField.Mode.RAISE, "Ziffer 1: Pinsel-Modus = Heben") _check(not main.terrain_palette.visible, "Ziffer 1: Material-Palette weg") # [ / ] : Preset-Zyklus Klein(4)/Mittel(12)/Groß(32), Slider + Label mit. # Slider steht nach dem Klein-Klick oben noch auf 4 m — die Kette startet # also von Klein: ] ] ] = 12 → 32 → wrap 4, [ = 32, [ [ = 12 → 4. main._unhandled_key_input(_press_key(KEY_BRACKETRIGHT, false)) _check(is_equal_approx(float(main.terrain_radius_slider.value), 12.0), "] ab 4 m: Slider auf 12 m (Mittel)") _check(str(main.terrain_radius_label.text).contains("12"), "]: Label zeigt 12 m") _check(main.terrain_size_btn_medium.button_pressed, "]: Preset-Button Mittel aktiv") main._unhandled_key_input(_press_key(KEY_BRACKETRIGHT, false)) _check(is_equal_approx(float(main.terrain_radius_slider.value), 32.0), "] ab 12 m: Slider auf 32 m (Groß)") _check(str(main.terrain_radius_label.text).contains("32"), "]: Label zeigt 32 m") _check(main.terrain_size_btn_large.button_pressed, "]: Preset-Button Groß aktiv") main._unhandled_key_input(_press_key(KEY_BRACKETRIGHT, false)) _check(is_equal_approx(float(main.terrain_radius_slider.value), 4.0), "] ab 32 m: Zyklus wrappt auf Klein (4 m)") main._unhandled_key_input(_press_key(KEY_BRACKETLEFT, false)) _check(is_equal_approx(float(main.terrain_radius_slider.value), 32.0), "[ ab 4 m: wrappt zurück auf Groß (32 m)") main._unhandled_key_input(_press_key(KEY_BRACKETLEFT, false)) main._unhandled_key_input(_press_key(KEY_BRACKETLEFT, false)) _check(is_equal_approx(float(main.terrain_radius_slider.value), 4.0), "[ zweimal ab 32 m: 12 → 4 (Klein)") # Konfliktfreiheit: außerhalb des Terrain-Werkzeugs tun Ziffern/[ NICHTS. main._set_tool_mode("objects") var slider_vorher: float = main.terrain_radius_slider.value main._unhandled_key_input(_press_key(KEY_2, false)) main._unhandled_key_input(_press_key(KEY_BRACKETRIGHT, false)) _check(is_equal_approx(float(main.terrain_radius_slider.value), slider_vorher), "Ziffern/[ im Objekt-Modus: Pinsel bleibt unangetastet") _check(int(main.terrain_tool.get("brush_mode")) == TerrainField.Mode.RAISE, "Ziffern im Objekt-Modus: Pinsel-Modus bleibt unangetastet")