Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GD-24: Fixes the script editor context menu to show Create Test #26

Merged
merged 2 commits into from
Nov 5, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 50 additions & 89 deletions addons/gdUnit4/src/ui/GdUnitInspector.gd
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ enum EDITOR_ACTIONS {
WINDOW_SELECT_BASE = 100
}

const MENU_ID_TEST_RUN := 1000
const MENU_ID_TEST_DEBUG := 1001
const MENU_ID_CREATE_TEST := 1010
const MENU_ID_TEST_RUN := GdUnitContextMenuItem.MENU_ID.TEST_RUN
const MENU_ID_TEST_DEBUG := GdUnitContextMenuItem.MENU_ID.TEST_DEBUG
const MENU_ID_CREATE_TEST := GdUnitContextMenuItem.MENU_ID.CREATE_TEST

# header
@onready var _runButton :Button = $VBoxContainer/Header/ToolBar/Tools/run
Expand Down Expand Up @@ -77,10 +77,19 @@ func _ready():
if Engine.is_editor_hint():
_getEditorThemes(_editor_interface)
add_file_system_dock_context_menu()
add_script_editor_context_menu()
# preload previous test execution
_runner_config.load()


func _enter_tree():
if Engine.is_editor_hint():
add_script_editor_context_menu()


func _exit_tree():
ScriptEditorControls.unregister_context_menu()


func _process(_delta):
_check_test_run_stopped_manually()

Expand Down Expand Up @@ -151,84 +160,45 @@ func _on_file_system_dock_context_menu_pressed(id :int, file_tree :Tree) -> void
var debug = id == MENU_ID_TEST_DEBUG
run_test_suites(selected_test_suites, debug)


func add_script_editor_context_menu():
if _editor_interface == null:
return
var script_editor := _editor_interface.get_script_editor()
# register tab changed to modify the context menu for all script editors
var tab_containers := GdObjects.find_nodes_by_class(script_editor, "TabContainer", true)
var tab_container := tab_containers[0] as TabContainer
if not tab_container.is_connected("tab_changed", Callable(self, "_on_script_editor_tab_changed")):
tab_container.connect("tab_changed", Callable(self, "_on_script_editor_tab_changed").bind(tab_container))

func _on_script_editor_tab_changed(tab_index :int, tab_container :TabContainer):
var tab := tab_container.get_tab_control(tab_index)
# we only extend context menu for script editors
if tab.get_class() == "ScriptTextEditor":
var current_script := _editor_interface.get_script_editor().get_current_script()
if current_script != null:
extend_script_editor_popup(tab)

func extend_script_editor_popup(tab_container :Control) -> void:
# find editor popup menus
var popups := GdObjects.find_nodes_by_class(tab_container, "PopupMenu", true)
# find the underlaying text editor (need for grab current cursor position)
var text_edits := GdObjects.find_nodes_by_class(tab_container, "CodeEdit", true)
# editor is not loaded yet?
if text_edits.size() == 0:
return
var text_edit :TextEdit = text_edits[0] as TextEdit
var is_test_suite := func is_visible(script :GDScript, is_test_suite :bool):
return GdObjects.is_test_suite(script) == is_test_suite
var is_enabled := func is_enabled(script :GDScript):
return !_runButton.disabled
var run_test := func run_test(script :Script, text_edit :TextEdit, debug :bool):
var cursor_line := text_edit.get_caret_line()
#run test case?
var regex := RegEx.new()
regex.compile("(^func[ ,\t])(test_[a-zA-Z0-9_]*)")
var result := regex.search(text_edit.get_line(cursor_line))
#var debug = id == GdUnitContextMenuItem.MENU_ID.TEST_DEBUG
MikeSchulze marked this conversation as resolved.
Show resolved Hide resolved
if result:
var func_name := result.get_string(2).strip_edges()
prints("Run test:", func_name, "debug", debug)
if func_name.begins_with("test_"):
run_test_case(script.resource_path, func_name, debug)
return
# otherwise run the full test suite
var selected_test_suites := [script.resource_path]
run_test_suites(selected_test_suites, debug)
var create_test := func create_test(script :Script, text_edit :TextEdit):
var cursor_line := text_edit.get_caret_line()
var result = GdUnitTestSuiteBuilder.create(script, cursor_line)
if result.is_error():
# show error dialog
push_error("Failed to create test case: %s" % result.error_message())
return
var info := result.value() as Dictionary
ScriptEditorControls.edit_script(info.get("path"), info.get("line"))

for popup in popups:
if not popup.is_connected("about_to_popup", Callable(self, '_on_script_editor_context_menu_show')):
popup.connect("about_to_popup", Callable(self, '_on_script_editor_context_menu_show').bind(popup))
if not popup.is_connected("id_pressed", Callable(self, '_on_fscript_editor_context_menu_pressed')):
popup.connect("id_pressed", Callable(self, "_on_fscript_editor_context_menu_pressed").bind(text_edit))

func _on_script_editor_context_menu_show(context_menu :PopupMenu):
var current_script := _editor_interface.get_script_editor().get_current_script()
if GdObjects.is_test_suite(current_script):
context_menu.add_separator()
# save menu entry index
var current_index := context_menu.get_item_count()
context_menu.add_item("Run Tests", MENU_ID_TEST_RUN)
context_menu.add_item("Debug Tests", MENU_ID_TEST_DEBUG)
# deactivate menu enties if currently a run in progress
context_menu.set_item_disabled(current_index+0, _runButton.disabled)
context_menu.set_item_disabled(current_index+1, _runButton.disabled)
return
context_menu.add_separator()
# save menu entry index
var current_index := context_menu.get_item_count()
context_menu.add_item("Create Test", MENU_ID_CREATE_TEST)
var menu := [
GdUnitContextMenuItem.new(GdUnitContextMenuItem.MENU_ID.TEST_RUN, "Run Tests", is_test_suite.bind(true), is_enabled, run_test.bind(false)),
GdUnitContextMenuItem.new(GdUnitContextMenuItem.MENU_ID.TEST_DEBUG, "Debug Tests", is_test_suite.bind(true), is_enabled, run_test.bind(true)),
GdUnitContextMenuItem.new(GdUnitContextMenuItem.MENU_ID.CREATE_TEST, "Create Test", is_test_suite.bind(false), is_enabled, create_test)
]
ScriptEditorControls.register_context_menu(menu)

func _on_fscript_editor_context_menu_pressed(id :int, text_edit :TextEdit):
if id != MENU_ID_TEST_RUN && id != MENU_ID_TEST_DEBUG && id != MENU_ID_CREATE_TEST:
return
var current_script := ScriptEditorControls.script_editor().get_current_script()
if current_script == null:
prints("no script selected")
return
var cursor_line := text_edit.get_caret_line()
# create new test case?
if id == MENU_ID_CREATE_TEST:
add_test_to_test_suite(current_script, cursor_line)
return
# run test case?
var regex := RegEx.new()
regex.compile("(^func[ ,\t])(test_[a-zA-Z0-9_]*)")
var result := regex.search(text_edit.get_line(cursor_line))
var debug = id == MENU_ID_TEST_DEBUG
if result:
var func_name := result.get_string(2).strip_edges()
prints("Run test:", func_name, "debug", debug)
if func_name.begins_with("test_"):
run_test_case(current_script.resource_path, func_name, debug)
return
# otherwise run the full test suite
var selected_test_suites := [current_script.resource_path]
run_test_suites(selected_test_suites, debug)
# ------------------------------------------------------------------------------------

func run_test_suites(test_suite_paths :Array, debug :bool, rerun :bool=false) -> void:
# create new runner runner_config for fresh run otherwise use saved one
Expand All @@ -252,6 +222,7 @@ func run_test_case(test_suite_resource_path :String, test_case :String, debug :b
return
_gdUnit_run(debug)


func _gdUnit_run(debug :bool) -> void:
# don't start is already running
if _is_running:
Expand Down Expand Up @@ -304,16 +275,6 @@ func _gdUnit_stop(client_id :int) -> void:
_current_runner_process_id = -1


func add_test_to_test_suite(source_script :Script, current_line_number :int) -> void:
var result = GdUnitTestSuiteBuilder.create(source_script, current_line_number)
if result.is_error():
# show error dialog
push_error("Failed to create test case: %s" % result.error_message())
return
var info := result.value() as Dictionary
ScriptEditorControls.edit_script(info.get("path"), info.get("line"))


################################################################################
# Event signal receiver
################################################################################
Expand Down
32 changes: 28 additions & 4 deletions addons/gdUnit4/src/ui/ScriptEditorControls.gd
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
class_name ScriptEditorControls
extends RefCounted


# https://github.com/godotengine/godot/blob/master/editor/plugins/script_editor_plugin.h
# the Editor menu popup items
enum {
Expand Down Expand Up @@ -56,6 +57,9 @@ static func script_editor() -> ScriptEditor:
# The script is saved when is opened in the editor.
# The script is closed when <close> is set to true.
static func save_an_open_script(script_path :String, close := false) -> bool:
#prints("save_an_open_script", script_path, close)
if !Engine.is_editor_hint():
return false
var editor_interface := editor_interface()
var script_editor := script_editor()
var editor_popup := _menu_popup()
Expand All @@ -64,20 +68,20 @@ static func save_an_open_script(script_path :String, close := false) -> bool:
for open_script in script_editor.get_open_scripts():
if open_script.resource_path == script_path:
# select the script in the editor
#var e: ScriptEditorBase = script_editor.get_open_script_editors()[open_file_index]
editor_interface.edit_script(open_script, 0);
# save and close
editor_popup.emit_signal("id_pressed", FILE_SAVE)
editor_popup.id_pressed.emit(FILE_SAVE)
if close:
editor_popup.emit_signal("id_pressed", FILE_CLOSE)
editor_popup.id_pressed.emit(FILE_CLOSE)
return true
open_file_index +=1
return false


# Saves all opened script
static func save_all_open_script() -> void:
_menu_popup().emit_signal("id_pressed", FILE_SAVE_ALL)
if Engine.is_editor_hint():
_menu_popup().id_pressed.emit(FILE_SAVE_ALL)


# Edits the given script.
Expand All @@ -95,6 +99,26 @@ static func edit_script(script_path :String, line_number :int = -1):
editor_interface.edit_script(script, line_number)


# Register the given context menu to the current script editor
# Is called when the plugin is activated
# The active script is connected to the ScriptEditorContextMenuHandler
static func register_context_menu(menu :Array) -> void:
script_editor().editor_script_changed.connect(ScriptEditorContextMenuHandler.create(menu).bind(script_editor()))


# Unregisteres all registerend context menus and gives the ScriptEditorContextMenuHandler> free
# Is called when the plugin is deactivated
static func unregister_context_menu() -> void:
for connection in script_editor().editor_script_changed.get_connections():
var cb :Callable = connection["callable"]
if cb.get_object() is ScriptEditorContextMenuHandler:
cb.get_object().free()


static func filesystem_add_context_menu() -> void:
pass


static func _menu_popup() -> PopupMenu:
return script_editor().get_child(0).get_child(0).get_child(0).get_popup()

Expand Down
45 changes: 45 additions & 0 deletions addons/gdUnit4/src/ui/menu/GdUnitContextMenuItem.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
class_name GdUnitContextMenuItem

enum MENU_ID {
TEST_RUN = 1000,
TEST_DEBUG = 1001,
CREATE_TEST = 1010,
}

var _is_visible :Callable
var _is_enabled :Callable
var _runnable: Callable


func _init(id :MENU_ID, name :StringName, is_visible :Callable, is_enabled: Callable, runnable: Callable):
self.id = id
self.name = name
_is_visible = is_visible
_is_enabled = is_enabled
_runnable = runnable


var id: MENU_ID:
set(value):
id = value
get:
return id


var name: StringName:
set(value):
name = value
get:
return name


func is_enabled(script :GDScript) -> bool:
return _is_enabled.call(script)


func is_visible(script :GDScript) -> bool:
return _is_visible.call(script)


func execute(args :Array) -> void:
_runnable.callv(args)
44 changes: 44 additions & 0 deletions addons/gdUnit4/src/ui/menu/ScriptEditorContextMenuHandler.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
class_name ScriptEditorContextMenuHandler
extends Object

var _context_menus := Dictionary()


func _init(context_menus :Array[GdUnitContextMenuItem]):
for menu in context_menus:
_context_menus[menu.id] = menu


static func create(context_menus :Array[GdUnitContextMenuItem]) -> Callable:
return Callable(ScriptEditorContextMenuHandler.new(context_menus), "on_script_changed")


func on_script_changed(script :GDScript, editor :ScriptEditor):
#prints("ContextMenuHandler:on_script_changed", script, editor)
var current_editor := editor.get_current_editor()
var popups := GdObjects.find_nodes_by_class(current_editor, "PopupMenu", true)
for popup in popups:
if not popup.is_connected("about_to_popup", Callable(self, 'on_context_menu_show')):
popup.connect("about_to_popup", Callable(self, 'on_context_menu_show').bind(script, popup))
if not popup.is_connected("id_pressed", Callable(self, 'on_context_menu_pressed')):
popup.connect("id_pressed", Callable(self, "on_context_menu_pressed").bind(script, current_editor.get_base_editor()))


func on_context_menu_show(script :GDScript, context_menu :PopupMenu):
#prints("on_context_menu_show", _context_menus.keys(), context_menu, self)
context_menu.add_separator()
var current_index := context_menu.get_item_count()
for menu_id in _context_menus.keys():
var menu_item :GdUnitContextMenuItem = _context_menus[menu_id]
if menu_item.is_visible(script):
context_menu.add_item(menu_item.name, menu_id)
context_menu.set_item_disabled(current_index, !menu_item.is_enabled(script))
current_index += 1


func on_context_menu_pressed(id :int, script :GDScript, text_edit :TextEdit):
#prints("on_context_menu_pressed", id, script, text_edit)
if !_context_menus.has(id):
return
var menu_item :GdUnitContextMenuItem = _context_menus[id]
menu_item.execute([script, text_edit])
4 changes: 2 additions & 2 deletions addons/gdUnit4/test/core/GdUnitTestSuiteBuilderTest.gd
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func test_create_gd_success() -> void:
assert_result(result).is_success()
var info := result.value() as Dictionary
assert_str(info.get("path")).is_equal("user://tmp/test/examples/test_person_test.gd")
assert_int(info.get("line")).is_equal(9)
assert_int(info.get("line")).is_equal(11)
assert_tests(load(info.get("path"))).contains_exactly(["test_first_name"])

# create additional test checked existing suite based checked function selected by line 15
Expand All @@ -47,7 +47,7 @@ func test_create_gd_success() -> void:
assert_result(result).is_success()
info = result.value() as Dictionary
assert_str(info.get("path")).is_equal("user://tmp/test/examples/test_person_test.gd")
assert_int(info.get("line")).is_equal(13)
assert_int(info.get("line")).is_equal(16)
assert_tests(load(info.get("path"))).contains_exactly_in_any_order(["test_first_name", "test_fully_name"])

func test_create_gd_fail() -> void:
Expand Down
9 changes: 5 additions & 4 deletions addons/gdUnit4/test/core/TestSuiteScannerTest.gd
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ func test_create_test_case():
var result := _TestSuiteScanner.create_test_case(test_suite_path, "last_name", source_path)
assert_that(result.is_success()).is_true()
var info :Dictionary = result.value()
assert_int(info.get("line")).is_equal(9)
assert_int(info.get("line")).is_equal(11)
assert_file(info.get("path")).exists()\
.is_file()\
.is_script()\
Expand All @@ -193,14 +193,15 @@ func test_create_test_case():
"# TestSuite generated from",
"const __source = '%s'" % source_path,
"",
"",
"func test_last_name() -> void:",
" # remove_at this line and complete your test",
" # remove this line and complete your test",
" assert_not_yet_implemented()",
""])
# try to add again
result = _TestSuiteScanner.create_test_case(test_suite_path, "last_name", source_path)
assert_that(result.is_success()).is_true()
assert_that(result.value()).is_equal({"line" : 10, "path": test_suite_path})
assert_that(result.value()).is_equal({"line" : 11, "path": test_suite_path})

# https://github.com/MikeSchulze/gdUnit4/issues/25
func test_build_test_suite_path() -> void:
Expand Down Expand Up @@ -295,7 +296,7 @@ func test_scan_by_inheritance_class_path() -> void:
ts.free()

func test_get_test_case_line_number() -> void:
assert_int(_TestSuiteScanner.get_test_case_line_number("res://addons/gdUnit4/test/core/TestSuiteScannerTest.gd", "get_test_case_line_number")).is_equal(297)
assert_int(_TestSuiteScanner.get_test_case_line_number("res://addons/gdUnit4/test/core/TestSuiteScannerTest.gd", "get_test_case_line_number")).is_equal(298)
assert_int(_TestSuiteScanner.get_test_case_line_number("res://addons/gdUnit4/test/core/TestSuiteScannerTest.gd", "unknown")).is_equal(-1)

func test__to_naming_convention() -> void:
Expand Down
Loading