From e7270d2d68df6fa692f78002216489c12b4f8336 Mon Sep 17 00:00:00 2001 From: domi41 Date: Sat, 8 Aug 2020 18:18:44 +0200 Subject: [PATCH] deps: add WAT, a Gdscript library for unit testing We also tried GUT in the past, so we wanted to try WAT this time. We're not sure which would be easier to extend in order to add a Gherkin parser and runner. --- addons/WAT/LICENSE | 21 + addons/WAT/assets/crash_warning.png | Bin 0 -> 892 bytes addons/WAT/assets/crash_warning.png.import | 34 ++ addons/WAT/assets/down.png | Bin 0 -> 317 bytes addons/WAT/assets/down.png.import | 34 ++ addons/WAT/assets/failed.png | Bin 0 -> 2056 bytes addons/WAT/assets/failed.png.import | 34 ++ addons/WAT/assets/function.png | Bin 0 -> 317 bytes addons/WAT/assets/function.png.import | 34 ++ addons/WAT/assets/function.svg | 3 + addons/WAT/assets/function.svg.import | 34 ++ addons/WAT/assets/icon_add.png | Bin 0 -> 351 bytes addons/WAT/assets/icon_add.png.import | 34 ++ addons/WAT/assets/kofi.png | Bin 0 -> 741 bytes addons/WAT/assets/kofi.png.import | 34 ++ addons/WAT/assets/more_button.png | Bin 0 -> 162 bytes addons/WAT/assets/more_button.png.import | 34 ++ addons/WAT/assets/more_button.svg | 87 ++++ addons/WAT/assets/more_button.svg.import | 34 ++ addons/WAT/assets/play.svg | 86 ++++ addons/WAT/assets/play.svg.import | 34 ++ addons/WAT/assets/remove.png | Bin 0 -> 519 bytes addons/WAT/assets/remove.png.import | 34 ++ addons/WAT/assets/replace.png | Bin 0 -> 435 bytes addons/WAT/assets/replace.png.import | 34 ++ addons/WAT/assets/success.png | Bin 0 -> 2544 bytes addons/WAT/assets/success.png.import | 34 ++ addons/WAT/assets/summary.png | Bin 0 -> 1311 bytes addons/WAT/assets/summary.png.import | 34 ++ addons/WAT/cli.tscn | 147 ++++++ addons/WAT/core/assertions/assertions.gd | 315 ++++++++++++ addons/WAT/core/assertions/base.gd | 19 + .../WAT/core/assertions/boolean/is_false.gd | 10 + addons/WAT/core/assertions/boolean/is_true.gd | 10 + addons/WAT/core/assertions/class_list.gd | 132 +++++ .../core/assertions/constants/type_library.gd | 59 +++ .../double/called_with_arguments.gd | 18 + .../assertions/double/script_was_called.gd | 9 + .../double/script_was_not_called.gd | 9 + .../WAT/core/assertions/equality/is_equal.gd | 12 + .../equality/is_equal_or_greater_than.gd | 11 + .../equality/is_equal_or_less_than.gd | 11 + .../assertions/equality/is_greater_than.gd | 11 + .../core/assertions/equality/is_less_than.gd | 11 + .../core/assertions/equality/is_not_equal.gd | 8 + .../assertions/file/file_does_not_exist.gd | 9 + .../WAT/core/assertions/file/file_exists.gd | 10 + addons/WAT/core/assertions/is/is_AABB.gd | 9 + addons/WAT/core/assertions/is/is_Array.gd | 9 + addons/WAT/core/assertions/is/is_Basis.gd | 9 + addons/WAT/core/assertions/is/is_Color.gd | 9 + .../WAT/core/assertions/is/is_Dictionary.gd | 9 + addons/WAT/core/assertions/is/is_NodePath.gd | 9 + addons/WAT/core/assertions/is/is_Object.gd | 9 + addons/WAT/core/assertions/is/is_Plane.gd | 9 + .../core/assertions/is/is_PoolByteArray.gd | 9 + .../core/assertions/is/is_PoolColorArray.gd | 9 + .../WAT/core/assertions/is/is_PoolIntArray.gd | 9 + .../core/assertions/is/is_PoolRealArray.gd | 9 + .../core/assertions/is/is_PoolStringArray.gd | 9 + .../core/assertions/is/is_PoolVector2Array.gd | 9 + .../core/assertions/is/is_PoolVector3Array.gd | 9 + addons/WAT/core/assertions/is/is_Quat.gd | 9 + addons/WAT/core/assertions/is/is_RID.gd | 9 + addons/WAT/core/assertions/is/is_Rect2.gd | 9 + addons/WAT/core/assertions/is/is_String.gd | 9 + addons/WAT/core/assertions/is/is_Transform.gd | 9 + .../WAT/core/assertions/is/is_Transform2D.gd | 9 + addons/WAT/core/assertions/is/is_Vector2.gd | 9 + addons/WAT/core/assertions/is/is_Vector3.gd | 9 + addons/WAT/core/assertions/is/is_bool.gd | 9 + .../core/assertions/is/is_built_in_type.gd | 8 + .../core/assertions/is/is_class_instance.gd | 9 + addons/WAT/core/assertions/is/is_float.gd | 9 + addons/WAT/core/assertions/is/is_int.gd | 9 + .../WAT/core/assertions/is_not/is_not_AABB.gd | 9 + .../core/assertions/is_not/is_not_Array.gd | 9 + .../core/assertions/is_not/is_not_Basis.gd | 9 + .../core/assertions/is_not/is_not_Color.gd | 9 + .../assertions/is_not/is_not_Dictionary.gd | 9 + .../core/assertions/is_not/is_not_NodePath.gd | 9 + .../core/assertions/is_not/is_not_Object.gd | 9 + .../core/assertions/is_not/is_not_Plane.gd | 9 + .../assertions/is_not/is_not_PoolByteArray.gd | 9 + .../is_not/is_not_PoolColorArray.gd | 9 + .../assertions/is_not/is_not_PoolIntArray.gd | 9 + .../assertions/is_not/is_not_PoolRealArray.gd | 9 + .../is_not/is_not_PoolStringArray.gd | 9 + .../is_not/is_not_PoolVector2Array.gd | 9 + .../is_not/is_not_PoolVector3Array.gd | 9 + .../WAT/core/assertions/is_not/is_not_Quat.gd | 9 + .../WAT/core/assertions/is_not/is_not_RID.gd | 9 + .../core/assertions/is_not/is_not_Rect2.gd | 9 + .../core/assertions/is_not/is_not_String.gd | 9 + .../assertions/is_not/is_not_Transform.gd | 9 + .../assertions/is_not/is_not_Transform2D.gd | 9 + .../core/assertions/is_not/is_not_Vector2.gd | 9 + .../core/assertions/is_not/is_not_Vector3.gd | 9 + .../WAT/core/assertions/is_not/is_not_bool.gd | 9 + .../assertions/is_not/is_not_built_in_type.gd | 7 + .../is_not/is_not_class_instance.gd | 9 + .../core/assertions/is_not/is_not_float.gd | 9 + .../WAT/core/assertions/is_not/is_not_int.gd | 9 + addons/WAT/core/assertions/misc/fail.gd | 9 + addons/WAT/core/assertions/misc/that.gd | 8 + .../WAT/core/assertions/null/is_not_null.gd | 10 + addons/WAT/core/assertions/null/is_null.gd | 10 + .../assertions/object/does_not_have_meta.gd | 9 + .../assertions/object/does_not_have_method.gd | 9 + .../object/does_not_have_user_signal.gd | 9 + addons/WAT/core/assertions/object/has_meta.gd | 9 + .../WAT/core/assertions/object/has_method.gd | 9 + .../core/assertions/object/has_user_signal.gd | 9 + .../assertions/object/is_blocking_signals.gd | 9 + .../core/assertions/object/is_connected.gd | 9 + addons/WAT/core/assertions/object/is_freed.gd | 9 + .../object/is_not_blocking_signals.gd | 9 + .../assertions/object/is_not_connected.gd | 9 + .../core/assertions/object/is_not_freed.gd | 6 + .../object/is_not_queued_for_deletion.gd | 9 + .../object/is_queued_for_deletion.gd | 9 + .../core/assertions/property/does_not_have.gd | 10 + addons/WAT/core/assertions/property/has.gd | 10 + .../WAT/core/assertions/range/is_in_range.gd | 9 + .../core/assertions/range/is_not_in_range.gd | 9 + .../assertions/signal/signal_was_emitted.gd | 9 + .../signal_was_emitted_with_arguments.gd | 33 ++ .../signal/signal_was_emitted_x_times.gd | 9 + .../signal/signal_was_not_emitted.gd | 7 + .../assertions/string/string_begins_with.gd | 9 + .../core/assertions/string/string_contains.gd | 9 + .../string/string_does_not_begin_with.gd | 9 + .../string/string_does_not_contain.gd | 9 + .../string/string_does_not_end_with.gd | 9 + .../assertions/string/string_ends_with.gd | 9 + addons/WAT/core/double/factory.gd | 29 ++ addons/WAT/core/double/method.gd | 95 ++++ addons/WAT/core/double/methods.gd | 9 + addons/WAT/core/double/registry.gd | 22 + addons/WAT/core/double/scene_director.gd | 50 ++ addons/WAT/core/double/script_director.gd | 114 ++++ addons/WAT/core/double/script_writer.gd | 46 ++ addons/WAT/core/test/any.gd | 8 + addons/WAT/core/test/base_test.gd | 97 ++++ addons/WAT/core/test/case.gd | 43 ++ addons/WAT/core/test/parameters.gd | 21 + addons/WAT/core/test/recorder.gd | 32 ++ addons/WAT/core/test/suite.gd | 3 + addons/WAT/core/test/template.gd | 25 + addons/WAT/core/test/test.gd | 89 ++++ addons/WAT/core/test/timer.gd | 26 + addons/WAT/core/test/watcher.gd | 34 ++ addons/WAT/core/test/yielder.gd | 41 ++ addons/WAT/core/test_runner/TestRunner.tscn | 6 + addons/WAT/core/test_runner/directory.gd | 70 +++ addons/WAT/core/test_runner/execute.gd | 20 + addons/WAT/core/test_runner/test_loader.gd | 96 ++++ addons/WAT/core/test_runner/test_loader.tres | 7 + addons/WAT/core/test_runner/test_runner.gd | 108 ++++ addons/WAT/gui.tscn | 485 ++++++++++++++++++ addons/WAT/namespace.gd | 19 + addons/WAT/plugin.cfg | 6 + addons/WAT/plugin.gd | 29 ++ addons/WAT/resources/JUnitXML.gd | 30 ++ addons/WAT/resources/base/results.gd | 26 + addons/WAT/resources/results.tres | 8 + addons/WAT/system/filesystem.gd | 70 +++ addons/WAT/system/initializer.gd | 71 +++ addons/WAT/ui/dock.gd | 73 +++ addons/WAT/ui/metadata/Metadata.tscn | 75 +++ addons/WAT/ui/metadata/Tag.tscn | 36 ++ addons/WAT/ui/metadata/TagArray.tscn | 33 ++ addons/WAT/ui/metadata/editor.gd | 36 ++ addons/WAT/ui/metadata/index.gd | 11 + addons/WAT/ui/metadata/metadata.gd | 87 ++++ addons/WAT/ui/metadata/tag.gd | 6 + addons/WAT/ui/results/ResultTree.tscn | 12 + addons/WAT/ui/results/ResultsForest.tscn | 16 + addons/WAT/ui/results/result_forest.gd | 65 +++ addons/WAT/ui/results/result_tree.gd | 95 ++++ 180 files changed, 4675 insertions(+) create mode 100644 addons/WAT/LICENSE create mode 100644 addons/WAT/assets/crash_warning.png create mode 100644 addons/WAT/assets/crash_warning.png.import create mode 100644 addons/WAT/assets/down.png create mode 100644 addons/WAT/assets/down.png.import create mode 100644 addons/WAT/assets/failed.png create mode 100644 addons/WAT/assets/failed.png.import create mode 100644 addons/WAT/assets/function.png create mode 100644 addons/WAT/assets/function.png.import create mode 100644 addons/WAT/assets/function.svg create mode 100644 addons/WAT/assets/function.svg.import create mode 100644 addons/WAT/assets/icon_add.png create mode 100644 addons/WAT/assets/icon_add.png.import create mode 100644 addons/WAT/assets/kofi.png create mode 100644 addons/WAT/assets/kofi.png.import create mode 100644 addons/WAT/assets/more_button.png create mode 100644 addons/WAT/assets/more_button.png.import create mode 100644 addons/WAT/assets/more_button.svg create mode 100644 addons/WAT/assets/more_button.svg.import create mode 100644 addons/WAT/assets/play.svg create mode 100644 addons/WAT/assets/play.svg.import create mode 100644 addons/WAT/assets/remove.png create mode 100644 addons/WAT/assets/remove.png.import create mode 100644 addons/WAT/assets/replace.png create mode 100644 addons/WAT/assets/replace.png.import create mode 100644 addons/WAT/assets/success.png create mode 100644 addons/WAT/assets/success.png.import create mode 100644 addons/WAT/assets/summary.png create mode 100644 addons/WAT/assets/summary.png.import create mode 100644 addons/WAT/cli.tscn create mode 100644 addons/WAT/core/assertions/assertions.gd create mode 100644 addons/WAT/core/assertions/base.gd create mode 100644 addons/WAT/core/assertions/boolean/is_false.gd create mode 100644 addons/WAT/core/assertions/boolean/is_true.gd create mode 100644 addons/WAT/core/assertions/class_list.gd create mode 100644 addons/WAT/core/assertions/constants/type_library.gd create mode 100644 addons/WAT/core/assertions/double/called_with_arguments.gd create mode 100644 addons/WAT/core/assertions/double/script_was_called.gd create mode 100644 addons/WAT/core/assertions/double/script_was_not_called.gd create mode 100644 addons/WAT/core/assertions/equality/is_equal.gd create mode 100644 addons/WAT/core/assertions/equality/is_equal_or_greater_than.gd create mode 100644 addons/WAT/core/assertions/equality/is_equal_or_less_than.gd create mode 100644 addons/WAT/core/assertions/equality/is_greater_than.gd create mode 100644 addons/WAT/core/assertions/equality/is_less_than.gd create mode 100644 addons/WAT/core/assertions/equality/is_not_equal.gd create mode 100644 addons/WAT/core/assertions/file/file_does_not_exist.gd create mode 100644 addons/WAT/core/assertions/file/file_exists.gd create mode 100644 addons/WAT/core/assertions/is/is_AABB.gd create mode 100644 addons/WAT/core/assertions/is/is_Array.gd create mode 100644 addons/WAT/core/assertions/is/is_Basis.gd create mode 100644 addons/WAT/core/assertions/is/is_Color.gd create mode 100644 addons/WAT/core/assertions/is/is_Dictionary.gd create mode 100644 addons/WAT/core/assertions/is/is_NodePath.gd create mode 100644 addons/WAT/core/assertions/is/is_Object.gd create mode 100644 addons/WAT/core/assertions/is/is_Plane.gd create mode 100644 addons/WAT/core/assertions/is/is_PoolByteArray.gd create mode 100644 addons/WAT/core/assertions/is/is_PoolColorArray.gd create mode 100644 addons/WAT/core/assertions/is/is_PoolIntArray.gd create mode 100644 addons/WAT/core/assertions/is/is_PoolRealArray.gd create mode 100644 addons/WAT/core/assertions/is/is_PoolStringArray.gd create mode 100644 addons/WAT/core/assertions/is/is_PoolVector2Array.gd create mode 100644 addons/WAT/core/assertions/is/is_PoolVector3Array.gd create mode 100644 addons/WAT/core/assertions/is/is_Quat.gd create mode 100644 addons/WAT/core/assertions/is/is_RID.gd create mode 100644 addons/WAT/core/assertions/is/is_Rect2.gd create mode 100644 addons/WAT/core/assertions/is/is_String.gd create mode 100644 addons/WAT/core/assertions/is/is_Transform.gd create mode 100644 addons/WAT/core/assertions/is/is_Transform2D.gd create mode 100644 addons/WAT/core/assertions/is/is_Vector2.gd create mode 100644 addons/WAT/core/assertions/is/is_Vector3.gd create mode 100644 addons/WAT/core/assertions/is/is_bool.gd create mode 100644 addons/WAT/core/assertions/is/is_built_in_type.gd create mode 100644 addons/WAT/core/assertions/is/is_class_instance.gd create mode 100644 addons/WAT/core/assertions/is/is_float.gd create mode 100644 addons/WAT/core/assertions/is/is_int.gd create mode 100644 addons/WAT/core/assertions/is_not/is_not_AABB.gd create mode 100644 addons/WAT/core/assertions/is_not/is_not_Array.gd create mode 100644 addons/WAT/core/assertions/is_not/is_not_Basis.gd create mode 100644 addons/WAT/core/assertions/is_not/is_not_Color.gd create mode 100644 addons/WAT/core/assertions/is_not/is_not_Dictionary.gd create mode 100644 addons/WAT/core/assertions/is_not/is_not_NodePath.gd create mode 100644 addons/WAT/core/assertions/is_not/is_not_Object.gd create mode 100644 addons/WAT/core/assertions/is_not/is_not_Plane.gd create mode 100644 addons/WAT/core/assertions/is_not/is_not_PoolByteArray.gd create mode 100644 addons/WAT/core/assertions/is_not/is_not_PoolColorArray.gd create mode 100644 addons/WAT/core/assertions/is_not/is_not_PoolIntArray.gd create mode 100644 addons/WAT/core/assertions/is_not/is_not_PoolRealArray.gd create mode 100644 addons/WAT/core/assertions/is_not/is_not_PoolStringArray.gd create mode 100644 addons/WAT/core/assertions/is_not/is_not_PoolVector2Array.gd create mode 100644 addons/WAT/core/assertions/is_not/is_not_PoolVector3Array.gd create mode 100644 addons/WAT/core/assertions/is_not/is_not_Quat.gd create mode 100644 addons/WAT/core/assertions/is_not/is_not_RID.gd create mode 100644 addons/WAT/core/assertions/is_not/is_not_Rect2.gd create mode 100644 addons/WAT/core/assertions/is_not/is_not_String.gd create mode 100644 addons/WAT/core/assertions/is_not/is_not_Transform.gd create mode 100644 addons/WAT/core/assertions/is_not/is_not_Transform2D.gd create mode 100644 addons/WAT/core/assertions/is_not/is_not_Vector2.gd create mode 100644 addons/WAT/core/assertions/is_not/is_not_Vector3.gd create mode 100644 addons/WAT/core/assertions/is_not/is_not_bool.gd create mode 100644 addons/WAT/core/assertions/is_not/is_not_built_in_type.gd create mode 100644 addons/WAT/core/assertions/is_not/is_not_class_instance.gd create mode 100644 addons/WAT/core/assertions/is_not/is_not_float.gd create mode 100644 addons/WAT/core/assertions/is_not/is_not_int.gd create mode 100644 addons/WAT/core/assertions/misc/fail.gd create mode 100644 addons/WAT/core/assertions/misc/that.gd create mode 100644 addons/WAT/core/assertions/null/is_not_null.gd create mode 100644 addons/WAT/core/assertions/null/is_null.gd create mode 100644 addons/WAT/core/assertions/object/does_not_have_meta.gd create mode 100644 addons/WAT/core/assertions/object/does_not_have_method.gd create mode 100644 addons/WAT/core/assertions/object/does_not_have_user_signal.gd create mode 100644 addons/WAT/core/assertions/object/has_meta.gd create mode 100644 addons/WAT/core/assertions/object/has_method.gd create mode 100644 addons/WAT/core/assertions/object/has_user_signal.gd create mode 100644 addons/WAT/core/assertions/object/is_blocking_signals.gd create mode 100644 addons/WAT/core/assertions/object/is_connected.gd create mode 100644 addons/WAT/core/assertions/object/is_freed.gd create mode 100644 addons/WAT/core/assertions/object/is_not_blocking_signals.gd create mode 100644 addons/WAT/core/assertions/object/is_not_connected.gd create mode 100644 addons/WAT/core/assertions/object/is_not_freed.gd create mode 100644 addons/WAT/core/assertions/object/is_not_queued_for_deletion.gd create mode 100644 addons/WAT/core/assertions/object/is_queued_for_deletion.gd create mode 100644 addons/WAT/core/assertions/property/does_not_have.gd create mode 100644 addons/WAT/core/assertions/property/has.gd create mode 100644 addons/WAT/core/assertions/range/is_in_range.gd create mode 100644 addons/WAT/core/assertions/range/is_not_in_range.gd create mode 100644 addons/WAT/core/assertions/signal/signal_was_emitted.gd create mode 100644 addons/WAT/core/assertions/signal/signal_was_emitted_with_arguments.gd create mode 100644 addons/WAT/core/assertions/signal/signal_was_emitted_x_times.gd create mode 100644 addons/WAT/core/assertions/signal/signal_was_not_emitted.gd create mode 100644 addons/WAT/core/assertions/string/string_begins_with.gd create mode 100644 addons/WAT/core/assertions/string/string_contains.gd create mode 100644 addons/WAT/core/assertions/string/string_does_not_begin_with.gd create mode 100644 addons/WAT/core/assertions/string/string_does_not_contain.gd create mode 100644 addons/WAT/core/assertions/string/string_does_not_end_with.gd create mode 100644 addons/WAT/core/assertions/string/string_ends_with.gd create mode 100644 addons/WAT/core/double/factory.gd create mode 100644 addons/WAT/core/double/method.gd create mode 100644 addons/WAT/core/double/methods.gd create mode 100644 addons/WAT/core/double/registry.gd create mode 100644 addons/WAT/core/double/scene_director.gd create mode 100644 addons/WAT/core/double/script_director.gd create mode 100644 addons/WAT/core/double/script_writer.gd create mode 100644 addons/WAT/core/test/any.gd create mode 100644 addons/WAT/core/test/base_test.gd create mode 100644 addons/WAT/core/test/case.gd create mode 100644 addons/WAT/core/test/parameters.gd create mode 100644 addons/WAT/core/test/recorder.gd create mode 100644 addons/WAT/core/test/suite.gd create mode 100644 addons/WAT/core/test/template.gd create mode 100644 addons/WAT/core/test/test.gd create mode 100644 addons/WAT/core/test/timer.gd create mode 100644 addons/WAT/core/test/watcher.gd create mode 100644 addons/WAT/core/test/yielder.gd create mode 100644 addons/WAT/core/test_runner/TestRunner.tscn create mode 100644 addons/WAT/core/test_runner/directory.gd create mode 100644 addons/WAT/core/test_runner/execute.gd create mode 100644 addons/WAT/core/test_runner/test_loader.gd create mode 100644 addons/WAT/core/test_runner/test_loader.tres create mode 100644 addons/WAT/core/test_runner/test_runner.gd create mode 100644 addons/WAT/gui.tscn create mode 100644 addons/WAT/namespace.gd create mode 100644 addons/WAT/plugin.cfg create mode 100644 addons/WAT/plugin.gd create mode 100644 addons/WAT/resources/JUnitXML.gd create mode 100644 addons/WAT/resources/base/results.gd create mode 100644 addons/WAT/resources/results.tres create mode 100644 addons/WAT/system/filesystem.gd create mode 100644 addons/WAT/system/initializer.gd create mode 100644 addons/WAT/ui/dock.gd create mode 100644 addons/WAT/ui/metadata/Metadata.tscn create mode 100644 addons/WAT/ui/metadata/Tag.tscn create mode 100644 addons/WAT/ui/metadata/TagArray.tscn create mode 100644 addons/WAT/ui/metadata/editor.gd create mode 100644 addons/WAT/ui/metadata/index.gd create mode 100644 addons/WAT/ui/metadata/metadata.gd create mode 100644 addons/WAT/ui/metadata/tag.gd create mode 100644 addons/WAT/ui/results/ResultTree.tscn create mode 100644 addons/WAT/ui/results/ResultsForest.tscn create mode 100644 addons/WAT/ui/results/result_forest.gd create mode 100644 addons/WAT/ui/results/result_tree.gd diff --git a/addons/WAT/LICENSE b/addons/WAT/LICENSE new file mode 100644 index 0000000..8b23445 --- /dev/null +++ b/addons/WAT/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 + +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. diff --git a/addons/WAT/assets/crash_warning.png b/addons/WAT/assets/crash_warning.png new file mode 100644 index 0000000000000000000000000000000000000000..8a6707f6e2fe7888608d8ec9eca96b9dadaa4e21 GIT binary patch literal 892 zcmeAS@N?(olHy`uVBq!ia0vp^k|4~%1|*NXY)uAIEa{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZKG?e4&pI^O-w*jag)8!Dh39o22U5q5Rc<~uX$&LhKsO0czlR9VDV!Jt>t=BeA2wci1Qlexxp&`MA zXT?swZ@WG|d2%m%(H3sKoOAc8|GxhJegF4efA}Z*rUsS@oH-I6(PO=%ar({1?<}29 zHV7==Aj7|b$JwHL;&#T+WezD~=Z;j^|9br0(oaEO#MpMip<{Vxj(hT~ZrW|p=a-|o zcD~tUzBdQYSMWsM|8d7a?}5TIzuBG(SH<1oI~g`*TJAw_o1^d2a%LGuHtWS4Gt|*l zjp|kYA#1H_?8B=v>B$el`p42aQ)I$=js_k{-=Q~iq26pxg{zGp6T?HoZpR#)bm)?e z!<4RAB?32UR`Spi3-{!4To;*$nSY6|mxsSK~WBa6~*+pC39@%f?%=Ycu zc43cN_TJ;AlT%*SUwH126wLgSkGp`o_SJEoH-`gj?kD)_S-#QQXOOSdm(UxZi6GE)6T)rXrT=_uvM%mjR97CddxZ)n#=_Sd&d+4cWd)404 zHu*wO!lBHC7T?;WdIfVXjMkKK8qDKG~V_&^w5|cJdM{ zfdt(sr_<#?e+SRK;QIe}1#fFsdujNc#pi*^Q?2OC7#SE^>Kd5o z8X1Kcnpv3`S{WPb8kkra7|i?sW;Tk3-29Zxv`X9>-Y*JK2WnvOboFyt=akR{0LCVD A{r~^~ literal 0 HcmV?d00001 diff --git a/addons/WAT/assets/crash_warning.png.import b/addons/WAT/assets/crash_warning.png.import new file mode 100644 index 0000000..b72de8d --- /dev/null +++ b/addons/WAT/assets/crash_warning.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/crash_warning.png-e91fc83896a759bc0e8033b98cc4897b.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/WAT/assets/crash_warning.png" +dest_files=[ "res://.import/crash_warning.png-e91fc83896a759bc0e8033b98cc4897b.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/addons/WAT/assets/down.png b/addons/WAT/assets/down.png new file mode 100644 index 0000000000000000000000000000000000000000..908e06836b7ce5d3fdd85dce938e5cb785d8a972 GIT binary patch literal 317 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6T^Jl0 zZZceFvv2kTif|TqL>4nJa0`PlBg3pY5H=O_FL@itR_No_Y6gVLVG=3978Nl zU!82tbtpi<^}HzeMUh?1+XXoC4ux`v*$I{y2W~vicBfTg2kYxCN>Qmge-i5c1zA3w zJ@fR;nZHvcLKnFmU7-`8@BO3E`|P&cdS6T()n{GmOWQBVpt7;?O>v{nwxevPp3gge zQm~@=mQ!+FYPV7(8A5 KT-G@yGywoV0(C|J literal 0 HcmV?d00001 diff --git a/addons/WAT/assets/down.png.import b/addons/WAT/assets/down.png.import new file mode 100644 index 0000000..807f464 --- /dev/null +++ b/addons/WAT/assets/down.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/down.png-26c0271889a75bba3617a8a20841e34c.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/WAT/assets/down.png" +dest_files=[ "res://.import/down.png-26c0271889a75bba3617a8a20841e34c.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/addons/WAT/assets/failed.png b/addons/WAT/assets/failed.png new file mode 100644 index 0000000000000000000000000000000000000000..5397c9f2380c81be2502a06c5de4eb9468efb4e8 GIT binary patch literal 2056 zcmah~X;c$e7#$D<+1d(nEK(f@wG_-u7K4%!wy;*hK?oX9EMqc(kt}0oA_36?M{H?9 z1*-xgT9;FiMN!li>xo5NQ2{*_+&xxBZC#2SajDWb3B?6le@rs(`|fw&efN9sO<`PY zq_cyE0|Y_N(NSUX;QwRxu^$e8TkaGnAjoC|DU&mDX^cpr)1bIgH;+J#8VckgC?Lp4 z;ff4`f#(rvq*jbP*l$8$QYl7e38h>qB_YzusBArvkR2;iWM?S+l}J#aLx52P2s8wP z!$yr-ON)$R#LO!K?`#-DU^9ft5F>JF94yi430Q~iU`L9akuX18!Dj#eV zwu$CxlHQrJbV>xKr=>b2p&n>Eu-qIQ7_ul)9BCxf@-R{Zuwd>%t*??vLzBQy5g;a2 zSz$OsfEq5(pTqUz@cA-6PsH^T3Aw&p@L~~aMuUy7#2Ng3cy{~30p@;*qDY$2>9fos z%brdk7Fb4>p_(*rW*9biON1*}_rwUL*C`DOLTPph&{?vyPQ_&6dLkqZG)jzws8l5I zISU5ogiO;CdYFe!MR{-5WTq3WBmcQNY(N{v9-unyz=7D-L$E%>g<8;d|_Gqne5^*XUZ$xA3pbV{2@H@g`ReC3@NB=CtNmDe9e* zcDDpERaAQS>^q*!r&;!Ms3oI_7U{Gw)FSJgFs!s)iL`F)b!x?SgJh%<&-l6Vf; zPc@8Kzhb`JvHuId1sMS@x*yv;^Y@NPpX2kmwz2K_Fq_VsHTAe|+_M`wtvw$+$Vi{i zY_EI{z78X(s3aPK99`MR3aY3a4O)~J9Tp;sD!jM4L(?4Ww9GUeg{_^pc`?=zsRt=WM=iOPw2a@bf1gi*5(4%XRg(xz*5i*zV4*bRNGINKez3w>Y-Jj~vd(mqmtqRY5+^2pGyTVBphRa~!XL{Rt4 zy)v(j`*^uWPByN8&Z!$#b^;wf(JG`hc>W*T_!|>!ra4=MCdL?ME~t**P%fKPUB7#! zkJ|mnj0h_peA@edQ~4#Ar_L=apvTthWSwNp=B=hXld;Z_T7zyKdG=TT=!#Ow)G5{( zmxEMIxA}jKpJeAStkG@TA;(qP9=rX=o{TAfrmHTOS)baHd$r7Iao1S;=`J@-;!8`Q YHA_F$uRIf9$bP`1!(+qhLsRqr0lz=&zW@LL literal 0 HcmV?d00001 diff --git a/addons/WAT/assets/failed.png.import b/addons/WAT/assets/failed.png.import new file mode 100644 index 0000000..82ad4b3 --- /dev/null +++ b/addons/WAT/assets/failed.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/failed.png-18188bee7d18ad72bc7cbc6222c355b1.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/WAT/assets/failed.png" +dest_files=[ "res://.import/failed.png-18188bee7d18ad72bc7cbc6222c355b1.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/addons/WAT/assets/function.png b/addons/WAT/assets/function.png new file mode 100644 index 0000000000000000000000000000000000000000..8c19f70d76ce0a8e29958922d4dc0a7a612eef1c GIT binary patch literal 317 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}T7XZ8E0F&G|NqsiS06uq{Nlxn zJ9qBfy?gim{re9eK791((E~65vVj072n0X@2pb{=5rMFAi2%hnhc4^{+N4+#(TYT$Vl?k6o~qw%q#b-S?;G=k)us=e5uI`wD0W NgQu&X%Q~loCIBI|r~3c^ literal 0 HcmV?d00001 diff --git a/addons/WAT/assets/function.png.import b/addons/WAT/assets/function.png.import new file mode 100644 index 0000000..3a9368f --- /dev/null +++ b/addons/WAT/assets/function.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/function.png-b5f751bc93a3ee5a29c4d32864c250fc.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/WAT/assets/function.png" +dest_files=[ "res://.import/function.png-b5f751bc93a3ee5a29c4d32864c250fc.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/addons/WAT/assets/function.svg b/addons/WAT/assets/function.svg new file mode 100644 index 0000000..57c9333 --- /dev/null +++ b/addons/WAT/assets/function.svg @@ -0,0 +1,3 @@ + + + diff --git a/addons/WAT/assets/function.svg.import b/addons/WAT/assets/function.svg.import new file mode 100644 index 0000000..b4a4159 --- /dev/null +++ b/addons/WAT/assets/function.svg.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/function.svg-14e03c4a8904011639649cd74908f843.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/WAT/assets/function.svg" +dest_files=[ "res://.import/function.svg-14e03c4a8904011639649cd74908f843.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/addons/WAT/assets/icon_add.png b/addons/WAT/assets/icon_add.png new file mode 100644 index 0000000000000000000000000000000000000000..26283ca67ceb558f17d35c02e4f8b8f6d2edee46 GIT binary patch literal 351 zcmV-l0igbgP)aA*P2C$F&-qHrUP000>X1^@s6#OZ}&00084NklUY$6gM2>G{z=mFs7D_ichD{-Bj$3N<4{co3v7VksE`e(|DV2k#7aa^VE)9 z#rI+_N52(IM)a<7N2}h*!?jgN2;t~)wp%(Z8Tll>m_#e6(8S}0#6OUp z4uJramBG&+kdvPfL#7vis((AMYr22vKUgu#H!FFEe#gVhpCgoOqm#F+GLnC z9uDktvWZqEvS`{~O=A1o0z`jGee}PVCfQhGLWL#;`LK4C9oS^@1h{zqzf8pFdSV%( zclj3G@1#m5RGx__J`QG0mrNoCV!CA`WTa#3S&oM}4|9-}6A{J;-u1Fub~u_{Qpz4VB{#V94)>xY+Q7Z{7%h{_wA? zKnUsF=Rz;Css=28P&U7GHnt*1HC@g2B_(&t;uc GLK6V}^fe*? literal 0 HcmV?d00001 diff --git a/addons/WAT/assets/more_button.png.import b/addons/WAT/assets/more_button.png.import new file mode 100644 index 0000000..df31ed7 --- /dev/null +++ b/addons/WAT/assets/more_button.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/more_button.png-94fdfc4de255b283c1556198e1efe904.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/WAT/assets/more_button.png" +dest_files=[ "res://.import/more_button.png-94fdfc4de255b283c1556198e1efe904.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/addons/WAT/assets/more_button.svg b/addons/WAT/assets/more_button.svg new file mode 100644 index 0000000..490d5de --- /dev/null +++ b/addons/WAT/assets/more_button.svg @@ -0,0 +1,87 @@ + + + + + + image/svg+xml + + + + + + + + + + + Codes 60-69 General Group: Rain. + Code: 64 + Description: Rain, not freezing, intermittent (heavy at time of observation) + + + + + diff --git a/addons/WAT/assets/more_button.svg.import b/addons/WAT/assets/more_button.svg.import new file mode 100644 index 0000000..416aa85 --- /dev/null +++ b/addons/WAT/assets/more_button.svg.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/more_button.svg-f52f2977a26f7d8685b3e0a5dcb7978e.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/WAT/assets/more_button.svg" +dest_files=[ "res://.import/more_button.svg-f52f2977a26f7d8685b3e0a5dcb7978e.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/addons/WAT/assets/play.svg b/addons/WAT/assets/play.svg new file mode 100644 index 0000000..9b5f643 --- /dev/null +++ b/addons/WAT/assets/play.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/addons/WAT/assets/play.svg.import b/addons/WAT/assets/play.svg.import new file mode 100644 index 0000000..9d67c3c --- /dev/null +++ b/addons/WAT/assets/play.svg.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/play.svg-0b6b527de6d3691c6b38f55eee12b863.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/WAT/assets/play.svg" +dest_files=[ "res://.import/play.svg-0b6b527de6d3691c6b38f55eee12b863.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/addons/WAT/assets/remove.png b/addons/WAT/assets/remove.png new file mode 100644 index 0000000000000000000000000000000000000000..f0f814e30439202436ec0c3a3f324ead10d0655e GIT binary patch literal 519 zcmV+i0{H!jP)I|K zGE+q8ugpA)>o|rfHt;+m-mL61D6od(7(~QgW!jPa@)eo0*E(#Ye1H!eT{WP_e`b%sR3=&pJ-lho+o#B2D2r+Oj{+%%-j6 z7I$zLlS=s3JI41awpDl+H#+6c?iS8el}%j6k&27BfW$V=RbJabL@cmZ=evc+wc#GF zm75pX!4RLjzBIiijjQ?xjEWoLUExtge5!rp;+B1ZQ8lOw_1q}DR*hcPxPNxH#t8fV zZ_WSaVA^xB{D;6{^-F&W-G}|U2eaa4B@FY3n3aRh?Xq2QFu^69E^fQV3#T&ke7*m2 z&=>exx9??-d)mWSE8zW~91QUgH*wIr=F7s1`cK~Cb!IkL`~asFgd4-veRTi;002ov JPDHLkV1fa{auCGXH}E;6v(Dl(h?@??r-Av7HY&vt*#`8fB$zm+k@0q`~t&suv11g`5&thM9ydi}T$ptV*2${6E| zNW5CD-T`{O-my~ZLqrbKG=15TwSYSsjkbVbxmmR2nUK#k-mdB7miF_kOR>df=JOH@5 zs}!I(3PK4Rp>SBnB0j3EClX)@ix})Apb8XTPz212S3&{tzJcQSC^17qba%tMs+br- z41|INRZO%DVX9a}9WN96t_@R&1RVs8Vi83GA;C+oga}S#Co+}jh9|fxB~qq8+k3JY z8?lHHD5_vmC~=J#j}F@^mvNJN+pU3`_nT*Ahj?YTFeggR!aU zA4)_YaxCHsI<`@?P1H>j^4ZixrWMagyRLura2&<$dpZExG%=bOM#H+zOd#mG#RSD#_gF-QQZ9)VLlT`!7~Mn`kxS7y zPzia4V~t`FJ*83@^I1*6&IueYgOmgsc?FsFX-!-Nq;=$fu8uOPjiNnZe~5FXOp))Z z1AKHMvAT&EcG9scgYy2)zy|Mc9Y}^MrK-?QxzD0%`w37a$fi8dr19@f}hXbOLEHgZ;ww~+@6#7#pt+& z(E$$ktx+u6wtas_<-t-%WWMK5x9+jNaO^qLvi3&n+bF^H?DE7zo~pprC-O#1MxU+q zby9Ort>$8SwJ2qVx)BOjo{`cL(QcJ66rls8p!trSHN9Tm~vy({|UNY&ajkL~!K6=2lR?DOp zUeAsm>p%d~V#U^=4q~mM$}oRdq{!&qD(6j6uEz2o+boi57DueJ8!M}By*y3-=H1eA zP;T{lK-2tW&ZDRZo5q=vaqQQ@P{9j90KjOz_Q3(UdFB8x4d$~w19>~L_6cMbD@>B> zhnH~QHFok%8Y=H}>4$iq_0&uopXs)ZQRmTbq`E)fWySaMFw(K`jxJH+0%yIYLK|Lg zjx-MPtdZKZ@{EJmOR6@U>largzoTg+z?uVH`1C!L+e=UC-`4M*dCRO9`o^rycvcD1 z8apnd$ z^X!yA`gFO<$%ed5znZjY`ZLpu=O%k9;LQP}mL633wrQL~kLR^Tq)|#@=H{e3)zm7( z&PCjS$bt^LyO9f9qWX#k#>I-OOOFXt2xY)4g1u=ngDR{xPXD<(L{xGAPV~^+>I&u9 znPuPW2^3fP>4kr*CvTb2w)L*{g&%O{hOZI1{gdS`>Bnzu*xd}JwERmm;MSH2{oN?N z82>ch;z69-t#5+}na#^vdjHwi4%Y9J*KK%Q(J-Pn`*iwc?sohxPw%9XkR3LU78mhg-96~-wv;Z!r9ipRLqa~|6MwlbI8>{QyM2|eG7KdvXl%`tmrvJR=6R}$T0;xly8d>(kh zXfyeUUM~r!v5mfFX0Tg&{(AXN#JUOJHaB~26Ky%SH1x{7JMLdLY@JqnU`U|-qsHg> KvddS7CH)R7)Z=;p literal 0 HcmV?d00001 diff --git a/addons/WAT/assets/success.png.import b/addons/WAT/assets/success.png.import new file mode 100644 index 0000000..8e23da6 --- /dev/null +++ b/addons/WAT/assets/success.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/success.png-22ff6871ccf3e0018fe6815cfb7f3a42.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/WAT/assets/success.png" +dest_files=[ "res://.import/success.png-22ff6871ccf3e0018fe6815cfb7f3a42.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/addons/WAT/assets/summary.png b/addons/WAT/assets/summary.png new file mode 100644 index 0000000000000000000000000000000000000000..ab9ea81878f80e670842bc2186699d321a892865 GIT binary patch literal 1311 zcmV+)1>pLLP)EX>4Tx04R}tkv&MmKpe$iQ$^8A2Rn#5WT;LSq>4C76^me@v=v%)FuC*#nlvOS zE{=k0!NHHks)LKOt`4q(Aou~|}?mh0_0Yam~RI_UmP&La) z#baVNw<`9$!jBOI(T}LaOg)ia%)oPe-NVP%y9m$nKKJJsQ1T`Nd?N82(+!JwgLr1s z(mC%FhgeBch|h^947wokBiCh@-#8Z?7I zj29_;-Q(T8oxS~grq$mMlKyhEZ9v2j00006VoOIv0RI600RN!9r;`8x010qNS#tmY zE+YT{E+YYWr9XB6000McNlirueSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00Q?(L_t(Y$L*I*h*o73$A4!kU8sFbFfmT6Hem_PrbsZ- z2>Kve6|=}bY$F7Pw1__4)EH!&TC@rpB-#W)A%s}tDs|Dq&uMa^WEux5q%2c&CVy?t zosPG6#u#0`&*t3wd(Zjbd;ZUJpL<`(p-zpKxxr;&C)IPRz{>pjhnD{5MZXz)G7e_kl5t-|d^TrTM4XL?jVZnn__Bzs>0r)ShXxPK z$JAb+zm<0ru=HYj|F>3s6xank06YUM2OcWA2V44wIxNV2fWUzIAaH;3mz#v1njI0p zM8pViA8;!1(Y&Fnfq`V>$(94_lI>5{5A-EFlJVAZnIkRp=h&sx0#7yf5fM8g;v8^K zvh%>RofVVPtMbTcJ`_+5EHLckn z?9j|YP0s)mm!bA#v-;E<)%C!pWZy-^>6ZSt^K5&2GxoPvSF^&_0#5>?z)!$5@H8+C z3`WFsx_=zl2K-hY+q!A)Z!P&VW#4F;o{or void: + parse(arguments()) + +func arguments() -> Array: + return Array(OS.get_cmdline_args()).pop_back().split(\"=\") as Array + +func repeat(args) -> int: + if not args.empty() and args.back().is_valid_integer(): + return args.back() as int + else: + return 1 + +func parse(arguments: Array) -> void: + ProjectSettings.set(\"WAT/TestStrategy\", {}) + ProjectSettings.save() + var command: String = arguments.pop_front() + match command: + RUN_ALL: + var strat = strategy() + strat[\"strategy\"] = \"RunAll\" + strat[\"repeat\"] = repeat(arguments) + ProjectSettings.set(\"WAT/TestStrategy\", strat) + _run() + RUN_DIRECTORY: + var strat = strategy() + strat[\"strategy\"] = \"RunDirectory\" + strat[\"directory\"] = arguments.front() + strat[\"repeat\"] = repeat(arguments) + ProjectSettings.set(\"WAT/TestStrategy\", strat) + _run() + RUN_SCRIPT: + var strat = strategy() + strat[\"strategy\"] = \"RunScript\" + strat[\"script\"] = arguments.front() + strat[\"repeat\"] = repeat(arguments) + ProjectSettings.set(\"WAT/TestStrategy\", strat) + _run() + RUN_TAG: + var strat = strategy() + strat[\"strategy\"] = \"RunTag\" + strat[\"tag\"] = arguments.front() + print(strat[\"tag\"]) + strat[\"repeat\"] = repeat(arguments) + ProjectSettings.set(\"WAT/TestStrategy\", strat) + _run() + RUN_METHOD: + var strat = strategy() + strat[\"strategy\"] = \"RunMethod\" + strat[\"script\"] = arguments[0] + strat[\"method\"] = arguments[1] + strat[\"repeat\"] = repeat(arguments) + ProjectSettings.set(\"WAT/TestStrategy\", strat) + _run() + RUN_FAILURES: + var strat = strategy() + strat[\"strategy\"] = \"RerunFailures\" + strat[\"repeat\"] = repeat(arguments) + ProjectSettings.set(\"WAT/TestStrategy\", strat) + _run() + LIST_ALL: + _list() + get_tree().quit() + LIST_DIR: + _list(arguments.pop_front()) + get_tree().quit() + _: + push_error(\"Invalid Argument\") + get_tree().quit() + +func strategy() -> Dictionary: + return ProjectSettings.get_setting(\"WAT/TestStrategy\") + +func test_directory() -> String: + return ProjectSettings.get_setting(\"WAT/Test_Directory\") + +func _list(path: String = test_directory()): + print() + print(FileSystem.scripts(path)) + +func _run() -> void: + _runner = TestRunner.instance() + _runner.connect(\"ended\", self, \"_on_testrunner_ended\") + _start_time = OS.get_ticks_msec() + add_child(_runner) + +static func set_run_path(path: String) -> void: + ProjectSettings.set(\"WAT/ActiveRunPath\", path) + +func _on_testrunner_ended() -> void: + _runner.queue_free() + var caselist: Array = WAT.Results.withdraw() + var cases = {passed = 0, total = 0, crashed = 0} + for case in caselist: + cases.total += 1 + if case.success: + cases.passed += 1 + else: + display_failures(case) + display_summary(cases) + set_exit_code(cases) + +func display_failures(case) -> void: + print(\"%s (%s)\" % [case.context, case.path]) + for method in case.methods: + if not method.success: + print(\"\\n %s\" % method.context) + for assertion in method.assertions: + if not assertion.success: + print(\"\\t%s\" % assertion.context, \"\\n\\t (EXPECTED: %s) | (RESULTED: %s)\" % [assertion.expected, assertion.actual]) + + +func display_summary(cases: Dictionary) -> void: + cases.seconds = (OS.get_ticks_msec() - _start_time) / 1000 + print(\"\"\" + -------RESULTS------- + Took {seconds} seconds + {crashed} Tests Crashed + {passed} / {total} Tests Passed + -------RESULTS------- + \"\"\".format(cases).dedent()) + +func set_exit_code(cases: Dictionary) -> void: + OS.exit_code = PASSED if cases.total > 0 and cases.total == cases.passed and cases.crashed == 0 else FAILED +" + +[node name="cli" type="Node"] +script = SubResource( 1 ) diff --git a/addons/WAT/core/assertions/assertions.gd b/addons/WAT/core/assertions/assertions.gd new file mode 100644 index 0000000..e38b049 --- /dev/null +++ b/addons/WAT/core/assertions/assertions.gd @@ -0,0 +1,315 @@ +extends "class_list.gd" + +signal OUTPUT +signal asserted + +func output(data) -> void: + emit_signal("asserted", data) + +func loop(method: String, data: Array) -> void: + for set in data: + callv(method, set) + +func is_true(condition: bool, context: String = "") -> void: + output(IsTrue.new(condition, context)) + +func is_false(condition: bool, context: String = "") -> void: + output(IsFalse.new(condition, context)) + +func is_equal(a, b, context: String = "") -> void: + output(IsEqual.new(a, b, context)) + +func is_not_equal(a, b, context: String = "") -> void: + output(IsNotEqual.new(a, b, context)) + +func is_greater_than(a, b, context: String = "") -> void: + output(IsGreaterThan.new(a, b, context)) + +func is_less_than(a, b, context: String = "") -> void: + output(IsLessThan.new(a, b, context)) + +func is_equal_or_greater_than(a, b, context: String = "") -> void: + output(IsEqualOrGreaterThan.new(a, b, context)) + +func is_equal_or_less_than(a, b, context: String = "") -> void: + output(IsEqualOrLessThan.new(a, b, context)) + +func is_in_range(value, low, high, context: String = "") -> void: + output(IsInRange.new(value, low, high, context)) + +func is_not_in_range(value, low, high, context: String = "") -> void: + output(IsNotInRange.new(value, low, high, context)) + +func has(value, container, context: String = "") -> void: + output(Has.new(value, container, context)) + +func does_not_have(value, container, context: String = "") -> void: + output(DoesNotHave.new(value, container, context)) + +func is_class_instance(instance, type, context: String = "") -> void: + output(IsClassInstance.new(instance, type, context)) + +func is_not_class_instance(instance, type, context: String = "") -> void: + output(IsNotClassInstance.new(instance, type, context)) + +func is_built_in_type(value, type, context: String = "") -> void: + print("WARNING: is_built_in_type is deprecated. Use is_x where x is builtin type") + output(IsBuiltInType.new(value, type, context)) + +func is_not_built_in_type(value, type: int, context: String = "") -> void: + output(IsNotBuiltInType.new(value, type, context)) + +func is_null(value, context: String = "") -> void: + output(IsNull.new(value, context)) + +func is_not_null(value, context: String = "") -> void: + output(IsNotNull.new(value, context)) + +func string_contains(value, string: String, context: String = "") -> void: + output(Contains.new(value, string, context)) + +func string_does_not_contain(value, string: String, context: String = "") -> void: + output(DoesNotContain.new(value, string, context)) + +func string_begins_with(value, string: String, context: String = "") -> void: + output(BeginsWith.new(value, string, context)) + +func string_does_not_begin_with(value, string: String, context: String = "") -> void: + output(DoesNotBeginWith.new(value, string, context)) + +func string_ends_with(value, string: String, context: String = "") -> void: + output(EndsWith.new(value, string, context)) + +func string_does_not_end_with(value, string: String, context: String = "") -> void: + output(DoesNotEndWith.new(value, string, context)) + +func was_called(double, method: String, context: String = "") -> void: + output(ScriptWasCalled.new(double, method, context)) + +func was_not_called(double, method: String, context: String = "") -> void: + output(ScriptWasNotCalled.new(double, method, context)) + +func was_called_with_arguments(double, method: String, arguments: Array, context: String = "") -> void: + output(CalledWithArguments.new(double, method, arguments, context)) + +func signal_was_emitted(emitter, _signal, context: String = "") -> void: + output(WasEmitted.new(emitter, _signal, context)) + +func signal_was_emitted_x_times(emitter, _signal, times: int, context: String = "") -> void: + output(WasEmittedXTimes.new(emitter, _signal, times, context)) + +func signal_was_not_emitted(emitter, _signal: String, context: String = "") -> void: + output(WasNotEmitted.new(emitter, _signal, context)) + +func signal_was_emitted_with_arguments(emitter, _signal, arguments: Array, context: String = "") -> void: + output(WasEmittedWithArguments.new(emitter, _signal, arguments, context)) + +func file_exists(path: String, context: String = "") -> void: + output(FileExists.new(path, context)) + +func file_does_not_exist(path: String, context: String = "") -> void: + output(FileDoesNotExist.new(path, context)) + +func that(obj: Object, method: String, arguments: Array = [], context: String = "", passed: String = "", failed: String = "") -> void: + output(That.new(obj, method, arguments, context, passed, failed)) + +func object_has_meta(obj: Object, meta: String, context: String) -> void: + output(ObjectHasMeta.new(obj, meta, context)) + +func object_does_not_have_meta(obj: Object, meta: String, context: String) -> void: + output(ObjectDoesNotHaveMeta.new(obj, meta, context)) + +func object_has_method(obj: Object, method: String, context: String) -> void: + output(ObjectHasMethod.new(obj, method, context)) + +func object_does_not_have_method(obj: Object, method: String, context: String) -> void: + output(ObjectDoesNotHaveMethod.new(obj, method, context)) + +func object_is_queued_for_deletion(obj: Object, context: String) -> void: + output(ObjectIsQueuedForDeletion.new(obj, context)) + +func object_is_not_queued_for_deletion(obj: Object, context: String) -> void: + output(ObjectIsNotQueuedForDeletion.new(obj, context)) + +func object_is_connected(sender: Object, _signal: String, receiver: Object, method: String, context: String) -> void: + output(ObjectIsConnected.new(sender, _signal, receiver, method, context)) + +func object_is_not_connected(sender: Object, _signal: String, receiver: Object, method: String, context: String) -> void: + output(ObjectIsNotConnected.new(sender, _signal, receiver, method, context)) + +func object_is_blocking_signals(obj: Object, context: String) -> void: + output(ObjectIsBlockingSignals.new(obj, context)) + +func object_is_not_blocking_signals(obj: Object, context: String) -> void: + output(ObjectIsNotBlockingSignals.new(obj, context)) + +func object_has_user_signal(obj: Object, _signal: String, context: String) -> void: + output(ObjectHasUserSignal.new(obj, _signal, context)) + +func object_does_not_have_user_signal(obj: Object, _signal: String, context: String) -> void: + output(ObjectDoesNotHaveUserSignal.new(obj, _signal, context)) + +func is_freed(obj: Object, context: String = "") -> void: + output(ObjectIsFreed.new(obj, context)) + +func is_not_freed(obj: Object, context: String = "") -> void: + output(ObjectIsNotFreed.new(obj, context)) + +func is_bool(value, context: String = "") -> void: + output(IsBool.new(value, context)) + +func is_not_bool(value, context: String = "") -> void: + output(IsNotBool.new(value, context)) + +func is_int(value, context: String = "") -> void: + output(IsInt.new(value, context)) + +func is_not_int(value, context: String = "") -> void: + output(IsNotInt.new(value, context)) + +func is_float(value, context: String = "") -> void: + output(IsFloat.new(value, context)) + +func is_not_float(value, context: String = "") -> void: + output(IsNotFloat.new(value, context)) + +func is_String(value, context: String = "") -> void: + output(IsString.new(value, context)) + +func is_not_String(value, context: String = "") -> void: + output(IsNotString.new(value, context)) + +func is_Vector2(value, context: String = "") -> void: + output(IsVector2.new(value, context)) + +func is_not_Vector2(value, context: String = "") -> void: + output(IsNotVector2.new(value, context)) + +func is_Rect2(value, context: String = "") -> void: + output(IsRect2.new(value, context)) + +func is_not_Rect2(value, context: String = "") -> void: + output(IsNotRect2.new(value, context)) + +func is_Vector3(value, context: String = "") -> void: + output(IsVector3.new(value, context)) + +func is_not_Vector3(value, context: String = "") -> void: + output(IsNotVector3.new(value, context)) + +func is_Transform2D(value, context: String = "") -> void: + output(IsTransform2D.new(value, context)) + +func is_not_Transform2D(value, context: String = "") -> void: + output(IsNotTransform2D.new(value, context)) + +func is_Plane(value, context: String = "") -> void: + output(IsPlane.new(value, context)) + +func is_not_Plane(value, context: String = "") -> void: + output(IsNotPlane.new(value, context)) + +func is_Quat(value, context: String = "") -> void: + output(IsQuat.new(value, context)) + +func is_not_Quat(value, context: String = "") -> void: + output(IsNotQuat.new(value, context)) + +func is_AABB(value, context: String = "") -> void: + output(IsAABB.new(value, context)) + +func is_not_AABB(value, context: String = "") -> void: + output(IsNotAABB.new(value, context)) + +func is_Basis(value, context: String = "") -> void: + output(IsBasis.new(value, context)) + +func is_not_Basis(value, context: String = "") -> void: + output(IsNotBasis.new(value, context)) + +func is_Transform(value, context: String = "") -> void: + output(IsTransform.new(value, context)) + +func is_not_Transform(value, context: String = "") -> void: + output(IsNotTransform.new(value, context)) + +func is_Color(value, context: String = "") -> void: + output(IsColor.new(value, context)) + +func is_not_Color(value, context: String = "") -> void: + output(IsNotColor.new(value, context)) + +func is_NodePath(value, context: String = "") -> void: + output(IsNodePath.new(value, context)) + +func is_not_NodePath(value, context: String = "") -> void: + output(IsNotNodePath.new(value, context)) + +func is_RID(value, context: String = "") -> void: + output(IsRID.new(value, context)) + +func is_not_RID(value, context: String = "") -> void: + output(IsNotRID.new(value, context)) + +func is_Object(value, context: String = "") -> void: + output(IsObject.new(value, context)) + +func is_not_Object(value, context: String = "") -> void: + output(IsNotObject.new(value, context)) + +func is_Dictionary(value, context: String = "") -> void: + output(IsDictionary.new(value, context)) + +func is_not_Dictionary(value, context: String = "") -> void: + output(IsNotDictionary.new(value, context)) + +func is_Array(value, context: String = "") -> void: + output(IsArray.new(value, context)) + +func is_not_Array(value, context: String = "") -> void: + output(IsNotArray.new(value, context)) + +func is_PoolByteArray(value, context: String = "") -> void: + output(IsPoolByteArray.new(value, context)) + +func is_not_PoolByteArray(value, context: String = "") -> void: + output(IsNotPoolByteArray.new(value, context)) + +func is_PoolIntArray(value, context: String = "") -> void: + output(IsPoolIntArray.new(value, context)) + +func is_not_PoolIntArray(value, context: String = "") -> void: + output(IsNotPoolIntArray.new(value, context)) + +func is_PoolRealArray(value, context: String = "") -> void: + output(IsPoolRealArray.new(value, context)) + +func is_not_PoolRealArray(value, context: String = "") -> void: + output(IsNotPoolRealArray.new(value, context)) + +func is_PoolStringArray(value, context: String = "") -> void: + output(IsPoolStringArray.new(value, context)) + +func is_not_PoolStringArray(value, context: String = "") -> void: + output(IsNotPoolStringArray.new(value, context)) + +func is_PoolVector2Array(value, context: String = "") -> void: + output(IsPoolVector2Array.new(value, context)) + +func is_not_PoolVector2Array(value, context: String = "") -> void: + output(IsNotPoolVector2Array.new(value, context)) + +func is_PoolVector3Array(value, context: String = "") -> void: + output(IsPoolVector3Array.new(value, context)) + +func is_not_PoolVector3Array(value, context: String = "") -> void: + output(IsNotPoolVector3Array.new(value, context)) + +func is_PoolColorArray(value, context: String = "") -> void: + output(IsPoolColorArray.new(value, context)) + +func is_not_PoolColorArray(value, context: String = "") -> void: + output(IsNotPoolColorArray.new(value, context)) + +func fail(context: String = "Unimplemented Test") -> void: + output(Fail.new(context)) diff --git a/addons/WAT/core/assertions/base.gd b/addons/WAT/core/assertions/base.gd new file mode 100644 index 0000000..917820c --- /dev/null +++ b/addons/WAT/core/assertions/base.gd @@ -0,0 +1,19 @@ +extends Reference + +const TYPES = preload("constants/type_library.gd") +var success: bool +var expected: String = "NULL" +var result: String +var notes: String = "No Notes" +var context + +func type2str(value): + return TYPES.get_type_string(typeof(value)) + +func to_dictionary() -> Dictionary: + return { + "success": success, + "expected": expected, + "actual": result, + "context": context + } diff --git a/addons/WAT/core/assertions/boolean/is_false.gd b/addons/WAT/core/assertions/boolean/is_false.gd new file mode 100644 index 0000000..c703d61 --- /dev/null +++ b/addons/WAT/core/assertions/boolean/is_false.gd @@ -0,0 +1,10 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var type = type2str(value) + var passed: String = "|%s| %s == false" % [type, value] + var failed: String = "|%s| %s != false" % [type, value] + self.context = context + self.success = (value == false) + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/boolean/is_true.gd b/addons/WAT/core/assertions/boolean/is_true.gd new file mode 100644 index 0000000..f195cf9 --- /dev/null +++ b/addons/WAT/core/assertions/boolean/is_true.gd @@ -0,0 +1,10 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var type = type2str(value) + var passed: String = "|%s| %s == true" % [type, value] + var failed: String = "|%s| %s != true" % [type, value] + self.context = context + self.success = (value == true) + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/class_list.gd b/addons/WAT/core/assertions/class_list.gd new file mode 100644 index 0000000..058c9ac --- /dev/null +++ b/addons/WAT/core/assertions/class_list.gd @@ -0,0 +1,132 @@ +extends Reference + +# Boolean Assertions +const IsFalse: Script = preload("boolean/is_false.gd") +const IsTrue: Script = preload("boolean/is_true.gd") + +# Test Double Assertions +const CalledWithArguments: Script = preload("double/called_with_arguments.gd") +const ScriptWasCalled: Script = preload("double/script_was_called.gd") +const ScriptWasNotCalled: Script = preload("double/script_was_not_called.gd") + +# Equality Assertions +const IsEqual: Script = preload("equality/is_equal.gd") +const IsEqualOrGreaterThan: Script = preload("equality/is_equal_or_greater_than.gd") +const IsEqualOrLessThan: Script = preload("equality/is_equal_or_less_than.gd") +const IsGreaterThan: Script = preload("equality/is_greater_than.gd") +const IsLessThan: Script = preload("equality/is_less_than.gd") +const IsNotEqual: Script = preload("equality/is_not_equal.gd") + +# File Assertions +const FileDoesNotExist: Script = preload("file/file_does_not_exist.gd") +const FileExists: Script = preload("file/file_exists.gd") + +# Is Assertions +const IsAABB: Script = preload("is/is_AABB.gd") +const IsArray: Script = preload("is/is_Array.gd") +const IsBasis: Script = preload("is/is_Basis.gd") +const IsBool: Script = preload("is/is_bool.gd") +const IsBuiltInType: Script = preload("is/is_built_in_type.gd") +const IsClassInstance: Script = preload("is/is_class_instance.gd") +const IsColor: Script = preload("is/is_Color.gd") +const IsDictionary: Script = preload("is/is_Dictionary.gd") +const IsFloat: Script = preload("is/is_float.gd") +const IsInt: Script = preload("is/is_int.gd") +const IsNodePath: Script = preload("is/is_NodePath.gd") +const IsObject: Script = preload("is/is_Object.gd") +const IsPlane: Script = preload("is/is_Plane.gd") +const IsPoolByteArray: Script = preload("is/is_PoolByteArray.gd") +const IsPoolColorArray: Script = preload("is/is_PoolColorArray.gd") +const IsPoolIntArray: Script = preload("is/is_PoolIntArray.gd") +const IsPoolRealArray: Script = preload("is/is_PoolRealArray.gd") +const IsPoolStringArray: Script = preload("is/is_PoolStringArray.gd") +const IsPoolVector2Array: Script = preload("is/is_PoolVector2Array.gd") +const IsPoolVector3Array: Script = preload("is/is_PoolVector3Array.gd") +const IsQuat: Script = preload("is/is_Quat.gd") +const IsRect2: Script = preload("is/is_Rect2.gd") +const IsRID: Script = preload("is/is_RID.gd") +const IsString: Script = preload("is/is_String.gd") +const IsTransform: Script = preload("is/is_Transform.gd") +const IsTransform2D: Script = preload("is/is_Transform2D.gd") +const IsVector2: Script = preload("is/is_Vector2.gd") +const IsVector3: Script = preload("is/is_Vector3.gd") + +# Is Not Assertions +const IsNotAABB: Script = preload("is_not/is_not_AABB.gd") +const IsNotArray: Script = preload("is_not/is_not_Array.gd") +const IsNotBasis: Script = preload("is_not/is_not_Basis.gd") +const IsNotBool: Script = preload("is_not/is_not_bool.gd") +const IsNotBuiltInType: Script = preload("is_not/is_not_built_in_type.gd") +const IsNotClassInstance: Script = preload("is_not/is_not_class_instance.gd") +const IsNotColor: Script = preload("is_not/is_not_Color.gd") +const IsNotDictionary: Script = preload("is_not/is_not_Dictionary.gd") +const IsNotFloat: Script = preload("is_not/is_not_float.gd") +const IsNotInt: Script = preload("is_not/is_not_int.gd") +const IsNotNodePath: Script = preload("is_not/is_not_NodePath.gd") +const IsNotObject: Script = preload("is_not/is_not_Object.gd") +const IsNotPlane: Script = preload("is_not/is_not_Plane.gd") +const IsNotPoolByteArray: Script = preload("is_not/is_not_PoolByteArray.gd") +const IsNotPoolColorArray: Script = preload("is_not/is_not_PoolColorArray.gd") +const IsNotPoolIntArray: Script = preload("is_not/is_not_PoolIntArray.gd") +const IsNotPoolRealArray: Script = preload("is_not/is_not_PoolRealArray.gd") +const IsNotPoolStringArray: Script = preload("is_not/is_not_PoolStringArray.gd") +const IsNotPoolVector2Array: Script = preload("is_not/is_not_PoolVector2Array.gd") +const IsNotPoolVector3Array: Script = preload("is_not/is_not_PoolVector3Array.gd") +const IsNotQuat: Script = preload("is_not/is_not_Quat.gd") +const IsNotRect2: Script = preload("is_not/is_not_Rect2.gd") +const IsNotRID: Script = preload("is_not/is_not_RID.gd") +const IsNotString: Script = preload("is_not/is_not_String.gd") +const IsNotTransform: Script = preload("is_not/is_not_Transform.gd") +const IsNotTransform2D: Script = preload("is_not/is_not_Transform2D.gd") +const IsNotVector2: Script = preload("is_not/is_not_Vector2.gd") +const IsNotVector3: Script = preload("is_not/is_not_Vector3.gd") + +# Null Assertions +const IsNull: Script = preload("null/is_null.gd") +const IsNotNull: Script = preload("null/is_not_null.gd") + +# Object Assertions +# Note: The elements this assertions act on exist throughout every object +# ..in godot so it was necessary. For other class-specific assertions.. +# users should use assert.that +const ObjectIsFreed: Script = preload("object/is_freed.gd") +const ObjectIsNotFreed: Script = preload("object/is_not_freed.gd") +const ObjectHasMeta: Script = preload("object/has_meta.gd") +const ObjectDoesNotHaveMeta: Script = preload("object/does_not_have_meta.gd") +const ObjectHasMethod: Script = preload("object/has_method.gd") +const ObjectDoesNotHaveMethod: Script = preload("object/does_not_have_method.gd") +const ObjectHasUserSignal: Script = preload("object/has_user_signal.gd") +const ObjectDoesNotHaveUserSignal: Script = preload("object/does_not_have_user_signal.gd") + +const ObjectIsQueuedForDeletion: Script = preload("object/is_queued_for_deletion.gd") +const ObjectIsNotQueuedForDeletion: Script = preload("object/is_not_queued_for_deletion.gd") +const ObjectIsBlockingSignals: Script = preload("object/is_blocking_signals.gd") +const ObjectIsNotBlockingSignals: Script = preload("object/is_not_blocking_signals.gd") +const ObjectIsConnected: Script = preload("object/is_connected.gd") +const ObjectIsNotConnected: Script = preload("object/is_not_connected.gd") + +# Property Assertions +const Has: Script = preload("property/has.gd") +const DoesNotHave: Script = preload("property/does_not_have.gd") + +# Range Assertions +const IsInRange: Script = preload("range/is_in_range.gd") +const IsNotInRange: Script = preload("range/is_not_in_range.gd") + +# Signal Assertions +const WasEmitted: Script = preload("signal/signal_was_emitted.gd") +const WasEmittedXTimes: Script = preload("signal/signal_was_emitted_x_times.gd") +const WasEmittedWithArguments: Script = preload("signal/signal_was_emitted_with_arguments.gd") +const WasNotEmitted: Script = preload("signal/signal_was_not_emitted.gd") + +# String Assertions +const BeginsWith: Script = preload("string/string_begins_with.gd") +const Contains: Script = preload("string/string_contains.gd") +const DoesNotBeginWith: Script = preload("string/string_does_not_begin_with.gd") +const DoesNotContain: Script = preload("string/string_does_not_contain.gd") +const DoesNotEndWith: Script = preload("string/string_does_not_end_with.gd") +const EndsWith: Script = preload("string/string_ends_with.gd") + +# Misc Utility Assertions +const That: Script = preload("misc/that.gd") +const Fail: Script = preload("misc/fail.gd") diff --git a/addons/WAT/core/assertions/constants/type_library.gd b/addons/WAT/core/assertions/constants/type_library.gd new file mode 100644 index 0000000..91691d9 --- /dev/null +++ b/addons/WAT/core/assertions/constants/type_library.gd @@ -0,0 +1,59 @@ +extends Reference + +static func get_type_string(property_id: int) -> String: + match property_id: + TYPE_NIL: + return "null" + TYPE_BOOL: + return "bool" + TYPE_INT: + return "int" + TYPE_REAL: + return "float" + TYPE_STRING: + return "String" + TYPE_VECTOR2: + return "Vector2" + TYPE_RECT2: + return "Rect2" + TYPE_VECTOR3: + return "Vector3" + TYPE_TRANSFORM2D: + return "Transform2D" + TYPE_PLANE: + return "Plane" + TYPE_QUAT: + return "Quat" + TYPE_AABB: + return "AABB" + TYPE_BASIS: + return "Basis" + TYPE_TRANSFORM: + return "Transform" + TYPE_COLOR: + return "Color" + TYPE_NODE_PATH: + return "NodePath" + TYPE_RID: + return "RID" + TYPE_OBJECT: + return "Object" + TYPE_DICTIONARY: + return "Dictionary" + TYPE_ARRAY: + return "Array" + TYPE_RAW_ARRAY: + return "PoolByteArray" + TYPE_INT_ARRAY: + return "PoolIntArray" + TYPE_REAL_ARRAY: + return "PoolRealArray" + TYPE_STRING_ARRAY: + return "PoolStringArray" + TYPE_VECTOR2_ARRAY: + return "PoolVector2Array" + TYPE_VECTOR3_ARRAY: + return "PoolVector3Array" + TYPE_COLOR_ARRAY: + return "PoolColorArray" + return "OutOfBounds" diff --git a/addons/WAT/core/assertions/double/called_with_arguments.gd b/addons/WAT/core/assertions/double/called_with_arguments.gd new file mode 100644 index 0000000..df90aea --- /dev/null +++ b/addons/WAT/core/assertions/double/called_with_arguments.gd @@ -0,0 +1,18 @@ +extends "../base.gd" + +func _init(double, method: String, args: Array, context: String) -> void: + var passed: String = "method: %s was called with arguments: %s" % [method, args] + var failed: String = "method: %s was not called with arguments: %s" % [method, args] + var alt_failed: String = "method: %s was not called at all" % method + self.context = context + self.expected = passed + + if double.call_count(method) == 0: + self.success = false + self.result = alt_failed + elif double.found_matching_call(method, args): + self.success = true + self.result = passed + else: + self.success = false + self.result = failed diff --git a/addons/WAT/core/assertions/double/script_was_called.gd b/addons/WAT/core/assertions/double/script_was_called.gd new file mode 100644 index 0000000..6706bee --- /dev/null +++ b/addons/WAT/core/assertions/double/script_was_called.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(double, method: String, context: String) -> void: + var passed: String = "%s was called" % method + var failed: String = "%s was not called" % method + self.context = context + self.success = double.call_count(method) > 0 + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/double/script_was_not_called.gd b/addons/WAT/core/assertions/double/script_was_not_called.gd new file mode 100644 index 0000000..674c3cb --- /dev/null +++ b/addons/WAT/core/assertions/double/script_was_not_called.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(double, method: String, context: String) -> void: + var passed: String = "%s was not called" % method + var failed: String = "%s was called" % method + self.context = context + self.success = double.call_count(method) <= 0 + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/equality/is_equal.gd b/addons/WAT/core/assertions/equality/is_equal.gd new file mode 100644 index 0000000..78bde25 --- /dev/null +++ b/addons/WAT/core/assertions/equality/is_equal.gd @@ -0,0 +1,12 @@ +extends "../base.gd" + + +func _init(a, b, context: String) -> void: + var typeofa = type2str(a) + var typeofb = type2str(b) + var passed: String = "|%s| %s is equal to |%s| %s" % [typeofa, a, typeofb, b] + var failed: String = "|%s| %s is not equal to |%s| %s" % [typeofa, a, typeofb, b] + self.context = context + self.success = (a == b) + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/equality/is_equal_or_greater_than.gd b/addons/WAT/core/assertions/equality/is_equal_or_greater_than.gd new file mode 100644 index 0000000..2138485 --- /dev/null +++ b/addons/WAT/core/assertions/equality/is_equal_or_greater_than.gd @@ -0,0 +1,11 @@ +extends "../base.gd" + +func _init(a, b, context: String) -> void: + var typeofa = type2str(a) + var typeofb = type2str(b) + var passed: String = "|%s| %s is equal or greater |%s| %s" % [typeofa, a, typeofb, b] + var failed: String = "|%s| %s is less than |%s| %s" % [typeofa, a, typeofb, b] + self.context = context + self.success = (a >= b) + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/equality/is_equal_or_less_than.gd b/addons/WAT/core/assertions/equality/is_equal_or_less_than.gd new file mode 100644 index 0000000..2668416 --- /dev/null +++ b/addons/WAT/core/assertions/equality/is_equal_or_less_than.gd @@ -0,0 +1,11 @@ +extends "../base.gd" + +func _init(a, b, context: String) -> void: + var typeofa = type2str(a) + var typeofb = type2str(b) + var passed: String = "|%s| %s is equal or less than |%s| %s" % [typeofa, a, typeofb, b] + var failed: String = "|%s| %s is greater than |%s| %s" % [typeofa, a, typeofb, b] + self.context = context + self.success = (a <= b) + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/equality/is_greater_than.gd b/addons/WAT/core/assertions/equality/is_greater_than.gd new file mode 100644 index 0000000..bbfb20f --- /dev/null +++ b/addons/WAT/core/assertions/equality/is_greater_than.gd @@ -0,0 +1,11 @@ +extends "../base.gd" + +func _init(a, b, context: String) -> void: + var typeofa = type2str(a) + var typeofb = type2str(b) + var passed: String = "|%s| %s is greater than |%s| %s" % [typeofa, a, typeofb, b] + var failed: String = "|%s| %s if equal or less than |%s| %s" % [typeofa, a, typeofb, b] + self.context = context + self.success = (a > b) + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/equality/is_less_than.gd b/addons/WAT/core/assertions/equality/is_less_than.gd new file mode 100644 index 0000000..06243d7 --- /dev/null +++ b/addons/WAT/core/assertions/equality/is_less_than.gd @@ -0,0 +1,11 @@ +extends "../base.gd" + +func _init(a, b, context: String) -> void: + var typeofa = type2str(a) + var typeofb = type2str(b) + var passed: String = "|%s| %s is less than |%s| %s" % [typeofa, a, typeofb, b] + var failed: String = "|%s| %s is equal or greater than |%s| %s" % [typeofa, a, typeofb, b] + self.context = context + self.success = (a < b) + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/equality/is_not_equal.gd b/addons/WAT/core/assertions/equality/is_not_equal.gd new file mode 100644 index 0000000..36c15cd --- /dev/null +++ b/addons/WAT/core/assertions/equality/is_not_equal.gd @@ -0,0 +1,8 @@ +extends "../base.gd" + + +func _init(a, b, context: String) -> void: + self.success = (a != b) + self.context = context + self.expected = "|%s| %s != |%s| %s" % [type2str(a), a, type2str(b), b] + self.result = "|%s| %s %s |%s| %s" % [type2str(a), a, ("is not equal to" if self.success else "is equal to"), type2str(b),b] \ No newline at end of file diff --git a/addons/WAT/core/assertions/file/file_does_not_exist.gd b/addons/WAT/core/assertions/file/file_does_not_exist.gd new file mode 100644 index 0000000..ab7fb92 --- /dev/null +++ b/addons/WAT/core/assertions/file/file_does_not_exist.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(path: String, context: String) -> void: + var passed: String = "%s does not exist" % path + var failed: String = "%s exists" % path + self.context = context + self.success = not File.new().file_exists(path) + self.expected = "%s does not exist" % path + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/file/file_exists.gd b/addons/WAT/core/assertions/file/file_exists.gd new file mode 100644 index 0000000..6bc7542 --- /dev/null +++ b/addons/WAT/core/assertions/file/file_exists.gd @@ -0,0 +1,10 @@ +extends "../base.gd" + + +func _init(path: String, context: String) -> void: + var passed: String = "%s exists" % path + var failed: String = "%s does not exist" % path + self.context = context + self.success = File.new().file_exists(path) + self.expected = "%s exists" % path + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/is/is_AABB.gd b/addons/WAT/core/assertions/is/is_AABB.gd new file mode 100644 index 0000000..4ee2291 --- /dev/null +++ b/addons/WAT/core/assertions/is/is_AABB.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is builtin: AABB" % value + var failed: String = "%s is not builtin: AABB" % value + self.context = context + self.success = value is AABB + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is/is_Array.gd b/addons/WAT/core/assertions/is/is_Array.gd new file mode 100644 index 0000000..efbff77 --- /dev/null +++ b/addons/WAT/core/assertions/is/is_Array.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is builtin: Array" % value as String + var failed: String = "%s is not builtin: Array" % value as String + self.context = context + self.success = value is Array + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/is/is_Basis.gd b/addons/WAT/core/assertions/is/is_Basis.gd new file mode 100644 index 0000000..dc331b4 --- /dev/null +++ b/addons/WAT/core/assertions/is/is_Basis.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is builtin: Basis" % value + var failed: String = "%s is not builtin: Basis" % value + self.context = context + self.success = value is Basis + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/is/is_Color.gd b/addons/WAT/core/assertions/is/is_Color.gd new file mode 100644 index 0000000..4fd0226 --- /dev/null +++ b/addons/WAT/core/assertions/is/is_Color.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is builtin: Color" % value + var failed: String = "%s is not builtin: Color" % value + self.context = context + self.success = value is Color + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/is/is_Dictionary.gd b/addons/WAT/core/assertions/is/is_Dictionary.gd new file mode 100644 index 0000000..ecef511 --- /dev/null +++ b/addons/WAT/core/assertions/is/is_Dictionary.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is builtin: Dictionary" % value + var failed: String = "%s is not builtin: Dictionary" % value + self.context = context + self.success = value is Dictionary + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/is/is_NodePath.gd b/addons/WAT/core/assertions/is/is_NodePath.gd new file mode 100644 index 0000000..8e58757 --- /dev/null +++ b/addons/WAT/core/assertions/is/is_NodePath.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is builtin: NodePath" % value + var failed: String = "%s is not builtin: NodePath" % value + self.context = context + self.success = value is NodePath + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is/is_Object.gd b/addons/WAT/core/assertions/is/is_Object.gd new file mode 100644 index 0000000..d0e0a33 --- /dev/null +++ b/addons/WAT/core/assertions/is/is_Object.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is builtin: Object" % value + var failed: String = "%s is not builtin: Object" % value + self.context = context + self.success = value is Object + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is/is_Plane.gd b/addons/WAT/core/assertions/is/is_Plane.gd new file mode 100644 index 0000000..77797ba --- /dev/null +++ b/addons/WAT/core/assertions/is/is_Plane.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is builtin: Plane" % value + var failed: String = "%s is not builtin: Plane" % value + self.context = context + self.success = value is Plane + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is/is_PoolByteArray.gd b/addons/WAT/core/assertions/is/is_PoolByteArray.gd new file mode 100644 index 0000000..2b910a3 --- /dev/null +++ b/addons/WAT/core/assertions/is/is_PoolByteArray.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is builtin: PoolByteArray" % value + var failed: String = "%s is not builtin: PoolByteArray" % value + self.context = context + self.success = value is PoolByteArray + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is/is_PoolColorArray.gd b/addons/WAT/core/assertions/is/is_PoolColorArray.gd new file mode 100644 index 0000000..921b375 --- /dev/null +++ b/addons/WAT/core/assertions/is/is_PoolColorArray.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is builtin: PoolColorArray" % value + var failed: String = "%s is not builtin: PoolColorArray" % value + self.context = context + self.success = value is PoolColorArray + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is/is_PoolIntArray.gd b/addons/WAT/core/assertions/is/is_PoolIntArray.gd new file mode 100644 index 0000000..a00164d --- /dev/null +++ b/addons/WAT/core/assertions/is/is_PoolIntArray.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is builtin: PoolIntArray" % value + var failed: String = "%s is not builtin: PoolIntArray" % value + self.context = context + self.success = value is PoolIntArray + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is/is_PoolRealArray.gd b/addons/WAT/core/assertions/is/is_PoolRealArray.gd new file mode 100644 index 0000000..914ae90 --- /dev/null +++ b/addons/WAT/core/assertions/is/is_PoolRealArray.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is builtin: PoolRealArray" % value + var failed: String = "%s is not builtin: PoolRealArray" % value + self.context = context + self.success = value is PoolRealArray + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is/is_PoolStringArray.gd b/addons/WAT/core/assertions/is/is_PoolStringArray.gd new file mode 100644 index 0000000..6d356d5 --- /dev/null +++ b/addons/WAT/core/assertions/is/is_PoolStringArray.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is builtin: PoolStringArray" % value + var failed: String = "%s is not builtin: PoolStringArray" % value + self.context = context + self.success = value is PoolStringArray + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is/is_PoolVector2Array.gd b/addons/WAT/core/assertions/is/is_PoolVector2Array.gd new file mode 100644 index 0000000..e6d34fc --- /dev/null +++ b/addons/WAT/core/assertions/is/is_PoolVector2Array.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is builtin: PoolVector2Array" % value + var failed: String = "%s is not builtin: PoolVector2Array" % value + self.context = context + self.success = value is PoolVector2Array + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is/is_PoolVector3Array.gd b/addons/WAT/core/assertions/is/is_PoolVector3Array.gd new file mode 100644 index 0000000..b9ea8e3 --- /dev/null +++ b/addons/WAT/core/assertions/is/is_PoolVector3Array.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is builtin: PoolVector3Array" % value + var failed: String = "%s is not builtin: PoolVector3Array" % value + self.context = context + self.success = value is PoolVector3Array + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is/is_Quat.gd b/addons/WAT/core/assertions/is/is_Quat.gd new file mode 100644 index 0000000..ee087e6 --- /dev/null +++ b/addons/WAT/core/assertions/is/is_Quat.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is builtin: Quat" % value + var failed: String = "%s is not builtin: Quat" % value + self.context = context + self.success = value is Quat + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is/is_RID.gd b/addons/WAT/core/assertions/is/is_RID.gd new file mode 100644 index 0000000..8cf3abf --- /dev/null +++ b/addons/WAT/core/assertions/is/is_RID.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is builtin: RID" % value + var failed: String = "%s is not builtin: RID" % value + self.context = context + self.success = value is RID + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is/is_Rect2.gd b/addons/WAT/core/assertions/is/is_Rect2.gd new file mode 100644 index 0000000..f46bf7b --- /dev/null +++ b/addons/WAT/core/assertions/is/is_Rect2.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is builtin: Rect2" % value + var failed: String = "%s is not builtin: Rect2" % value + self.context = context + self.success = value is Rect2 + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is/is_String.gd b/addons/WAT/core/assertions/is/is_String.gd new file mode 100644 index 0000000..88e9e73 --- /dev/null +++ b/addons/WAT/core/assertions/is/is_String.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is builtin: String" % value + var failed: String = "%s is not builtin: String" % value + self.context = context + self.success = value is String + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is/is_Transform.gd b/addons/WAT/core/assertions/is/is_Transform.gd new file mode 100644 index 0000000..ebcfd60 --- /dev/null +++ b/addons/WAT/core/assertions/is/is_Transform.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is builtin: Transform" % value + var failed: String = "%s is not builtin: Transform" % value + self.context = context + self.success = value is Transform + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is/is_Transform2D.gd b/addons/WAT/core/assertions/is/is_Transform2D.gd new file mode 100644 index 0000000..6146362 --- /dev/null +++ b/addons/WAT/core/assertions/is/is_Transform2D.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is builtin: Transform2D" % value + var failed: String = "%s is not builtin: Transform2D" % value + self.context = context + self.success = value is Transform2D + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is/is_Vector2.gd b/addons/WAT/core/assertions/is/is_Vector2.gd new file mode 100644 index 0000000..a606cfe --- /dev/null +++ b/addons/WAT/core/assertions/is/is_Vector2.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is builtin: Vector2" % value + var failed: String = "%s is not builtin: Vector2" % value + self.context = context + self.success = value is Vector2 + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is/is_Vector3.gd b/addons/WAT/core/assertions/is/is_Vector3.gd new file mode 100644 index 0000000..3b3bbbd --- /dev/null +++ b/addons/WAT/core/assertions/is/is_Vector3.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is builtin: Vector3" % value + var failed: String = "%s is not builtin: Vector3" % value + self.context = context + self.success = value is Vector3 + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is/is_bool.gd b/addons/WAT/core/assertions/is/is_bool.gd new file mode 100644 index 0000000..0cf4ad5 --- /dev/null +++ b/addons/WAT/core/assertions/is/is_bool.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is builtin: bool" % value + var failed: String = "%s is not builtin: bool" % value + self.context = context + self.success = value is bool + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is/is_built_in_type.gd b/addons/WAT/core/assertions/is/is_built_in_type.gd new file mode 100644 index 0000000..293408a --- /dev/null +++ b/addons/WAT/core/assertions/is/is_built_in_type.gd @@ -0,0 +1,8 @@ +extends "../base.gd" + + +func _init(value, type: int, context: String) -> void: + self.success = (typeof(value)) == type + self.context = context + var string_type = type2str(type) + self.result = "%s %s %s" % [value, ("is builtin type: " if self.success else "is not builtin type: "), string_type] diff --git a/addons/WAT/core/assertions/is/is_class_instance.gd b/addons/WAT/core/assertions/is/is_class_instance.gd new file mode 100644 index 0000000..4d430e1 --- /dev/null +++ b/addons/WAT/core/assertions/is/is_class_instance.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(instance, klass: Script, context: String) -> void: + var passed: String = "%s is instance of class: %s" % [instance, klass] + var failed: String = "%s is not instance of class: %s" % [instance, klass] + self.context = context + self.success = instance is klass + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/is/is_float.gd b/addons/WAT/core/assertions/is/is_float.gd new file mode 100644 index 0000000..04bdae2 --- /dev/null +++ b/addons/WAT/core/assertions/is/is_float.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is builtin: float" % value + var failed: String = "%s is not builtin: float" % value + self.context = context + self.success = value is float + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/is/is_int.gd b/addons/WAT/core/assertions/is/is_int.gd new file mode 100644 index 0000000..756f013 --- /dev/null +++ b/addons/WAT/core/assertions/is/is_int.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is builtin: int" % value + var failed: String = "%s is not builtin: int" % value + self.context = context + self.success = value is int + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is_not/is_not_AABB.gd b/addons/WAT/core/assertions/is_not/is_not_AABB.gd new file mode 100644 index 0000000..2451f98 --- /dev/null +++ b/addons/WAT/core/assertions/is_not/is_not_AABB.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is not builtin: AABB" % value + var failed: String = "%s is builtin: AABB" % value + self.context = context + self.success = not value is AABB + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is_not/is_not_Array.gd b/addons/WAT/core/assertions/is_not/is_not_Array.gd new file mode 100644 index 0000000..4313433 --- /dev/null +++ b/addons/WAT/core/assertions/is_not/is_not_Array.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is not builtin: Array" % value + var failed: String = "%s is builtin: Array" % value + self.context = context + self.success = not value is Array + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is_not/is_not_Basis.gd b/addons/WAT/core/assertions/is_not/is_not_Basis.gd new file mode 100644 index 0000000..00dc9b5 --- /dev/null +++ b/addons/WAT/core/assertions/is_not/is_not_Basis.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is not builtin: Basis" % value + var failed: String = "%s is builtin: Basis" % value + self.context = context + self.success = not value is Basis + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/is_not/is_not_Color.gd b/addons/WAT/core/assertions/is_not/is_not_Color.gd new file mode 100644 index 0000000..4a081ae --- /dev/null +++ b/addons/WAT/core/assertions/is_not/is_not_Color.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is not builtin: Color" % value + var failed: String = "%s is builtin: Color" % value + self.context = context + self.success = not value is Color + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is_not/is_not_Dictionary.gd b/addons/WAT/core/assertions/is_not/is_not_Dictionary.gd new file mode 100644 index 0000000..c2c190a --- /dev/null +++ b/addons/WAT/core/assertions/is_not/is_not_Dictionary.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is not builtin: Dictionary" % value + var failed: String = "%s is builtin: Dictionary" % value + self.context = context + self.success = not value is Dictionary + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/is_not/is_not_NodePath.gd b/addons/WAT/core/assertions/is_not/is_not_NodePath.gd new file mode 100644 index 0000000..bcf5bf5 --- /dev/null +++ b/addons/WAT/core/assertions/is_not/is_not_NodePath.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is not builtin: NodePath" % value + var failed: String = "%s is builtin: NodePath" % value + self.context = context + self.success = not value is NodePath + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/is_not/is_not_Object.gd b/addons/WAT/core/assertions/is_not/is_not_Object.gd new file mode 100644 index 0000000..0a951d0 --- /dev/null +++ b/addons/WAT/core/assertions/is_not/is_not_Object.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is not builtin: Object" % value + var failed: String = "%s is builtin: Object" % value + self.context = context + self.success = not value is Object + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/is_not/is_not_Plane.gd b/addons/WAT/core/assertions/is_not/is_not_Plane.gd new file mode 100644 index 0000000..a118ee5 --- /dev/null +++ b/addons/WAT/core/assertions/is_not/is_not_Plane.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is not builtin: Plane" % value + var failed: String = "%s is builtin: Plane" % value + self.context = context + self.success = not value is Plane + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is_not/is_not_PoolByteArray.gd b/addons/WAT/core/assertions/is_not/is_not_PoolByteArray.gd new file mode 100644 index 0000000..a4023ff --- /dev/null +++ b/addons/WAT/core/assertions/is_not/is_not_PoolByteArray.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is not builtin: PoolByteArray" % value + var failed: String = "%s is builtin: PoolByteArray" % value + self.context = context + self.success = not value is PoolByteArray + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is_not/is_not_PoolColorArray.gd b/addons/WAT/core/assertions/is_not/is_not_PoolColorArray.gd new file mode 100644 index 0000000..f8b4840 --- /dev/null +++ b/addons/WAT/core/assertions/is_not/is_not_PoolColorArray.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is not builtin: PoolColorArray" % value + var failed: String = "%s is builtin: PoolColorArray" % value + self.context = context + self.success = not value is PoolColorArray + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is_not/is_not_PoolIntArray.gd b/addons/WAT/core/assertions/is_not/is_not_PoolIntArray.gd new file mode 100644 index 0000000..d2aa0cb --- /dev/null +++ b/addons/WAT/core/assertions/is_not/is_not_PoolIntArray.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is not builtin: PoolIntArray" % value + var failed: String = "%s is builtin: PoolIntArray" % value + self.context = context + self.success = not value is PoolIntArray + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is_not/is_not_PoolRealArray.gd b/addons/WAT/core/assertions/is_not/is_not_PoolRealArray.gd new file mode 100644 index 0000000..cf9147c --- /dev/null +++ b/addons/WAT/core/assertions/is_not/is_not_PoolRealArray.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is not builtin: PoolRealArray" % value + var failed: String = "%s is builtin: PoolRealArray" % value + self.context = context + self.success = not value is PoolRealArray + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/is_not/is_not_PoolStringArray.gd b/addons/WAT/core/assertions/is_not/is_not_PoolStringArray.gd new file mode 100644 index 0000000..6f8b401 --- /dev/null +++ b/addons/WAT/core/assertions/is_not/is_not_PoolStringArray.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is not builtin: PoolStringArray" % value + var failed: String = "%s is builtin: PoolStringArray" % value + self.context = context + self.success = not value is PoolStringArray + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is_not/is_not_PoolVector2Array.gd b/addons/WAT/core/assertions/is_not/is_not_PoolVector2Array.gd new file mode 100644 index 0000000..478b360 --- /dev/null +++ b/addons/WAT/core/assertions/is_not/is_not_PoolVector2Array.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is not builtin: PoolVector2Array" % value + var failed: String = "%s is builtin: PoolVector2Array" % value + self.context = context + self.success = not value is PoolVector2Array + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is_not/is_not_PoolVector3Array.gd b/addons/WAT/core/assertions/is_not/is_not_PoolVector3Array.gd new file mode 100644 index 0000000..f39ee14 --- /dev/null +++ b/addons/WAT/core/assertions/is_not/is_not_PoolVector3Array.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is not builtin: PoolVector3Array" % value + var failed: String = "%s is builtin: PoolVector3Array" % value + self.context = context + self.success = not value is PoolVector3Array + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is_not/is_not_Quat.gd b/addons/WAT/core/assertions/is_not/is_not_Quat.gd new file mode 100644 index 0000000..a89fca5 --- /dev/null +++ b/addons/WAT/core/assertions/is_not/is_not_Quat.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is not builtin: Quat" % value + var failed: String = "%s is builtin: Quat" % value + self.context = context + self.success = not value is Quat + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is_not/is_not_RID.gd b/addons/WAT/core/assertions/is_not/is_not_RID.gd new file mode 100644 index 0000000..e6a3611 --- /dev/null +++ b/addons/WAT/core/assertions/is_not/is_not_RID.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is not builtin: RID" % value + var failed: String = "%s is builtin: RID" % value + self.context = context + self.success = not value is RID + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is_not/is_not_Rect2.gd b/addons/WAT/core/assertions/is_not/is_not_Rect2.gd new file mode 100644 index 0000000..4ebfa96 --- /dev/null +++ b/addons/WAT/core/assertions/is_not/is_not_Rect2.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is not builtin: Rect2" % value + var failed: String = "%s is builtin: Rect2" % value + self.context = context + self.success = not value is Rect2 + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is_not/is_not_String.gd b/addons/WAT/core/assertions/is_not/is_not_String.gd new file mode 100644 index 0000000..150543f --- /dev/null +++ b/addons/WAT/core/assertions/is_not/is_not_String.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is not builtin: String" % value + var failed: String = "%s is builtin: String" % value + self.context = context + self.success = not value is String + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is_not/is_not_Transform.gd b/addons/WAT/core/assertions/is_not/is_not_Transform.gd new file mode 100644 index 0000000..02377d2 --- /dev/null +++ b/addons/WAT/core/assertions/is_not/is_not_Transform.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is not builtin: Transform" % value + var failed: String = "%s is builtin: Transform" % value + self.context = context + self.success = not value is Transform + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is_not/is_not_Transform2D.gd b/addons/WAT/core/assertions/is_not/is_not_Transform2D.gd new file mode 100644 index 0000000..73c58ee --- /dev/null +++ b/addons/WAT/core/assertions/is_not/is_not_Transform2D.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is not builtin: Transform2D" % value + var failed: String = "%s is builtin: Transform2D" % value + self.context = context + self.success = not value is Transform2D + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is_not/is_not_Vector2.gd b/addons/WAT/core/assertions/is_not/is_not_Vector2.gd new file mode 100644 index 0000000..ef3f1d7 --- /dev/null +++ b/addons/WAT/core/assertions/is_not/is_not_Vector2.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is not builtin: Vector2" % value + var failed: String = "%s is builtin: Vector2" % value + self.context = context + self.success = not value is Vector2 + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is_not/is_not_Vector3.gd b/addons/WAT/core/assertions/is_not/is_not_Vector3.gd new file mode 100644 index 0000000..99494e5 --- /dev/null +++ b/addons/WAT/core/assertions/is_not/is_not_Vector3.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is not builtin: Vector3" % value + var failed: String = "%s is builtin: Vector3" % value + self.context = context + self.success = not value is Vector3 + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is_not/is_not_bool.gd b/addons/WAT/core/assertions/is_not/is_not_bool.gd new file mode 100644 index 0000000..3410468 --- /dev/null +++ b/addons/WAT/core/assertions/is_not/is_not_bool.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is not builtin: bool" % value + var failed: String = "%s is builtin: bool" % value + self.context = context + self.success = not value is bool + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is_not/is_not_built_in_type.gd b/addons/WAT/core/assertions/is_not/is_not_built_in_type.gd new file mode 100644 index 0000000..9990cac --- /dev/null +++ b/addons/WAT/core/assertions/is_not/is_not_built_in_type.gd @@ -0,0 +1,7 @@ +extends "../base.gd" + +func _init(value, type: int, context: String) -> void: + self.success = not (typeof(value)) == type + self.context = context + var string_type = type2str(type) + self.result = "|%s| %s %s %s" % [type2str(value), value, ("is not builtin type: " if self.success else "is builtin type: "), string_type] diff --git a/addons/WAT/core/assertions/is_not/is_not_class_instance.gd b/addons/WAT/core/assertions/is_not/is_not_class_instance.gd new file mode 100644 index 0000000..e8471cb --- /dev/null +++ b/addons/WAT/core/assertions/is_not/is_not_class_instance.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(instance, klass: Script, context: String) -> void: + var passed: String = "%s is not instance of class: %s" % [instance, klass] + var failed: String = "%s is instance of class: %s" % [instance, klass] + self.context = context + self.success = not instance is klass + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/is_not/is_not_float.gd b/addons/WAT/core/assertions/is_not/is_not_float.gd new file mode 100644 index 0000000..bcae580 --- /dev/null +++ b/addons/WAT/core/assertions/is_not/is_not_float.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is not builtin: float" % value + var failed: String = "%s is builtin: float" % value + self.context = context + self.success = not value is float + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/is_not/is_not_int.gd b/addons/WAT/core/assertions/is_not/is_not_int.gd new file mode 100644 index 0000000..d6c2224 --- /dev/null +++ b/addons/WAT/core/assertions/is_not/is_not_int.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var passed: String = "%s is not builtin: int" % value + var failed: String = "%s is builtin: int" % value + self.context = context + self.success = not value is int + self.expected = passed + self.result = passed if self.success else failed \ No newline at end of file diff --git a/addons/WAT/core/assertions/misc/fail.gd b/addons/WAT/core/assertions/misc/fail.gd new file mode 100644 index 0000000..4a9a62b --- /dev/null +++ b/addons/WAT/core/assertions/misc/fail.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(context: String) -> void: + # Intentionally Fails Test + var failed: String = "Test Not Implemented" + self.context = context + self.success = false + self.expected = "N/A" + self.result = "N/A" diff --git a/addons/WAT/core/assertions/misc/that.gd b/addons/WAT/core/assertions/misc/that.gd new file mode 100644 index 0000000..9896b8b --- /dev/null +++ b/addons/WAT/core/assertions/misc/that.gd @@ -0,0 +1,8 @@ +extends "../base.gd" + +func _init(obj, method: String, arguments: Array, context: String, passed: String, failed: String) -> void: + passed = passed % ([obj] + arguments) + failed = failed % ([obj] + arguments) + self.success = obj.callv(method, arguments) + self.context = context + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/null/is_not_null.gd b/addons/WAT/core/assertions/null/is_not_null.gd new file mode 100644 index 0000000..e472958 --- /dev/null +++ b/addons/WAT/core/assertions/null/is_not_null.gd @@ -0,0 +1,10 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var type = type2str(value) + var passed: String = "|%s| %s != null" % [type, value] + var failed: String = "|%s| %s == null" % [type, value] + self.context = context + self.success = (value != null) + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/null/is_null.gd b/addons/WAT/core/assertions/null/is_null.gd new file mode 100644 index 0000000..efb3990 --- /dev/null +++ b/addons/WAT/core/assertions/null/is_null.gd @@ -0,0 +1,10 @@ +extends "../base.gd" + +func _init(value, context: String) -> void: + var type = type2str(value) + var passed: String = "|%s| %s == null" % [type, value] + var failed: String = "|%s| %s != null" % [type, value] + self.context = context + self.success = (value == null) + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/object/does_not_have_meta.gd b/addons/WAT/core/assertions/object/does_not_have_meta.gd new file mode 100644 index 0000000..5548cea --- /dev/null +++ b/addons/WAT/core/assertions/object/does_not_have_meta.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(object: Object, meta: String, context: String) -> void: + var passed: String = "%s does not have meta: %s" % [object, meta] + var failed: String = "%s has meta: %s" % [object, meta] + self.context = context + self.success = not object.has_meta(meta) + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/object/does_not_have_method.gd b/addons/WAT/core/assertions/object/does_not_have_method.gd new file mode 100644 index 0000000..c04aec4 --- /dev/null +++ b/addons/WAT/core/assertions/object/does_not_have_method.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(obj: Object, method: String, context: String) -> void: + var passed: String = "%s does not have method: %s" % [obj, method] + var failed: String = "%s has method: %s" % [obj, method] + self.context = context + self.success = not obj.has_method(method) + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/object/does_not_have_user_signal.gd b/addons/WAT/core/assertions/object/does_not_have_user_signal.gd new file mode 100644 index 0000000..5267ed9 --- /dev/null +++ b/addons/WAT/core/assertions/object/does_not_have_user_signal.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(obj: Object, _signal: String, context: String) -> void: + var passed: String = "%s does not have user signal: %s" % [obj, _signal] + var failed: String = "%s has user signal: %s" % [obj, _signal] + self.context = context + self.success = not obj.has_user_signal(_signal) + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/object/has_meta.gd b/addons/WAT/core/assertions/object/has_meta.gd new file mode 100644 index 0000000..314f1ff --- /dev/null +++ b/addons/WAT/core/assertions/object/has_meta.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(object: Object, meta: String, context: String) -> void: + var passed: String = "%s has meta: %s" % [object, meta] + var failed: String = "%s does not have meta: %s" % [object, meta] + self.context = context + self.success = object.has_meta(meta) + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/object/has_method.gd b/addons/WAT/core/assertions/object/has_method.gd new file mode 100644 index 0000000..1ab772b --- /dev/null +++ b/addons/WAT/core/assertions/object/has_method.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(obj: Object, method: String, context: String) -> void: + var passed: String = "%s has method: %s" % [obj, method] + var failed: String = "%s does not have method: %s" % [obj, method] + self.context = context + self.success = obj.has_method(method) + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/object/has_user_signal.gd b/addons/WAT/core/assertions/object/has_user_signal.gd new file mode 100644 index 0000000..0e21da6 --- /dev/null +++ b/addons/WAT/core/assertions/object/has_user_signal.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(obj: Object, _signal: String, context: String) -> void: + var passed: String = "%s has user signal: %s" % [obj, _signal] + var failed: String = "%s does not have user signal: %s" % [obj, _signal] + self.context = context + self.success = obj.has_user_signal(_signal) + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/object/is_blocking_signals.gd b/addons/WAT/core/assertions/object/is_blocking_signals.gd new file mode 100644 index 0000000..9597408 --- /dev/null +++ b/addons/WAT/core/assertions/object/is_blocking_signals.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(obj, context: String) -> void: + var passed: String = "%s is blocking signals" % obj + var failed: String = "%s is not blocking signals" % obj + self.context = context + self.success = obj.is_blocking_signals() + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/object/is_connected.gd b/addons/WAT/core/assertions/object/is_connected.gd new file mode 100644 index 0000000..08bc341 --- /dev/null +++ b/addons/WAT/core/assertions/object/is_connected.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(sender: Object, _signal: String, receiver: Object, method: String, context: String) -> void: + var passed: String = "%s.%s is connected to %s.%s" % [sender, _signal, receiver, method] + var failed: String = "%s.%s is not connected to %s.%s" % [sender, _signal, receiver, method] + self.context = context + self.success = sender.is_connected(_signal, receiver, method) + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/object/is_freed.gd b/addons/WAT/core/assertions/object/is_freed.gd new file mode 100644 index 0000000..a51d62c --- /dev/null +++ b/addons/WAT/core/assertions/object/is_freed.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(object: Object, context: String) -> void: + var passed: String = "%s is freed from memory" % object + var failed: String = "%s is not freed from memory" % object + self.context = context + self.success = not is_instance_valid(object) + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/object/is_not_blocking_signals.gd b/addons/WAT/core/assertions/object/is_not_blocking_signals.gd new file mode 100644 index 0000000..10362b3 --- /dev/null +++ b/addons/WAT/core/assertions/object/is_not_blocking_signals.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(obj, context: String) -> void: + var passed: String = "%s is not blocking signals" % obj + var failed: String = "%s is blocking signals" % obj + self.context = context + self.success = not obj.is_blocking_signals() + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/object/is_not_connected.gd b/addons/WAT/core/assertions/object/is_not_connected.gd new file mode 100644 index 0000000..7288bf1 --- /dev/null +++ b/addons/WAT/core/assertions/object/is_not_connected.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(sender: Object, _signal: String, receiver: Object, method: String, context: String) -> void: + var passed: String = "%s.%s is not connected to %s.%s" % [sender, _signal, receiver, method] + var failed: String = "%s.%s is connected to %s.%s" % [sender, _signal, receiver, method] + self.context = context + self.success = not sender.is_connected(_signal, receiver, method) + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/object/is_not_freed.gd b/addons/WAT/core/assertions/object/is_not_freed.gd new file mode 100644 index 0000000..e5dba08 --- /dev/null +++ b/addons/WAT/core/assertions/object/is_not_freed.gd @@ -0,0 +1,6 @@ +extends "../base.gd" + +func _init(obj: Object, context: String) -> void: + self.context = context + self.success = is_instance_valid(obj) + self.result = "%s is %s freed" % [obj, "not" if self.success else ""] diff --git a/addons/WAT/core/assertions/object/is_not_queued_for_deletion.gd b/addons/WAT/core/assertions/object/is_not_queued_for_deletion.gd new file mode 100644 index 0000000..7119a66 --- /dev/null +++ b/addons/WAT/core/assertions/object/is_not_queued_for_deletion.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(obj: Object, context: String) -> void: + var passed: String = "%s is not queued for deletion" % obj + var failed: String = "%s is queued for deletion" % obj + self.context = context + self.expected = passed + self.success = not obj.is_queued_for_deletion() + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/object/is_queued_for_deletion.gd b/addons/WAT/core/assertions/object/is_queued_for_deletion.gd new file mode 100644 index 0000000..6c6bc5b --- /dev/null +++ b/addons/WAT/core/assertions/object/is_queued_for_deletion.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(obj: Object, context: String) -> void: + var passed: String = "%s is queued for deletion" % obj + var failed: String = "%s is not queued for deletion" % obj + self.expected = passed + self.context = context + self.success = obj.is_queued_for_deletion() + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/property/does_not_have.gd b/addons/WAT/core/assertions/property/does_not_have.gd new file mode 100644 index 0000000..7980aae --- /dev/null +++ b/addons/WAT/core/assertions/property/does_not_have.gd @@ -0,0 +1,10 @@ +extends "../base.gd" + +func _init(value, container, context: String) -> void: + var passed: String = "%s does not have %s" % [container, value] + var failed: String = "%s has %s" % [container, value] + self.context = context + self.success = not container.has(value) + self.expected = "%s does not have %s" % [container, value] + self.result = passed if self.success else failed + diff --git a/addons/WAT/core/assertions/property/has.gd b/addons/WAT/core/assertions/property/has.gd new file mode 100644 index 0000000..a18e3f2 --- /dev/null +++ b/addons/WAT/core/assertions/property/has.gd @@ -0,0 +1,10 @@ +extends "../base.gd" + +func _init(value, container, context: String) -> void: + var passed: String = "%s has %s" % [container, value] + var failed: String = "%s has %s" % [container, value] + self.context = context + self.success = container.has(value) + self.expected = "%s has %s" % [container, value] + self.result = passed if self.success else failed + diff --git a/addons/WAT/core/assertions/range/is_in_range.gd b/addons/WAT/core/assertions/range/is_in_range.gd new file mode 100644 index 0000000..de1b833 --- /dev/null +++ b/addons/WAT/core/assertions/range/is_in_range.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, low, high, context: String) -> void: + var passed: String = "%s is in range(%s, %s)" % [value, low, high] + var failed: String = "%s is not in range(%s, %s)" % [value, low, high] + self.context = context + self.success = value in range(low, high) + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/range/is_not_in_range.gd b/addons/WAT/core/assertions/range/is_not_in_range.gd new file mode 100644 index 0000000..e857e5e --- /dev/null +++ b/addons/WAT/core/assertions/range/is_not_in_range.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value, low, high, context: String) -> void: + var passed: String = "%s is not in range(%s, %s)" % [value, low, high] + var failed: String = "%s is in range(%s, %s)" % [value, low, high] + self.context = context + self.success = not value in range(low, high) + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/signal/signal_was_emitted.gd b/addons/WAT/core/assertions/signal/signal_was_emitted.gd new file mode 100644 index 0000000..095a146 --- /dev/null +++ b/addons/WAT/core/assertions/signal/signal_was_emitted.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(emitter, event: String, context: String) -> void: + var passed: String = "signal: %s was emitted from %s" % [event, emitter] + var failed: String = "signal: %s was not emitted from %s" % [event, emitter] + self.context = context + self.success = emitter.get_meta("watcher").watching[event].emit_count > 0 + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/signal/signal_was_emitted_with_arguments.gd b/addons/WAT/core/assertions/signal/signal_was_emitted_with_arguments.gd new file mode 100644 index 0000000..823b4e3 --- /dev/null +++ b/addons/WAT/core/assertions/signal/signal_was_emitted_with_arguments.gd @@ -0,0 +1,33 @@ +extends "../base.gd" + +func _init(emitter: Object, event: String, arguments: Array, context: String) -> void: + var passed: String = "Signal: %s was emitted from %s with arguments: %s" % [event, emitter, arguments] + var failed: String = "Signal: %s was not emitted from %s with arguments: %s" % [event, emitter, arguments] + var alt_failure: String = "Signal: %s was not emitted from %s" % [event, emitter] + self.context = context + self.expected = passed + + var data = emitter.get_meta("watcher").watching[event] + if data.emit_count <= 0: + self.success = false + self.result = alt_failure + + elif _found_matching_call(arguments, data.calls): + self.success = true + self.result = passed + + else: + self.success = false + self.result = failed + +func _found_matching_call(args, calls) -> bool: + for call in calls: + if _match(args, call.args): + return true + return false + +func _match(args, call_args) -> bool: + for i in args.size(): + if args[i] != call_args[i]: + return false + return true diff --git a/addons/WAT/core/assertions/signal/signal_was_emitted_x_times.gd b/addons/WAT/core/assertions/signal/signal_was_emitted_x_times.gd new file mode 100644 index 0000000..fc984db --- /dev/null +++ b/addons/WAT/core/assertions/signal/signal_was_emitted_x_times.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(emitter, event: String, times: int, context: String) -> void: + var passed: String = "signal: %s was emitted from %s %s" % [event, emitter, times as String] + var failed: String = "signal: %s was not emitted from %s %s" % [event, emitter, times as String] + self.context = context + self.success = emitter.get_meta("watcher").watching[event].emit_count == times + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/signal/signal_was_not_emitted.gd b/addons/WAT/core/assertions/signal/signal_was_not_emitted.gd new file mode 100644 index 0000000..ac4936c --- /dev/null +++ b/addons/WAT/core/assertions/signal/signal_was_not_emitted.gd @@ -0,0 +1,7 @@ +extends "../base.gd" + + +func _init(emitter, _signal: String, context: String) -> void: + self.context = context + self.success = emitter.get_meta("watcher").watching[_signal].emit_count <= 0 + self.result = "Signal: %s was %s emitted from %s" % [_signal, ("not" if self.success else ""), emitter] diff --git a/addons/WAT/core/assertions/string/string_begins_with.gd b/addons/WAT/core/assertions/string/string_begins_with.gd new file mode 100644 index 0000000..09e2cef --- /dev/null +++ b/addons/WAT/core/assertions/string/string_begins_with.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value: String, string: String, context: String) -> void: + var passed: String = "%s begins with %s" % [string, value] + var failed: String = "%s does not begins with %s" % [string, value] + self.context = context + self.success = string.begins_with(value) + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/string/string_contains.gd b/addons/WAT/core/assertions/string/string_contains.gd new file mode 100644 index 0000000..7df28e5 --- /dev/null +++ b/addons/WAT/core/assertions/string/string_contains.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value: String, string: String, context: String) -> void: + var passed: String = "%s contains %s" % [string, value] + var failed: String = "%s does not contain %s" % [string, value] + self.context = context + self.success = value in string + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/string/string_does_not_begin_with.gd b/addons/WAT/core/assertions/string/string_does_not_begin_with.gd new file mode 100644 index 0000000..563bea2 --- /dev/null +++ b/addons/WAT/core/assertions/string/string_does_not_begin_with.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value: String, string: String, context: String) -> void: + var passed: String = "%s does not begin with %s" % [string, value] + var failed: String = "%s begins with %s" % [string, value] + self.context = context + self.success = not string.begins_with(value) + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/string/string_does_not_contain.gd b/addons/WAT/core/assertions/string/string_does_not_contain.gd new file mode 100644 index 0000000..39d3f87 --- /dev/null +++ b/addons/WAT/core/assertions/string/string_does_not_contain.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value: String, string: String, context: String) -> void: + var passed: String = "%s does not contain %s" % [string, value] + var failed: String = "%s contains %s" % [string, value] + self.context = context + self.success = not value in string + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/string/string_does_not_end_with.gd b/addons/WAT/core/assertions/string/string_does_not_end_with.gd new file mode 100644 index 0000000..644df80 --- /dev/null +++ b/addons/WAT/core/assertions/string/string_does_not_end_with.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value: String, string: String, context: String) -> void: + var passed: String = "%s does not end with %s" % [string, value] + var failed: String = "%s ends with %s" % [string, value] + self.context = context + self.success = not string.ends_with(value) + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/assertions/string/string_ends_with.gd b/addons/WAT/core/assertions/string/string_ends_with.gd new file mode 100644 index 0000000..dd76f8b --- /dev/null +++ b/addons/WAT/core/assertions/string/string_ends_with.gd @@ -0,0 +1,9 @@ +extends "../base.gd" + +func _init(value: String, string: String, context: String) -> void: + var passed: String = "%s ends with %s" % [string, value] + var failed: String = "%s does not end with %s" % [string, value] + self.context = context + self.success = string.ends_with(value) + self.expected = passed + self.result = passed if self.success else failed diff --git a/addons/WAT/core/double/factory.gd b/addons/WAT/core/double/factory.gd new file mode 100644 index 0000000..7e21871 --- /dev/null +++ b/addons/WAT/core/double/factory.gd @@ -0,0 +1,29 @@ +extends Reference + +const ScriptDirector = preload("script_director.gd") +const SceneDirector = preload("scene_director.gd") + +func script(path, inner: String = "", deps: Array = []) -> ScriptDirector: + if path is GDScript: path = path.resource_path + return ScriptDirector.new(path, inner, deps) + +func scene(tscn) -> SceneDirector: + # Must be String.tscn or PackedScene + var scene: PackedScene = load(tscn) if tscn is String else tscn + var instance: Node = scene.instance() + var nodes: Dictionary = {} + var frontier: Array = [] + frontier.append(instance) + while not frontier.empty(): + var next: Node = frontier.pop_front() + if next.name.begins_with("@@"): + # Don't double engine-generated classes (usually begin with @@) + continue + frontier += next.get_children() + var path: String = instance.get_path_to(next) + if next.get_script() != null: + nodes[path] = script(next.get_script().resource_path) + elif ClassDB.class_exists(next.get_class()): + nodes[path] = script(next.get_class()) + instance.queue_free() + return SceneDirector.new(nodes) diff --git a/addons/WAT/core/double/method.gd b/addons/WAT/core/double/method.gd new file mode 100644 index 0000000..74add92 --- /dev/null +++ b/addons/WAT/core/double/method.gd @@ -0,0 +1,95 @@ +extends Reference + +var name: String = "" +var spying: bool = false +var stubbed: bool = false +var calls_super: bool = false +var args: String = "" +var keyword: String = "" +var calls: Array = [] +var stubs: Array = [] +var supers: Array = [] +var callables: Array = [] +var default +var double + +func _init(name: String, keyword: String, args: String) -> void: + self.name = name + self.keyword = keyword + self.args = args + +func dummy() -> Reference: + stubbed = true + default = null + return self + +func spy() -> Reference: + push_warning("Deprecated. Spying on Methods is now Automatic. Please Remove") + spying = true + return self + +func stub(return_value, arguments: Array = []): + stubbed = true + if arguments.empty(): + default = return_value + else: + stubs.append({args = arguments, "return_value": return_value}) + return self + +func primary(args: Array): + if stubbed: + return get_stub(args) + elif calls.size() > 0: + for call in callables: + return call.call_func(double, args) + else: + return null + +func add_call(args: Array = []) -> void: + calls.append(args) + +func subcall(function: Object, deprecated_var = null) -> void: + if deprecated_var != null: + push_warning("Users no longer need to pass in the return boolean") + callables.append(function) + +func get_stub(args: Array = []): + for stub in stubs: + if _pattern_matched(stub.args, args): + return stub.return_value + return default + +func executes(args: Array) -> bool: + for s in supers: + if _pattern_matched(s, args): + return true + for s in stubs: + if _pattern_matched(s.args, args): + return false + if supers.has([]): + return true + return false + +func call_super(args: Array = []) -> void: + supers.append(args) + calls_super = true + +func found_matching_call(expected_args: Array = []) -> bool: + for call in calls: + if _pattern_matched(expected_args, call): + return true + return false + +func _pattern_matched(pattern: Array = [], args: Array = []) -> bool: + var indices: Array = [] + if pattern.size() != args.size(): + return false + for index in pattern.size(): + if pattern[index] is Object and pattern[index].get_class() == "Any": + continue + indices.append(index) + for i in indices: + # We check based on type first otherwise some errors occur (ie object can't be compared to int) + if typeof(pattern[i]) != typeof(args[i]) or pattern[i] != args[i]: + return false + return true diff --git a/addons/WAT/core/double/methods.gd b/addons/WAT/core/double/methods.gd new file mode 100644 index 0000000..b9e5bc7 --- /dev/null +++ b/addons/WAT/core/double/methods.gd @@ -0,0 +1,9 @@ +extends Reference + +var base: Array = [] + +func set_base_methods(director: Reference) -> void: + pass + +func call_count(): + pass diff --git a/addons/WAT/core/double/registry.gd b/addons/WAT/core/double/registry.gd new file mode 100644 index 0000000..872758b --- /dev/null +++ b/addons/WAT/core/double/registry.gd @@ -0,0 +1,22 @@ +extends Node + +var test_directors: Dictionary = {} + +func register(director) -> void: + # This probably doesn't need to exist as a singleton? + # We could likely store a cache within each test + # This way we can isolate it from other caches + # (We could even probably store it directly within the factory) + if director.get_instance_id() in test_directors: + push_warning("Director Object is already registered") + return + test_directors[director.get_instance_id()] = director + +func method(instance_id: int, method: String) -> Object: + return test_directors[instance_id].methods[method] + +func clear() -> void: + var directors = test_directors.values() + while not directors.empty(): + var director = directors.pop_back() + director.clear() diff --git a/addons/WAT/core/double/scene_director.gd b/addons/WAT/core/double/scene_director.gd new file mode 100644 index 0000000..06c6e85 --- /dev/null +++ b/addons/WAT/core/double/scene_director.gd @@ -0,0 +1,50 @@ +extends Reference +tool +# We don't seem to need tool here yet but I'm keeping this comment JIC + +var nodes: Dictionary = {} +var _created: bool = false +var cache: Array = [] + +func _init(nodes: Dictionary = {}) -> void: + self.nodes = nodes + +func get_node(path: String) -> Node: + return nodes[path] + +func double() -> Node: + if _created: + push_error("WAT: You can only create one instance of a double" + + "Create a new doubler Object for new Test Doubles") + return nodes["."] + _created = true + var root: Node = nodes["."].double() + for nodepath in nodes: + var path: PoolStringArray = nodepath.split("/") + if nodepath == ".": + # Skip if root node since already defined + continue + elif path.size() == 1: + _add_child(path, nodepath, root) + elif path.size() > 1: + _add_grandchild(path, nodepath, root) + return root + +func _add_child(path: PoolStringArray, nodepath: String, root: Node) -> void: + var node: Node = nodes[nodepath].double() + node.name = path[0] + root.add_child(node) + +func _add_grandchild(path: PoolStringArray, nodepath: String, root: Node) -> void: + var node: Node = nodes[nodepath].double() + var p = Array(path) + node.name = p.pop_back() + var parent = "" + for element in p: + parent += "%s/" % element + parent = parent.rstrip("/") + var grandparent = root.get_node(parent) + grandparent.add_child(node) + +func clear() -> void: + nodes = {} diff --git a/addons/WAT/core/double/script_director.gd b/addons/WAT/core/double/script_director.gd new file mode 100644 index 0000000..7cddfd8 --- /dev/null +++ b/addons/WAT/core/double/script_director.gd @@ -0,0 +1,114 @@ +extends Reference + +const STATIC: String = "static " +const REMOTE: String = "remote " +const REMOTESYNC: String = "remotesync " +const MASTER: String = "master " +const PUPPET: String = "puppet " +const SCRIPT_WRITER = preload("script_writer.gd") +const Method = preload("method.gd") +var klass: String +var inner_klass: String = "" +var methods: Dictionary = {} +var _created: bool = false +var is_scene: bool = false +var klasses: Array = [] +var base_methods: Dictionary = {} +var dependecies: Array = [] +var is_built_in: bool = false +var object + +func _init(_klass: String, _inner_klass: String, deps: Array = []) -> void: + klass = _klass + inner_klass = _inner_klass + dependecies = deps + is_built_in = ClassDB.class_exists(_klass) + ProjectSettings.get_setting("WAT/TestDouble").register(self) + set_methods() + +func method(name: String, keyword: String = "") -> Method: + if not methods.has(name): + methods[name] = Method.new(name, keyword, base_methods[name]) + return methods[name] + +func clear(): + if is_instance_valid(object) and object is Object and not object is Reference: + object.free() + object = null + +## BEGIN METHOD CLASS +func call_count(method: String) -> int: + return methods[method].calls.size() + +func get_stub(method: String, args: Array): + # We might be able to write this into source? + # However reducing how much we write might be best + return methods[method].get_stub(args) + +func found_matching_call(method, expected_args: Array): + # Requires changing an expectation method + return methods[method].found_matching_call(expected_args) + +func add_call(method: String, args: Array = []) -> void: + methods[method].add_call(args) + +func set_methods() -> void: + var params: String = "abcdefghij" + for m in method_list(): + var arguments: String = "" + for i in m.args.size(): + arguments = arguments + params[i] + ", " + arguments = arguments.rstrip(", ") + base_methods[m.name] = arguments + +func method_list() -> Array: + var list: Array = [] + if is_built_in: + return ClassDB.class_get_method_list(klass) + var script = load(klass) if inner_klass == "" else _load_nested_class() + # We get our script methods first in case there is a custom constructor + # This way we don't end up reading the empty base constructors of Object + list += script.get_script_method_list() + list += ClassDB.class_get_method_list(script.get_instance_base_type()) + var filtered = {} + for m in list: + if m.name in filtered: + continue + filtered[m.name] = m + return filtered.values() +## END METHOD CLASS + +func add_inner_class(klass: Object, name: String) -> void: + klasses.append({"director": klass, "name": name}) + +func script(): + var script = GDScript.new() + for klass in klasses: + klass.director.script() + script.source_code = SCRIPT_WRITER.new().write(self) + script.reload() # Necessary to load source code into memory + return script + +func double(deps: Array = [], show_error = true) -> Object: + if _created: + # Can only create unique instances + if show_error: + push_error("WAT: You can only create one instance of a double. Create a new doubler Object for new Test Doubles") + return object + _created = true + if not deps.empty() and dependecies.empty(): + dependecies = deps + object = script().callv("new", dependecies) + for m in methods.values(): + m.double = object + # This is a nasty abuse of const collections not being strongly-typed + # We're mainly doing this for easy use of static methods + return object + + + +func _load_nested_class() -> Script: + var expression = Expression.new() + var script = load(klass) + expression.parse("%s" % [inner_klass]) + return expression.execute([], script, true) diff --git a/addons/WAT/core/double/script_writer.gd b/addons/WAT/core/double/script_writer.gd new file mode 100644 index 0000000..9f21554 --- /dev/null +++ b/addons/WAT/core/double/script_writer.gd @@ -0,0 +1,46 @@ +extends Reference + + +func write(double) -> String: + var source: String = "" + source += _extension_to_string(double) + + if double.base_methods.has("_init"): + source += _constructor_to_string(double.base_methods["_init"]) + + for name in double.methods: + var m = double.methods[name] + source += _method_to_string(double.get_instance_id(), m) + for klass in double.klasses: + source += _inner_class(klass) + source = source.replace(",)", ")") + return source + +func _extension_to_string(double) -> String: + if double.is_built_in: + return 'extends %s' % double.klass + if double.inner_klass != "": + return 'extends "%s".%s\n' % [double.klass, double.inner_klass] + return 'extends "%s"\n' % double.klass + +func _constructor_to_string(parameters: String) -> String: + var constructor: String = "" + constructor += "\nfunc _init(%s).(%s):" % [parameters, parameters] + constructor += "\n\tpass\n" + return constructor + +func _method_to_string(id: int, method: Object) -> String: + var text: String + text += "{keyword}func {name}({args}):" + text += "\n\tvar args = [{args}]" + text += "\n\tvar method = ProjectSettings.get_setting('WAT/TestDouble').method({id}, '{name}')" + text += "\n\tmethod.add_call(args)" + text += "\n\tif method.executes(args):" + text += "\n\t\treturn .{name}({args})" # We may want to add a retval check here + text += "\n\treturn method.primary(args)\n\n" + text = text.format({"id": id, "keyword": method.keyword, + "name": method.name, "args": method.args}) + return text + +func _inner_class(klass: Dictionary) -> String: + return "\nclass %s extends '%s'.%s:\n\tconst PLACEHOLDER = 0" % [klass.name, klass.director.klass, klass.name] diff --git a/addons/WAT/core/test/any.gd b/addons/WAT/core/test/any.gd new file mode 100644 index 0000000..a29ac7e --- /dev/null +++ b/addons/WAT/core/test/any.gd @@ -0,0 +1,8 @@ +extends Reference + +# I hate this +# Used for do not care values in stubs +# Probably a better way to handle it + +func get_class() -> String: + return "Any" diff --git a/addons/WAT/core/test/base_test.gd b/addons/WAT/core/test/base_test.gd new file mode 100644 index 0000000..c4d5f08 --- /dev/null +++ b/addons/WAT/core/test/base_test.gd @@ -0,0 +1,97 @@ +extends Node + +const TEST: bool = true +const YIELD: String = "finished" +const CRASH_IF_TEST_FAILS: bool = true + +var asserts: Reference +var direct: Reference +var rerun_method: bool = false + +# This is used to access variable data with the user test +# e.g assert.is_equal(p.augend + p.addend, p.result, etc; +var p: Dictionary + +var _watcher: Reference +var _parameters: Reference +var _yielder: Timer +var _testcase: Reference +var _recorder: Script + +signal described + +func methods() -> PoolStringArray: + var output: PoolStringArray = [] + for method in get_method_list(): + if method.name.begins_with("test"): + output.append(method.name) + return output + +func setup(assertions, yielder, testcase, director, + signal_watcher, parameters, recorder): + asserts = assertions + direct = director + _testcase = testcase + _yielder = yielder + _watcher = signal_watcher + _parameters = parameters + _recorder = recorder + +func record(who: Object, properties: Array) -> Node: + var record = _recorder.new() + record.record(who, properties) + add_child(record) + return record + +func _ready() -> void: + p = _parameters.parameters + asserts.connect("asserted", _testcase, "_on_asserted") + connect("described", _testcase, "_on_test_method_described") + +func any(): + return preload("any.gd").new() + +func describe(message: String) -> void: + emit_signal("described", message) + +func parameters(list: Array) -> void: + rerun_method = _parameters.parameters(list) + +func path() -> String: + var path = get_script().get_path() + return path if path != "" else get_script().get_meta("path") + +func title() -> String: + var path: String = get_script().get_path() + var substr: String = path.substr(path.find_last("/") + 1, + path.find(".gd")).replace(".gd", "").replace("test", "").replace(".", " ").capitalize() + return substr + +func watch(emitter, event: String) -> void: + _watcher.watch(emitter, event) + +func unwatch(emitter, event: String) -> void: + _watcher.unwatch(emitter, event) + +## Untested +## Thanks to bitwes @ https://github.com/bitwes/Gut/ +func simulate(obj, times, delta): + for i in range(times): + if(obj.has_method("_process")): + obj._process(delta) + if(obj.has_method("_physics_process")): + obj._physics_process(delta) + + for kid in obj.get_children(): + simulate(kid, 1, delta) + +func until_signal(emitter: Object, event: String, time_limit: float) -> Node: + watch(emitter, event) + return _yielder.until_signal(time_limit, emitter, event) + +func until_timeout(time_limit: float) -> Node: + return _yielder.until_timeout(time_limit) + +func _notification(what: int) -> void: + if what == NOTIFICATION_PREDELETE: + _watcher.clear() diff --git a/addons/WAT/core/test/case.gd b/addons/WAT/core/test/case.gd new file mode 100644 index 0000000..d4f8ed5 --- /dev/null +++ b/addons/WAT/core/test/case.gd @@ -0,0 +1,43 @@ +extends Reference + +var total: int = 0 +var passed: int = 0 +var title: String +var path: String +var methods: Array = [] +var success: bool = false +var time_taken: float = 0.0 + +func _init(test_title: String, test_path: String) -> void: + title = test_title + path = test_path + +func add_method(name: String) -> void: + name = name.replace("_", " ").lstrip("test") + methods.append({context = name, assertions = [], total = 0, passed = 0, success = false, time = 0.0}) + +func _on_test_method_described(description: String) -> void: + methods.back().context = description + +func _on_asserted(assertion: Object) -> void: + methods.back().assertions.append(assertion.to_dictionary()) + +func calculate() -> void: + for method in methods: + for assertion in method.assertions: + method.passed += assertion.success as int + method.total = method.assertions.size() + method.success = method.total > 0 and method.total == method.passed + passed += method.success as int + total = methods.size() + success = total > 0 and total == passed + +func to_dictionary() -> Dictionary: + return { "total": total, + "passed": passed, + "context": title, + "methods": methods, + "success": success, + "path": path, + "time_taken": time_taken + } diff --git a/addons/WAT/core/test/parameters.gd b/addons/WAT/core/test/parameters.gd new file mode 100644 index 0000000..34d1cdf --- /dev/null +++ b/addons/WAT/core/test/parameters.gd @@ -0,0 +1,21 @@ +extends Reference + + +var _keys: Array = [] +var _values: Array = [] + +var parameters: Dictionary = {} +#parameters([["a", "b", "expected"], [2, 2, 4], [5, 5, 10], [7, 7, 14]]) +func parameters(list: Array) -> bool: + if _keys.empty() or _values.empty(): + # Keys aren't empty, so we'll be updating this implicilty every time a call is made instead + _keys = list.pop_front() + _values = list + return _update() + +func _update() -> bool: + parameters.clear() + var values = _values.pop_front() + for i in _keys.size(): + parameters[_keys[i]] = values[i] + return not _values.empty() diff --git a/addons/WAT/core/test/recorder.gd b/addons/WAT/core/test/recorder.gd new file mode 100644 index 0000000..5c4423e --- /dev/null +++ b/addons/WAT/core/test/recorder.gd @@ -0,0 +1,32 @@ +extends Node + +var _recording: Object +var _properties: Dictionary +var _is_recording: bool = false + +func record(what: Object, properties: Array) -> void: + _recording = what + for property in properties: + _properties[property] = [] + +func start() -> void: + _is_recording = true + +func stop() -> void: + _is_recording = false + +func _process(delta: float) -> void: + if _is_recording: + _capture() + +func _capture() -> void: + if not is_instance_valid(_recording): + return + for property in _properties: + _properties[property].append(_recording.get(property)) + +func get_property_timeline(property: String): + return _properties[property] + +func get_property_map() -> Dictionary: + return _properties diff --git a/addons/WAT/core/test/suite.gd b/addons/WAT/core/test/suite.gd new file mode 100644 index 0000000..482e995 --- /dev/null +++ b/addons/WAT/core/test/suite.gd @@ -0,0 +1,3 @@ +extends Reference + +const IS_WAT_SUITE: bool = true diff --git a/addons/WAT/core/test/template.gd b/addons/WAT/core/test/template.gd new file mode 100644 index 0000000..0980073 --- /dev/null +++ b/addons/WAT/core/test/template.gd @@ -0,0 +1,25 @@ +extends WAT.Test + +func title(): + return "TEST GROUP NAME" + +func start(): + # Runs before all test related methods once + pass + +func pre(): + # Runs before each test method + pass + +func test_METHOD(): + describe("TEST DESCRIPTION") + # Create one for each test with a relevant name that MUST start with `test_` + pass + +func post(): + # Runs after each test method + pass + +func end(): + # Runs after all other test related methods once + pass diff --git a/addons/WAT/core/test/test.gd b/addons/WAT/core/test/test.gd new file mode 100644 index 0000000..3b8dd8a --- /dev/null +++ b/addons/WAT/core/test/test.gd @@ -0,0 +1,89 @@ +extends "base_test.gd" +class_name WATTest + +class State: + const START: String = "start" + const PRE: String = "pre" + const EXECUTE: String = "execute" + const POST: String = "post" + const END: String = "end" + +var _state: String +var _methods: Array = [] +var _method: String +var time: float = 0.0 +signal completed + +func _ready() -> void: + _yielder.connect("finished", self, "_next") + add_child(_yielder) + +func _next(vargs = null): + # When yielding until signals or timeouts, this gets called on resume + # We call defer here to give the __testcase method time to reach either the end + # or an extra yield at which point we're able to check the _state of the yield and + # see if we stay paused or can continue + call_deferred("_change_state") + +func _change_state() -> void: + if _yielder.is_active(): + return + match _state: + State.START: + _pre() + State.PRE: + _execute() + State.EXECUTE: + _post() + State.POST: + _pre() + State.END: + _end() + +func _start(): + _state = State.START + start() + _next() + +func _pre(): + time = OS.get_ticks_msec() + if _methods.empty() and not rerun_method: + _state = State.END + _next() + return + _state = State.PRE + pre() + _next() + +func _execute(): + _state = State.EXECUTE + _method = _method if rerun_method else _methods.pop_back() + _testcase.add_method(_method) + call(_method) + _next() + +func _post(): + _testcase.methods.back().time = (OS.get_ticks_msec() - time) / 1000.0 + _state = State.POST + post() + _next() + +func _end(): + _state = State.END + end() + emit_signal("completed") + +func _exit_tree() -> void: + queue_free() + +func start(): + pass + +func pre(): + pass + +func post(): + pass + +func end(): + pass diff --git a/addons/WAT/core/test/timer.gd b/addons/WAT/core/test/timer.gd new file mode 100644 index 0000000..c94f624 --- /dev/null +++ b/addons/WAT/core/test/timer.gd @@ -0,0 +1,26 @@ +extends Label +tool + +var base: float = 0.0 +var count: float = 0.0 +var running: bool = false +var dots = 10 + +func start(): + base = OS.get_ticks_msec() + running = true + +func _process(delta): + if running: + text = "" + for i in dots: + text += "." + dots += 1 + if dots > 10: + dots = 0 +# +func _stop(x): + running = false + count = OS.get_ticks_msec() - base + text = "Total Time: %s msecs" % count as String + update() diff --git a/addons/WAT/core/test/watcher.gd b/addons/WAT/core/test/watcher.gd new file mode 100644 index 0000000..28829ef --- /dev/null +++ b/addons/WAT/core/test/watcher.gd @@ -0,0 +1,34 @@ +extends Reference + +var watching: Dictionary = {} +var _objects: Array = [] + +func watch(emitter, event: String) -> void: + _objects.append(emitter) + if emitter.is_connected(event, self, "_add_emit"): + return + emitter.set_meta("watcher", self) + emitter.connect(event, self, "_add_emit", [emitter, event]) + watching[event] = {emit_count = 0, calls = []} + + +func _add_emit(a = null, b = null, c = null, d = null, e = null, f = null, g = null, h = null, i = null, j = null, k = null): + var arguments: Array = [a, b, c, d, e, f, g, h, i, j, k] + var event + while not event: + event = arguments.pop_back() + var obj = arguments.pop_back() + watching[event].emit_count += 1 + watching[event].calls.append({emitter = obj, args = arguments}) + +func unwatch(emitter, event: String) -> void: + if emitter.is_connected(event, self, "_add_emit"): + emitter.disconnect(event, self, "_add_emit") + watching.erase(event) + emitter.set_meta("watcher", null) + +func clear() -> void: + pass + for object in _objects: + if is_instance_valid(object): + object.set_meta("watcher", null) diff --git a/addons/WAT/core/test/yielder.gd b/addons/WAT/core/test/yielder.gd new file mode 100644 index 0000000..c145982 --- /dev/null +++ b/addons/WAT/core/test/yielder.gd @@ -0,0 +1,41 @@ +extends Timer + +signal finished +var _emitter: Object +var _event: String + +func _init() -> void: + paused = true + one_shot = true + +func until_timeout(time: float) -> Timer: + # Oneshot doesn't disconnect properly without also being deferred + connect("timeout", self, "_on_resume", [], CONNECT_DEFERRED) + wait_time = time + paused = false + start() + return self + +func until_signal(time: float, emitter: Object, event: String) -> Timer: + _emitter = emitter + _event = event + _emitter.connect(event, self, "_on_resume", [], CONNECT_DEFERRED) + connect("timeout", self, "_on_resume", [], CONNECT_DEFERRED) + wait_time = time + paused = false + start() + return self + +func _on_resume(a = null, b = null, c = null, d = null, e = null, f = null) -> void: + paused = true + disconnect("timeout", self, "_on_resume") + if is_instance_valid(_emitter) and _emitter.is_connected(_event, self, "_on_resume"): + _emitter.disconnect(_event, self, "_on_resume") + # Our adapter is connected to this. When this is emitted our adapter + # ..will call "_next" which call defers _change_state. Since it is a deferred + # ..call the test will resume first. Therefore if a new yield gets constructed + # ..in the interim we will be able to check if we have restarted the yield clock + emit_signal("finished", [a, b, c, d, e, f]) + +func is_active() -> bool: + return not paused and time_left > 0 diff --git a/addons/WAT/core/test_runner/TestRunner.tscn b/addons/WAT/core/test_runner/TestRunner.tscn new file mode 100644 index 0000000..69353e6 --- /dev/null +++ b/addons/WAT/core/test_runner/TestRunner.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://addons/WAT/core/test_runner/test_runner.gd" type="Script" id=1] + +[node name="TestRunner" type="Node"] +script = ExtResource( 1 ) diff --git a/addons/WAT/core/test_runner/directory.gd b/addons/WAT/core/test_runner/directory.gd new file mode 100644 index 0000000..5469868 --- /dev/null +++ b/addons/WAT/core/test_runner/directory.gd @@ -0,0 +1,70 @@ +extends Directory +# Not sure if we need tool? + +# 1) On Plugin Or GUI Load: Initialize +# 2) Validate & Cache Scripts +# 3) Update Metadata (exist in dictionaries) // we loop through a tag array +# 4) We save a new dictionary (do we? yes for the test runner!) into the resource + +var _directory: Resource +var _cache: Array = [] +var _dir: Dictionary = {} + +func _init(): + initialize() + +func index(dir: String = test_directory()) -> void: + + var dirs: Array = [] + + open(dir) + _directory.directory.set(dir, []) + + list_dir_begin(true) + + var name = get_next() + while name != "": + var p = "%s/%s" % [dir, name] + if p.ends_with(".gd") and file_exists(p): + var test = load(p) + if is_test(test): + _cache.append(test) + _dir[dir].append(p) + elif dir_exists(p): + dirs.append(p) + name = get_next() + + list_dir_end() + + for dir in dirs: + index(dir) + +func is_test(test) -> bool: + # Fix Up For Suite / if multi-suite, use () + return is_instance_valid(test) and test.get_instance_base_type() == "WAT.Test" + +func initialize() -> void: + var savepath: String = "%s/.test.tres" % test_directory() + _directory = load(savepath) if file_exists(savepath) else Repo.new() + index() + # Implement Tag checks here? + save() + +func save() -> void: + ResourceSaver.save("%s/.test.tres" % test_directory(), _directory) + +func test_directory() -> String: + return ProjectSettings.get_setting("WAT/Test_Directory") + +func fetch() -> Array: + var tests: Array = [] + var execution_path: String = ProjectSettings.get_setting("WAT/ActiveRunPath") + for key in _directory.directory: + if key.begins_with(execution_path): + for t in _directory.directory.key: + tests.append(load(t)) + return tests + +class Repo extends Resource: + + export(Dictionary) var directory: Dictionary = {} diff --git a/addons/WAT/core/test_runner/execute.gd b/addons/WAT/core/test_runner/execute.gd new file mode 100644 index 0000000..53f0448 --- /dev/null +++ b/addons/WAT/core/test_runner/execute.gd @@ -0,0 +1,20 @@ +extends Reference + +const RUN_CURRENT_SCENE_GODOT_3_2: int = 39 +const RUN_CURRENT_SCENE_GODOT_3_1: int = 33 + +func run(test_runner_path: String) -> void: + var plugin = EditorPlugin.new() + plugin.get_editor_interface().open_scene_from_path(test_runner_path) + var previous_resource = plugin.get_editor_interface().get_script_editor().get_current_script() + var version = Engine.get_version_info() + if version.minor == 1: + _run(RUN_CURRENT_SCENE_GODOT_3_1) + elif version.minor == 2: + _run(RUN_CURRENT_SCENE_GODOT_3_2) + if previous_resource: + plugin.get_editor_interface().edit_resource(previous_resource) + +func _run(version: int) -> void: + var plugin = EditorPlugin.new() + plugin.get_editor_interface().get_parent()._menu_option(version) diff --git a/addons/WAT/core/test_runner/test_loader.gd b/addons/WAT/core/test_runner/test_loader.gd new file mode 100644 index 0000000..0242dfb --- /dev/null +++ b/addons/WAT/core/test_runner/test_loader.gd @@ -0,0 +1,96 @@ +extends Reference + +const FileSystem: Script = preload("res://addons/WAT/system/filesystem.gd") +var _tests: Array = [] + +func metadata() -> Resource: + var path = ProjectSettings.get_setting("WAT/Test_Directory") + var loadpath: String = "%s/.test/metadata.tres" % path + var object = load(loadpath) + return object + +func deposit(tests: Array) -> void: + _tests = tests + +func last_failed() -> Array: + _tests = load("res://addons/WAT/resources/results.tres").failures + var tests = _load_tests() + _tests = [] + return tests + +func all() -> Array: + _tests = FileSystem.scripts(ProjectSettings.get_setting("WAT/Test_Directory")) + var tests = _load_tests() + _tests = [] + return tests + +func directory(_directory: String) -> Array: + _tests = FileSystem.scripts(_directory) + var tests = _load_tests() + _tests = [] + return tests + +func script(_script: String) -> Array: + _tests = [_script] + var tests = _load_tests() + _tests = [] + return tests + +func tag(tag: String) -> Array: + var tagged: Array = [] + var path = ProjectSettings.get_setting("WAT/Test_Directory") + var loadpath: String = "res://.test/metadata.tres" + var Index = load(loadpath) + for i in Index.scripts.size(): + if Index.tags[i].has(tag): + tagged.append(Index.scripts[i].resource_path) + _tests = tagged + var tests = _load_tests() + _tests = [] + return tests + +func deposited() -> Array: + return _tests +# var tests = _tests.duplicate() +# _tests = [] +# return tests + +func _load_tests() -> Array: + var tests: Array = [] + for path in _tests: + # Can't load WAT.Test here for whatever reason + if path is String and not path.ends_with(".gd"): + path = path.substr(0, path.find(".gd") + 3) + var test = load(path) if path is String else path + if test.get("TEST") != null: + tests.append(test) + elif test.get("IS_WAT_SUITE") and Engine.get_version_info().minor == 2: + tests += _suite_of_suites_3p2(test) + elif test.get("IS_WAT_SUITE") and Engine.get_version_info().minor == 1: + tests += _suite_of_suites_3p1(test) + return tests + +func _suite_of_suites_3p2(suite_of_suites) -> Array: + var subtests: Array = [] + for constant in suite_of_suites.get_script_constant_map(): + var expression: Expression = Expression.new() + expression.parse(constant) + var subtest = expression.execute([], suite_of_suites) + if subtest.get("TEST") != null: + subtest.set_meta("path", "%s.%s" % [suite_of_suites.get_path(), constant]) + subtests.append(subtest) + return subtests + +func _suite_of_suites_3p1(suite_of_suites) -> Array: + var subtests: Array = [] + var source = suite_of_suites.source_code + for l in source.split("\n"): + if l.begins_with("class"): + var classname = l.split(" ")[1] + var expr = Expression.new() + expr.parse(classname) + var subtest = expr.execute([], suite_of_suites) + if subtest.get("TEST") != null: + subtest.set_meta("path", "%s.%s" % [suite_of_suites.get_path(), classname]) + subtests.append(subtest) + return subtests diff --git a/addons/WAT/core/test_runner/test_loader.tres b/addons/WAT/core/test_runner/test_loader.tres new file mode 100644 index 0000000..5160f22 --- /dev/null +++ b/addons/WAT/core/test_runner/test_loader.tres @@ -0,0 +1,7 @@ +[gd_resource type="Resource" load_steps=2 format=2] + +[ext_resource path="res://addons/WAT/core/test_runner/test_loader.gd" type="Script" id=1] + +[resource] +script = ExtResource( 1 ) +tests = [ ] diff --git a/addons/WAT/core/test_runner/test_runner.gd b/addons/WAT/core/test_runner/test_runner.gd new file mode 100644 index 0000000..1b0547a --- /dev/null +++ b/addons/WAT/core/test_runner/test_runner.gd @@ -0,0 +1,108 @@ +extends Node + +const COMPLETED: String = "completed" +var JunitXML = preload("res://addons/WAT/resources/JUnitXML.gd").new() +var test_loader: Reference = preload("test_loader.gd").new() +var test_results: Resource = WAT.Results +var _tests: Array = [] +var _cases: Array = [] +var _strategy: Dictionary = {} +signal ended + +func strategy() -> Dictionary: + # We consume this so we can avoid worrying about mutatiosn + var m_strategy = {} + var strat = ProjectSettings.get_setting("WAT/TestStrategy") + for key in strat: + m_strategy[key] = strat[key] + ProjectSettings.set_setting("WAT/TestStrategy", {}) + ProjectSettings.save() + m_strategy["repeat"] = m_strategy["repeat"] as int + return m_strategy + +var _time: float +func _ready() -> void: + _time = OS.get_ticks_msec() + _strategy = strategy() + if get_tree().root.get_child(0) == self: + print("Starting WAT Test Runner") + OS.window_minimized = ProjectSettings.get_setting( + "WAT/Minimize_Window_When_Running_Tests") + _create_test_double_registry() + _begin() + +func _begin(): + _tests = get_tests() + + if _tests.empty(): + push_warning("No Scripts To Test") + _run_tests() + +func get_tests() -> Array: + match _strategy["strategy"]: + "RunAll": + return test_loader.all() + "RunDirectory": + return test_loader.directory(_strategy["directory"]) + "RunScript": + return test_loader.script(_strategy["script"]) + "RunTag": + return test_loader.tag(_strategy["tag"]) + "RunMethod": + return test_loader.script(_strategy["script"]) + "RerunFailures": + return test_loader.last_failed() + _: + return _tests + + +var time_taken: float +func _run_tests() -> void: + while not _tests.empty(): + yield(run(), COMPLETED) + _strategy["repeat"] -= 1 + if _strategy["repeat"] > 0: + call_deferred("_begin") + else: + time_taken = _time / 1000.0 + end() + +func run(test: WAT.Test = _tests.pop_front().new()) -> void: + var testcase = WAT.TestCase.new(test.title(), test.path()) + test.setup(WAT.Asserts.new(), WAT.Yielder.new(), testcase, \ + WAT.TestDoubleFactory.new(), WAT.SignalWatcher.new(), WAT.Parameters.new(), + WAT.Recorder) + var start_time = OS.get_ticks_msec() + add_child(test) + # Add Strategy Here? + if _strategy.has("method"): + test._methods = [_strategy.method] + else: + test._methods = test.methods() + test._start() + var time = OS.get_ticks_msec() - start_time + testcase.time_taken = time / 1000.0 + yield(test, COMPLETED) + testcase.calculate() + _cases.append(testcase.to_dictionary()) + remove_child(test) + +func end() -> void: + print("Ending WAT Test Runner") + OS.window_minimized = false + JunitXML.save(_cases, time_taken) + test_results.deposit(_cases) + emit_signal("ended") + clear() + get_tree().quit() + +func _create_test_double_registry() -> void: + if not ProjectSettings.has_setting("WAT/TestDouble"): + var registry = load("res://addons/WAT/core/double/registry.gd") + ProjectSettings.set_setting("WAT/TestDouble", registry.new()) + +func clear() -> void: + if ProjectSettings.has_setting("WAT/TestDouble"): + ProjectSettings.get_setting("WAT/TestDouble").clear() + ProjectSettings.get_setting("WAT/TestDouble").free() + diff --git a/addons/WAT/gui.tscn b/addons/WAT/gui.tscn new file mode 100644 index 0000000..8edc5fd --- /dev/null +++ b/addons/WAT/gui.tscn @@ -0,0 +1,485 @@ +[gd_scene load_steps=8 format=2] + +[ext_resource path="res://addons/WAT/assets/icon_add.png" type="Texture" id=1] +[ext_resource path="res://addons/WAT/ui/results/ResultsForest.tscn" type="PackedScene" id=2] +[ext_resource path="res://addons/WAT/assets/play.svg" type="Texture" id=8] +[ext_resource path="res://addons/WAT/assets/kofi.png" type="Texture" id=9] + +[sub_resource type="GDScript" id=1] +script/source = "tool +extends PanelContainer + +enum RESULTS { EXPAND_ALL, COLLAPSE_ALL, EXPAND_FAILURES } +enum RUN { ALL, DIRECTORY, SCRIPT, TAGGED, METHOD, RERUN_FAILURES } +const NOTHING_SELECTED: int = -1 +const filesystem = preload(\"res://addons/WAT/system/filesystem.gd\") +const TestRunner: String = \"res://addons/WAT/core/test_runner/TestRunner.tscn\" +onready var GUI: VBoxContainer = $GUI +onready var Interact: HBoxContainer = $GUI/Interact +onready var Summary: Label = $GUI/Summary +onready var Results: TabContainer = $GUI/Results +onready var Run: HBoxContainer = $GUI/Interact/Run +onready var Select: HBoxContainer = $GUI/Interact/Select +onready var ViewMenu: PopupMenu = $GUI/Interact/View.get_popup() +onready var QuickStart: Button = $GUI/Interact/Run/QuickStart +onready var Menu: PopupMenu = $GUI/Interact/Run/Menu.get_popup() +onready var DirectorySelector: OptionButton = $GUI/Interact/Select/Directory +onready var ScriptSelector: OptionButton = $GUI/Interact/Select/Script +onready var TagSelector: OptionButton = $GUI/Interact/Select/Tag +onready var Repeater: SpinBox = $GUI/Interact/Repeat +onready var HiddenBorder: Separator = $GUI/HiddenBorder +onready var MethodSelector: OptionButton = $GUI/Method +onready var More: Button = $GUI/Interact/More +var execute = preload(\"res://addons/WAT/core/test_runner/execute.gd\").new() + +func _on_view_pressed(id: int) -> void: + match id: + RESULTS.EXPAND_ALL: + Results.expand_all() + RESULTS.COLLAPSE_ALL: + Results.collapse_all() + RESULTS.EXPAND_FAILURES: + Results.expand_failures() + +func _ready() -> void: + set_process(false) + More.connect(\"pressed\", self, \"_show_more\") + _link($GUI/Links/Issue, \"https://github.com/CodeDarigan/WAT/issues/new\") + _link($GUI/Links/RequestDocs, \"https://github.com/CodeDarigan/WATDocs/issues/new\") + _link($GUI/Links/OnlineDocs, \"https://wat.readthedocs.io/en/latest/index.html\") + _link($GUI/Links/Support, \"https://www.ko-fi.com/alexanddraw\") + Menu.clear() + Menu.add_item(\"Run All Tests\") + Menu.add_item(\"Run Selected Directory\") + Menu.add_item(\"Run Selected Script\") + Menu.add_item(\"Run Tagged\") + Menu.add_item(\"Run Method\") + Menu.add_item(\"Rerun Failures\") + ViewMenu.clear() + ViewMenu.add_item(\"Expand All Results\") + ViewMenu.add_item(\"Collapse All Results\") + ViewMenu.add_item(\"Expand All Failures\") + QuickStart.connect(\"pressed\", self, \"_on_run_pressed\", [RUN.ALL]) + Menu.connect(\"id_pressed\", self, \"_on_run_pressed\") + ViewMenu.connect(\"id_pressed\", $GUI/Results, \"_on_view_pressed\") + DirectorySelector.clear() + ScriptSelector.clear() + TagSelector.clear() + DirectorySelector.add_item(\"Select Directory\") + ScriptSelector.add_item(\"Select Script\") + TagSelector.add_item(\"Select Tag\") + DirectorySelector.connect(\"pressed\", self, \"_on_directory_selector_pressed\") + ScriptSelector.connect(\"pressed\", self, \"_on_script_selector_pressed\") + TagSelector.connect(\"pressed\", self, \"_on_tag_selector_pressed\") + MethodSelector.connect(\"pressed\", self, \"_on_method_selector_pressed\") + ScriptSelector.get_popup().hide() + TagSelector.get_popup().hide() + +func _show_more() -> void: + MethodSelector.visible = not MethodSelector.visible + HiddenBorder.visible = MethodSelector.visible + +func _on_method_selector_pressed() -> void: + MethodSelector.clear() + var path: String = ScriptSelector.get_item_text(ScriptSelector.selected) + if not path.ends_with(\".gd\"): + MethodSelector.add_item(\"Please Select A Script First\") + return + var script = load(path) + for method in script.get_script_method_list(): + if method.name.begins_with(\"test\"): + MethodSelector.add_item(method.name) + +func _on_run_pressed(option: int) -> void: + set_process(true) + ProjectSettings.set(\"WAT/TestStrategy\", {}) + ProjectSettings.save() + match option: + RUN.ALL: + var strat = strategy() + strat[\"strategy\"] = \"RunAll\" + strat[\"repeat\"] = Repeater.value as int + ProjectSettings.set(\"WAT/TestStrategy\", strat) + _run() + RUN.DIRECTORY: + var strat = strategy() + strat[\"strategy\"] = \"RunDirectory\" + strat[\"directory\"] = selected(DirectorySelector) + strat[\"repeat\"] = Repeater.value as int + ProjectSettings.set(\"WAT/TestStrategy\", strat) + _run() + RUN.SCRIPT: + var strat = strategy() + strat[\"strategy\"] = \"RunScript\" + strat[\"script\"] = selected(ScriptSelector) + strat[\"repeat\"] = Repeater.value as int + ProjectSettings.set(\"WAT/TestStrategy\", strat) + _run() + RUN.TAGGED: + var strat = strategy() + strat[\"strategy\"] = \"RunTag\" + strat[\"tag\"] = selected(TagSelector) + strat[\"repeat\"] = Repeater.value as int + ProjectSettings.set(\"WAT/TestStrategy\", strat) + _run() + RUN.METHOD: + var strat = strategy() + strat[\"strategy\"] = \"RunMethod\" + strat[\"script\"] = selected(ScriptSelector) + strat[\"method\"] = selected(MethodSelector) + strat[\"repeat\"] = Repeater.value as int + ProjectSettings.set(\"WAT/TestStrategy\", strat) + _run() + RUN.RERUN_FAILURES: + var strat = strategy() + strat[\"strategy\"] = \"RerunFailures\" + strat[\"repeat\"] = Repeater.value as int + ProjectSettings.set(\"WAT/TestStrategy\", strat) + _run() + +func strategy() -> Dictionary: + return ProjectSettings.get_setting(\"WAT/TestStrategy\") + +func _run() -> void: + start_time() + Results.clear() + execute.run(TestRunner) + EditorPlugin.new().make_bottom_panel_item_visible(self) + +func _process(delta): + if WAT.Results.exist(): + var results = WAT.Results.withdraw() + summarize(results) + Results.display(results) + set_process(false) + +func selected(selector: OptionButton) -> String: + if selector.selected == NOTHING_SELECTED: + push_warning(\"Nothing Selected\") + return selector.get_item_text(selector.selected) + +func _on_directory_selector_pressed() -> void: + DirectorySelector.clear() + DirectorySelector.add_item(ProjectSettings.get_setting(\"WAT/Test_Directory\")) + for directory in filesystem.directories(): + DirectorySelector.add_item(directory) + +func _on_script_selector_pressed() -> void: + ScriptSelector.clear() + for script in filesystem.scripts(): + if script.ends_with(\".gd\"): + if load(script).get(\"TEST\") != null: + ScriptSelector.add_item(script) + if load(script).get(\"IS_WAT_SUITE\"): + ScriptSelector.add_item(script) + +func _on_tag_selector_pressed() -> void: + TagSelector.clear() + for tag in ProjectSettings.get_setting(\"WAT/Tags\"): + TagSelector.add_item(tag) + +func _link(button: Button, link: String): + button.connect(\"pressed\", OS, \"shell_open\", [link], CONNECT_DEFERRED) + +func test_directory() -> String: + return ProjectSettings.get_setting(\"WAT/Test_Directory\") + +func set_run_path(path: String) -> void: + ProjectSettings.set(\"WAT/ActiveRunPath\", path) + +const SUMMARY: String = \\ +\"Time Taken: {t} | Ran {r} Tests | {p} Tests Passed | {f} Tests Failed | Ran Tests {e} Times\" + +var time: float = 0 +var passed: int = 0 +var failed: int = 0 +var total: int = 0 +var runcount: int = 0 + +func start_time() -> void: + runcount += 1 + time = OS.get_ticks_msec() + +func summarize(caselist: Array) -> void: + time = (OS.get_ticks_msec() - time) / 1000 + passed = 0 + failed = 0 + total = 0 + for case in caselist: + total += 1 + if case.success: + passed += 1 + else: + failed += 1 + var summary = {t = time, r = total, p = passed, f = failed, e = runcount} + $GUI/Summary.text = SUMMARY.format(summary) +" + +[sub_resource type="InputEventKey" id=2] +control = true +command = true +pressed = true +scancode = 178 + +[sub_resource type="ShortCut" id=3] +shortcut = SubResource( 2 ) + +[node name="Tests" type="PanelContainer"] +margin_right = 1024.0 +margin_bottom = 600.0 +rect_min_size = Vector2( 0, 300 ) +size_flags_horizontal = 3 +size_flags_vertical = 3 +script = SubResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="GUI" type="VBoxContainer" parent="."] +margin_left = 7.0 +margin_top = 7.0 +margin_right = 1017.0 +margin_bottom = 593.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Interact" type="HBoxContainer" parent="GUI"] +margin_right = 1010.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 +custom_constants/separation = 10 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Run" type="HBoxContainer" parent="GUI/Interact"] +margin_right = 76.0 +margin_bottom = 24.0 + +[node name="QuickStart" type="Button" parent="GUI/Interact/Run"] +margin_right = 28.0 +margin_bottom = 24.0 +hint_tooltip = "Run All Tests" +shortcut = SubResource( 3 ) +icon = ExtResource( 8 ) +flat = true + +[node name="VSeparator" type="VSeparator" parent="GUI/Interact/Run"] +margin_left = 32.0 +margin_right = 36.0 +margin_bottom = 24.0 + +[node name="Menu" type="MenuButton" parent="GUI/Interact/Run"] +margin_left = 40.0 +margin_right = 76.0 +margin_bottom = 24.0 +text = "Run" +items = [ "Run All Tests", null, 0, false, false, 0, 0, null, "", false, "Run Selected Directory", null, 0, false, false, 1, 0, null, "", false, "Run Selected Script", null, 0, false, false, 2, 0, null, "", false, "Run Tagged", null, 0, false, false, 3, 0, null, "", false, "Run Method", null, 0, false, false, 4, 0, null, "", false, "Rerun Failures", null, 0, false, false, 5, 0, null, "", false ] +switch_on_hover = true + +[node name="View" type="MenuButton" parent="GUI/Interact"] +margin_left = 86.0 +margin_right = 128.0 +margin_bottom = 24.0 +text = "View" +items = [ "Expand All Results", null, 0, false, false, 0, 0, null, "", false, "Collapse All Results", null, 0, false, false, 1, 0, null, "", false, "Expand All Failures", null, 0, false, false, 2, 0, null, "", false ] +switch_on_hover = true + +[node name="VSeparator" type="VSeparator" parent="GUI/Interact"] +margin_left = 138.0 +margin_right = 142.0 +margin_bottom = 24.0 + +[node name="Select" type="HBoxContainer" parent="GUI/Interact"] +margin_left = 152.0 +margin_right = 818.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Directory" type="OptionButton" parent="GUI/Interact/Select"] +margin_right = 219.0 +margin_bottom = 24.0 +grow_horizontal = 0 +grow_vertical = 0 +hint_tooltip = "select a directory of tests to run" +size_flags_horizontal = 3 +size_flags_vertical = 3 +custom_constants/hseparation = 20 +button_mask = 3 +text = "Select Directory" +align = 1 +items = [ "Select Directory", null, false, 0, null ] +selected = 0 + +[node name="Script" type="OptionButton" parent="GUI/Interact/Select"] +margin_left = 223.0 +margin_right = 442.0 +margin_bottom = 24.0 +hint_tooltip = "Select a single test script to run (your choices depend on which folder is selected)." +size_flags_horizontal = 3 +size_flags_vertical = 3 +text = "Select Script" +align = 1 +items = [ "Select Script", null, false, 0, null ] +selected = 0 +__meta__ = { +"_editor_description_": "select a test script to run" +} + +[node name="Tag" type="OptionButton" parent="GUI/Interact/Select"] +margin_left = 446.0 +margin_right = 666.0 +margin_bottom = 24.0 +hint_tooltip = "select a tag and then run all tests that have that tag" +size_flags_horizontal = 3 +text = "Select Tag" +align = 1 +items = [ "Select Tag", null, false, 0, null ] +selected = 0 + +[node name="VSeparator2" type="VSeparator" parent="GUI/Interact"] +margin_left = 828.0 +margin_right = 832.0 +margin_bottom = 24.0 + +[node name="Repeat" type="SpinBox" parent="GUI/Interact"] +margin_left = 842.0 +margin_right = 972.0 +margin_bottom = 24.0 +rect_min_size = Vector2( 130, 0 ) +size_flags_horizontal = 0 +max_value = 10.0 +value = 1.0 +allow_greater = true +allow_lesser = true +prefix = "Repeat" +suffix = "Times" + +[node name="More" type="Button" parent="GUI/Interact"] +margin_left = 982.0 +margin_right = 1010.0 +margin_bottom = 24.0 +icon = ExtResource( 1 ) +flat = true + +[node name="HiddenBorder" type="HSeparator" parent="GUI"] +visible = false +margin_top = 28.0 +margin_right = 1010.0 +margin_bottom = 38.0 +rect_min_size = Vector2( 0, 10 ) +size_flags_horizontal = 3 + +[node name="Method" type="OptionButton" parent="GUI"] +visible = false +margin_top = 28.0 +margin_right = 1010.0 +margin_bottom = 48.0 +text = "Run Test Method" +align = 1 + +[node name="Results" parent="GUI" instance=ExtResource( 2 )] +margin_top = 28.0 +margin_bottom = 518.0 + +[node name="Summary" type="Label" parent="GUI"] +margin_top = 522.0 +margin_right = 1010.0 +margin_bottom = 536.0 +size_flags_vertical = 1 +text = "Time Taken: 0.00 | Ran 0 Tests | 0 Tests Passed | 0 Tests Failed | Ran Tests 0 Times" +valign = 1 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="TopSeperator" type="HSeparator" parent="GUI"] +margin_top = 540.0 +margin_right = 1010.0 +margin_bottom = 544.0 +size_flags_horizontal = 3 + +[node name="Links" type="HBoxContainer" parent="GUI"] +margin_top = 548.0 +margin_right = 1010.0 +margin_bottom = 578.0 +rect_min_size = Vector2( 0, 30 ) +size_flags_horizontal = 3 +custom_constants/separation = 0 +alignment = 1 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Support" type="Button" parent="GUI/Links"] +margin_right = 249.0 +margin_bottom = 30.0 +grow_horizontal = 0 +grow_vertical = 0 +rect_min_size = Vector2( 30, 30 ) +hint_tooltip = "Support WAT on Kofi" +focus_mode = 1 +size_flags_horizontal = 3 +size_flags_vertical = 3 +enabled_focus_mode = 1 +text = " Support WAT " +icon = ExtResource( 9 ) +flat = true +expand_icon = true + +[node name="VSeparator" type="VSeparator" parent="GUI/Links"] +margin_left = 249.0 +margin_right = 253.0 +margin_bottom = 30.0 + +[node name="Issue" type="Button" parent="GUI/Links"] +margin_left = 253.0 +margin_right = 502.0 +margin_bottom = 30.0 +size_flags_horizontal = 3 +shortcut_in_tooltip = false +action_mode = 0 +text = "Report An Issue" +flat = true + +[node name="VSeparator2" type="VSeparator" parent="GUI/Links"] +margin_left = 502.0 +margin_right = 506.0 +margin_bottom = 30.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="RequestDocs" type="Button" parent="GUI/Links"] +margin_left = 506.0 +margin_right = 755.0 +margin_bottom = 30.0 +size_flags_horizontal = 3 +text = "Request Docs" +flat = true + +[node name="VSeparator3" type="VSeparator" parent="GUI/Links"] +margin_left = 755.0 +margin_right = 759.0 +margin_bottom = 30.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="OnlineDocs" type="Button" parent="GUI/Links"] +margin_left = 759.0 +margin_right = 1010.0 +margin_bottom = 30.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +text = "Online Docs" +flat = true + +[node name="BottomSeperator" type="HSeparator" parent="GUI"] +margin_top = 582.0 +margin_right = 1010.0 +margin_bottom = 586.0 diff --git a/addons/WAT/namespace.gd b/addons/WAT/namespace.gd new file mode 100644 index 0000000..4d57834 --- /dev/null +++ b/addons/WAT/namespace.gd @@ -0,0 +1,19 @@ +extends Reference +class_name WAT + +const TestDoubleFactory = preload("res://addons/WAT/core/double/factory.gd") +const BaseTest = preload("res://addons/WAT/core/test/base_test.gd") +const Test: Script = preload("res://addons/WAT/core/test/test.gd") +const Results: Resource = preload("res://addons/WAT/resources/results.tres") +const TestSuiteOfSuites = preload("res://addons/WAT/core/test/suite.gd") +const SignalWatcher = preload("res://addons/WAT/core/test/watcher.gd") +const Parameters = preload("res://addons/WAT/core/test/parameters.gd") +const Yielder = preload("res://addons/WAT/core/test/yielder.gd") +const Asserts = preload("res://addons/WAT/core/assertions/assertions.gd") +const TestCase = preload("res://addons/WAT/core/test/case.gd") +const Recorder = preload("res://addons/WAT/core/test/recorder.gd") + +class Icon: + const SUCCESS = preload("res://addons/WAT/assets/success.png") + const FAILED = preload("res://addons/WAT/assets/failed.png") + const SUPPORT = preload("res://addons/WAT/assets/kofi.png") diff --git a/addons/WAT/plugin.cfg b/addons/WAT/plugin.cfg new file mode 100644 index 0000000..0b89f79 --- /dev/null +++ b/addons/WAT/plugin.cfg @@ -0,0 +1,6 @@ +[plugin] +name="WAT Test Runner" +description="A Testing Plugin" +author="https://github.com/CodeDarigan" +version="4.2.0" +script="plugin.gd" \ No newline at end of file diff --git a/addons/WAT/plugin.gd b/addons/WAT/plugin.gd new file mode 100644 index 0000000..3a9b28f --- /dev/null +++ b/addons/WAT/plugin.gd @@ -0,0 +1,29 @@ +tool +extends EditorPlugin + +const TITLE: String = "Tests" +const ControlPanel: PackedScene = preload("res://addons/WAT/gui.tscn") +const TestMetadataEditor: Script = preload("res://addons/WAT/ui/metadata/editor.gd") +const DockController: Script = preload("ui/dock.gd") +const SystemInitializer: Script = preload("system/initializer.gd") + +var _ControlPanel: PanelContainer +var _TestMetadataEditor: EditorInspectorPlugin +var _DockController: Node + +func get_plugin_name() -> String: + return "WAT" + +func _enter_tree() -> void: + SystemInitializer.new() + _ControlPanel = ControlPanel.instance() + _TestMetadataEditor = TestMetadataEditor.new() + add_inspector_plugin(_TestMetadataEditor) + _DockController = DockController.new(self, _ControlPanel) + add_child(_DockController) + + +func _exit_tree() -> void: + _DockController.free() + _ControlPanel.free() + remove_inspector_plugin(_TestMetadataEditor) diff --git a/addons/WAT/resources/JUnitXML.gd b/addons/WAT/resources/JUnitXML.gd new file mode 100644 index 0000000..4f25aae --- /dev/null +++ b/addons/WAT/resources/JUnitXML.gd @@ -0,0 +1,30 @@ +extends Reference + +func save(results, time: float = 0.0) -> void: + if not ProjectSettings.has_setting("WAT/Results_Directory"): + return + var path = ProjectSettings.get_setting("WAT/Results_Directory") + if not Directory.new().dir_exists(path): + Directory.new().make_dir_recursive(path) + var tests: int = results.size() + var failures: int = 0 + for i in results: + if not i.success: + failures += 1 + var output: String = "" + output += '' + output += '\n' % [failures, tests, time] + for result in results: + output += '\n' % [result.total - result.passed, result.context, result.total, result.time_taken] + for case in result.methods: + output += '\n' % [case.context, case.time] + for assertion in case.assertions: + if not assertion.success: + output += '\n' % [assertion.expected, assertion.actual] + output += '\n' + output += "\n" + output += '\n' + var XML = File.new() + XML.open("%s/results.xml" % path, File.WRITE) + XML.store_string(output) + XML.close() diff --git a/addons/WAT/resources/base/results.gd b/addons/WAT/resources/base/results.gd new file mode 100644 index 0000000..505a8ab --- /dev/null +++ b/addons/WAT/resources/base/results.gd @@ -0,0 +1,26 @@ +tool +extends Resource + +export(Array, String) var failures: Array = [] +export(Array, Dictionary) var _list: Array = [] + +func deposit(results: Array) -> void: + _list = results + _add_failures(results) + ResourceSaver.save(resource_path, self) + +func _add_failures(results) -> void: + failures = [] + for result in results: + if not result.success: + failures.append(result.path) + +func withdraw() -> Array: + var deep_copy: bool = true + var results: Array = _list.duplicate(deep_copy) + _list.clear() + ResourceSaver.save(resource_path, self) + return results + +func exist() -> bool: + return not _list.empty() diff --git a/addons/WAT/resources/results.tres b/addons/WAT/resources/results.tres new file mode 100644 index 0000000..6888efe --- /dev/null +++ b/addons/WAT/resources/results.tres @@ -0,0 +1,8 @@ +[gd_resource type="Resource" load_steps=2 format=2] + +[ext_resource path="res://addons/WAT/resources/base/results.gd" type="Script" id=1] + +[resource] +script = ExtResource( 1 ) +failures = [ ] +_list = [ ] diff --git a/addons/WAT/system/filesystem.gd b/addons/WAT/system/filesystem.gd new file mode 100644 index 0000000..286f0e7 --- /dev/null +++ b/addons/WAT/system/filesystem.gd @@ -0,0 +1,70 @@ +extends Reference + +const DO_NOT_SEARCH_PARENT_DIRECTORIES: bool = true + +static func test_folder(): + return ProjectSettings.get_setting("WAT/Test_Directory") + +static func scripts(path: String = test_folder()) -> PoolStringArray: + if path.ends_with(".gd"): + var list: PoolStringArray = [path] + return list + else: + return _parse_for_tests("file_exists", _list_dir(path)) + +static func directories(path: String = test_folder()) -> PoolStringArray: + return _parse_for("dir_exists", _list_dir(path)) + +static func _list_dir(path: String) -> PoolStringArray: + var list: PoolStringArray = [] + var subdirectories: PoolStringArray = [] + + var directory: Directory = Directory.new() + directory.open(path) + directory.list_dir_begin(DO_NOT_SEARCH_PARENT_DIRECTORIES) + var name: String = directory.get_next() + while name != "": + + var absolute_path: String = "%s/%s" % [path, name] + if directory.dir_exists(absolute_path): + subdirectories.append(absolute_path) + list.append(absolute_path) + name = directory.get_next() + directory.list_dir_end() + + for subdirectory in subdirectories: + list += _list_dir(subdirectory) + + return list + +static func _parse_for(what_exists: String, list: PoolStringArray) -> PoolStringArray: + var output: PoolStringArray = [] + var directory: Directory = Directory.new() + for path in list: + if directory.call(what_exists, path): + output.append(path) + return output + +static func _parse_for_tests(what_exists: String, list: PoolStringArray) -> PoolStringArray: + var output: PoolStringArray = [] + var directory: Directory = Directory.new() + for path in list: + if directory.call(what_exists, path): + if path.ends_with(".gd"): + output.append(path) + return output + +static func templates(): + var template_directory: String = ProjectSettings.get_setting("editor/script_templates_search_path") + var dir: Directory = Directory.new() + if not dir.dir_exists(template_directory): + dir.make_dir_recursive(template_directory) + var test_template: String = "wat.test.gd" + var scripts: Array = scripts(template_directory) + var template_exist = false + for script in scripts: + var title = script.substr(script.find_last("/") + 1, -1) + if title == test_template: + template_exist = true + break + return {savepath = template_directory, exists = template_exist} diff --git a/addons/WAT/system/initializer.gd b/addons/WAT/system/initializer.gd new file mode 100644 index 0000000..91055eb --- /dev/null +++ b/addons/WAT/system/initializer.gd @@ -0,0 +1,71 @@ +extends Reference + +func _init() -> void: + _create_test_folder() + _create_results_folder() + _add_script_templates() + _add_window_setting() + _add_test_strategy_setting() + _add_test_metadata_folder() + _add_tag_setting() + +func _add_tag_setting() -> void: + if ProjectSettings.has_setting("WAT/Tags"): + return + var property_info: Dictionary = {"name": "WAT/Tags", + "type": TYPE_STRING_ARRAY} + ProjectSettings.set("WAT/Tags", PoolStringArray()) + ProjectSettings.add_property_info(property_info) + +func _add_test_metadata_folder() -> void: + if Directory.new().dir_exists("res://.test/"): + return + Directory.new().make_dir("res://.test/") + push_warning("Created hidden metadata folder at res://.test/") + +func _create_test_folder() -> void: + var title: String = "WAT/Test_Directory" + if not ProjectSettings.has_setting(title): + var property_info: Dictionary = {"name": title, "type": TYPE_STRING, + "hint_string": "Store your WATTests here"} + ProjectSettings.set(title, "res://tests") + ProjectSettings.add_property_info(property_info) + push_warning("Set Test Directory to 'res://tests'. You can change this in Project -> Project Settings -> General -> WAT") + +func _create_results_folder() -> void: + var title: String = "WAT/Results_Directory" + if not ProjectSettings.has_setting(title): + var property_info: Dictionary = {"name": title, "type": TYPE_STRING, + "hint_string": "You can save JUnit XML Results Here"} + ProjectSettings.set(title, "res://tests/results/WAT") + ProjectSettings.add_property_info(property_info) + push_warning("Set Result Directory to 'res://tests/results/WAT'. You can change this in Project -> Project Settings -> General -> WAT") + +func _add_script_templates() -> void: + var data = preload("res://addons/WAT/system/filesystem.gd").templates() + if data.exists: + return + var path = ProjectSettings.get_setting("editor/script_templates_search_path") + var wat_template = load("res://addons/WAT/core/test/template.gd") + var savepath: String = "%s/wat.test.gd" % path + if Directory.new().dir_exists(savepath): + return + ResourceSaver.save(savepath, wat_template) + push_warning("Added WAT Script Template to %s" % path) + +func _add_window_setting() -> void: + if not ProjectSettings.has_setting("WAT/Minimize_Window_When_Running_Tests"): + ProjectSettings.set_setting("WAT/Minimize_Window_When_Running_Tests", false) + var property = {} + property.name = "WAT/Minimize_Window_When_Running_Tests" + property.type = TYPE_BOOL + ProjectSettings.add_property_info(property) + ProjectSettings.save() + +func _add_test_strategy_setting() -> void: + if not ProjectSettings.has_setting("WAT/TestStrategy"): + var property_info: Dictionary = {"name": "WAT/TestStrategy", "type": TYPE_DICTIONARY, + "hint_string": "Used by WAT internally to determine how to run tests"} + ProjectSettings.set("WAT/TestStrategy", {}) + ProjectSettings.add_property_info(property_info) + ProjectSettings.save() diff --git a/addons/WAT/ui/dock.gd b/addons/WAT/ui/dock.gd new file mode 100644 index 0000000..61a1b4f --- /dev/null +++ b/addons/WAT/ui/dock.gd @@ -0,0 +1,73 @@ +extends Node + +const displays: Dictionary = { + 0: "Left.UL Dock", 1: "Left.BL Dock", 2: "Left.UR Dock", + 3: "Left.BR Dock", 4: "Right.UL Dock", 5: "Right.BL Dock", + 6: "Right.UR Dock", 7: "Right.BR Dock", 8: "Bottom Panel", +} + +enum { LEFT_UPPER_LEFT, LEFT_BOTTOM_LEFT, LEFT_UPPER_RIGHT, + LEFT_BOTTOM_RIGHT, RIGHT_UPPER_LEFT, RIGHT_BOTTOM_LEFT, + RIGHT_UPPER_RIGHT, RIGHT_BOTTOM_RIGHT, BOTTOM_PANEL, + OUT_OF_BOUNDS +} + +var _plugin: EditorPlugin +var _scene: Control +var _state: int + +func _init(plugin: EditorPlugin, scene: Control) -> void: + _plugin = plugin + _scene = scene + +func _ready() -> void: + construct() + +func _process(delta: float) -> void: + update() + +func _notification(what) -> void: + if what == NOTIFICATION_PREDELETE: + deconstruct() + +func construct() -> void: + if not ProjectSettings.has_setting("WAT/Display"): + ProjectSettings.set_setting("WAT/Display", BOTTOM_PANEL) + ProjectSettings.save() + add_setting() + _state = get_window_state() + + if _state == BOTTOM_PANEL: + _plugin.add_control_to_bottom_panel(_scene, "Tests") + else: + _plugin.add_control_to_dock(_state, _scene) + +func deconstruct() -> void: + if _state == BOTTOM_PANEL: + _plugin.remove_control_from_bottom_panel(_scene) + else: + _plugin.remove_control_from_docks(_scene) + +func update() -> void: + var state = get_window_state() + if state == _state: + return + + deconstruct() + _state = state + construct() + + ProjectSettings.set_setting("WAT/Display", _state) + ProjectSettings.save() + +func get_window_state() -> int: + return ProjectSettings.get_setting("WAT/Display") + +func add_setting() -> void: + var property = {} + property.name = "WAT/Display" + property.type = TYPE_INT + property.hint = PROPERTY_HINT_ENUM + property.hint_string = PoolStringArray(displays.values()).join(",") + ProjectSettings.add_property_info(property) + ProjectSettings.save() diff --git a/addons/WAT/ui/metadata/Metadata.tscn b/addons/WAT/ui/metadata/Metadata.tscn new file mode 100644 index 0000000..bc61f09 --- /dev/null +++ b/addons/WAT/ui/metadata/Metadata.tscn @@ -0,0 +1,75 @@ +[gd_scene load_steps=4 format=2] + +[ext_resource path="res://addons/WAT/ui/metadata/metadata.gd" type="Script" id=1] +[ext_resource path="res://addons/WAT/assets/down.png" type="Texture" id=2] +[ext_resource path="res://addons/WAT/assets/replace.png" type="Texture" id=3] + +[node name="Main" type="VBoxContainer"] +margin_left = 7.0 +margin_top = 7.0 +margin_right = 1017.0 +margin_bottom = 83.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +custom_constants/separation = 0 +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Data" type="HBoxContainer" parent="."] +margin_right = 1010.0 +margin_bottom = 22.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Title" type="Label" parent="Data"] +margin_right = 978.0 +margin_bottom = 22.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +text = "Test Metadata" +align = 1 + +[node name="ClearAll" type="Button" parent="Data"] +margin_left = 982.0 +margin_right = 1010.0 +margin_bottom = 22.0 +icon = ExtResource( 3 ) + +[node name="HSeparator" type="HSeparator" parent="."] +margin_top = 22.0 +margin_right = 1010.0 +margin_bottom = 38.0 +grow_horizontal = 0 +grow_vertical = 0 +size_flags_vertical = 3 + +[node name="TagList" type="VBoxContainer" parent="."] +margin_top = 38.0 +margin_right = 1010.0 +margin_bottom = 54.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +custom_constants/separation = 0 + +[node name="Tags" type="HBoxContainer" parent="."] +margin_top = 54.0 +margin_right = 1010.0 +margin_bottom = 76.0 + +[node name="Select" type="MenuButton" parent="Tags"] +margin_right = 978.0 +margin_bottom = 22.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +text = "Add New Tag" +switch_on_hover = true + +[node name="TextureRect" type="Button" parent="Tags"] +margin_left = 982.0 +margin_right = 1010.0 +margin_bottom = 22.0 +size_flags_vertical = 3 +icon = ExtResource( 2 ) +flat = true diff --git a/addons/WAT/ui/metadata/Tag.tscn b/addons/WAT/ui/metadata/Tag.tscn new file mode 100644 index 0000000..3a279c7 --- /dev/null +++ b/addons/WAT/ui/metadata/Tag.tscn @@ -0,0 +1,36 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://addons/WAT/assets/remove.png" type="Texture" id=1] +[ext_resource path="res://addons/WAT/ui/metadata/tag.gd" type="Script" id=2] + + +[node name="Tag" type="HBoxContainer"] +margin_right = 1010.0 +margin_bottom = 22.0 +script = ExtResource( 2 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Name" type="Label" parent="."] +margin_right = 978.0 +margin_bottom = 22.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +text = "SOME TAG" +align = 1 +valign = 1 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Delete" type="Button" parent="."] +margin_left = 982.0 +margin_right = 1010.0 +margin_bottom = 22.0 +size_flags_vertical = 3 +icon = ExtResource( 1 ) +flat = true +__meta__ = { +"_edit_use_anchors_": false +} diff --git a/addons/WAT/ui/metadata/TagArray.tscn b/addons/WAT/ui/metadata/TagArray.tscn new file mode 100644 index 0000000..00e5da5 --- /dev/null +++ b/addons/WAT/ui/metadata/TagArray.tscn @@ -0,0 +1,33 @@ +[gd_scene format=2] + +[node name="TagArray" type="VBoxContainer"] +margin_top = 38.0 +margin_right = 1010.0 +margin_bottom = 58.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Element" type="HBoxContainer" parent="."] +margin_right = 1010.0 +margin_bottom = 20.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Position" type="Label" parent="Element"] +margin_right = 503.0 +margin_bottom = 20.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +text = "0" +align = 1 + +[node name="SelectTag" type="MenuButton" parent="Element"] +margin_left = 507.0 +margin_right = 1010.0 +margin_bottom = 20.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +text = "Select Tag" diff --git a/addons/WAT/ui/metadata/editor.gd b/addons/WAT/ui/metadata/editor.gd new file mode 100644 index 0000000..d0bddbf --- /dev/null +++ b/addons/WAT/ui/metadata/editor.gd @@ -0,0 +1,36 @@ +tool +extends EditorInspectorPlugin + +func _enter_tree() -> void: + add_setting() + +func add_setting() -> void: + if ProjectSettings.has_setting("WAT/Tags"): + return + var tags: PoolStringArray = [] + var property_info: Dictionary = { + "name": "WAT/Tags", + "type": TYPE_STRING_ARRAY, + "hint_string": "Defines Tags to group Tests" + } + ProjectSettings.set("WAT/Tags", tags) + ProjectSettings.add_property_info(property_info) + +func can_handle(object): + if object is GDScript and object.get("TEST"): + add_property_editor_for_multiple_properties("Tags", [], TagList.new(object)) + return true + +class TagList extends EditorProperty: + + const Metadata: PackedScene = preload("res://addons/WAT/ui/metadata/Metadata.tscn") + var metadata: Control = Metadata.instance() + var test: Script + + func _init(_test: Script) -> void: + test = _test + metadata.test = _test + add_child(metadata) + set_bottom_editor(metadata) + + diff --git a/addons/WAT/ui/metadata/index.gd b/addons/WAT/ui/metadata/index.gd new file mode 100644 index 0000000..6fb2db2 --- /dev/null +++ b/addons/WAT/ui/metadata/index.gd @@ -0,0 +1,11 @@ +extends Resource +tool +# Dictionary's cannot take type hints. +# The keys are test scripts +# The values are an array of tags +#export(Dictionary) var index = {} + +# Seems we cannot cache scripts properly without hints +# so we'll have to use some indexing magic for this +export(Array, Script) var scripts: Array = [] +export(Array, Array, String) var tags: Array = [] diff --git a/addons/WAT/ui/metadata/metadata.gd b/addons/WAT/ui/metadata/metadata.gd new file mode 100644 index 0000000..707bbfc --- /dev/null +++ b/addons/WAT/ui/metadata/metadata.gd @@ -0,0 +1,87 @@ +tool +extends Control + +var test: Resource +const Tag: PackedScene = preload("Tag.tscn") +onready var TagSelector: Control = $Tags/Select.get_popup() +onready var TagList: Control = $TagList +var metadata + +func _initalize() -> void: + var testdir: String = ProjectSettings.get_setting("WAT/Test_Directory") + var dir: Directory = Directory.new() +# var config: String = "%s/.test" % testdir +# if not dir.dir_exists(config): +# dir.make_dir(config) + var savepath: String = "res://.test/metadata.tres" + if not dir.file_exists(savepath): + var res: Resource = load("res://addons/WAT/ui/metadata/index.gd").new() + ResourceSaver.save(savepath, res) + +func set_metadata() -> void: + _initalize() + var test_dir: String = ProjectSettings.get_setting("WAT/Test_Directory") + var savepath: String = "res://.test/metadata.tres" + metadata = load(savepath) + +func global_tags() -> Array: + # Add this to settings? + return ProjectSettings.get_setting("WAT/Tags") + +func tags() -> Array: + # May need to add error handling here + return metadata.tags[id()] + +func id() -> int: + # returns position in scripts array + return metadata.scripts.find(test) + +func _ready() -> void: + set_metadata() + $Data/ClearAll.connect("pressed", self, "_delete_all") + test = load(test.resource_path) # I think this gives us the binary path? + if not metadata.scripts.has(test): + metadata.scripts.append(test) + metadata.tags.append([]) + save() + TagSelector.connect("about_to_show", self, "_update_selectable_tags") + TagSelector.connect("id_pressed", self, "_add_tag") + +func _delete_all() -> void: + for child in TagList.get_children(): + delete(child) + +func populate() -> void: + for t in tags(): + var tag: Control = Tag.instance() + TagList.add_child(tag) + tag.Name.text = t + tag.Delete.connect("pressed", self, "delete", [tag]) + +func _update_selectable_tags() -> void: + TagSelector.clear() + for tag in global_tags(): + if not tag as String in tags(): + TagSelector.add_item(tag) + TagSelector.update() + +func _add_tag(id: int) -> void: + var tagtext: String = TagSelector.get_item_text(id) + var tag: Control = Tag.instance() + TagList.add_child(tag) + tag.Name.text = tagtext + tag.Delete.connect("pressed", self, "delete", [tag]) + metadata.tags[id()].append(tagtext as String) + save() + +func delete(tag: Control) -> void: + print("deleting") + TagList.remove_child(tag) + tag.queue_free() + metadata.tags[id()].erase(tag.Name.text as String) + save() + +func save() -> void: + var err = ResourceSaver.save(metadata.resource_path, metadata) + if err != OK: + push_warning(err as String) diff --git a/addons/WAT/ui/metadata/tag.gd b/addons/WAT/ui/metadata/tag.gd new file mode 100644 index 0000000..8496450 --- /dev/null +++ b/addons/WAT/ui/metadata/tag.gd @@ -0,0 +1,6 @@ +tool +extends HBoxContainer + + +onready var Name: Label = $Name +onready var Delete: Button = $Delete diff --git a/addons/WAT/ui/results/ResultTree.tscn b/addons/WAT/ui/results/ResultTree.tscn new file mode 100644 index 0000000..8d41131 --- /dev/null +++ b/addons/WAT/ui/results/ResultTree.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://addons/WAT/ui/results/result_tree.gd" type="Script" id=1] + +[node name="ResultsView" type="Tree"] +anchor_right = 1.0 +anchor_bottom = 1.0 +hide_root = true +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} diff --git a/addons/WAT/ui/results/ResultsForest.tscn b/addons/WAT/ui/results/ResultsForest.tscn new file mode 100644 index 0000000..3ac9b36 --- /dev/null +++ b/addons/WAT/ui/results/ResultsForest.tscn @@ -0,0 +1,16 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://addons/WAT/ui/results/result_forest.gd" type="Script" id=1] + +[node name="Results" type="TabContainer"] +margin_top = 26.0 +margin_right = 1010.0 +margin_bottom = 282.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +tab_align = 0 +drag_to_rearrange_enabled = true +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} diff --git a/addons/WAT/ui/results/result_forest.gd b/addons/WAT/ui/results/result_forest.gd new file mode 100644 index 0000000..944fbe1 --- /dev/null +++ b/addons/WAT/ui/results/result_forest.gd @@ -0,0 +1,65 @@ +tool +extends TabContainer + +const ResultTree: PackedScene = preload("res://addons/WAT/ui/results/ResultTree.tscn") +var _results: Resource +var _tabs: Dictionary + +func display(results: Array): + _add_result_tree(results) + +func _add_result_tree(results: Array) -> void: + _tabs = {} + var tab_count: int = 0 + var sorted = sort(results) + for path in sorted: + var result_tree = ResultTree.instance() + result_tree.connect("calculated", self, "_on_tree_results_calculated") + result_tree.name = path + add_child(result_tree) + _tabs[result_tree] = tab_count + result_tree.display(sorted[path]) + tab_count += 1 + +func sort(results: Array) -> Dictionary: + var sorted: Dictionary = {} + for result in results: + var end: int = result.path.find_last("/") + var path: String = result.path.substr(0, end).replace("res://", "").replace("tests/", "").replace("/", " ").capitalize() + if sorted.has(path): + sorted[path].append(result) + else: + sorted[path] = [result] + return sorted + +func _on_tree_results_calculated(tree: Tree, passed: int, total: int, success: bool) -> void: + tree.name += " (%s|%s)" % [passed, total] + set_tab_icon(_tabs[tree], WAT.Icon.SUCCESS if success else WAT.Icon.FAILED) + +func clear() -> void: + var children: Array = get_children() + while not children.empty(): + var child: Tree = children.pop_back() + child.free() + +enum { EXPAND_ALL, COLLAPSE_ALL, EXPAND_FAILURES } +func _on_view_pressed(option: int) -> void: + match option: + EXPAND_ALL: + expand_all() + COLLAPSE_ALL: + collapse_all() + EXPAND_FAILURES: + expand_failures() + +func expand_all(): + for results in get_children(): + results.expand_all() + +func collapse_all(): + for results in get_children(): + results.collapse_all() + +func expand_failures(): + for results in get_children(): + results.expand_failures() diff --git a/addons/WAT/ui/results/result_tree.gd b/addons/WAT/ui/results/result_tree.gd new file mode 100644 index 0000000..4221c8b --- /dev/null +++ b/addons/WAT/ui/results/result_tree.gd @@ -0,0 +1,95 @@ +tool +extends Tree + +const PASSED: Color = Color(0, 1, 0, 1) +const FAILED: Color = Color(1, 1, 1, 1) +signal calculated +var _cache: Array = [] +var _mega_cache: Array = [] + +func _ready(): + connect("button_pressed", self, "_on_button_pressed") + +func goto_function(path: String, function: String) -> void: + var p = EditorPlugin.new() + var script: Script = load(path) + p.get_editor_interface().edit_resource(script) + var source: PoolStringArray = script.source_code.split("\n") + for i in source.size(): + if function in source[i] and "describe" in source[i]: + p.get_editor_interface().get_script_editor().goto_line(i) + return + +func _on_button_pressed(item, column, id): + goto_function(item.get_meta("path"), item.get_meta("context")) + +func display(cases: Array) -> void: + var total = cases.size() + var passed = 0 + var root = create_item() + + for c in cases: + passed += c.success as int + var script = create_item(root) + script.set_text(0, "(%s/%s) %s" % [c.passed, c.total, c.context]) + script.set_custom_color(0, _color(c.success)) + script.set_icon(0, _icon(c.success)) + _cache.append(script) + _mega_cache.append(script) + + for m in c.methods: + var method = create_item(script) + method.set_text(0, "%s" % m.context) + method.set_custom_color(0, _color(m.success)) + method.set_icon(0, _icon(m.success)) + _cache.append(method) + _mega_cache.append(method) + method.add_button(0, load("res://addons/WAT/assets/function.svg")) + method.set_tooltip(0, "Click icon to show test method in editor") + method.set_meta("path", c.path) + method.set_meta("context", m.context) + + for a in m.assertions: + if a.context != "": + method.collapsed = false + var assertion = create_item(method) + assertion.set_text(0, a.context) + assertion.set_custom_color(0, _color(a.success)) + assertion.set_icon(0, _icon(a.success)) + assertion.collapsed = true + _mega_cache.append(assertion) + + var expected = create_item(assertion) + var actual = create_item(assertion) + expected.set_text(0, "EXPECTED: %s" % a.expected) + actual.set_text(0, "RESULTED: %s" % a.actual) + else: + method.collapsed = true + var expected = create_item(method) + var actual = create_item(method) + expected.set_text(0, "EXPECTED: %s" % a.expected) + actual.set_text(0, "RESULTED: %s" % a.actual) + + var success = total > 0 and total == passed + root.set_text(0, "%s/%s" % [passed, total]) + root.set_custom_color(0, _color(success)) + root.set_icon(0, _icon(success)) + emit_signal("calculated", self, passed, total, success) + +func _color(success: bool) -> Color: + return PASSED if success else FAILED + +func _icon(success: bool) -> Texture: + return WAT.Icon.SUCCESS if success else WAT.Icon.FAILED + +func expand_all() -> void: + for item in _cache: + item.collapsed = false + +func collapse_all() -> void: + for item in _cache: + item.collapsed = true + +func expand_failures() -> void: + for item in _mega_cache: + item.collapsed = true if item.get_icon(0) == WAT.Icon.SUCCESS else false