diff --git a/src/ui/main.gd b/src/ui/main.gd index f8164de..e3a079f 100644 --- a/src/ui/main.gd +++ b/src/ui/main.gd @@ -656,7 +656,10 @@ func _build_right_panel() -> PanelContainer: terrain_size_btn_small = _terrain_size_button("Klein\n(4 m)", size_group, 4.0) terrain_size_btn_medium = _terrain_size_button("Mittel\n(12 m)", size_group, 12.0) terrain_size_btn_large = _terrain_size_button("Groß\n(32 m)", size_group, 32.0) - terrain_size_btn_medium.button_pressed = true + # Kein Signal: Der Handler greift auf terrain_radius_slider zu, der erst + # unten erzeugt wird — beim Panel-Bau wäre das ein Crash (GUI-Start-Bug + # 15.10.). Der Slider startet unten passend mit 12 m (= Mittel). + terrain_size_btn_medium.set_pressed_no_signal(true) size_row.add_child(terrain_size_btn_small) size_row.add_child(terrain_size_btn_medium) size_row.add_child(terrain_size_btn_large) @@ -970,8 +973,12 @@ func _refresh_history_buttons() -> void: func _unhandled_key_input(event: InputEvent) -> void: if not (event is InputEventKey) or not event.is_pressed() or event.is_echo(): return - var key := (event as InputEventKey).keycode - var ctrl: bool = event.ctrl_pressed or event.command_pressed + var key_evt := event as InputEventKey + var key := key_evt.keycode + # InputEventKey hat KEIN `command_pressed` (Godot-3-API-Relikt — crashete + # bei jeder Taste, GUI-Start-Befund 15.10.). Die Godot-4-Ersatz-API ist + # is_command_or_control_pressed(): Ctrl auf Linux/Windows, Cmd auf macOS. + var ctrl: bool = key_evt.is_command_or_control_pressed() match key: KEY_Z: if ctrl: @@ -1236,7 +1243,9 @@ func _terrain_size_button(text: String, group: ButtonGroup, radius_m: float) -> func _on_terrain_size_preset(pressed: bool, radius_m: float) -> void: - if pressed: + # Null-Check als Schutznetz: beim Panel-Bau existiert der Slider noch + # nicht (Programmatic-Pressed darf den Handler nie früh treffen). + if pressed and terrain_radius_slider != null: terrain_radius_slider.value = radius_m diff --git a/tests/test_gui_smoke.gd b/tests/test_gui_smoke.gd new file mode 100644 index 0000000..01c619a --- /dev/null +++ b/tests/test_gui_smoke.gd @@ -0,0 +1,147 @@ +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() + + _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") + + # Vorher-Zustand für die unbehandelte Taste merken. + var status_vorher := str(main.status_label.text) + + main._unhandled_key_input(_press_key(KEY_F, false)) + _check(str(main.status_label.text) == status_vorher, + "unbehandelte Taste (F) ändert nichts, läuft aber 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") diff --git a/tests/test_gui_smoke.gd.uid b/tests/test_gui_smoke.gd.uid new file mode 100644 index 0000000..ffd0251 --- /dev/null +++ b/tests/test_gui_smoke.gd.uid @@ -0,0 +1 @@ +uid://beenyx2nncelt