scopedvr-map-editor/tests/test_pointer_router.gd
2026-11-01 19:34:05 +01:00

156 lines
5.6 KiB
GDScript

extends SceneTree
## Editor-Test (headless): Werkzeuge laufen ohne Maus. Dieselben Züge einmal als
## echte Maus-Ereignisse und einmal als PointerEvent müssen dieselbe Karte ergeben.
## Ausführen: godot --headless --path . --script res://tests/test_pointer_router.gd
const OID := "box_001"
var failures := 0
var checks := 0
func _initialize() -> void:
_run.call_deferred()
func _check(cond: bool, label: String) -> void:
checks += 1
if cond:
print(" ok: ", label)
else:
failures += 1
print(" FAIL: ", label)
func _run() -> void:
print("== Objekte: Maus gegen PointerEvent ==")
var maus := await _objekt_zuege(true)
var zeiger := await _objekt_zuege(false)
# Neue Karten spiegeln: jedes gesetzte Objekt bekommt einen Zwilling.
_check(int(zeiger["anzahl"]) == 4, "Zeiger: zwei Objekte mit Zwillingen gesetzt (%d)" % int(zeiger["anzahl"]))
_check(int(zeiger["verschoben"]) == 1, "Zeiger: Ziehen hat ein Objekt bewegt")
_check(int(zeiger["undo"]) == 3, "Zeiger: drei Undo-Schritte")
_check(str(maus["objekte"]) == str(zeiger["objekte"]), "Maus und Zeiger ergeben dieselben Objekte")
_check(int(maus["undo"]) == int(zeiger["undo"]), "Maus und Zeiger ergeben gleich viele Undo-Schritte")
print("== Terrain und Abbruch ==")
await _terrain_und_abbruch()
if failures == 0:
print("ALL OK: %d checks" % checks)
quit(0)
else:
print("FAILED: %d/%d checks" % [failures, checks])
quit(1)
func _neu() -> Control:
var main: Control = (load("res://core/scenes/main.tscn") as PackedScene).instantiate()
root.add_child(main)
await process_frame
await process_frame
main.camera.global_position = Vector3(0.0, 200.0, 0.0)
main.camera.look_at(Vector3.ZERO, Vector3.FORWARD)
main.call("_set_tool_mode", "objects")
await process_frame
return main
func _bild(main: Control, welt: Vector3) -> Vector2:
return main.camera.unproject_position(welt)
## Ein Zug: als Mausereignis durch den echten Viewport oder direkt über den Router.
func _zug(main: Control, ueber_maus: bool, kind: PointerEvent.Kind, pos: Vector2, shift := false) -> void:
if ueber_maus:
var ev: InputEvent
if kind == PointerEvent.Kind.MOVE:
var mm := InputEventMouseMotion.new()
mm.position = pos
ev = mm
else:
var mb := InputEventMouseButton.new()
mb.position = pos
mb.button_index = MOUSE_BUTTON_LEFT
mb.pressed = kind == PointerEvent.Kind.PRESS
mb.shift_pressed = shift
ev = mb
main.camera.get_viewport().push_input(ev)
else:
var p := PointerEvent.make(kind, pos)
p.shift = shift
PointerRouter.dispatch(main, p)
await process_frame
func _objekt_zuege(ueber_maus: bool) -> Dictionary:
var main := await _neu()
var davor: int = (main.doc.get("objects", []) as Array).size()
var a := Vector3(10.0, 0.0, 10.0)
var b := Vector3(-20.0, 0.0, 5.0)
var ziel := Vector3(30.0, 0.0, -15.0)
# Serienmodus: Umschalt setzt und behält das Objekt in der Hand.
main.interaction.start_placement(OID)
await _zug(main, ueber_maus, PointerEvent.Kind.MOVE, _bild(main, a))
await _zug(main, ueber_maus, PointerEvent.Kind.PRESS, _bild(main, a), true)
await _zug(main, ueber_maus, PointerEvent.Kind.RELEASE, _bild(main, a), true)
await _zug(main, ueber_maus, PointerEvent.Kind.MOVE, _bild(main, b))
await _zug(main, ueber_maus, PointerEvent.Kind.PRESS, _bild(main, b))
await _zug(main, ueber_maus, PointerEvent.Kind.RELEASE, _bild(main, b))
main.interaction.cancel_placement()
main.interaction.clear_selection()
await process_frame
var objekte: Array = main.doc.get("objects", [])
var vorher: Array = (objekte[0]["pos"] as Array).duplicate()
# Greifen, ziehen, loslassen. Die Mitte des Objekts statt des Gizmos treffen:
# ohne Auswahl ist das Gizmo unsichtbar.
var start := Vector3(float(vorher[0]), float(vorher[1]), float(vorher[2]))
await _zug(main, ueber_maus, PointerEvent.Kind.PRESS, _bild(main, start))
main.gizmo.visible = false
await _zug(main, ueber_maus, PointerEvent.Kind.MOVE, _bild(main, ziel))
await _zug(main, ueber_maus, PointerEvent.Kind.RELEASE, _bild(main, ziel))
var ergebnis := {
"anzahl": objekte.size() - davor,
"objekte": JSON.stringify(objekte),
"verschoben": 1 if str(objekte[0]["pos"]) != str(vorher) else 0,
"undo": (main.history.index + 1),
}
main.queue_free()
await process_frame
return ergebnis
func _terrain_und_abbruch() -> void:
var main := await _neu()
main.call("_set_tool_mode", "terrain")
await process_frame
var hoehen_vorher := str(main.doc["terrain"].get("heightmap_r16_b64", ""))
var undo_vorher: int = main.history.index + 1
var mitte := _bild(main, Vector3.ZERO)
_check(not PointerRouter.dispatch(main, PointerEvent.make(PointerEvent.Kind.MOVE, mitte)),
"Terrain: Bewegen wird nicht verschluckt")
_check(PointerRouter.dispatch(main, PointerEvent.make(PointerEvent.Kind.PRESS, mitte)),
"Terrain: Drücken nimmt der Pinsel")
for i in 6:
await process_frame
_check(PointerRouter.dispatch(main, PointerEvent.make(PointerEvent.Kind.RELEASE, mitte)),
"Terrain: Loslassen nimmt der Pinsel")
await process_frame
_check(str(main.doc["terrain"].get("heightmap_r16_b64", "")) != hoehen_vorher, "Terrain: Strich ändert die Höhen")
_check((main.history.index + 1) == undo_vorher + 1, "Terrain: ein Strich, ein Undo-Schritt")
main.call("_set_tool_mode", "objects")
await process_frame
_check(not PointerRouter.dispatch(main, PointerEvent.make(PointerEvent.Kind.CANCEL)),
"Esc ohne laufenden Zug bleibt frei fürs Hauptfenster")
var ohne := MousePointer.from_input(_rechts())
_check(ohne == null, "Rechte Maustaste bleibt beim Flug")
main.queue_free()
await process_frame
func _rechts() -> InputEvent:
var mb := InputEventMouseButton.new()
mb.button_index = MOUSE_BUTTON_RIGHT
mb.pressed = true
return mb