SceneHelper class for gdUnit4: load scenes into test tree with assert_node_exists, assert_signal_emitted, get_node_at helpers. 14 tests for GameState.apply_snapshot() covering v2+ fields. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
100 lines
3.6 KiB
GDScript
100 lines
3.6 KiB
GDScript
## Scene testing utilities for gdUnit4 tests.
|
|
##
|
|
## Loads a scene, instantiates it into the test suite's node tree,
|
|
## and provides helpers for node existence, signal, and node-path queries.
|
|
##
|
|
## Usage (from a GdUnitTestSuite subclass):
|
|
## var helper := SceneHelper.create(self, "res://scenes/main.tscn")
|
|
## helper.assert_node_exists("World")
|
|
## var world := helper.get_node_at("World")
|
|
## helper.monitor_signal(world, "ready")
|
|
## # ... trigger something ...
|
|
## helper.assert_signal_emitted(world, "ready")
|
|
##
|
|
## Design constraints (D-030): server-free, no running autoload dependencies.
|
|
class_name SceneHelper
|
|
extends RefCounted
|
|
|
|
var _suite # GdUnitTestSuite — untyped to avoid load-order dependency
|
|
var _scene: Node
|
|
# signal_key -> int. Key is "<node_instance_id>:<signal_name>" for uniqueness.
|
|
var _signal_hits: Dictionary = {}
|
|
|
|
|
|
## Load, instantiate, and attach a scene to the test suite's node tree.
|
|
## The scene node is registered for auto-free by gdUnit4.
|
|
## Returns a helper instance; fails the test if the scene cannot be loaded.
|
|
static func create(suite: GdUnitTestSuite, scene_path: String) -> SceneHelper:
|
|
var helper := SceneHelper.new()
|
|
helper._suite = suite
|
|
|
|
var packed: PackedScene = load(scene_path)
|
|
if packed == null:
|
|
suite.assert_that(packed).override_failure_message(
|
|
"SceneHelper: could not load scene at '%s'" % scene_path
|
|
).is_not_null()
|
|
return helper
|
|
|
|
helper._scene = packed.instantiate()
|
|
suite.auto_free(helper._scene)
|
|
suite.add_child(helper._scene)
|
|
return helper
|
|
|
|
|
|
## Returns the scene root node.
|
|
func scene() -> Node:
|
|
return _scene
|
|
|
|
|
|
## Assert that a node at node_path exists under the scene root.
|
|
## Fails the current test if the node is absent.
|
|
func assert_node_exists(node_path: String) -> void:
|
|
var node := _scene.get_node_or_null(NodePath(node_path))
|
|
_suite.assert_that(node).override_failure_message(
|
|
"SceneHelper: expected node at path '%s' — not found" % node_path
|
|
).is_not_null()
|
|
|
|
|
|
## Return the node at node_path under the scene root, or null if absent.
|
|
func get_node_at(node_path: String) -> Node:
|
|
return _scene.get_node_or_null(NodePath(node_path))
|
|
|
|
|
|
## Begin tracking emissions of signal_name on node.
|
|
## Must be called before the action that triggers the signal.
|
|
## Fails the test if node does not have the named signal.
|
|
func monitor_signal(node: Node, signal_name: String) -> void:
|
|
if not node.has_signal(signal_name):
|
|
_suite.assert_that(false).override_failure_message(
|
|
"SceneHelper: node '%s' has no signal '%s'" % [node.name, signal_name]
|
|
).is_true()
|
|
return
|
|
var key := _signal_key(node, signal_name)
|
|
_signal_hits[key] = 0
|
|
# Lambda accepts up to 4 positional args to tolerate signals with up to 4 params.
|
|
# GDScript default-param lambdas handle being called with fewer args correctly.
|
|
node.connect(signal_name, func(a := null, b := null, c := null, d := null):
|
|
_signal_hits[key] = _signal_hits.get(key, 0) + 1
|
|
)
|
|
|
|
|
|
## Assert that signal_name was emitted at least once since monitor_signal().
|
|
## Fails the test if monitor_signal() was not called first, or if count is zero.
|
|
func assert_signal_emitted(node: Node, signal_name: String) -> void:
|
|
var key := _signal_key(node, signal_name)
|
|
if not _signal_hits.has(key):
|
|
_suite.assert_that(false).override_failure_message(
|
|
"SceneHelper: '%s' was not monitored — call monitor_signal() first" % signal_name
|
|
).is_true()
|
|
return
|
|
var count: int = _signal_hits[key]
|
|
_suite.assert_int(count).override_failure_message(
|
|
"SceneHelper: signal '%s' on '%s' was not emitted (count=%d)" % [
|
|
signal_name, node.name, count
|
|
]
|
|
).is_greater(0)
|
|
|
|
|
|
static func _signal_key(node: Node, signal_name: String) -> String:
|
|
return "%d:%s" % [node.get_instance_id(), signal_name]
|