godot-xterm/addons/gut/gui/GutControl.gd
2024-01-06 23:33:15 +13:00

322 lines
8.9 KiB
GDScript

@tool
extends Control
const RUNNER_JSON_PATH = "res://.gut_editor_config.json"
var GutConfig = load("res://addons/gut/gut_config.gd")
var GutRunnerScene = load("res://addons/gut/gui/GutRunner.tscn")
var GutConfigGui = load("res://addons/gut/gui/gut_config_gui.gd")
var _config = GutConfig.new()
var _config_gui = null
var _gut_runner = GutRunnerScene.instantiate()
var _has_connected = false
var _tree_root: TreeItem = null
var _script_icon = load("res://addons/gut/images/Script.svg")
var _folder_icon = load("res://addons/gut/images/Folder.svg")
var _tree_scripts = {}
var _tree_directories = {}
const TREE_SCRIPT = "Script"
const TREE_DIR = "Directory"
@onready var _ctrls = {
run_tests_button = $VBox/Buttons/RunTests,
run_selected = $VBox/Buttons/RunSelected,
test_tree = $VBox/Tabs/Tests,
settings_vbox = $VBox/Tabs/SettingsScroll/Settings,
tabs = $VBox/Tabs,
bg = $Bg
}
@export var bg_color: Color = Color(.36, .36, .36):
get:
return bg_color
set(val):
bg_color = val
if is_inside_tree():
$Bg.color = bg_color
func _ready():
if Engine.is_editor_hint():
return
$Bg.color = bg_color
_ctrls.tabs.set_tab_title(0, "Tests")
_ctrls.tabs.set_tab_title(1, "Settings")
_config_gui = GutConfigGui.new(_ctrls.settings_vbox)
_ctrls.test_tree.hide_root = true
# Stop tests from kicking off when the runner is "ready" and
# prevents it from writing results file that is used by
# the panel.
_gut_runner.set_cmdln_mode(true)
add_child(_gut_runner)
# Becuase of the janky _utils psuedo-global script, we cannot
# do all this in _ready. If we do this in _ready, it generates
# a bunch of errors. The errors don't matter, but it looks bad.
call_deferred("_post_ready")
func _draw():
if Engine.is_editor_hint():
return
var gut = _gut_runner.get_gut()
if !gut.is_running():
var r = Rect2(Vector2(0, 0), get_rect().size)
draw_rect(r, Color.BLACK, false, 2)
func _post_ready():
var gut = _gut_runner.get_gut()
gut.start_run.connect(_on_gut_run_started)
gut.end_run.connect(_on_gut_run_ended)
_refresh_tree_and_settings()
func _set_meta_for_script_tree_item(item, script, test = null):
var meta = {
type = TREE_SCRIPT, script = script.path, inner_class = script.inner_class_name, test = ""
}
if test != null:
meta.test = test.name
item.set_metadata(0, meta)
func _set_meta_for_directory_tree_item(item, path, temp_item):
var meta = {type = TREE_DIR, path = path, temp_item = temp_item}
item.set_metadata(0, meta)
func _get_script_tree_item(script, parent_item):
if !_tree_scripts.has(script.path):
var item = _ctrls.test_tree.create_item(parent_item)
item.set_text(0, script.path.get_file())
item.set_icon(0, _script_icon)
_tree_scripts[script.path] = item
_set_meta_for_script_tree_item(item, script)
return _tree_scripts[script.path]
func _get_directory_tree_item(path):
var parent = _tree_root
if !_tree_directories.has(path):
var item: TreeItem = null
if parent != _tree_root:
item = parent.create_child(0)
else:
item = parent.create_child()
_tree_directories[path] = item
item.collapsed = false
item.set_text(0, path)
item.set_icon(0, _folder_icon)
item.set_icon_modulate(0, Color.ROYAL_BLUE)
# temp_item is used in calls with move_before since you must use
# move_before or move_after to reparent tree items. This ensures that
# there is an item on all directories. These are deleted later.
var temp_item = item.create_child()
temp_item.set_text(0, "<temp>")
_set_meta_for_directory_tree_item(item, path, temp_item)
return _tree_directories[path]
func _find_dir_item_to_move_before(path):
var max_matching_len = 0
var best_parent = null
# Go through all the directory items finding the one that has the longest
# path that contains our path.
for key in _tree_directories.keys():
if path != key and path.begins_with(key) and key.length() > max_matching_len:
max_matching_len = key.length()
best_parent = _tree_directories[key]
var to_return = null
if best_parent != null:
to_return = best_parent.get_metadata(0).temp_item
return to_return
func _reorder_dir_items():
var the_keys = _tree_directories.keys()
the_keys.sort()
for key in _tree_directories.keys():
var to_move = _tree_directories[key]
to_move.collapsed = false
var move_before = _find_dir_item_to_move_before(key)
if move_before != null:
to_move.move_before(move_before)
var new_text = key.substr(move_before.get_parent().get_metadata(0).path.length())
to_move.set_text(0, new_text)
func _remove_dir_temp_items():
for key in _tree_directories.keys():
var item = _tree_directories[key].get_metadata(0).temp_item
_tree_directories[key].remove_child(item)
func _add_dir_and_script_tree_items():
var tree: Tree = _ctrls.test_tree
tree.clear()
_tree_root = _ctrls.test_tree.create_item()
var scripts = _gut_runner.get_gut().get_test_collector().scripts
for script in scripts:
var dir_item = _get_directory_tree_item(script.path.get_base_dir())
var item = _get_script_tree_item(script, dir_item)
if script.inner_class_name != "":
var inner_item = tree.create_item(item)
inner_item.set_text(0, script.inner_class_name)
_set_meta_for_script_tree_item(inner_item, script)
item = inner_item
for test in script.tests:
var test_item = tree.create_item(item)
test_item.set_text(0, test.name)
_set_meta_for_script_tree_item(test_item, script, test)
func _populate_tree():
_add_dir_and_script_tree_items()
_tree_root.set_collapsed_recursive(true)
_tree_root.set_collapsed(false)
_reorder_dir_items()
_remove_dir_temp_items()
func _refresh_tree_and_settings():
if _config.options.has("panel_options"):
_config_gui.set_options(_config.options)
_config.apply_options(_gut_runner.get_gut())
_gut_runner.set_gut_config(_config)
_populate_tree()
# ---------------------------
# Events
# ---------------------------
func _on_gut_run_started():
_ctrls.run_tests_button.disabled = true
_ctrls.run_selected.visible = false
_ctrls.tabs.visible = false
_ctrls.bg.visible = false
_ctrls.run_tests_button.text = "Running"
queue_redraw()
func _on_gut_run_ended():
_ctrls.run_tests_button.disabled = false
_ctrls.run_selected.visible = true
_ctrls.tabs.visible = true
_ctrls.bg.visible = true
_ctrls.run_tests_button.text = "Run All"
queue_redraw()
func _on_run_tests_pressed():
run_all()
func _on_run_selected_pressed():
run_selected()
func _on_tests_item_activated():
run_selected()
# ---------------------------
# Public
# ---------------------------
func get_gut():
return _gut_runner.get_gut()
func get_config():
return _config
func run_all():
_config.options.selected = ""
_config.options.inner_class_name = ""
_config.options.unit_test_name = ""
run_tests()
func run_tests(options = null):
if options == null:
_config.options = _config_gui.get_options(_config.options)
else:
_config.options = options
_gut_runner.get_gut().get_test_collector().clear()
_gut_runner.set_gut_config(_config)
_gut_runner.run_tests()
func run_selected():
var sel_item = _ctrls.test_tree.get_selected()
if sel_item == null:
return
var options = _config_gui.get_options(_config.options)
var meta = sel_item.get_metadata(0)
if meta.type == TREE_SCRIPT:
options.selected = meta.script.get_file()
options.inner_class_name = meta.inner_class
options.unit_test_name = meta.test
elif meta.type == TREE_DIR:
options.dirs = [meta.path]
options.include_subdirectories = true
options.selected = ""
options.inner_class_name = ""
options.unit_test_name = ""
run_tests(options)
func load_config_file(path):
_config.load_panel_options(path)
_config.options.selected = ""
_config.options.inner_class_name = ""
_config.options.unit_test_name = ""
# ##############################################################################
# The MIT License (MIT)
# =====================
#
# Copyright (c) 2023 Tom "Butch" Wesley
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ##############################################################################