diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp index fe4ee99204..816d9d1082 100644 --- a/core/input/input_map.cpp +++ b/core/input/input_map.cpp @@ -746,3 +746,7 @@ InputMap::InputMap() { ERR_FAIL_COND_MSG(singleton, "Singleton in InputMap already exist."); singleton = this; } + +InputMap::~InputMap() { + singleton = nullptr; +} diff --git a/core/input/input_map.h b/core/input/input_map.h index a2d3952f94..c724fdb142 100644 --- a/core/input/input_map.h +++ b/core/input/input_map.h @@ -95,6 +95,7 @@ public: const OrderedHashMap>> &get_builtins(); InputMap(); + ~InputMap(); }; #endif // INPUT_MAP_H diff --git a/main/main.cpp b/main/main.cpp index ece194b0f1..fe6df43364 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -403,6 +403,7 @@ Error Main::test_setup() { GLOBAL_DEF("debug/settings/crash_handler/message", String("Please include this when reporting the bug on https://github.com/godotengine/godot/issues")); + GLOBAL_DEF_RST("rendering/occlusion_culling/bvh_build_quality", 2); translation_server = memnew(TranslationServer); diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index 4fbb5194e6..740548d559 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -248,7 +248,6 @@ private: void _text_changed(); protected: - void gui_input(const Ref &p_gui_input) override; void _notification(int p_what); static void _bind_methods(); @@ -265,6 +264,7 @@ protected: public: /* General overrides */ + virtual void gui_input(const Ref &p_gui_input) override; virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override; /* Indent management */ diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index e996bba983..525464c302 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -545,7 +545,6 @@ private: protected: void _notification(int p_what); - virtual void gui_input(const Ref &p_gui_input) override; static void _bind_methods(); @@ -594,6 +593,7 @@ protected: public: /* General overrides. */ + virtual void gui_input(const Ref &p_gui_input) override; virtual Size2 get_minimum_size() const override; virtual bool is_text_field() const override; virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override; diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index acad31092e..fb86d37280 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -823,7 +823,9 @@ Rect2 Viewport::get_visible_rect() const { } void Viewport::_update_listener_2d() { - AudioServer::get_singleton()->notify_listener_changed(); + if (AudioServer::get_singleton()) { + AudioServer::get_singleton()->notify_listener_changed(); + } } void Viewport::set_as_audio_listener_2d(bool p_enable) { @@ -3063,7 +3065,9 @@ bool Viewport::is_audio_listener_3d() const { } void Viewport::_update_listener_3d() { - AudioServer::get_singleton()->notify_listener_changed(); + if (AudioServer::get_singleton()) { + AudioServer::get_singleton()->notify_listener_changed(); + } } void Viewport::_listener_transform_3d_changed_notify() { diff --git a/servers/display_server.cpp b/servers/display_server.cpp index 3d44484033..cdf892094d 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -605,4 +605,5 @@ DisplayServer::DisplayServer() { } DisplayServer::~DisplayServer() { + singleton = nullptr; } diff --git a/servers/rendering/rasterizer_dummy.h b/servers/rendering/rasterizer_dummy.h index f58d124140..35bb7722e7 100644 --- a/servers/rendering/rasterizer_dummy.h +++ b/servers/rendering/rasterizer_dummy.h @@ -197,7 +197,7 @@ public: TypedArray bake_render_uv2(RID p_base, const Vector &p_material_overrides, const Size2i &p_image_size) override { return TypedArray(); } - bool free(RID p_rid) override { return true; } + bool free(RID p_rid) override { return false; } void update() override {} void sdfgi_set_debug_probe_select(const Vector3 &p_position, const Vector3 &p_dir) override {} @@ -664,8 +664,9 @@ public: DummyTexture *texture = texture_owner.getornull(p_rid); texture_owner.free(p_rid); memdelete(texture); + return true; } - return true; + return false; } virtual void update_memory_info() override {} diff --git a/tests/test_code_edit.h b/tests/test_code_edit.h new file mode 100644 index 0000000000..9579d8ebef --- /dev/null +++ b/tests/test_code_edit.h @@ -0,0 +1,813 @@ +/*************************************************************************/ +/* test_code_edit.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef TEST_CODE_EDIT_H +#define TEST_CODE_EDIT_H + +#include "core/input/input_map.h" +#include "core/object/message_queue.h" +#include "core/os/keyboard.h" +#include "core/string/string_builder.h" +#include "scene/gui/code_edit.h" +#include "scene/resources/default_theme/default_theme.h" + +#include "tests/test_macros.h" + +namespace TestCodeEdit { + +TEST_CASE("[SceneTree][CodeEdit] line gutters") { + CodeEdit *code_edit = memnew(CodeEdit); + SceneTree::get_singleton()->get_root()->add_child(code_edit); + + SUBCASE("[CodeEdit] breakpoints") { + SIGNAL_WATCH(code_edit, "breakpoint_toggled"); + + SUBCASE("[CodeEdit] draw breakpoints gutter") { + code_edit->set_draw_breakpoints_gutter(false); + CHECK_FALSE(code_edit->is_drawing_breakpoints_gutter()); + + code_edit->set_draw_breakpoints_gutter(true); + CHECK(code_edit->is_drawing_breakpoints_gutter()); + } + + SUBCASE("[CodeEdit] set line as breakpoint") { + /* Out of bounds. */ + ERR_PRINT_OFF; + + code_edit->set_line_as_breakpoint(-1, true); + CHECK_FALSE(code_edit->is_line_breakpointed(-1)); + SIGNAL_CHECK_FALSE("breakpoint_toggled"); + + code_edit->set_line_as_breakpoint(1, true); + CHECK_FALSE(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK_FALSE("breakpoint_toggled"); + + ERR_PRINT_ON; + + Array arg1; + arg1.push_back(0); + Array args; + args.push_back(arg1); + + code_edit->set_line_as_breakpoint(0, true); + CHECK(code_edit->is_line_breakpointed(0)); + CHECK(code_edit->get_breakpointed_lines()[0] == Variant(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + + code_edit->set_line_as_breakpoint(0, false); + CHECK_FALSE(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + } + + SUBCASE("[CodeEdit] clear breakpointed lines") { + code_edit->clear_breakpointed_lines(); + SIGNAL_CHECK_FALSE("breakpoint_toggled"); + + Array arg1; + arg1.push_back(0); + Array args; + args.push_back(arg1); + + code_edit->set_line_as_breakpoint(0, true); + CHECK(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + + code_edit->clear_breakpointed_lines(); + CHECK_FALSE(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + } + + SUBCASE("[CodeEdit] breakpoints and set text") { + Array arg1; + arg1.push_back(0); + Array args; + args.push_back(arg1); + + code_edit->set_text("test\nline"); + code_edit->set_line_as_breakpoint(0, true); + CHECK(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + + /* breakpoint on lines that still exist are kept. */ + code_edit->set_text(""); + MessageQueue::get_singleton()->flush(); + CHECK(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK_FALSE("breakpoint_toggled"); + + /* breakpoint on lines that are removed should also be removed. */ + code_edit->clear_breakpointed_lines(); + SIGNAL_DISCARD("breakpoint_toggled") + + ((Array)args[0])[0] = 1; + code_edit->set_text("test\nline"); + code_edit->set_line_as_breakpoint(1, true); + CHECK(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK("breakpoint_toggled", args); + + code_edit->set_text(""); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_breakpointed(0)); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_breakpointed(1)); + ERR_PRINT_ON; + SIGNAL_CHECK("breakpoint_toggled", args); + } + + SUBCASE("[CodeEdit] breakpoints and clear") { + Array arg1; + arg1.push_back(0); + Array args; + args.push_back(arg1); + + code_edit->set_text("test\nline"); + code_edit->set_line_as_breakpoint(0, true); + CHECK(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + + /* breakpoint on lines that still exist are removed. */ + code_edit->clear(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + + /* breakpoint on lines that are removed should also be removed. */ + code_edit->clear_breakpointed_lines(); + SIGNAL_DISCARD("breakpoint_toggled") + + ((Array)args[0])[0] = 1; + code_edit->set_text("test\nline"); + code_edit->set_line_as_breakpoint(1, true); + CHECK(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK("breakpoint_toggled", args); + + code_edit->clear(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_breakpointed(0)); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_breakpointed(1)); + ERR_PRINT_ON; + SIGNAL_CHECK("breakpoint_toggled", args); + } + + SUBCASE("[CodeEdit] breakpoints and new lines no text") { + Array arg1; + arg1.push_back(0); + Array args; + args.push_back(arg1); + + /* No text moves breakpoint. */ + code_edit->set_line_as_breakpoint(0, true); + CHECK(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + + /* Normal. */ + ((Array)args[0])[0] = 0; + Array arg2; + arg2.push_back(1); + args.push_back(arg2); + + SEND_GUI_ACTION(code_edit, "ui_text_newline"); + CHECK(code_edit->get_line_count() == 2); + CHECK_FALSE(code_edit->is_line_breakpointed(0)); + CHECK(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK("breakpoint_toggled", args); + + /* Non-Breaking. */ + ((Array)args[0])[0] = 1; + ((Array)args[1])[0] = 2; + SEND_GUI_ACTION(code_edit, "ui_text_newline_blank"); + CHECK(code_edit->get_line_count() == 3); + CHECK_FALSE(code_edit->is_line_breakpointed(1)); + CHECK(code_edit->is_line_breakpointed(2)); + SIGNAL_CHECK("breakpoint_toggled", args); + + /* Above. */ + ((Array)args[0])[0] = 2; + ((Array)args[1])[0] = 3; + SEND_GUI_ACTION(code_edit, "ui_text_newline_above"); + CHECK(code_edit->get_line_count() == 4); + CHECK_FALSE(code_edit->is_line_breakpointed(2)); + CHECK(code_edit->is_line_breakpointed(3)); + SIGNAL_CHECK("breakpoint_toggled", args); + } + + SUBCASE("[CodeEdit] breakpoints and new lines with text") { + Array arg1; + arg1.push_back(0); + Array args; + args.push_back(arg1); + + /* Having text does not move breakpoint. */ + code_edit->insert_text_at_caret("text"); + code_edit->set_line_as_breakpoint(0, true); + CHECK(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + + /* Normal. */ + SEND_GUI_ACTION(code_edit, "ui_text_newline"); + CHECK(code_edit->get_line_count() == 2); + CHECK(code_edit->is_line_breakpointed(0)); + CHECK_FALSE(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK_FALSE("breakpoint_toggled"); + + /* Non-Breaking. */ + code_edit->set_caret_line(0); + SEND_GUI_ACTION(code_edit, "ui_text_newline_blank"); + CHECK(code_edit->get_line_count() == 3); + CHECK(code_edit->is_line_breakpointed(0)); + CHECK_FALSE(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK_FALSE("breakpoint_toggled"); + + /* Above does move. */ + ((Array)args[0])[0] = 0; + Array arg2; + arg2.push_back(1); + args.push_back(arg2); + + code_edit->set_caret_line(0); + SEND_GUI_ACTION(code_edit, "ui_text_newline_above"); + CHECK(code_edit->get_line_count() == 4); + CHECK_FALSE(code_edit->is_line_breakpointed(0)); + CHECK(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK("breakpoint_toggled", args); + } + + SUBCASE("[CodeEdit] breakpoints and backspace") { + Array arg1; + arg1.push_back(1); + Array args; + args.push_back(arg1); + + code_edit->set_text("\n\n"); + code_edit->set_line_as_breakpoint(1, true); + CHECK(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK("breakpoint_toggled", args); + + code_edit->set_caret_line(2); + + /* backspace onto line does not remove breakpoint */ + SEND_GUI_ACTION(code_edit, "ui_text_backspace"); + CHECK(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK_FALSE("breakpoint_toggled"); + + /* backspace on breakpointed line removes it */ + SEND_GUI_ACTION(code_edit, "ui_text_backspace"); + CHECK_FALSE(code_edit->is_line_breakpointed(0)); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_breakpointed(1)); + ERR_PRINT_ON; + SIGNAL_CHECK("breakpoint_toggled", args); + } + + SUBCASE("[CodeEdit] breakpoints and delete") { + Array arg1; + arg1.push_back(1); + Array args; + args.push_back(arg1); + + code_edit->set_text("\n\n"); + code_edit->set_line_as_breakpoint(1, true); + CHECK(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK("breakpoint_toggled", args); + code_edit->set_caret_line(1); + + /* Delete onto breakpointed lines does not remove it. */ + SEND_GUI_ACTION(code_edit, "ui_text_delete"); + CHECK(code_edit->get_line_count() == 2); + CHECK(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK_FALSE("breakpoint_toggled"); + + /* Delete moving breakpointed line up removes it. */ + code_edit->set_caret_line(0); + SEND_GUI_ACTION(code_edit, "ui_text_delete"); + CHECK(code_edit->get_line_count() == 1); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_breakpointed(1)); + ERR_PRINT_ON; + SIGNAL_CHECK("breakpoint_toggled", args); + } + + SUBCASE("[CodeEdit] breakpoints and delete selection") { + Array arg1; + arg1.push_back(1); + Array args; + args.push_back(arg1); + + code_edit->set_text("\n\n"); + code_edit->set_line_as_breakpoint(1, true); + CHECK(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK("breakpoint_toggled", args); + + code_edit->select(0, 0, 2, 0); + code_edit->delete_selection(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + } + + SUBCASE("[CodeEdit] breakpoints and undo") { + Array arg1; + arg1.push_back(1); + Array args; + args.push_back(arg1); + + code_edit->set_text("\n\n"); + code_edit->set_line_as_breakpoint(1, true); + CHECK(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK("breakpoint_toggled", args); + + code_edit->select(0, 0, 2, 0); + code_edit->delete_selection(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + + /* Undo does not restore breakpoint. */ + code_edit->undo(); + CHECK_FALSE(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK_FALSE("breakpoint_toggled"); + } + + SIGNAL_UNWATCH(code_edit, "breakpoint_toggled"); + } + + SUBCASE("[CodeEdit] bookmarks") { + SUBCASE("[CodeEdit] draw bookmarks gutter") { + code_edit->set_draw_bookmarks_gutter(false); + CHECK_FALSE(code_edit->is_drawing_bookmarks_gutter()); + + code_edit->set_draw_bookmarks_gutter(true); + CHECK(code_edit->is_drawing_bookmarks_gutter()); + } + + SUBCASE("[CodeEdit] set line as bookmarks") { + /* Out of bounds. */ + ERR_PRINT_OFF; + + code_edit->set_line_as_bookmarked(-1, true); + CHECK_FALSE(code_edit->is_line_bookmarked(-1)); + + code_edit->set_line_as_bookmarked(1, true); + CHECK_FALSE(code_edit->is_line_bookmarked(1)); + + ERR_PRINT_ON; + + code_edit->set_line_as_bookmarked(0, true); + CHECK(code_edit->get_bookmarked_lines()[0] == Variant(0)); + CHECK(code_edit->is_line_bookmarked(0)); + + code_edit->set_line_as_bookmarked(0, false); + CHECK_FALSE(code_edit->is_line_bookmarked(0)); + } + + SUBCASE("[CodeEdit] clear bookmarked lines") { + code_edit->clear_bookmarked_lines(); + + code_edit->set_line_as_bookmarked(0, true); + CHECK(code_edit->is_line_bookmarked(0)); + + code_edit->clear_bookmarked_lines(); + CHECK_FALSE(code_edit->is_line_bookmarked(0)); + } + + SUBCASE("[CodeEdit] bookmarks and set text") { + code_edit->set_text("test\nline"); + code_edit->set_line_as_bookmarked(0, true); + CHECK(code_edit->is_line_bookmarked(0)); + + /* bookmarks on lines that still exist are kept. */ + code_edit->set_text(""); + MessageQueue::get_singleton()->flush(); + CHECK(code_edit->is_line_bookmarked(0)); + + /* bookmarks on lines that are removed should also be removed. */ + code_edit->clear_bookmarked_lines(); + + code_edit->set_text("test\nline"); + code_edit->set_line_as_bookmarked(1, true); + CHECK(code_edit->is_line_bookmarked(1)); + + code_edit->set_text(""); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_bookmarked(0)); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_bookmarked(1)); + ERR_PRINT_ON; + } + + SUBCASE("[CodeEdit] bookmarks and clear") { + code_edit->set_text("test\nline"); + code_edit->set_line_as_bookmarked(0, true); + CHECK(code_edit->is_line_bookmarked(0)); + + /* bookmarks on lines that still exist are removed. */ + code_edit->clear(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_bookmarked(0)); + + /* bookmarks on lines that are removed should also be removed. */ + code_edit->clear_bookmarked_lines(); + + code_edit->set_text("test\nline"); + code_edit->set_line_as_bookmarked(1, true); + CHECK(code_edit->is_line_bookmarked(1)); + + code_edit->clear(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_bookmarked(0)); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_bookmarked(1)); + ERR_PRINT_ON; + } + + SUBCASE("[CodeEdit] bookmarks and new lines no text") { + /* No text moves bookmarks. */ + code_edit->set_line_as_bookmarked(0, true); + CHECK(code_edit->is_line_bookmarked(0)); + + /* Normal. */ + SEND_GUI_ACTION(code_edit, "ui_text_newline"); + CHECK(code_edit->get_line_count() == 2); + CHECK_FALSE(code_edit->is_line_bookmarked(0)); + CHECK(code_edit->is_line_bookmarked(1)); + + /* Non-Breaking. */ + SEND_GUI_ACTION(code_edit, "ui_text_newline_blank"); + CHECK(code_edit->get_line_count() == 3); + CHECK_FALSE(code_edit->is_line_bookmarked(1)); + CHECK(code_edit->is_line_bookmarked(2)); + + /* Above. */ + SEND_GUI_ACTION(code_edit, "ui_text_newline_above"); + CHECK(code_edit->get_line_count() == 4); + CHECK_FALSE(code_edit->is_line_bookmarked(2)); + CHECK(code_edit->is_line_bookmarked(3)); + } + + SUBCASE("[CodeEdit] bookmarks and new lines with text") { + /* Having text does not move bookmark. */ + code_edit->insert_text_at_caret("text"); + code_edit->set_line_as_bookmarked(0, true); + CHECK(code_edit->is_line_bookmarked(0)); + + /* Normal. */ + SEND_GUI_ACTION(code_edit, "ui_text_newline"); + CHECK(code_edit->get_line_count() == 2); + CHECK(code_edit->is_line_bookmarked(0)); + CHECK_FALSE(code_edit->is_line_bookmarked(1)); + + /* Non-Breaking. */ + code_edit->set_caret_line(0); + SEND_GUI_ACTION(code_edit, "ui_text_newline_blank"); + CHECK(code_edit->get_line_count() == 3); + CHECK(code_edit->is_line_bookmarked(0)); + CHECK_FALSE(code_edit->is_line_bookmarked(1)); + + /* Above does move. */ + code_edit->set_caret_line(0); + SEND_GUI_ACTION(code_edit, "ui_text_newline_above"); + CHECK(code_edit->get_line_count() == 4); + CHECK_FALSE(code_edit->is_line_bookmarked(0)); + CHECK(code_edit->is_line_bookmarked(1)); + } + + SUBCASE("[CodeEdit] bookmarks and backspace") { + code_edit->set_text("\n\n"); + code_edit->set_line_as_bookmarked(1, true); + CHECK(code_edit->is_line_bookmarked(1)); + + code_edit->set_caret_line(2); + + /* backspace onto line does not remove bookmark */ + SEND_GUI_ACTION(code_edit, "ui_text_backspace"); + CHECK(code_edit->is_line_bookmarked(1)); + + /* backspace on bookmarked line removes it */ + SEND_GUI_ACTION(code_edit, "ui_text_backspace"); + CHECK_FALSE(code_edit->is_line_bookmarked(0)); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_bookmarked(1)); + ERR_PRINT_ON; + } + + SUBCASE("[CodeEdit] bookmarks and delete") { + code_edit->set_text("\n\n"); + code_edit->set_line_as_bookmarked(1, true); + CHECK(code_edit->is_line_bookmarked(1)); + code_edit->set_caret_line(1); + + /* Delete onto bookmarked lines does not remove it. */ + SEND_GUI_ACTION(code_edit, "ui_text_delete"); + CHECK(code_edit->get_line_count() == 2); + CHECK(code_edit->is_line_bookmarked(1)); + + /* Delete moving bookmarked line up removes it. */ + code_edit->set_caret_line(0); + SEND_GUI_ACTION(code_edit, "ui_text_delete"); + CHECK(code_edit->get_line_count() == 1); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_bookmarked(1)); + ERR_PRINT_ON; + } + + SUBCASE("[CodeEdit] bookmarks and delete selection") { + code_edit->set_text("\n\n"); + code_edit->set_line_as_bookmarked(1, true); + CHECK(code_edit->is_line_bookmarked(1)); + + code_edit->select(0, 0, 2, 0); + code_edit->delete_selection(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_bookmarked(0)); + } + + SUBCASE("[CodeEdit] bookmarks and undo") { + code_edit->set_text("\n\n"); + code_edit->set_line_as_bookmarked(1, true); + CHECK(code_edit->is_line_bookmarked(1)); + + code_edit->select(0, 0, 2, 0); + code_edit->delete_selection(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_bookmarked(0)); + + /* Undo does not restore bookmark. */ + code_edit->undo(); + CHECK_FALSE(code_edit->is_line_bookmarked(1)); + } + } + + SUBCASE("[CodeEdit] executing lines") { + SUBCASE("[CodeEdit] draw executing lines gutter") { + code_edit->set_draw_executing_lines_gutter(false); + CHECK_FALSE(code_edit->is_drawing_executing_lines_gutter()); + + code_edit->set_draw_executing_lines_gutter(true); + CHECK(code_edit->is_drawing_executing_lines_gutter()); + } + + SUBCASE("[CodeEdit] set line as executing lines") { + /* Out of bounds. */ + ERR_PRINT_OFF; + + code_edit->set_line_as_executing(-1, true); + CHECK_FALSE(code_edit->is_line_executing(-1)); + + code_edit->set_line_as_executing(1, true); + CHECK_FALSE(code_edit->is_line_executing(1)); + + ERR_PRINT_ON; + + code_edit->set_line_as_executing(0, true); + CHECK(code_edit->get_executing_lines()[0] == Variant(0)); + CHECK(code_edit->is_line_executing(0)); + + code_edit->set_line_as_executing(0, false); + CHECK_FALSE(code_edit->is_line_executing(0)); + } + + SUBCASE("[CodeEdit] clear executing lines lines") { + code_edit->clear_executing_lines(); + + code_edit->set_line_as_executing(0, true); + CHECK(code_edit->is_line_executing(0)); + + code_edit->clear_executing_lines(); + CHECK_FALSE(code_edit->is_line_executing(0)); + } + + SUBCASE("[CodeEdit] executing lines and set text") { + code_edit->set_text("test\nline"); + code_edit->set_line_as_executing(0, true); + CHECK(code_edit->is_line_executing(0)); + + /* executing on lines that still exist are kept. */ + code_edit->set_text(""); + MessageQueue::get_singleton()->flush(); + CHECK(code_edit->is_line_executing(0)); + + /* executing on lines that are removed should also be removed. */ + code_edit->clear_executing_lines(); + + code_edit->set_text("test\nline"); + code_edit->set_line_as_executing(1, true); + CHECK(code_edit->is_line_executing(1)); + + code_edit->set_text(""); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_executing(0)); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_executing(1)); + ERR_PRINT_ON; + } + + SUBCASE("[CodeEdit] executing lines and clear") { + code_edit->set_text("test\nline"); + code_edit->set_line_as_executing(0, true); + CHECK(code_edit->is_line_executing(0)); + + /* executing on lines that still exist are removed. */ + code_edit->clear(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_executing(0)); + + /* executing on lines that are removed should also be removed. */ + code_edit->clear_executing_lines(); + + code_edit->set_text("test\nline"); + code_edit->set_line_as_executing(1, true); + CHECK(code_edit->is_line_executing(1)); + + code_edit->clear(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_executing(0)); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_executing(1)); + ERR_PRINT_ON; + } + + SUBCASE("[CodeEdit] executing lines and new lines no text") { + /* No text moves executing lines. */ + code_edit->set_line_as_executing(0, true); + CHECK(code_edit->is_line_executing(0)); + + /* Normal. */ + SEND_GUI_ACTION(code_edit, "ui_text_newline"); + CHECK(code_edit->get_line_count() == 2); + CHECK_FALSE(code_edit->is_line_executing(0)); + CHECK(code_edit->is_line_executing(1)); + + /* Non-Breaking. */ + SEND_GUI_ACTION(code_edit, "ui_text_newline_blank"); + CHECK(code_edit->get_line_count() == 3); + CHECK_FALSE(code_edit->is_line_executing(1)); + CHECK(code_edit->is_line_executing(2)); + + /* Above. */ + SEND_GUI_ACTION(code_edit, "ui_text_newline_above"); + CHECK(code_edit->get_line_count() == 4); + CHECK_FALSE(code_edit->is_line_executing(2)); + CHECK(code_edit->is_line_executing(3)); + } + + SUBCASE("[CodeEdit] executing lines and new lines with text") { + /* Having text does not move executing lines. */ + code_edit->insert_text_at_caret("text"); + code_edit->set_line_as_executing(0, true); + CHECK(code_edit->is_line_executing(0)); + + /* Normal. */ + SEND_GUI_ACTION(code_edit, "ui_text_newline"); + CHECK(code_edit->get_line_count() == 2); + CHECK(code_edit->is_line_executing(0)); + CHECK_FALSE(code_edit->is_line_executing(1)); + + /* Non-Breaking. */ + code_edit->set_caret_line(0); + SEND_GUI_ACTION(code_edit, "ui_text_newline_blank"); + CHECK(code_edit->get_line_count() == 3); + CHECK(code_edit->is_line_executing(0)); + CHECK_FALSE(code_edit->is_line_executing(1)); + + /* Above does move. */ + code_edit->set_caret_line(0); + SEND_GUI_ACTION(code_edit, "ui_text_newline_above"); + CHECK(code_edit->get_line_count() == 4); + CHECK_FALSE(code_edit->is_line_executing(0)); + CHECK(code_edit->is_line_executing(1)); + } + + SUBCASE("[CodeEdit] executing lines and backspace") { + code_edit->set_text("\n\n"); + code_edit->set_line_as_executing(1, true); + CHECK(code_edit->is_line_executing(1)); + + code_edit->set_caret_line(2); + + /* backspace onto line does not remove executing lines. */ + SEND_GUI_ACTION(code_edit, "ui_text_backspace"); + CHECK(code_edit->is_line_executing(1)); + + /* backspace on executing line removes it */ + SEND_GUI_ACTION(code_edit, "ui_text_backspace"); + CHECK_FALSE(code_edit->is_line_executing(0)); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_executing(1)); + ERR_PRINT_ON; + } + + SUBCASE("[CodeEdit] executing lines and delete") { + code_edit->set_text("\n\n"); + code_edit->set_line_as_executing(1, true); + CHECK(code_edit->is_line_executing(1)); + code_edit->set_caret_line(1); + + /* Delete onto executing lines does not remove it. */ + SEND_GUI_ACTION(code_edit, "ui_text_delete"); + CHECK(code_edit->get_line_count() == 2); + CHECK(code_edit->is_line_executing(1)); + + /* Delete moving executing line up removes it. */ + code_edit->set_caret_line(0); + SEND_GUI_ACTION(code_edit, "ui_text_delete"); + CHECK(code_edit->get_line_count() == 1); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_executing(1)); + ERR_PRINT_ON; + } + + SUBCASE("[CodeEdit] executing lines and delete selection") { + code_edit->set_text("\n\n"); + code_edit->set_line_as_executing(1, true); + CHECK(code_edit->is_line_executing(1)); + + code_edit->select(0, 0, 2, 0); + code_edit->delete_selection(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_executing(0)); + } + + SUBCASE("[CodeEdit] executing lines and undo") { + code_edit->set_text("\n\n"); + code_edit->set_line_as_executing(1, true); + CHECK(code_edit->is_line_executing(1)); + + code_edit->select(0, 0, 2, 0); + code_edit->delete_selection(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_executing(0)); + + /* Undo does not restore executing lines. */ + code_edit->undo(); + CHECK_FALSE(code_edit->is_line_executing(1)); + } + } + + SUBCASE("[CodeEdit] line numbers") { + SUBCASE("[CodeEdit] draw line numbers gutter and padding") { + code_edit->set_draw_line_numbers(false); + CHECK_FALSE(code_edit->is_draw_line_numbers_enabled()); + + code_edit->set_draw_line_numbers(true); + CHECK(code_edit->is_draw_line_numbers_enabled()); + + code_edit->set_line_numbers_zero_padded(false); + CHECK_FALSE(code_edit->is_line_numbers_zero_padded()); + + code_edit->set_line_numbers_zero_padded(true); + CHECK(code_edit->is_line_numbers_zero_padded()); + + code_edit->set_line_numbers_zero_padded(false); + CHECK_FALSE(code_edit->is_line_numbers_zero_padded()); + + code_edit->set_draw_line_numbers(false); + CHECK_FALSE(code_edit->is_draw_line_numbers_enabled()); + + code_edit->set_line_numbers_zero_padded(true); + CHECK(code_edit->is_line_numbers_zero_padded()); + } + } + + SUBCASE("[CodeEdit] line folding") { + SUBCASE("[CodeEdit] draw line folding gutter") { + code_edit->set_draw_fold_gutter(false); + CHECK_FALSE(code_edit->is_drawing_fold_gutter()); + + code_edit->set_draw_fold_gutter(true); + CHECK(code_edit->is_drawing_fold_gutter()); + } + } + + memdelete(code_edit); +} + +} // namespace TestCodeEdit + +#endif // TEST_CODE_EDIT_H diff --git a/tests/test_macros.h b/tests/test_macros.h index a1f1932db4..bf1001fa67 100644 --- a/tests/test_macros.h +++ b/tests/test_macros.h @@ -31,6 +31,8 @@ #ifndef TEST_MACROS_H #define TEST_MACROS_H +#include "core/object/callable_method_pointer.h" +#include "core/object/class_db.h" #include "core/templates/map.h" #include "core/variant/variant.h" @@ -129,4 +131,186 @@ int register_test_command(String p_command, TestFunc p_function); register_test_command(m_command, m_function); \ DOCTEST_GLOBAL_NO_WARNINGS_END() +// Utility macro to send an action event to a given object +// Requires Message Queue and InputMap to be setup. + +#define SEND_GUI_ACTION(m_object, m_action) \ + { \ + const List> *events = InputMap::get_singleton()->action_get_events(m_action); \ + const List>::Element *first_event = events->front(); \ + Ref event = first_event->get(); \ + event->set_pressed(true); \ + m_object->gui_input(event); \ + MessageQueue::get_singleton()->flush(); \ + } + +// Utility class / macros for testing signals +// +// Use SIGNAL_WATCH(*object, "signal_name") to start watching +// Makes sure to call SIGNAL_UNWATCH(*object, "signal_name") to stop watching in cleanup, this is not done automatically. +// +// The SignalWatcher will capture all signals and their args sent between checks. +// +// Use SIGNAL_CHECK("signal_name"), Vector>), to check the arguments of all fired signals. +// The outer vector is each fired signal, the inner vector the list of arguments for that signal. Order does matter. +// +// Use SIGNAL_CHECK_FALSE("signal_name") to check if a signal was not fired. +// +// Use SIGNAL_DISCARD("signal_name") to discard records all of the given signal, use only in placed you don't need to check. +// +// All signals are automaticaly discared between test/sub test cases. + +class SignalWatcher : public Object { +private: + inline static SignalWatcher *singleton; + + /* Equal to: Map>> */ + Map _signals; + void _add_signal_entry(const Array &p_args, const String &p_name) { + if (!_signals.has(p_name)) { + _signals[p_name] = Array(); + } + _signals[p_name].push_back(p_args); + } + + void _signal_callback_zero(const String &p_name) { + Array args; + _add_signal_entry(args, p_name); + } + + void _signal_callback_one(Variant p_arg1, const String &p_name) { + Array args; + args.push_back(p_arg1); + _add_signal_entry(args, p_name); + } + + void _signal_callback_two(Variant p_arg1, Variant p_arg2, const String &p_name) { + Array args; + args.push_back(p_arg1); + args.push_back(p_arg2); + _add_signal_entry(args, p_name); + } + + void _signal_callback_three(Variant p_arg1, Variant p_arg2, Variant p_arg3, const String &p_name) { + Array args; + args.push_back(p_arg1); + args.push_back(p_arg2); + args.push_back(p_arg3); + _add_signal_entry(args, p_name); + } + +public: + static SignalWatcher *get_singleton() { return singleton; } + + void watch_signal(Object *p_object, const String &p_signal) { + Vector args; + args.push_back(p_signal); + MethodInfo method_info; + ClassDB::get_signal(p_object->get_class(), p_signal, &method_info); + switch (method_info.arguments.size()) { + case 0: { + p_object->connect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_zero), args); + } break; + case 1: { + p_object->connect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_one), args); + } break; + case 2: { + p_object->connect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_two), args); + } break; + case 3: { + p_object->connect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_three), args); + } break; + default: { + MESSAGE("Signal ", p_signal, " arg count not supported."); + } break; + } + } + + void unwatch_signal(Object *p_object, const String &p_signal) { + MethodInfo method_info; + ClassDB::get_signal(p_object->get_class(), p_signal, &method_info); + switch (method_info.arguments.size()) { + case 0: { + p_object->disconnect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_zero)); + } break; + case 1: { + p_object->disconnect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_one)); + } break; + case 2: { + p_object->disconnect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_two)); + } break; + case 3: { + p_object->disconnect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_three)); + } break; + default: { + MESSAGE("Signal ", p_signal, " arg count not supported."); + } break; + } + } + + bool check(const String &p_name, const Array &p_args) { + if (!_signals.has(p_name)) { + MESSAGE("Signal ", p_name, " not emitted"); + return false; + } + + if (p_args.size() != _signals[p_name].size()) { + MESSAGE("Signal has " << _signals[p_name] << " expected " << p_args); + discard_signal(p_name); + return false; + } + + bool match = true; + for (int i = 0; i < p_args.size(); i++) { + if (((Array)p_args[i]).size() != ((Array)_signals[p_name][i]).size()) { + MESSAGE("Signal has " << _signals[p_name][i] << " expected " << p_args[i]); + match = false; + continue; + } + + for (int j = 0; j < ((Array)p_args[i]).size(); j++) { + if (((Array)p_args[i])[j] != ((Array)_signals[p_name][i])[j]) { + MESSAGE("Signal has " << _signals[p_name][i] << " expected " << p_args[i]); + match = false; + break; + } + } + } + + discard_signal(p_name); + return match; + } + + bool check_false(const String &p_name) { + bool has = _signals.has(p_name); + discard_signal(p_name); + return !has; + } + + void discard_signal(const String &p_name) { + if (_signals.has(p_name)) { + _signals.erase(p_name); + } + } + + void _clear_signals() { + _signals.clear(); + } + + SignalWatcher() { + singleton = this; + } + + ~SignalWatcher() { + singleton = nullptr; + } +}; + +#define SIGNAL_WATCH(m_object, m_signal) SignalWatcher::get_singleton()->watch_signal(m_object, m_signal); +#define SIGNAL_UNWATCH(m_object, m_signal) SignalWatcher::get_singleton()->unwatch_signal(m_object, m_signal); + +#define SIGNAL_CHECK(m_signal, m_args) CHECK(SignalWatcher::get_singleton()->check(m_signal, m_args)); +#define SIGNAL_CHECK_FALSE(m_signal) CHECK(SignalWatcher::get_singleton()->check_false(m_signal)); +#define SIGNAL_DISCARD(m_signal) SignalWatcher::get_singleton()->discard_signal(m_signal); + #endif // TEST_MACROS_H diff --git a/tests/test_main.cpp b/tests/test_main.cpp index d0466d1e2d..e4aa4c38ff 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -37,6 +37,7 @@ #include "test_astar.h" #include "test_basis.h" #include "test_class_db.h" +#include "test_code_edit.h" #include "test_color.h" #include "test_command_queue.h" #include "test_config_file.h" @@ -146,3 +147,153 @@ int test_main(int argc, char *argv[]) { return test_context.run(); } + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "servers/navigation_server_2d.h" +#include "servers/navigation_server_3d.h" +#include "servers/rendering/rendering_server_default.h" + +struct GodotTestCaseListener : public doctest::IReporter { + GodotTestCaseListener(const doctest::ContextOptions &p_in) {} + + SignalWatcher *signal_watcher = nullptr; + + PhysicsServer3D *physics_3d_server = nullptr; + PhysicsServer2D *physics_2d_server = nullptr; + NavigationServer3D *navigation_3d_server = nullptr; + NavigationServer2D *navigation_2d_server = nullptr; + + void test_case_start(const doctest::TestCaseData &p_in) override { + SignalWatcher::get_singleton()->_clear_signals(); + + String name = String(p_in.m_name); + + if (name.find("[SceneTree]") != -1) { + GLOBAL_DEF("memory/limits/multithreaded_server/rid_pool_prealloc", 60); + memnew(MessageQueue); + + GLOBAL_DEF("internationalization/rendering/force_right_to_left_layout_direction", false); + memnew(TextServerManager); + Error err = OK; + TextServerManager::initialize(0, err); + + OS::get_singleton()->set_has_server_feature_callback(nullptr); + for (int i = 0; i < DisplayServer::get_create_function_count(); i++) { + if (String("headless") == DisplayServer::get_create_function_name(i)) { + DisplayServer::create(i, "", DisplayServer::WindowMode::WINDOW_MODE_MINIMIZED, DisplayServer::VSyncMode::VSYNC_ENABLED, 0, Vector2i(0, 0), err); + break; + } + } + memnew(RenderingServerDefault()); + RenderingServerDefault::get_singleton()->init(); + RenderingServerDefault::get_singleton()->set_render_loop_enabled(false); + + physics_3d_server = PhysicsServer3DManager::new_default_server(); + physics_3d_server->init(); + + physics_2d_server = PhysicsServer2DManager::new_default_server(); + physics_2d_server->init(); + + navigation_3d_server = NavigationServer3DManager::new_default_server(); + navigation_2d_server = memnew(NavigationServer2D); + + memnew(InputMap); + InputMap::get_singleton()->load_default(); + + make_default_theme(false, Ref()); + + memnew(SceneTree); + SceneTree::get_singleton()->initialize(); + return; + } + } + + void test_case_end(const doctest::CurrentTestCaseStats &) override { + if (SceneTree::get_singleton()) { + SceneTree::get_singleton()->finalize(); + } + + if (MessageQueue::get_singleton()) { + MessageQueue::get_singleton()->flush(); + } + + if (SceneTree::get_singleton()) { + memdelete(SceneTree::get_singleton()); + } + + clear_default_theme(); + + if (TextServerManager::get_singleton()) { + memdelete(TextServerManager::get_singleton()); + } + + if (navigation_3d_server) { + memdelete(navigation_3d_server); + navigation_3d_server = nullptr; + } + + if (navigation_2d_server) { + memdelete(navigation_2d_server); + navigation_2d_server = nullptr; + } + + if (physics_3d_server) { + physics_3d_server->finish(); + memdelete(physics_3d_server); + physics_3d_server = nullptr; + } + + if (physics_2d_server) { + physics_2d_server->finish(); + memdelete(physics_2d_server); + physics_2d_server = nullptr; + } + + if (RenderingServer::get_singleton()) { + RenderingServer::get_singleton()->sync(); + RenderingServer::get_singleton()->global_variables_clear(); + RenderingServer::get_singleton()->finish(); + memdelete(RenderingServer::get_singleton()); + } + + if (DisplayServer::get_singleton()) { + memdelete(DisplayServer::get_singleton()); + } + + if (InputMap::get_singleton()) { + memdelete(InputMap::get_singleton()); + } + + if (MessageQueue::get_singleton()) { + MessageQueue::get_singleton()->flush(); + memdelete(MessageQueue::get_singleton()); + } + } + + void test_run_start() override { + signal_watcher = memnew(SignalWatcher); + } + + void test_run_end(const doctest::TestRunStats &) override { + memdelete(signal_watcher); + } + + void test_case_reenter(const doctest::TestCaseData &) override { + SignalWatcher::get_singleton()->_clear_signals(); + } + + void subcase_start(const doctest::SubcaseSignature &) override { + SignalWatcher::get_singleton()->_clear_signals(); + } + + void report_query(const doctest::QueryData &) override {} + void test_case_exception(const doctest::TestCaseException &) override {} + void subcase_end() override {} + + void log_assert(const doctest::AssertData &in) override {} + void log_message(const doctest::MessageData &) override {} + void test_case_skipped(const doctest::TestCaseData &) override {} +}; + +REGISTER_LISTENER("GodotTestCaseListener", 1, GodotTestCaseListener);