extends Object class_name VestMixins ## Manages mixins for [VestTest]. ## ## Instead of having to implement all the functionality in one huge class, ## the functionality can be separated into mixins. ## [br][br] ## Mixins are classes whose functionality is combined into a single class, to ## produce the [VestTest] base class. This is done by generating scripts that ## Godot can then load. ## [br][br] ## When working with custom mixins, [b]make sure to refresh[/b] when a change ## is applied! ## List all active mixins. static func list() -> Array[Script]: var result: Array[Script] = [] result.assign((VestTest.new()).__get_vest_mixins()) return result ## Add a custom script to the list of mixins. ## [br][br] ## Make sure to call [method refresh], otherwise the generated classes are not ## updated. static func add(mixin: Script): var active_mixins := list() if not active_mixins.has(mixin): active_mixins.append(mixin) refresh(active_mixins) ## Remove a custom script from the list of mixins. ## [br][br] ## Make sure to call [method refresh], otherwise the generated classes are not ## updated. static func remove(mixin: Script): var active_mixins := list() if active_mixins.has(mixin): active_mixins.erase(mixin) refresh(active_mixins) ## Refresh the generated classes, making sure that all mixins used are up to ## date. static func refresh(mixins: Array[Script] = []): if mixins.is_empty(): mixins = list() # Generate mixin chain var mixin_chain: Array[Script] = [] mixin_chain.append(_get_test_base()) mixin_chain.append_array(_get_builtin_mixins()) var active_mixins: Array[Script] = [] for mixin_script in mixins: if not mixin_script: # Not script? continue if mixin_chain.has(mixin_script): # Don't include the same mixin twice continue mixin_chain.append(mixin_script) active_mixins.append(mixin_script) mixin_chain.append(VestTest) # Generate scripts _clean_mixin_directory() DirAccess.make_dir_recursive_absolute(_get_mixin_directory()) var extends_pattern := RegEx.create_from_string("^extends.*") for i in range(1, mixin_chain.size()): var parent_script := mixin_chain[i - 1] var target_script := mixin_chain[i] var parent_path := parent_script.resource_path var target_path := target_script.resource_path if i != 1: parent_path = _get_generated_mixin_path(parent_script, i - 1) if i != mixin_chain.size() - 1: target_path = _get_generated_mixin_path(target_script, i) # Generate source var target_source := target_script.source_code var script_header := ( "# This file is generated by Vest!\n" + "# Do not modify!\n" + "# source: %s\n" % [target_script.resource_path] + "extends \"%s\"\n\n" % [parent_path]) target_source = extends_pattern.sub(target_source, "", true) if target_script != VestTest: target_source = script_header + target_source else: target_source = script_header target_source += "class_name VestTest\n\n" var mixin_fragments = active_mixins\ .map(func(it): return it.resource_path)\ .map(func(it): return "preload(\"%s\")" % it) mixin_fragments = ", ".join(mixin_fragments) target_source += "func __get_vest_mixins() -> Array:\n\treturn [%s]" % [mixin_fragments] var target_file := FileAccess.open(target_path, FileAccess.WRITE) target_file.store_string(target_source) target_file.close() static func _get_generated_mixin_name(script: Script, idx: int) -> String: return "%d-%x.gd" % [idx, hash(script.resource_path.get_file())] static func _get_generated_mixin_path(script: Script, idx: int) -> String: return _get_mixin_directory() + _get_generated_mixin_name(script, idx) static func _get_builtin_mixins() -> Array[Script]: return [ preload("res://addons/vest/test/mixins/gather-suite-mixin.gd"), preload("res://addons/vest/test/mixins/expect-mixin.gd"), preload("res://addons/vest/test/mixins/assert-that-mixin.gd"), preload("res://addons/vest/test/mixins/mock-mixin.gd"), preload("res://addons/vest/test/mixins/capture-signal-mixin.gd"), preload("res://addons/vest/test/mixins/logging-mixin.gd") ] static func _get_mixin_directory() -> String: return "res://addons/vest/_generated-mixins/" static func _clean_mixin_directory(): for file in DirAccess.get_files_at(_get_mixin_directory()): DirAccess.remove_absolute(_get_mixin_directory() + file) static func _get_test_base() -> Script: return preload("res://addons/vest/test/vest-test-base.gd") as Script