- R = Auswahl 90° drehen (Szene um Auswahl-Mitte, Bauen-Bauteil um Fußpunkt, Gruppe komplett; Umschalt+R = -90°), Mausrad-Feindrehen 15° bleibt - Pfeiltasten = 2-m-Raster-Verschiebung, Richtung = Kamera-Blickrichtung auf nächste Weltachse gerastet (MoveMath.nudge_delta, pure) - Bild↑/↓ = ±0,25 m auf der GANZEN Auswahl (EIN Undo), Strg+Bild↑ = Fußpunkt aufs Terrain/Boden absetzen - G = Greifen (Blender-Semantik): Auswahl hängt an der Maus, LMB setzt mit Raster-Snap, Esc rollt byte-genau zurück (ShortcutPolicy cancel_grab) - Terrain-Pinsel: [ / ] = Preset-Zyklus Klein/Mittel/Groß (Slider/Label/Preset-Buttons wandern mit), 1-4 = Heben/Senken/Glätten/Malen (PAINT-Clamp-Fix, Palette mit Malen sichtbar); Ziffern NUR im Terrain-Werkzeug - Strg halten = temporärer Snap-Zwang beim Bewegen/Greifen/Platzieren - Tests: test_shortcuts.gd 157 Checks (R byte-genau, Pfeil-Raster, Lift-Stufen, Boden-Snap, Greifen+Esc, Pinsel-Zyklus, History-Eintrag pro Operation), test_gui_smoke.gd erweitert (38 Checks) und in run_tests.sh aufgenommen; F-Regression seit Karte 1 damit abgedeckt - Suite 9/9 grün (696 Checks), Builder-Export 21/0, Parse-Check 11 Dateien grün
207 lines
9 KiB
GDScript
207 lines
9 KiB
GDScript
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")
|