Fix GUI-Start-Fehler (Karte t_d3b93793): Terrain-Preset-Crash + command_pressed-Spam.
1. main.gd _build_right_panel: terrain_size_btn_medium.button_pressed=true feuerte toggled SOFORT synchron — der Handler griff auf terrain_radius_slider zu, der erst spaeter erzeugt wird → SCRIPT ERROR in _on_terrain_size_preset bei jedem GUI-Start. Fix: set_pressed_no_signal(true) (Slider startet unten passend mit 12 m) + Null-Check als Schutznetz im Handler. 2. main.gd _unhandled_key_input: event.command_pressed existiert an InputEventKey nicht (Godot-3-API-Relikt) → SCRIPT ERROR bei JEDER Taste, Shortcuts tot. Fix: is_command_or_control_pressed() (Ctrl auf Linux/Windows, Cmd auf macOS). 3. Neuer Regressionstest tests/test_gui_smoke.gd: instanziert die ECHTE Main-UI headless (deckt Runtime-UI-Pfade ab, die die logik-only-Suite nie sah): Panel-Bau ueberlebt _ready, Presets initial/klickbar (Slider+Label), Strg+Z/Strg+Y wirken end-to-end, leere History = freundliche Meldung. Suite: 334 + 20 Checks gruen.
This commit is contained in:
parent
5dd37e32d7
commit
87e7a96fd5
3 changed files with 161 additions and 4 deletions
|
|
@ -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_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_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_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_small)
|
||||||
size_row.add_child(terrain_size_btn_medium)
|
size_row.add_child(terrain_size_btn_medium)
|
||||||
size_row.add_child(terrain_size_btn_large)
|
size_row.add_child(terrain_size_btn_large)
|
||||||
|
|
@ -970,8 +973,12 @@ func _refresh_history_buttons() -> void:
|
||||||
func _unhandled_key_input(event: InputEvent) -> void:
|
func _unhandled_key_input(event: InputEvent) -> void:
|
||||||
if not (event is InputEventKey) or not event.is_pressed() or event.is_echo():
|
if not (event is InputEventKey) or not event.is_pressed() or event.is_echo():
|
||||||
return
|
return
|
||||||
var key := (event as InputEventKey).keycode
|
var key_evt := event as InputEventKey
|
||||||
var ctrl: bool = event.ctrl_pressed or event.command_pressed
|
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:
|
match key:
|
||||||
KEY_Z:
|
KEY_Z:
|
||||||
if ctrl:
|
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:
|
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
|
terrain_radius_slider.value = radius_m
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
147
tests/test_gui_smoke.gd
Normal file
147
tests/test_gui_smoke.gd
Normal file
|
|
@ -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")
|
||||||
1
tests/test_gui_smoke.gd.uid
Normal file
1
tests/test_gui_smoke.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://beenyx2nncelt
|
||||||
Loading…
Reference in a new issue