Files
settled-reach/client/tests/util/scene_helper.gd
T
jpmschweitzerandClaude Opus 4.6 a7541aa27b test(client): scene testing utilities and GameState tests (#206)
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>
2026-02-25 11:45:39 +01:00

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]