gdharness 1.3.46 → 1.3.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cli.js +186 -49
- package/build/godot/addons/gdharness_editor/animation_reads.gd +20 -0
- package/build/godot/addons/gdharness_editor/file_reads.gd +6 -0
- package/build/godot/addons/gdharness_editor/scene_uids.gd +25 -13
- package/build/godot/addons/gdharness_editor/tools/animation_states.gd +20 -6
- package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +19 -0
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +23 -4
- package/build/godot/addons/gdharness_runtime/runtime_hover.gd +9 -7
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +7 -0
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +90 -30
- package/build/godot/addons/gdharness_runtime/runtime_words.gd +1 -1
- package/build/godot/operations/animation_reads.gd +20 -0
- package/build/godot/operations/dependencies.gd +65 -9
- package/build/godot/operations/export_presets.gd +32 -5
- package/build/godot/operations/file_reads.gd +6 -0
- package/build/godot/operations/import_pipeline.gd +10 -2
- package/build/godot/operations/project_config.gd +35 -0
- package/build/godot/operations/resource_files.gd +1 -2
- package/build/godot/tests/listed.gd +25 -0
- package/build/keeper.js +1308 -1026
- package/package.json +1 -1
|
@@ -121,6 +121,26 @@ static func _runnable(config: ConfigFile, section: String) -> bool:
|
|
|
121
121
|
return GdhRead.as_bool(config.get_value(section, "runnable", false))
|
|
122
122
|
|
|
123
123
|
|
|
124
|
+
## The name of the preset [param platform] runs, or "" when it has none: the one its section names,
|
|
125
|
+
## or in a file saved before that section, the last preset of the platform keeping the key, which is
|
|
126
|
+
## the one the editor ends up running. Read from the section alone, a file of that age had no
|
|
127
|
+
## runnable preset, so a preset added for its platform took the place of the one it had.
|
|
128
|
+
static func _runnable_name(config: ConfigFile, platform: String) -> String:
|
|
129
|
+
if config.has_section_key(RUNNABLE_SECTION, platform):
|
|
130
|
+
return str(config.get_value(RUNNABLE_SECTION, platform))
|
|
131
|
+
var named: String = ""
|
|
132
|
+
var at: int = 0
|
|
133
|
+
while config.has_section("preset." + str(at)):
|
|
134
|
+
var section: String = "preset." + str(at)
|
|
135
|
+
if (
|
|
136
|
+
str(config.get_value(section, "platform", "")) == platform
|
|
137
|
+
and GdhRead.as_bool(config.get_value(section, "runnable", false))
|
|
138
|
+
):
|
|
139
|
+
named = str(config.get_value(section, "name", ""))
|
|
140
|
+
at += 1
|
|
141
|
+
return named
|
|
142
|
+
|
|
143
|
+
|
|
124
144
|
## Adds an export preset to export_presets.cfg, written with the keys the editor's Project > Export
|
|
125
145
|
## writes for a new one, and every option in [code]known_options[/code] (what
|
|
126
146
|
## [method export_options] answers for the platform) at its default. The editor also writes the
|
|
@@ -160,7 +180,7 @@ func add_export_preset(params: Dictionary) -> Dictionary:
|
|
|
160
180
|
)
|
|
161
181
|
index += 1
|
|
162
182
|
# A platform with no preset to run takes the new one, as the editor's dialog does.
|
|
163
|
-
var runnable: bool =
|
|
183
|
+
var runnable: bool = _runnable_name(config, platform).is_empty()
|
|
164
184
|
if runnable:
|
|
165
185
|
config.set_value(RUNNABLE_SECTION, platform, preset_name)
|
|
166
186
|
|
|
@@ -609,6 +629,7 @@ func remove_export_preset(params: Dictionary) -> Dictionary:
|
|
|
609
629
|
var index: int = found["index"]
|
|
610
630
|
var section: String = found["section"]
|
|
611
631
|
var platform: String = str(config.get_value(section, "platform", ""))
|
|
632
|
+
var was_runnable: bool = _runnable_name(config, platform) == preset_name
|
|
612
633
|
var last: int = index
|
|
613
634
|
while config.has_section("preset." + str(last + 1)):
|
|
614
635
|
last += 1
|
|
@@ -622,11 +643,17 @@ func remove_export_preset(params: Dictionary) -> Dictionary:
|
|
|
622
643
|
if keeping != OK:
|
|
623
644
|
return _log.failure("Failed to save %s: %s" % [CREDENTIALS_FILE, error_string(keeping)])
|
|
624
645
|
answer["credentials"] = CREDENTIALS_FILE
|
|
625
|
-
if
|
|
626
|
-
config.
|
|
627
|
-
|
|
646
|
+
if was_runnable:
|
|
647
|
+
if config.has_section_key(RUNNABLE_SECTION, platform):
|
|
648
|
+
config.set_value(RUNNABLE_SECTION, platform, null)
|
|
649
|
+
# A file saved before the section can mark another preset of the platform too, which the
|
|
650
|
+
# editor then runs.
|
|
651
|
+
var kept: String = _runnable_name(config, platform)
|
|
652
|
+
answer["runnableNow"] = kept
|
|
653
|
+
if kept.is_empty():
|
|
654
|
+
answer["runnableNow"] = null
|
|
628
655
|
var at: int = 0
|
|
629
|
-
while config.has_section("preset." + str(at)):
|
|
656
|
+
while kept.is_empty() and config.has_section("preset." + str(at)):
|
|
630
657
|
if str(config.get_value("preset." + str(at), "platform", "")) == platform:
|
|
631
658
|
var next: String = str(config.get_value("preset." + str(at), "name", ""))
|
|
632
659
|
config.set_value(RUNNABLE_SECTION, platform, next)
|
|
@@ -356,6 +356,12 @@ static func _entry(
|
|
|
356
356
|
var data: Dictionary = {
|
|
357
357
|
"name": node["name"], "type": node["type"], "path": path, "children": children,
|
|
358
358
|
}
|
|
359
|
+
var given_values: Dictionary = node["properties"]
|
|
360
|
+
var attached: Variant = given_values.get("script")
|
|
361
|
+
if attached is Script:
|
|
362
|
+
var script: Script = attached
|
|
363
|
+
if not str(script.get_global_name()).is_empty():
|
|
364
|
+
data["scriptClass"] = str(script.get_global_name())
|
|
359
365
|
# The root's file is the scene being read, so only another node's names a second scene.
|
|
360
366
|
if path != "." and node.has("instanceOf"):
|
|
361
367
|
data["instanceOf"] = node["instanceOf"]
|
|
@@ -212,7 +212,7 @@ func set_import_options(params: Dictionary) -> Dictionary:
|
|
|
212
212
|
var refused: Array[String] = []
|
|
213
213
|
for key: Variant in options:
|
|
214
214
|
var name: String = str(key)
|
|
215
|
-
var held: Variant = config
|
|
215
|
+
var held: Variant = _held(config, name)
|
|
216
216
|
var given: Variant = _values.deserialize_value(options[key])
|
|
217
217
|
var value: Variant = _as_option(_values.fitted(options[key], typeof(held)), held)
|
|
218
218
|
if value is Dictionary:
|
|
@@ -248,7 +248,7 @@ func set_import_options(params: Dictionary) -> Dictionary:
|
|
|
248
248
|
var unchanged_keys: Array[String] = []
|
|
249
249
|
var changes: Dictionary = {}
|
|
250
250
|
for name: String in fitted_values:
|
|
251
|
-
var held: Variant = config
|
|
251
|
+
var held: Variant = _held(config, name)
|
|
252
252
|
var value: Variant = fitted_values[name]
|
|
253
253
|
if held != null and typeof(held) == typeof(value) and held == value:
|
|
254
254
|
unchanged_keys.append(name)
|
|
@@ -275,6 +275,14 @@ func set_import_options(params: Dictionary) -> Dictionary:
|
|
|
275
275
|
return answer
|
|
276
276
|
|
|
277
277
|
|
|
278
|
+
## What the option [param name] holds in [param config]'s params, or null when it holds none. Asked
|
|
279
|
+
## with a default of null, the engine prints an error for a key the file does not have.
|
|
280
|
+
static func _held(config: ConfigFile, name: String) -> Variant:
|
|
281
|
+
if not config.has_section_key("params", name):
|
|
282
|
+
return null
|
|
283
|
+
return config.get_value("params", name)
|
|
284
|
+
|
|
285
|
+
|
|
278
286
|
## [param given] as the option holds it. Every JSON number arrives as a float, and an enum option
|
|
279
287
|
## such as compress/mode written as 1.0 is kept that way by the import, so a whole number becomes
|
|
280
288
|
## an int unless the option already holds a float.
|
|
@@ -413,6 +413,9 @@ func add_autoload(params: Dictionary) -> Dictionary:
|
|
|
413
413
|
return _log.failure(
|
|
414
414
|
GdhFileWalk.new().with_nearest_files("Autoload file does not exist: " + path, path)
|
|
415
415
|
)
|
|
416
|
+
var unloadable: String = _not_an_autoload(path)
|
|
417
|
+
if not unloadable.is_empty():
|
|
418
|
+
return _log.failure(unloadable)
|
|
416
419
|
|
|
417
420
|
# Through ProjectSettings so the file is saved the way the editor saves it, which includes
|
|
418
421
|
# putting Godot's header back and dropping any comment somebody wrote into the file by hand.
|
|
@@ -453,6 +456,38 @@ func add_autoload(params: Dictionary) -> Dictionary:
|
|
|
453
456
|
return answer
|
|
454
457
|
|
|
455
458
|
|
|
459
|
+
## Why the file at [param path] cannot be an autoload, or empty when it can, as the editor's
|
|
460
|
+
## Autoload
|
|
461
|
+
## tab judges it: a scene, or a script whose instances are nodes. Written anyway, a script extending
|
|
462
|
+
## RefCounted saved fine and every start of the game then failed to instantiate it. A script that
|
|
463
|
+
## does not compile is let through, since it may be one being written, and its errors are its own.
|
|
464
|
+
static func _not_an_autoload(path: String) -> String:
|
|
465
|
+
var refused: String = "%s is %s, and an autoload is a scene or a script extending Node"
|
|
466
|
+
if not ResourceLoader.exists(path):
|
|
467
|
+
return (refused + "; nothing was written.") % [path, "a file Godot does not load"]
|
|
468
|
+
var loaded: Resource = load(path)
|
|
469
|
+
if loaded is PackedScene:
|
|
470
|
+
return ""
|
|
471
|
+
if not loaded is Script:
|
|
472
|
+
var kind: String = "nothing Godot could load"
|
|
473
|
+
if loaded != null:
|
|
474
|
+
kind = "a " + loaded.get_class()
|
|
475
|
+
return (refused + "; nothing was written.") % [path, kind]
|
|
476
|
+
var script: Script = loaded
|
|
477
|
+
if not script.can_instantiate():
|
|
478
|
+
return ""
|
|
479
|
+
var base: StringName = script.get_instance_base_type()
|
|
480
|
+
if ClassDB.is_parent_class(base, "Node"):
|
|
481
|
+
return ""
|
|
482
|
+
return (
|
|
483
|
+
(
|
|
484
|
+
"%s extends %s, which is not a Node, and Godot instantiates an autoload under the root "
|
|
485
|
+
+ "as a node, so every start of the game would fail to load it; nothing was written."
|
|
486
|
+
)
|
|
487
|
+
% [path, base]
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
|
|
456
491
|
## Why [param name] cannot name an autoload, or empty when it can. The editor's Autoload tab refuses
|
|
457
492
|
## the same names: an autoload named after an engine class or a built-in type takes that name in
|
|
458
493
|
## every script, so an autoload called Input made every Input.is_action_pressed call the autoload.
|
|
@@ -36,10 +36,9 @@ func get_uid(params: Dictionary) -> Dictionary:
|
|
|
36
36
|
var answer: Dictionary = { "file": file_path, "absolute_path": absolute_path }
|
|
37
37
|
var found: Dictionary = uid_of(file_path)
|
|
38
38
|
if found.is_empty():
|
|
39
|
-
answer["
|
|
39
|
+
answer["uid"] = null
|
|
40
40
|
answer["message"] = _why_none(file_path)
|
|
41
41
|
return answer
|
|
42
|
-
answer["exists"] = true
|
|
43
42
|
answer.merge(found)
|
|
44
43
|
return answer
|
|
45
44
|
|
|
@@ -357,6 +357,9 @@ class GdhRunner:
|
|
|
357
357
|
var case_began: bool = false
|
|
358
358
|
## The suite running, which a case's own event does not carry.
|
|
359
359
|
var suite_running: String = ""
|
|
360
|
+
## Whether gdUnit4 has minimised the window, or is about to, and it is not back yet: it does as
|
|
361
|
+
## its runner starts, and after every suite.
|
|
362
|
+
var minimise_pending: bool = true
|
|
360
363
|
|
|
361
364
|
## As the run ends, the marker of the walk's newest end, and every script held past the walk, on
|
|
362
365
|
## an engine whose walk can stop part way ([GdhShutdownWalk]).
|
|
@@ -446,7 +449,11 @@ class GdhRunner:
|
|
|
446
449
|
GdUnitEvent.SESSION_START:
|
|
447
450
|
if not GdhIndexedSession.indexes_itself(_test_session):
|
|
448
451
|
_test_session = GdhIndexedSession.of(_test_session)
|
|
452
|
+
var tree: SceneTree = get_tree()
|
|
453
|
+
if not tree.process_frame.is_connected(drawn_window):
|
|
454
|
+
var _watched: int = tree.process_frame.connect(drawn_window)
|
|
449
455
|
GdUnitEvent.TESTSUITE_BEFORE:
|
|
456
|
+
drawn_window()
|
|
450
457
|
suite_running = event.resource_path()
|
|
451
458
|
orphans_at_suite = orphan_ids()
|
|
452
459
|
orphans_named.clear()
|
|
@@ -461,6 +468,9 @@ class GdhRunner:
|
|
|
461
468
|
GdUnitEvent.TESTCASE_AFTER:
|
|
462
469
|
cases_ended += 1
|
|
463
470
|
super(event)
|
|
471
|
+
# gdUnit4 minimises the window once the suite's end is reported.
|
|
472
|
+
if event.type() == GdUnitEvent.TESTSUITE_AFTER:
|
|
473
|
+
minimise_pending = true
|
|
464
474
|
# After gdUnit4's own report of the case, so the line falls under it.
|
|
465
475
|
if event.orphan_nodes() == 0:
|
|
466
476
|
return
|
|
@@ -478,6 +488,21 @@ class GdhRunner:
|
|
|
478
488
|
"after", left.filter(func(id: int) -> bool: return not orphans_from_before.has(id))
|
|
479
489
|
)
|
|
480
490
|
|
|
491
|
+
## The window back from minimised, which gdUnit4 makes it as its runner starts and after every
|
|
492
|
+
## suite, and where Godot draws nothing: a suite given a window to render in waited for a
|
|
493
|
+
## frame_post_draw that never came. Called as a suite begins, ahead of its before(), and every
|
|
494
|
+
## frame after, since on macOS the minimise is an animation that reads as windowed until it
|
|
495
|
+
## ends, and a window not yet minimised cannot be put back. Only once for each minimise of
|
|
496
|
+
## gdUnit4's, so a suite minimising the window itself keeps it minimised.
|
|
497
|
+
func drawn_window() -> void:
|
|
498
|
+
if not minimise_pending or DisplayServer.get_name() == "headless":
|
|
499
|
+
return
|
|
500
|
+
if Engine.is_embedded_in_editor():
|
|
501
|
+
return
|
|
502
|
+
if DisplayServer.window_get_mode() == DisplayServer.WINDOW_MODE_MINIMIZED:
|
|
503
|
+
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
|
|
504
|
+
minimise_pending = false
|
|
505
|
+
|
|
481
506
|
## The orphans now that were not there in [param before] and no line has named yet.
|
|
482
507
|
func new_orphans(before: Dictionary[int, bool]) -> Array[int]:
|
|
483
508
|
var found: Array[int] = []
|