chore(client): add gdUnit4 test framework addon

Vendor gdUnit4 for Godot 4 client testing (D-030).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-11 16:52:00 +01:00
co-authored by Claude Opus 4.6
parent 3a2fe4bd71
commit 61e0762a84
979 changed files with 55221 additions and 0 deletions
@@ -0,0 +1,45 @@
class_name GdUnitContextMenuItem
var command_id: String:
set(value):
command_id = value
get:
return command_id
var name: StringName:
set(value):
name = value
get:
return name
var visible: Callable:
set(value):
visible = value
get:
return visible
var icon: Texture2D:
get:
return GdUnitCommandHandler.instance().command_icon(command_id)
func _init(p_command_id: String, p_name: StringName, p_is_visible: Callable) -> void:
assert(p_command_id != null and not p_command_id.is_empty(), "(%s) missing command id " % p_command_id)
assert(p_is_visible != null, "(%s) missing parameter 'GdUnitCommand'" % p_name)
self.command_id = p_command_id
self.name = p_name
self.visible = p_is_visible
func shortcut() -> Shortcut:
return GdUnitCommandHandler.instance().command_shortcut(command_id)
func is_visible(...args: Array) -> bool:
return visible.callv(args)
func execute(...args: Array) -> void:
GdUnitCommandHandler.instance().command_execute(command_id, args)
@@ -0,0 +1 @@
uid://c4i0p5jpownix
@@ -0,0 +1,37 @@
@tool
extends EditorContextMenuPlugin
var _context_menus: Array[GdUnitContextMenuItem] = []
func _init() -> void:
var is_test_suite := func is_visible(script: Script, is_ts: bool) -> bool:
if script == null:
return false
return GdUnitTestSuiteScanner.is_test_suite(script) == is_ts
_context_menus.append(GdUnitContextMenuItem.new(
GdUnitCommandFileSystemRunTests.ID,
"Run Testsuites",
is_test_suite.bind(true)
))
_context_menus.append(GdUnitContextMenuItem.new(
GdUnitCommandFileSystemDebugTests.ID,
"Debug Testsuites",
is_test_suite.bind(true)
))
# setup shortcuts
for menu_item in _context_menus:
if menu_item.shortcut():
var cb := func call(...files: Array) -> void:
var paths: Array = files[0]
menu_item.execute.callv(paths)
add_menu_shortcut(menu_item.shortcut(), cb)
func _popup_menu(paths: PackedStringArray) -> void:
for menu_item in _context_menus:
if menu_item.shortcut():
add_context_menu_item_from_shortcut(menu_item.name, menu_item.shortcut(), menu_item.icon)
else:
add_context_menu_item(menu_item.name, menu_item.execute.bindv(paths).unbind(1), menu_item.icon)
@@ -0,0 +1 @@
uid://ld10hjfqv3c3
@@ -0,0 +1,63 @@
@tool
class_name GdUnitInspectorContextMenu
extends PopupMenu
const CONTEXT_MENU_RUN_ID = 0
const CONTEXT_MENU_DEBUG_ID = 1
const CONTEXT_MENU_RERUN_UNTIL_ID = 2
# id 3 is the seperator
const CONTEXT_MENU_COLLAPSE_ALL = 4
const CONTEXT_MENU_EXPAND_ALL = 5
var command_handler: GdUnitCommandHandler
func _ready() -> void:
if not Engine.is_editor_hint():
return
command_handler = GdUnitCommandHandler.instance()
_setup_item(CONTEXT_MENU_RUN_ID, "Run Tests", GdUnitCommandInspectorRunTests.ID)
_setup_item(CONTEXT_MENU_DEBUG_ID, "Debug Tests", GdUnitCommandInspectorDebugTests.ID)
_setup_item(CONTEXT_MENU_RERUN_UNTIL_ID, "Run Tests Until Fail", GdUnitCommandInspectorRerunTestsUntilFailure.ID)
_setup_item(CONTEXT_MENU_EXPAND_ALL, "Expand All", GdUnitCommandInspectorTreeExpand.ID)
_setup_item(CONTEXT_MENU_COLLAPSE_ALL, "Collapse All", GdUnitCommandInspectorTreeCollapse.ID)
func _setup_item(item_id: int, item_name: String, command_id: String) -> void:
set_item_text(item_id, item_name)
set_item_icon(item_id, command_handler.command_icon(command_id))
set_item_shortcut(item_id, command_handler.command_shortcut(command_id))
func disable_items() -> void:
set_item_disabled(CONTEXT_MENU_RUN_ID, true)
set_item_disabled(CONTEXT_MENU_DEBUG_ID, true)
set_item_disabled(CONTEXT_MENU_RERUN_UNTIL_ID, true)
func enable_items() -> void:
set_item_disabled(CONTEXT_MENU_RUN_ID, false)
set_item_disabled(CONTEXT_MENU_DEBUG_ID, false)
set_item_disabled(CONTEXT_MENU_RERUN_UNTIL_ID, false)
func _on_tree_item_mouse_selected(mouse_position: Vector2, mouse_button_index: int, source: Tree) -> void:
if mouse_button_index == MOUSE_BUTTON_RIGHT:
position = source.get_screen_position() + mouse_position
popup()
func _on_index_pressed(index: int) -> void:
match index:
CONTEXT_MENU_RUN_ID:
command_handler.command_execute(GdUnitCommandInspectorRunTests.ID)
CONTEXT_MENU_DEBUG_ID:
command_handler.command_execute(GdUnitCommandInspectorDebugTests.ID)
CONTEXT_MENU_RERUN_UNTIL_ID:
command_handler.command_execute(GdUnitCommandInspectorRerunTestsUntilFailure.ID)
CONTEXT_MENU_EXPAND_ALL:
command_handler.command_execute(GdUnitCommandInspectorTreeExpand.ID)
CONTEXT_MENU_COLLAPSE_ALL:
command_handler.command_execute(GdUnitCommandInspectorTreeCollapse.ID)
@@ -0,0 +1 @@
uid://bxy2decxu8axg
@@ -0,0 +1,70 @@
[gd_scene format=3 uid="uid://bjcc71p2h1trr"]
[ext_resource type="Script" path="res://addons/gdUnit4/src/ui/menu/GdUnitInspectorContextMenu.gd" id="1_0x0vc"]
[sub_resource type="DPITexture" id="DPITexture_miuuy"]
_source = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\"><path fill=\"#e0e0e0\" d=\"M4 12a1 1 0 0 0 1.555.832l6-4a1 1 0 0 0 0-1.664l-6-4A1 1 0 0 0 4 4z\"/></svg>
"
saturation = 2.0
color_map = {
Color(1, 0.37254903, 0.37254903, 1): Color(1, 0.47, 0.42, 1),
Color(0.37254903, 1, 0.5921569, 1): Color(0.45, 0.95, 0.5, 1),
Color(1, 0.8666667, 0.39607844, 1): Color(0.83, 0.78, 0.62, 1)
}
[sub_resource type="DPITexture" id="DPITexture_ern2r"]
_source = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\"><path fill=\"#e0e0e0\" d=\"M3 3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h1a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1zm4.975 0A1 1 0 0 0 7 3.998v8a1 1 0 0 0 1.625.781l5-4a1 1 0 0 0 0-1.56l-5-4A1 1 0 0 0 7.975 3z\"/></svg>
"
saturation = 2.0
color_map = {
Color(1, 0.37254903, 0.37254903, 1): Color(1, 0.47, 0.42, 1),
Color(0.37254903, 1, 0.5921569, 1): Color(0.45, 0.95, 0.5, 1),
Color(1, 0.8666667, 0.39607844, 1): Color(0.83, 0.78, 0.62, 1)
}
[sub_resource type="DPITexture" id="DPITexture_qdci2"]
_source = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\"><path fill=\"#e0e0e0\" d=\"m8 9.669-3.536 2.583H7v2.537h2v-2.537h2.536zm0-3.314L4.464 3.772H7V1.235h2v2.537h2.536zm-7.296.73h14.591v1.831H.704z\"/></svg>
"
saturation = 2.0
color_map = {
Color(1, 0.37254903, 0.37254903, 1): Color(1, 0.47, 0.42, 1),
Color(0.37254903, 1, 0.5921569, 1): Color(0.45, 0.95, 0.5, 1),
Color(1, 0.8666667, 0.39607844, 1): Color(0.83, 0.78, 0.62, 1)
}
[sub_resource type="DPITexture" id="DPITexture_hed0i"]
_source = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\"><path fill=\"#e0e0e0\" d=\"m8 16-3.5-2.5H7V11h2v2.5h2.5zM8 0 4.5 2.5H7V5h2V2.5h2.5zM1 7h14v2H1z\"/></svg>
"
saturation = 2.0
color_map = {
Color(1, 0.37254903, 0.37254903, 1): Color(1, 0.47, 0.42, 1),
Color(0.37254903, 1, 0.5921569, 1): Color(0.45, 0.95, 0.5, 1),
Color(1, 0.8666667, 0.39607844, 1): Color(0.83, 0.78, 0.62, 1)
}
[node name="contextMenu" type="PopupMenu" unique_id=1824556050]
auto_translate_mode = 2
oversampling_override = 1.0
size = Vector2i(241, 170)
visible = true
item_count = 6
item_0/text = "Run Tests"
item_0/icon = SubResource("DPITexture_miuuy")
item_0/id = 0
item_1/text = "Debug Tests"
item_1/icon = SubResource("DPITexture_ern2r")
item_1/id = 1
item_2/text = "Run Tests Until Fail"
item_2/icon = SubResource("DPITexture_miuuy")
item_2/id = 2
item_3/id = 3
item_3/separator = true
item_4/text = "Collapse All"
item_4/icon = SubResource("DPITexture_qdci2")
item_4/id = 4
item_5/text = "Expand All"
item_5/icon = SubResource("DPITexture_hed0i")
item_5/id = 5
script = ExtResource("1_0x0vc")
[connection signal="index_pressed" from="." to="." method="_on_index_pressed"]
@@ -0,0 +1,41 @@
@tool
extends EditorContextMenuPlugin
var _context_menus: Array[GdUnitContextMenuItem] = []
func _init() -> void:
var is_test_suite := func is_visible(script: Script, is_ts: bool) -> bool:
return GdUnitTestSuiteScanner.is_test_suite(script) == is_ts
_context_menus.append(GdUnitContextMenuItem.new(
GdUnitCommandScriptEditorRunTests.ID,
"Run Tests",
is_test_suite.bind(true)
))
_context_menus.append(GdUnitContextMenuItem.new(
GdUnitCommandScriptEditorDebugTests.ID,
"Debug Tests",
is_test_suite.bind(true)
))
_context_menus.append(GdUnitContextMenuItem.new(
GdUnitCommandScriptEditorCreateTest.ID,
"Create Test",
is_test_suite.bind(false)
))
# setup shortcuts
for menu_item in _context_menus:
if menu_item.shortcut():
add_menu_shortcut(menu_item.shortcut(), menu_item.execute.unbind(1))
func _popup_menu(_paths: PackedStringArray) -> void:
var current_script := EditorInterface.get_script_editor().get_current_script()
for menu_item in _context_menus:
if menu_item.is_visible(current_script):
if menu_item.shortcut():
add_context_menu_item_from_shortcut(menu_item.name, menu_item.shortcut())
else:
add_context_menu_item(menu_item.name, menu_item.execute.unbind(1))
@@ -0,0 +1 @@
uid://6d0lvpnuj4wn