summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/test_aabb.h3
-rw-r--r--tests/test_array.h51
-rw-r--r--tests/test_code_edit.h3190
-rw-r--r--tests/test_macros.h219
-rw-r--r--tests/test_main.cpp145
-rw-r--r--tests/test_math.cpp4
-rw-r--r--tests/test_physics_2d.cpp16
-rw-r--r--tests/test_physics_3d.cpp15
-rw-r--r--tests/test_rect2.h12
-rw-r--r--tests/test_shader_lang.cpp57
-rw-r--r--tests/test_string.h101
-rw-r--r--tests/test_text_server.h67
-rw-r--r--tests/test_tools.h61
-rw-r--r--tests/test_translation.h31
-rw-r--r--tests/test_validate_testing.h12
-rw-r--r--tests/test_vector.h13
16 files changed, 3911 insertions, 86 deletions
diff --git a/tests/test_aabb.h b/tests/test_aabb.h
index c4daa56e5a..2724d9481a 100644
--- a/tests/test_aabb.h
+++ b/tests/test_aabb.h
@@ -65,6 +65,9 @@ TEST_CASE("[AABB] Basic getters") {
CHECK_MESSAGE(
aabb.get_end().is_equal_approx(Vector3(2.5, 7, 3.5)),
"get_end() should return the expected value.");
+ CHECK_MESSAGE(
+ aabb.get_center().is_equal_approx(Vector3(0.5, 4.5, 0.5)),
+ "get_center() should return the expected value.");
}
TEST_CASE("[AABB] Basic setters") {
diff --git a/tests/test_array.h b/tests/test_array.h
index 52da256860..3bd476fd27 100644
--- a/tests/test_array.h
+++ b/tests/test_array.h
@@ -39,6 +39,7 @@
#include "core/variant/container_type_validate.h"
#include "core/variant/variant.h"
#include "tests/test_macros.h"
+#include "tests/test_tools.h"
namespace TestArray {
@@ -170,6 +171,56 @@ TEST_CASE("[Array] push_front(), pop_front(), pop_back()") {
CHECK(arr.size() == 2);
}
+TEST_CASE("[Array] pop_at()") {
+ ErrorDetector ed;
+
+ Array arr;
+ arr.push_back(2);
+ arr.push_back(4);
+ arr.push_back(6);
+ arr.push_back(8);
+ arr.push_back(10);
+
+ REQUIRE(int(arr.pop_at(2)) == 6);
+ REQUIRE(arr.size() == 4);
+ CHECK(int(arr[0]) == 2);
+ CHECK(int(arr[1]) == 4);
+ CHECK(int(arr[2]) == 8);
+ CHECK(int(arr[3]) == 10);
+
+ REQUIRE(int(arr.pop_at(2)) == 8);
+ REQUIRE(arr.size() == 3);
+ CHECK(int(arr[0]) == 2);
+ CHECK(int(arr[1]) == 4);
+ CHECK(int(arr[2]) == 10);
+
+ // Negative index.
+ REQUIRE(int(arr.pop_at(-1)) == 10);
+ REQUIRE(arr.size() == 2);
+ CHECK(int(arr[0]) == 2);
+ CHECK(int(arr[1]) == 4);
+
+ // Invalid pop.
+ ed.clear();
+ ERR_PRINT_OFF;
+ const Variant ret = arr.pop_at(-15);
+ ERR_PRINT_ON;
+ REQUIRE(ret.is_null());
+ CHECK(ed.has_error);
+
+ REQUIRE(int(arr.pop_at(0)) == 2);
+ REQUIRE(arr.size() == 1);
+ CHECK(int(arr[0]) == 4);
+
+ REQUIRE(int(arr.pop_at(0)) == 4);
+ REQUIRE(arr.is_empty());
+
+ // Pop from empty array.
+ ed.clear();
+ REQUIRE(arr.pop_at(24).is_null());
+ CHECK_FALSE(ed.has_error);
+}
+
TEST_CASE("[Array] max() and min()") {
Array arr;
arr.push_back(3);
diff --git a/tests/test_code_edit.h b/tests/test_code_edit.h
new file mode 100644
index 0000000000..62235ed0ae
--- /dev/null
+++ b/tests/test_code_edit.h
@@ -0,0 +1,3190 @@
+/*************************************************************************/
+/* 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);
+
+ /* Backspace above breakpointed line moves it. */
+ ((Array)args[0])[0] = 2;
+
+ code_edit->set_text("\n\n");
+ code_edit->set_line_as_breakpoint(2, true);
+ CHECK(code_edit->is_line_breakpointed(2));
+ SIGNAL_CHECK("breakpoint_toggled", args);
+
+ code_edit->set_caret_line(1);
+
+ Array arg2;
+ arg2.push_back(1);
+ args.push_back(arg2);
+ SEND_GUI_ACTION(code_edit, "ui_text_backspace");
+ ERR_PRINT_OFF;
+ CHECK_FALSE(code_edit->is_line_breakpointed(2));
+ ERR_PRINT_ON;
+ CHECK(code_edit->is_line_breakpointed(1));
+ 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);
+
+ /* Delete above breakpointed line moves it. */
+ ((Array)args[0])[0] = 2;
+
+ code_edit->set_text("\n\n");
+ code_edit->set_line_as_breakpoint(2, true);
+ CHECK(code_edit->is_line_breakpointed(2));
+ SIGNAL_CHECK("breakpoint_toggled", args);
+
+ code_edit->set_caret_line(0);
+
+ Array arg2;
+ arg2.push_back(1);
+ args.push_back(arg2);
+ SEND_GUI_ACTION(code_edit, "ui_text_delete");
+ ERR_PRINT_OFF;
+ CHECK_FALSE(code_edit->is_line_breakpointed(2));
+ ERR_PRINT_ON;
+ CHECK(code_edit->is_line_breakpointed(1));
+ 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);
+
+ /* Should handle breakpoint move when deleting selection by adding less text then removed. */
+ ((Array)args[0])[0] = 9;
+
+ code_edit->set_text("\n\n\n\n\n\n\n\n\n");
+ code_edit->set_line_as_breakpoint(9, true);
+ CHECK(code_edit->is_line_breakpointed(9));
+ SIGNAL_CHECK("breakpoint_toggled", args);
+
+ code_edit->select(0, 0, 6, 0);
+
+ Array arg2;
+ arg2.push_back(4);
+ args.push_back(arg2);
+ SEND_GUI_ACTION(code_edit, "ui_text_newline");
+ ERR_PRINT_OFF;
+ CHECK_FALSE(code_edit->is_line_breakpointed(9));
+ ERR_PRINT_ON;
+ CHECK(code_edit->is_line_breakpointed(4));
+ SIGNAL_CHECK("breakpoint_toggled", args);
+
+ /* Should handle breakpoint move when deleting selection by adding more text then removed. */
+ ((Array)args[0])[0] = 9;
+ ((Array)args[1])[0] = 14;
+
+ code_edit->insert_text_at_caret("\n\n\n\n\n");
+ MessageQueue::get_singleton()->flush();
+ SIGNAL_DISCARD("breakpoint_toggled")
+ CHECK(code_edit->is_line_breakpointed(9));
+
+ code_edit->select(0, 0, 6, 0);
+ code_edit->insert_text_at_caret("\n\n\n\n\n\n\n\n\n\n\n");
+ MessageQueue::get_singleton()->flush();
+ CHECK(code_edit->is_line_breakpointed(14));
+ 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);
+}
+
+TEST_CASE("[SceneTree][CodeEdit] delimiters") {
+ CodeEdit *code_edit = memnew(CodeEdit);
+ SceneTree::get_singleton()->get_root()->add_child(code_edit);
+
+ const Point2 OUTSIDE_DELIMETER = Point2(-1, -1);
+
+ code_edit->clear_string_delimiters();
+ code_edit->clear_comment_delimiters();
+
+ SUBCASE("[CodeEdit] add and remove delimiters") {
+ SUBCASE("[CodeEdit] add and remove string delimiters") {
+ /* Add a delimiter.*/
+ code_edit->add_string_delimiter("\"", "\"", false);
+ CHECK(code_edit->has_string_delimiter("\""));
+ CHECK(code_edit->get_string_delimiters().size() == 1);
+
+ ERR_PRINT_OFF;
+
+ /* Adding a duplicate start key is not allowed. */
+ code_edit->add_string_delimiter("\"", "\'", false);
+ CHECK(code_edit->get_string_delimiters().size() == 1);
+
+ /* Adding a duplicate end key is allowed. */
+ code_edit->add_string_delimiter("'", "\"", false);
+ CHECK(code_edit->has_string_delimiter("'"));
+ CHECK(code_edit->get_string_delimiters().size() == 2);
+
+ /* Both start and end keys have to be symbols. */
+ code_edit->add_string_delimiter("f", "\"", false);
+ CHECK_FALSE(code_edit->has_string_delimiter("f"));
+ CHECK(code_edit->get_string_delimiters().size() == 2);
+
+ code_edit->add_string_delimiter("f", "\"", false);
+ CHECK_FALSE(code_edit->has_string_delimiter("f"));
+ CHECK(code_edit->get_string_delimiters().size() == 2);
+
+ code_edit->add_string_delimiter("@", "f", false);
+ CHECK_FALSE(code_edit->has_string_delimiter("@"));
+ CHECK(code_edit->get_string_delimiters().size() == 2);
+
+ code_edit->add_string_delimiter("f", "f", false);
+ CHECK_FALSE(code_edit->has_string_delimiter("f"));
+ CHECK(code_edit->get_string_delimiters().size() == 2);
+
+ /* Blank start keys are not allowed */
+ code_edit->add_string_delimiter("", "#", false);
+ CHECK_FALSE(code_edit->has_string_delimiter("#"));
+ CHECK(code_edit->get_string_delimiters().size() == 2);
+
+ ERR_PRINT_ON;
+
+ /* Blank end keys are allowed. */
+ code_edit->add_string_delimiter("#", "", false);
+ CHECK(code_edit->has_string_delimiter("#"));
+ CHECK(code_edit->get_string_delimiters().size() == 3);
+
+ /* Remove a delimiter. */
+ code_edit->remove_string_delimiter("#");
+ CHECK_FALSE(code_edit->has_string_delimiter("#"));
+ CHECK(code_edit->get_string_delimiters().size() == 2);
+
+ /* Set should override existing, and test multiline */
+ TypedArray<String> delimiters;
+ delimiters.push_back("^^ ^^");
+
+ code_edit->set_string_delimiters(delimiters);
+ CHECK_FALSE(code_edit->has_string_delimiter("\""));
+ CHECK(code_edit->has_string_delimiter("^^"));
+ CHECK(code_edit->get_string_delimiters().size() == 1);
+
+ /* clear should remove all. */
+ code_edit->clear_string_delimiters();
+ CHECK_FALSE(code_edit->has_string_delimiter("^^"));
+ CHECK(code_edit->get_string_delimiters().size() == 0);
+ }
+
+ SUBCASE("[CodeEdit] add and remove comment delimiters") {
+ /* Add a delimiter.*/
+ code_edit->add_comment_delimiter("\"", "\"", false);
+ CHECK(code_edit->has_comment_delimiter("\""));
+ CHECK(code_edit->get_comment_delimiters().size() == 1);
+
+ ERR_PRINT_OFF;
+
+ /* Adding a duplicate start key is not allowed. */
+ code_edit->add_comment_delimiter("\"", "\'", false);
+ CHECK(code_edit->get_comment_delimiters().size() == 1);
+
+ /* Adding a duplicate end key is allowed. */
+ code_edit->add_comment_delimiter("'", "\"", false);
+ CHECK(code_edit->has_comment_delimiter("'"));
+ CHECK(code_edit->get_comment_delimiters().size() == 2);
+
+ /* Both start and end keys have to be symbols. */
+ code_edit->add_comment_delimiter("f", "\"", false);
+ CHECK_FALSE(code_edit->has_comment_delimiter("f"));
+ CHECK(code_edit->get_comment_delimiters().size() == 2);
+
+ code_edit->add_comment_delimiter("f", "\"", false);
+ CHECK_FALSE(code_edit->has_comment_delimiter("f"));
+ CHECK(code_edit->get_comment_delimiters().size() == 2);
+
+ code_edit->add_comment_delimiter("@", "f", false);
+ CHECK_FALSE(code_edit->has_comment_delimiter("@"));
+ CHECK(code_edit->get_comment_delimiters().size() == 2);
+
+ code_edit->add_comment_delimiter("f", "f", false);
+ CHECK_FALSE(code_edit->has_comment_delimiter("f"));
+ CHECK(code_edit->get_comment_delimiters().size() == 2);
+
+ /* Blank start keys are not allowed. */
+ code_edit->add_comment_delimiter("", "#", false);
+ CHECK_FALSE(code_edit->has_comment_delimiter("#"));
+ CHECK(code_edit->get_comment_delimiters().size() == 2);
+
+ ERR_PRINT_ON;
+
+ /* Blank end keys are allowed. */
+ code_edit->add_comment_delimiter("#", "", false);
+ CHECK(code_edit->has_comment_delimiter("#"));
+ CHECK(code_edit->get_comment_delimiters().size() == 3);
+
+ /* Remove a delimiter. */
+ code_edit->remove_comment_delimiter("#");
+ CHECK_FALSE(code_edit->has_comment_delimiter("#"));
+ CHECK(code_edit->get_comment_delimiters().size() == 2);
+
+ /* Set should override existing, and test multiline. */
+ TypedArray<String> delimiters;
+ delimiters.push_back("^^ ^^");
+
+ code_edit->set_comment_delimiters(delimiters);
+ CHECK_FALSE(code_edit->has_comment_delimiter("\""));
+ CHECK(code_edit->has_comment_delimiter("^^"));
+ CHECK(code_edit->get_comment_delimiters().size() == 1);
+
+ /* clear should remove all. */
+ code_edit->clear_comment_delimiters();
+ CHECK_FALSE(code_edit->has_comment_delimiter("^^"));
+ CHECK(code_edit->get_comment_delimiters().size() == 0);
+ }
+
+ SUBCASE("[CodeEdit] add and remove mixed delimiters") {
+ code_edit->add_comment_delimiter("#", "", false);
+ CHECK(code_edit->has_comment_delimiter("#"));
+ CHECK(code_edit->get_comment_delimiters().size() == 1);
+
+ ERR_PRINT_OFF;
+
+ /* Disallow adding a string with the same start key as comment. */
+ code_edit->add_string_delimiter("#", "", false);
+ CHECK_FALSE(code_edit->has_string_delimiter("#"));
+ CHECK(code_edit->get_string_delimiters().size() == 0);
+
+ code_edit->add_string_delimiter("\"", "\"", false);
+ CHECK(code_edit->has_string_delimiter("\""));
+ CHECK(code_edit->get_comment_delimiters().size() == 1);
+
+ /* Disallow adding a comment with the same start key as string. */
+ code_edit->add_comment_delimiter("\"", "", false);
+ CHECK_FALSE(code_edit->has_comment_delimiter("\""));
+ CHECK(code_edit->get_comment_delimiters().size() == 1);
+
+ ERR_PRINT_ON;
+
+ /* Cannot remove string with remove comment. */
+ code_edit->remove_comment_delimiter("\"");
+ CHECK(code_edit->has_string_delimiter("\""));
+ CHECK(code_edit->get_string_delimiters().size() == 1);
+
+ /* Cannot remove comment with remove string. */
+ code_edit->remove_string_delimiter("#");
+ CHECK(code_edit->has_comment_delimiter("#"));
+ CHECK(code_edit->get_comment_delimiters().size() == 1);
+
+ /* Clear comments leave strings. */
+ code_edit->clear_comment_delimiters();
+ CHECK(code_edit->has_string_delimiter("\""));
+ CHECK(code_edit->get_string_delimiters().size() == 1);
+
+ /* Clear string leave comments. */
+ code_edit->add_comment_delimiter("#", "", false);
+ CHECK(code_edit->has_comment_delimiter("#"));
+ CHECK(code_edit->get_comment_delimiters().size() == 1);
+
+ code_edit->clear_string_delimiters();
+ CHECK(code_edit->has_comment_delimiter("#"));
+ CHECK(code_edit->get_comment_delimiters().size() == 1);
+ }
+ }
+
+ SUBCASE("[CodeEdit] single line delimiters") {
+ SUBCASE("[CodeEdit] single line string delimiters") {
+ /* Blank end key should set lineonly to true. */
+ code_edit->add_string_delimiter("#", "", false);
+ CHECK(code_edit->has_string_delimiter("#"));
+ CHECK(code_edit->get_string_delimiters().size() == 1);
+
+ /* Insert line above, line with string then line below. */
+ code_edit->insert_text_at_caret(" \n#\n ");
+
+ /* Check line above is not in string. */
+ CHECK(code_edit->is_in_string(0, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
+
+ /* Check column before start key is not in string. */
+ CHECK(code_edit->is_in_string(1, 0) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
+
+ /* Check column after start key is in string and start / end positions are correct. */
+ CHECK(code_edit->is_in_string(1, 1) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 1) == Point2(1, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 1) == Point2(2, 1));
+
+ /* Check line after is not in string. */
+ CHECK(code_edit->is_in_string(2, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(2, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(2, 1) == OUTSIDE_DELIMETER);
+
+ /* Check region metadata. */
+ int idx = code_edit->is_in_string(1, 1);
+ CHECK(code_edit->get_delimiter_start_key(idx) == "#");
+ CHECK(code_edit->get_delimiter_end_key(idx) == "");
+
+ /* Check nested strings are handeled correctly. */
+ code_edit->set_text(" \n# # \n ");
+
+ /* Check line above is not in string. */
+ CHECK(code_edit->is_in_string(0, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
+
+ /* Check column before first start key is not in string. */
+ CHECK(code_edit->is_in_string(1, 0) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
+
+ /* Check column after the first start key is in string and start / end positions are correct. */
+ CHECK(code_edit->is_in_string(1, 1) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 1) == Point2(1, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 1) == Point2(6, 1));
+
+ /* Check column after the second start key returns data for the first. */
+ CHECK(code_edit->is_in_string(1, 5) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 5) == Point2(1, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 5) == Point2(6, 1));
+
+ /* Check line after is not in string. */
+ CHECK(code_edit->is_in_string(2, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(2, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(2, 1) == OUTSIDE_DELIMETER);
+
+ /* Check is in string with no column retruns true if entire line is comment excluding whitespace. */
+ code_edit->set_text(" \n # # \n ");
+ CHECK(code_edit->is_in_string(1) != -1);
+
+ code_edit->set_text(" \n text # # \n ");
+ CHECK(code_edit->is_in_string(1) == -1);
+
+ /* Removing delimiter should update. */
+ code_edit->set_text(" \n # # \n ");
+
+ code_edit->remove_string_delimiter("#");
+ CHECK_FALSE(code_edit->has_string_delimiter("$"));
+ CHECK(code_edit->get_string_delimiters().size() == 0);
+
+ CHECK(code_edit->is_in_string(1) == -1);
+
+ /* Adding and clear should update. */
+ code_edit->add_string_delimiter("#", "", false);
+ CHECK(code_edit->has_string_delimiter("#"));
+ CHECK(code_edit->get_string_delimiters().size() == 1);
+ CHECK(code_edit->is_in_string(1) != -1);
+
+ code_edit->clear_string_delimiters();
+ CHECK_FALSE(code_edit->has_string_delimiter("$"));
+ CHECK(code_edit->get_string_delimiters().size() == 0);
+
+ CHECK(code_edit->is_in_string(1) == -1);
+ }
+
+ SUBCASE("[CodeEdit] single line comment delimiters") {
+ /* Blank end key should set lineonly to true. */
+ code_edit->add_comment_delimiter("#", "", false);
+ CHECK(code_edit->has_comment_delimiter("#"));
+ CHECK(code_edit->get_comment_delimiters().size() == 1);
+
+ /* Insert line above, line with comment then line below. */
+ code_edit->insert_text_at_caret(" \n#\n ");
+
+ /* Check line above is not in comment. */
+ CHECK(code_edit->is_in_comment(0, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
+
+ /* Check column before start key is not in comment. */
+ CHECK(code_edit->is_in_comment(1, 0) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
+
+ /* Check column after start key is in comment and start / end positions are correct. */
+ CHECK(code_edit->is_in_comment(1, 1) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 1) == Point2(1, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 1) == Point2(2, 1));
+
+ /* Check line after is not in comment. */
+ CHECK(code_edit->is_in_comment(2, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(2, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(2, 1) == OUTSIDE_DELIMETER);
+
+ /* Check region metadata. */
+ int idx = code_edit->is_in_comment(1, 1);
+ CHECK(code_edit->get_delimiter_start_key(idx) == "#");
+ CHECK(code_edit->get_delimiter_end_key(idx) == "");
+
+ /* Check nested comments are handeled correctly. */
+ code_edit->set_text(" \n# # \n ");
+
+ /* Check line above is not in comment. */
+ CHECK(code_edit->is_in_comment(0, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
+
+ /* Check column before first start key is not in comment. */
+ CHECK(code_edit->is_in_comment(1, 0) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
+
+ /* Check column after the first start key is in comment and start / end positions are correct. */
+ CHECK(code_edit->is_in_comment(1, 1) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 1) == Point2(1, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 1) == Point2(6, 1));
+
+ /* Check column after the second start key returns data for the first. */
+ CHECK(code_edit->is_in_comment(1, 5) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 5) == Point2(1, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 5) == Point2(6, 1));
+
+ /* Check line after is not in comment. */
+ CHECK(code_edit->is_in_comment(2, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(2, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(2, 1) == OUTSIDE_DELIMETER);
+
+ /* Check is in comment with no column retruns true if entire line is comment excluding whitespace. */
+ code_edit->set_text(" \n # # \n ");
+ CHECK(code_edit->is_in_comment(1) != -1);
+
+ code_edit->set_text(" \n text # # \n ");
+ CHECK(code_edit->is_in_comment(1) == -1);
+
+ /* Removing delimiter should update. */
+ code_edit->set_text(" \n # # \n ");
+
+ code_edit->remove_comment_delimiter("#");
+ CHECK_FALSE(code_edit->has_comment_delimiter("$"));
+ CHECK(code_edit->get_comment_delimiters().size() == 0);
+
+ CHECK(code_edit->is_in_comment(1) == -1);
+
+ /* Adding and clear should update. */
+ code_edit->add_comment_delimiter("#", "", false);
+ CHECK(code_edit->has_comment_delimiter("#"));
+ CHECK(code_edit->get_comment_delimiters().size() == 1);
+ CHECK(code_edit->is_in_comment(1) != -1);
+
+ code_edit->clear_comment_delimiters();
+ CHECK_FALSE(code_edit->has_comment_delimiter("$"));
+ CHECK(code_edit->get_comment_delimiters().size() == 0);
+
+ CHECK(code_edit->is_in_comment(1) == -1);
+ }
+
+ SUBCASE("[CodeEdit] single line mixed delimiters") {
+ /* Blank end key should set lineonly to true. */
+ /* Add string delimiter. */
+ code_edit->add_string_delimiter("&", "", false);
+ CHECK(code_edit->has_string_delimiter("&"));
+ CHECK(code_edit->get_string_delimiters().size() == 1);
+
+ /* Add comment delimiter. */
+ code_edit->add_comment_delimiter("#", "", false);
+ CHECK(code_edit->has_comment_delimiter("#"));
+ CHECK(code_edit->get_comment_delimiters().size() == 1);
+
+ /* Nest a string delimiter inside a comment. */
+ code_edit->set_text(" \n# & \n ");
+
+ /* Check line above is not in comment. */
+ CHECK(code_edit->is_in_comment(0, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
+
+ /* Check column before first start key is not in comment. */
+ CHECK(code_edit->is_in_comment(1, 0) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
+
+ /* Check column after the first start key is in comment and start / end positions are correct. */
+ CHECK(code_edit->is_in_comment(1, 1) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 1) == Point2(1, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 1) == Point2(6, 1));
+
+ /* Check column after the second start key returns data for the first, and does not state string. */
+ CHECK(code_edit->is_in_comment(1, 5) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 5) == Point2(1, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 5) == Point2(6, 1));
+ CHECK(code_edit->is_in_string(1, 5) == -1);
+
+ /* Check line after is not in comment. */
+ CHECK(code_edit->is_in_comment(2, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(2, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(2, 1) == OUTSIDE_DELIMETER);
+
+ /* Remove the comment delimiter. */
+ code_edit->remove_comment_delimiter("#");
+ CHECK_FALSE(code_edit->has_comment_delimiter("$"));
+ CHECK(code_edit->get_comment_delimiters().size() == 0);
+
+ /* The "first" comment region is no longer valid. */
+ CHECK(code_edit->is_in_comment(1, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 1) == OUTSIDE_DELIMETER);
+
+ /* The "second" region as string is now valid. */
+ CHECK(code_edit->is_in_string(1, 5) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 5) == Point2(4, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 5) == Point2(6, 1));
+ }
+ }
+
+ SUBCASE("[CodeEdit] multiline delimiters") {
+ SUBCASE("[CodeEdit] multiline string delimiters") {
+ code_edit->clear_string_delimiters();
+ code_edit->clear_comment_delimiters();
+
+ /* Add string delimiter. */
+ code_edit->add_string_delimiter("#", "#", false);
+ CHECK(code_edit->has_string_delimiter("#"));
+ CHECK(code_edit->get_string_delimiters().size() == 1);
+
+ /* First test over a single line. */
+ code_edit->set_text(" \n # # \n ");
+
+ /* Check line above is not in string. */
+ CHECK(code_edit->is_in_string(0, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
+
+ /* Check column before start key is not in string. */
+ CHECK(code_edit->is_in_string(1, 0) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
+
+ /* Check column before closing delimiter is in string. */
+ CHECK(code_edit->is_in_string(1, 2) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 2) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 2) == Point2(5, 1));
+
+ /* Check column after end key is not in string. */
+ CHECK(code_edit->is_in_string(1, 6) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 6) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 6) == OUTSIDE_DELIMETER);
+
+ /* Check line after is not in string. */
+ CHECK(code_edit->is_in_string(2, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(2, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(2, 1) == OUTSIDE_DELIMETER);
+
+ /* Check the region metadata. */
+ int idx = code_edit->is_in_string(1, 2);
+ CHECK(code_edit->get_delimiter_start_key(idx) == "#");
+ CHECK(code_edit->get_delimiter_end_key(idx) == "#");
+
+ /* Next test over a multiple blank lines. */
+ code_edit->set_text(" \n # \n\n # \n ");
+
+ /* Check line above is not in string. */
+ CHECK(code_edit->is_in_string(0, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
+
+ /* Check column before start key is not in string. */
+ CHECK(code_edit->is_in_string(1, 0) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
+
+ /* Check column just after start key is in string. */
+ CHECK(code_edit->is_in_string(1, 2) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 2) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 2) == Point2(2, 3));
+
+ /* Check blank middle line. */
+ CHECK(code_edit->is_in_string(2, 0) != -1);
+ CHECK(code_edit->get_delimiter_start_position(2, 0) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(2, 0) == Point2(2, 3));
+
+ /* Check column just before end key is in string. */
+ CHECK(code_edit->is_in_string(3, 0) != -1);
+ CHECK(code_edit->get_delimiter_start_position(3, 0) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(3, 0) == Point2(2, 3));
+
+ /* Check column after end key is not in string. */
+ CHECK(code_edit->is_in_string(3, 3) == -1);
+ CHECK(code_edit->get_delimiter_start_position(3, 3) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(3, 3) == OUTSIDE_DELIMETER);
+
+ /* Check line after is not in string. */
+ CHECK(code_edit->is_in_string(4, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(4, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(4, 1) == OUTSIDE_DELIMETER);
+
+ /* Next test over a multiple non-blank lines. */
+ code_edit->set_text(" \n # \n \n # \n ");
+
+ /* Check line above is not in string. */
+ CHECK(code_edit->is_in_string(0, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
+
+ /* Check column before start key is not in string. */
+ CHECK(code_edit->is_in_string(1, 0) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
+
+ /* Check column just after start key is in string. */
+ CHECK(code_edit->is_in_string(1, 2) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 2) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 2) == Point2(2, 3));
+
+ /* Check middle line. */
+ CHECK(code_edit->is_in_string(2, 0) != -1);
+ CHECK(code_edit->get_delimiter_start_position(2, 0) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(2, 0) == Point2(2, 3));
+
+ /* Check column just before end key is in string. */
+ CHECK(code_edit->is_in_string(3, 0) != -1);
+ CHECK(code_edit->get_delimiter_start_position(3, 0) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(3, 0) == Point2(2, 3));
+
+ /* Check column after end key is not in string. */
+ CHECK(code_edit->is_in_string(3, 3) == -1);
+ CHECK(code_edit->get_delimiter_start_position(3, 3) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(3, 3) == OUTSIDE_DELIMETER);
+
+ /* Check line after is not in string. */
+ CHECK(code_edit->is_in_string(4, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(4, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(4, 1) == OUTSIDE_DELIMETER);
+
+ /* check the region metadata. */
+ idx = code_edit->is_in_string(1, 2);
+ CHECK(code_edit->get_delimiter_start_key(idx) == "#");
+ CHECK(code_edit->get_delimiter_end_key(idx) == "#");
+
+ /* Next test nested strings. */
+ code_edit->add_string_delimiter("^", "^", false);
+ CHECK(code_edit->has_string_delimiter("^"));
+ CHECK(code_edit->get_string_delimiters().size() == 2);
+
+ code_edit->set_text(" \n # ^\n \n^ # \n ");
+
+ /* Check line above is not in string. */
+ CHECK(code_edit->is_in_string(0, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
+
+ /* Check column before start key is not in string. */
+ CHECK(code_edit->is_in_string(1, 0) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
+
+ /* Check column just after start key is in string. */
+ CHECK(code_edit->is_in_string(1, 2) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 2) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 2) == Point2(3, 3));
+
+ /* Check middle line. */
+ CHECK(code_edit->is_in_string(2, 0) != -1);
+ CHECK(code_edit->get_delimiter_start_position(2, 0) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(2, 0) == Point2(3, 3));
+
+ /* Check column just before end key is in string. */
+ CHECK(code_edit->is_in_string(3, 0) != -1);
+ CHECK(code_edit->get_delimiter_start_position(3, 0) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(3, 0) == Point2(3, 3));
+
+ /* Check column after end key is not in string. */
+ CHECK(code_edit->is_in_string(3, 3) == -1);
+ CHECK(code_edit->get_delimiter_start_position(3, 3) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(3, 3) == OUTSIDE_DELIMETER);
+
+ /* Check line after is not in string. */
+ CHECK(code_edit->is_in_string(4, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(4, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(4, 1) == OUTSIDE_DELIMETER);
+
+ /* check the region metadata. */
+ idx = code_edit->is_in_string(1, 2);
+ CHECK(code_edit->get_delimiter_start_key(idx) == "#");
+ CHECK(code_edit->get_delimiter_end_key(idx) == "#");
+
+ /* Next test no end key. */
+ code_edit->set_text(" \n # \n ");
+
+ /* check the region metadata. */
+ idx = code_edit->is_in_string(1, 2);
+ CHECK(code_edit->get_delimiter_start_position(1, 2) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 2) == Point2(-1, -1));
+ CHECK(code_edit->get_delimiter_start_key(idx) == "#");
+ CHECK(code_edit->get_delimiter_end_key(idx) == "#");
+
+ /* Check is in string with no column retruns true if entire line is string excluding whitespace. */
+ code_edit->set_text(" \n # \n\n #\n ");
+ CHECK(code_edit->is_in_string(1) != -1);
+ CHECK(code_edit->is_in_string(2) != -1);
+ CHECK(code_edit->is_in_string(3) != -1);
+
+ code_edit->set_text(" \n test # \n\n # test \n ");
+ CHECK(code_edit->is_in_string(1) == -1);
+ CHECK(code_edit->is_in_string(2) != -1);
+ CHECK(code_edit->is_in_string(3) == -1);
+ }
+
+ SUBCASE("[CodeEdit] multiline comment delimiters") {
+ /* Add comment delimiter. */
+ code_edit->add_comment_delimiter("#", "#", false);
+ CHECK(code_edit->has_comment_delimiter("#"));
+ CHECK(code_edit->get_comment_delimiters().size() == 1);
+
+ /* First test over a single line. */
+ code_edit->set_text(" \n # # \n ");
+
+ /* Check line above is not in comment. */
+ CHECK(code_edit->is_in_comment(0, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
+
+ /* Check column before start key is not in comment. */
+ CHECK(code_edit->is_in_comment(1, 0) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
+
+ /* Check column before closing delimiter is in comment. */
+ CHECK(code_edit->is_in_comment(1, 2) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 2) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 2) == Point2(5, 1));
+
+ /* Check column after end key is not in comment. */
+ CHECK(code_edit->is_in_comment(1, 6) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 6) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 6) == OUTSIDE_DELIMETER);
+
+ /* Check line after is not in comment. */
+ CHECK(code_edit->is_in_comment(2, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(2, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(2, 1) == OUTSIDE_DELIMETER);
+
+ /* Check the region metadata. */
+ int idx = code_edit->is_in_comment(1, 2);
+ CHECK(code_edit->get_delimiter_start_key(idx) == "#");
+ CHECK(code_edit->get_delimiter_end_key(idx) == "#");
+
+ /* Next test over a multiple blank lines. */
+ code_edit->set_text(" \n # \n\n # \n ");
+
+ /* Check line above is not in comment. */
+ CHECK(code_edit->is_in_comment(0, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
+
+ /* Check column before start key is not in comment. */
+ CHECK(code_edit->is_in_comment(1, 0) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
+
+ /* Check column just after start key is in comment. */
+ CHECK(code_edit->is_in_comment(1, 2) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 2) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 2) == Point2(2, 3));
+
+ /* Check blank middle line. */
+ CHECK(code_edit->is_in_comment(2, 0) != -1);
+ CHECK(code_edit->get_delimiter_start_position(2, 0) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(2, 0) == Point2(2, 3));
+
+ /* Check column just before end key is in comment. */
+ CHECK(code_edit->is_in_comment(3, 0) != -1);
+ CHECK(code_edit->get_delimiter_start_position(3, 0) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(3, 0) == Point2(2, 3));
+
+ /* Check column after end key is not in comment. */
+ CHECK(code_edit->is_in_comment(3, 3) == -1);
+ CHECK(code_edit->get_delimiter_start_position(3, 3) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(3, 3) == OUTSIDE_DELIMETER);
+
+ /* Check line after is not in comment. */
+ CHECK(code_edit->is_in_comment(4, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(4, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(4, 1) == OUTSIDE_DELIMETER);
+
+ /* Next test over a multiple non-blank lines. */
+ code_edit->set_text(" \n # \n \n # \n ");
+
+ /* Check line above is not in comment. */
+ CHECK(code_edit->is_in_comment(0, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
+
+ /* Check column before start key is not in comment. */
+ CHECK(code_edit->is_in_comment(1, 0) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
+
+ /* Check column just after start key is in comment. */
+ CHECK(code_edit->is_in_comment(1, 2) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 2) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 2) == Point2(2, 3));
+
+ /* Check middle line. */
+ CHECK(code_edit->is_in_comment(2, 0) != -1);
+ CHECK(code_edit->get_delimiter_start_position(2, 0) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(2, 0) == Point2(2, 3));
+
+ /* Check column just before end key is in comment. */
+ CHECK(code_edit->is_in_comment(3, 0) != -1);
+ CHECK(code_edit->get_delimiter_start_position(3, 0) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(3, 0) == Point2(2, 3));
+
+ /* Check column after end key is not in comment. */
+ CHECK(code_edit->is_in_comment(3, 3) == -1);
+ CHECK(code_edit->get_delimiter_start_position(3, 3) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(3, 3) == OUTSIDE_DELIMETER);
+
+ /* Check line after is not in comment. */
+ CHECK(code_edit->is_in_comment(4, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(4, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(4, 1) == OUTSIDE_DELIMETER);
+
+ /* check the region metadata. */
+ idx = code_edit->is_in_comment(1, 2);
+ CHECK(code_edit->get_delimiter_start_key(idx) == "#");
+ CHECK(code_edit->get_delimiter_end_key(idx) == "#");
+
+ /* Next test nested comments. */
+ code_edit->add_comment_delimiter("^", "^", false);
+ CHECK(code_edit->has_comment_delimiter("^"));
+ CHECK(code_edit->get_comment_delimiters().size() == 2);
+
+ code_edit->set_text(" \n # ^\n \n^ # \n ");
+
+ /* Check line above is not in comment. */
+ CHECK(code_edit->is_in_comment(0, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
+
+ /* Check column before start key is not in comment. */
+ CHECK(code_edit->is_in_comment(1, 0) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
+
+ /* Check column just after start key is in comment. */
+ CHECK(code_edit->is_in_comment(1, 2) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 2) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 2) == Point2(3, 3));
+
+ /* Check middle line. */
+ CHECK(code_edit->is_in_comment(2, 0) != -1);
+ CHECK(code_edit->get_delimiter_start_position(2, 0) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(2, 0) == Point2(3, 3));
+
+ /* Check column just before end key is in comment. */
+ CHECK(code_edit->is_in_comment(3, 0) != -1);
+ CHECK(code_edit->get_delimiter_start_position(3, 0) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(3, 0) == Point2(3, 3));
+
+ /* Check column after end key is not in comment. */
+ CHECK(code_edit->is_in_comment(3, 3) == -1);
+ CHECK(code_edit->get_delimiter_start_position(3, 3) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(3, 3) == OUTSIDE_DELIMETER);
+
+ /* Check line after is not in comment. */
+ CHECK(code_edit->is_in_comment(4, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(4, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(4, 1) == OUTSIDE_DELIMETER);
+
+ /* check the region metadata. */
+ idx = code_edit->is_in_comment(1, 2);
+ CHECK(code_edit->get_delimiter_start_key(idx) == "#");
+ CHECK(code_edit->get_delimiter_end_key(idx) == "#");
+
+ /* Next test no end key. */
+ code_edit->set_text(" \n # \n ");
+
+ /* check the region metadata. */
+ idx = code_edit->is_in_comment(1, 2);
+ CHECK(code_edit->get_delimiter_start_position(1, 2) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 2) == Point2(-1, -1));
+ CHECK(code_edit->get_delimiter_start_key(idx) == "#");
+ CHECK(code_edit->get_delimiter_end_key(idx) == "#");
+
+ /* Check is in comment with no column retruns true if entire line is comment excluding whitespace. */
+ code_edit->set_text(" \n # \n\n #\n ");
+ CHECK(code_edit->is_in_comment(1) != -1);
+ CHECK(code_edit->is_in_comment(2) != -1);
+ CHECK(code_edit->is_in_comment(3) != -1);
+
+ code_edit->set_text(" \n test # \n\n # test \n ");
+ CHECK(code_edit->is_in_comment(1) == -1);
+ CHECK(code_edit->is_in_comment(2) != -1);
+ CHECK(code_edit->is_in_comment(3) == -1);
+ }
+
+ SUBCASE("[CodeEdit] multiline mixed delimiters") {
+ /* Add comment delimiter. */
+ code_edit->add_comment_delimiter("#", "#", false);
+ CHECK(code_edit->has_comment_delimiter("#"));
+ CHECK(code_edit->get_comment_delimiters().size() == 1);
+
+ /* Add string delimiter. */
+ code_edit->add_string_delimiter("^", "^", false);
+ CHECK(code_edit->has_string_delimiter("^"));
+ CHECK(code_edit->get_string_delimiters().size() == 1);
+
+ /* Nest a string inside a comment. */
+ code_edit->set_text(" \n # ^\n \n^ # \n ");
+
+ /* Check line above is not in comment. */
+ CHECK(code_edit->is_in_comment(0, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
+
+ /* Check column before start key is not in comment. */
+ CHECK(code_edit->is_in_comment(1, 0) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
+
+ /* Check column just after start key is in comment. */
+ CHECK(code_edit->is_in_comment(1, 2) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 2) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 2) == Point2(3, 3));
+
+ /* Check middle line. */
+ CHECK(code_edit->is_in_comment(2, 0) != -1);
+ CHECK(code_edit->get_delimiter_start_position(2, 0) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(2, 0) == Point2(3, 3));
+
+ /* Check column just before end key is in comment. */
+ CHECK(code_edit->is_in_comment(3, 0) != -1);
+ CHECK(code_edit->get_delimiter_start_position(3, 0) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(3, 0) == Point2(3, 3));
+
+ /* Check column after end key is not in comment. */
+ CHECK(code_edit->is_in_comment(3, 3) == -1);
+ CHECK(code_edit->get_delimiter_start_position(3, 3) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(3, 3) == OUTSIDE_DELIMETER);
+
+ /* Check line after is not in comment. */
+ CHECK(code_edit->is_in_comment(4, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(4, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(4, 1) == OUTSIDE_DELIMETER);
+
+ /* check the region metadata. */
+ int idx = code_edit->is_in_comment(1, 2);
+ CHECK(code_edit->get_delimiter_start_key(idx) == "#");
+ CHECK(code_edit->get_delimiter_end_key(idx) == "#");
+
+ /* Check is in comment with no column retruns true as inner delimiter should not be counted. */
+ CHECK(code_edit->is_in_comment(1) != -1);
+ CHECK(code_edit->is_in_comment(2) != -1);
+ CHECK(code_edit->is_in_comment(3) != -1);
+ }
+ }
+
+ memdelete(code_edit);
+}
+
+TEST_CASE("[SceneTree][CodeEdit] indent") {
+ CodeEdit *code_edit = memnew(CodeEdit);
+ SceneTree::get_singleton()->get_root()->add_child(code_edit);
+
+ SUBCASE("[CodeEdit] indent settings") {
+ code_edit->set_indent_size(10);
+ CHECK(code_edit->get_indent_size() == 10);
+ CHECK(code_edit->get_tab_size() == 10);
+
+ code_edit->set_auto_indent_enabled(false);
+ CHECK_FALSE(code_edit->is_auto_indent_enabled());
+
+ code_edit->set_auto_indent_enabled(true);
+ CHECK(code_edit->is_auto_indent_enabled());
+
+ code_edit->set_indent_using_spaces(false);
+ CHECK_FALSE(code_edit->is_indent_using_spaces());
+
+ code_edit->set_indent_using_spaces(true);
+ CHECK(code_edit->is_indent_using_spaces());
+
+ /* Only the first char is registered. */
+ TypedArray<String> auto_indent_prefixes;
+ auto_indent_prefixes.push_back("::");
+ auto_indent_prefixes.push_back("s");
+ auto_indent_prefixes.push_back("1");
+ code_edit->set_auto_indent_prefixes(auto_indent_prefixes);
+
+ auto_indent_prefixes = code_edit->get_auto_indent_prefixes();
+ CHECK(auto_indent_prefixes.has(":"));
+ CHECK(auto_indent_prefixes.has("s"));
+ CHECK(auto_indent_prefixes.has("1"));
+ }
+
+ SUBCASE("[CodeEdit] indent tabs") {
+ code_edit->set_indent_size(4);
+ code_edit->set_auto_indent_enabled(true);
+ code_edit->set_indent_using_spaces(false);
+
+ /* Do nothing if not editable. */
+ code_edit->set_editable(false);
+
+ code_edit->do_indent();
+ CHECK(code_edit->get_line(0).is_empty());
+
+ code_edit->indent_lines();
+ CHECK(code_edit->get_line(0).is_empty());
+
+ code_edit->set_editable(true);
+
+ /* Simple indent. */
+ code_edit->do_indent();
+ CHECK(code_edit->get_line(0) == "\t");
+
+ /* Check input action. */
+ SEND_GUI_ACTION(code_edit, "ui_text_indent");
+ CHECK(code_edit->get_line(0) == "\t\t");
+
+ /* Insert in place. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test");
+ code_edit->do_indent();
+ CHECK(code_edit->get_line(0) == "test\t");
+
+ /* Indent lines does entire line and works without selection. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test");
+ code_edit->indent_lines();
+ CHECK(code_edit->get_line(0) == "\ttest");
+
+ /* Selection does entire line. */
+ code_edit->set_text("test");
+ code_edit->select_all();
+ code_edit->do_indent();
+ CHECK(code_edit->get_line(0) == "\ttest");
+
+ /* Handles multiple lines. */
+ code_edit->set_text("test\ntext");
+ code_edit->select_all();
+ code_edit->do_indent();
+ CHECK(code_edit->get_line(0) == "\ttest");
+ CHECK(code_edit->get_line(1) == "\ttext");
+
+ /* Do not indent line if last col is zero. */
+ code_edit->set_text("test\ntext");
+ code_edit->select(0, 0, 1, 0);
+ code_edit->do_indent();
+ CHECK(code_edit->get_line(0) == "\ttest");
+ CHECK(code_edit->get_line(1) == "text");
+
+ /* Indent even if last column of first line. */
+ code_edit->set_text("test\ntext");
+ code_edit->select(0, 4, 1, 0);
+ code_edit->do_indent();
+ CHECK(code_edit->get_line(0) == "\ttest");
+ CHECK(code_edit->get_line(1) == "text");
+
+ /* Check selection is adjusted. */
+ code_edit->set_text("test");
+ code_edit->select(0, 1, 0, 2);
+ code_edit->do_indent();
+ CHECK(code_edit->get_selection_from_column() == 2);
+ CHECK(code_edit->get_selection_to_column() == 3);
+ CHECK(code_edit->get_line(0) == "\ttest");
+ code_edit->undo();
+ }
+
+ SUBCASE("[CodeEdit] indent spaces") {
+ code_edit->set_indent_size(4);
+ code_edit->set_auto_indent_enabled(true);
+ code_edit->set_indent_using_spaces(true);
+
+ /* Do nothing if not editable. */
+ code_edit->set_editable(false);
+
+ code_edit->do_indent();
+ CHECK(code_edit->get_line(0).is_empty());
+
+ code_edit->indent_lines();
+ CHECK(code_edit->get_line(0).is_empty());
+
+ code_edit->set_editable(true);
+
+ /* Simple indent. */
+ code_edit->do_indent();
+ CHECK(code_edit->get_line(0) == " ");
+
+ /* Check input action. */
+ SEND_GUI_ACTION(code_edit, "ui_text_indent");
+ CHECK(code_edit->get_line(0) == " ");
+
+ /* Insert in place. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test");
+ code_edit->do_indent();
+ CHECK(code_edit->get_line(0) == "test ");
+
+ /* Indent lines does entire line and works without selection. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test");
+ code_edit->indent_lines();
+ CHECK(code_edit->get_line(0) == " test");
+
+ /* Selection does entire line. */
+ code_edit->set_text("test");
+ code_edit->select_all();
+ code_edit->do_indent();
+ CHECK(code_edit->get_line(0) == " test");
+
+ /* single indent only add required spaces. */
+ code_edit->set_text(" test");
+ code_edit->select_all();
+ code_edit->do_indent();
+ CHECK(code_edit->get_line(0) == " test");
+
+ /* Handles multiple lines. */
+ code_edit->set_text("test\ntext");
+ code_edit->select_all();
+ code_edit->do_indent();
+ CHECK(code_edit->get_line(0) == " test");
+ CHECK(code_edit->get_line(1) == " text");
+
+ /* Do not indent line if last col is zero. */
+ code_edit->set_text("test\ntext");
+ code_edit->select(0, 0, 1, 0);
+ code_edit->do_indent();
+ CHECK(code_edit->get_line(0) == " test");
+ CHECK(code_edit->get_line(1) == "text");
+
+ /* Indent even if last column of first line. */
+ code_edit->set_text("test\ntext");
+ code_edit->select(0, 4, 1, 0);
+ code_edit->do_indent();
+ CHECK(code_edit->get_line(0) == " test");
+ CHECK(code_edit->get_line(1) == "text");
+
+ /* Check selection is adjusted. */
+ code_edit->set_text("test");
+ code_edit->select(0, 1, 0, 2);
+ code_edit->do_indent();
+ CHECK(code_edit->get_selection_from_column() == 5);
+ CHECK(code_edit->get_selection_to_column() == 6);
+ CHECK(code_edit->get_line(0) == " test");
+ }
+
+ SUBCASE("[CodeEdit] unindent tabs") {
+ code_edit->set_indent_size(4);
+ code_edit->set_auto_indent_enabled(true);
+ code_edit->set_indent_using_spaces(false);
+
+ /* Do nothing if not editable. */
+ code_edit->set_text("\t");
+
+ code_edit->set_editable(false);
+
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == "\t");
+
+ code_edit->unindent_lines();
+ CHECK(code_edit->get_line(0) == "\t");
+
+ code_edit->set_editable(true);
+
+ /* Simple unindent. */
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == "");
+
+ /* Should inindent inplace. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test\t");
+
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == "test");
+
+ /* Backspace does a simple unindent. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("\t");
+ code_edit->backspace();
+ CHECK(code_edit->get_line(0) == "");
+
+ /* Unindent lines does entire line and works without selection. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("\ttest");
+ code_edit->unindent_lines();
+ CHECK(code_edit->get_line(0) == "test");
+
+ /* Caret on col zero unindent line. */
+ code_edit->set_text("\t\ttest");
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == "\ttest");
+
+ /* Check input action. */
+ code_edit->set_text("\t\ttest");
+ SEND_GUI_ACTION(code_edit, "ui_text_dedent");
+ CHECK(code_edit->get_line(0) == "\ttest");
+
+ /* Selection does entire line. */
+ code_edit->set_text("\t\ttest");
+ code_edit->select_all();
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == "\ttest");
+
+ /* Handles multiple lines. */
+ code_edit->set_text("\ttest\n\ttext");
+ code_edit->select_all();
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == "test");
+ CHECK(code_edit->get_line(1) == "text");
+
+ /* Do not unindent line if last col is zero. */
+ code_edit->set_text("\ttest\n\ttext");
+ code_edit->select(0, 0, 1, 0);
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == "test");
+ CHECK(code_edit->get_line(1) == "\ttext");
+
+ /* Unindent even if last column of first line. */
+ code_edit->set_text("\ttest\n\ttext");
+ code_edit->select(0, 5, 1, 1);
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == "test");
+ CHECK(code_edit->get_line(1) == "text");
+
+ /* Check selection is adjusted. */
+ code_edit->set_text("\ttest");
+ code_edit->select(0, 1, 0, 2);
+ code_edit->do_unindent();
+ CHECK(code_edit->get_selection_from_column() == 0);
+ CHECK(code_edit->get_selection_to_column() == 1);
+ CHECK(code_edit->get_line(0) == "test");
+ }
+
+ SUBCASE("[CodeEdit] unindent spaces") {
+ code_edit->set_indent_size(4);
+ code_edit->set_auto_indent_enabled(true);
+ code_edit->set_indent_using_spaces(true);
+
+ /* Do nothing if not editable. */
+ code_edit->set_text(" ");
+
+ code_edit->set_editable(false);
+
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == " ");
+
+ code_edit->unindent_lines();
+ CHECK(code_edit->get_line(0) == " ");
+
+ code_edit->set_editable(true);
+
+ /* Simple unindent. */
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == "");
+
+ /* Should inindent inplace. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test ");
+
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == "test");
+
+ /* Backspace does a simple unindent. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret(" ");
+ code_edit->backspace();
+ CHECK(code_edit->get_line(0) == "");
+
+ /* Backspace with letter. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret(" a");
+ code_edit->backspace();
+ CHECK(code_edit->get_line(0) == " ");
+
+ /* Unindent lines does entire line and works without selection. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret(" test");
+ code_edit->unindent_lines();
+ CHECK(code_edit->get_line(0) == "test");
+
+ /* Caret on col zero unindent line. */
+ code_edit->set_text(" test");
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == " test");
+
+ /* Only as far as needed */
+ code_edit->set_text(" test");
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == " test");
+
+ /* Check input action. */
+ code_edit->set_text(" test");
+ SEND_GUI_ACTION(code_edit, "ui_text_dedent");
+ CHECK(code_edit->get_line(0) == " test");
+
+ /* Selection does entire line. */
+ code_edit->set_text(" test");
+ code_edit->select_all();
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == " test");
+
+ /* Handles multiple lines. */
+ code_edit->set_text(" test\n text");
+ code_edit->select_all();
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == "test");
+ CHECK(code_edit->get_line(1) == "text");
+
+ /* Do not unindent line if last col is zero. */
+ code_edit->set_text(" test\n text");
+ code_edit->select(0, 0, 1, 0);
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == "test");
+ CHECK(code_edit->get_line(1) == " text");
+
+ /* Unindent even if last column of first line. */
+ code_edit->set_text(" test\n text");
+ code_edit->select(0, 5, 1, 1);
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == "test");
+ CHECK(code_edit->get_line(1) == "text");
+
+ /* Check selection is adjusted. */
+ code_edit->set_text(" test");
+ code_edit->select(0, 4, 0, 5);
+ code_edit->do_unindent();
+ CHECK(code_edit->get_selection_from_column() == 0);
+ CHECK(code_edit->get_selection_to_column() == 1);
+ CHECK(code_edit->get_line(0) == "test");
+ }
+
+ SUBCASE("[CodeEdit] auto indent") {
+ SUBCASE("[CodeEdit] auto indent tabs") {
+ code_edit->set_indent_size(4);
+ code_edit->set_auto_indent_enabled(true);
+ code_edit->set_indent_using_spaces(false);
+
+ /* Simple indent on new line. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test:");
+ SEND_GUI_ACTION(code_edit, "ui_text_newline");
+ CHECK(code_edit->get_line(0) == "test:");
+ CHECK(code_edit->get_line(1) == "\t");
+
+ /* new blank line should still indent. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test:");
+ SEND_GUI_ACTION(code_edit, "ui_text_newline_blank");
+ CHECK(code_edit->get_line(0) == "test:");
+ CHECK(code_edit->get_line(1) == "\t");
+
+ /* new line above should not indent. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test:");
+ SEND_GUI_ACTION(code_edit, "ui_text_newline_above");
+ CHECK(code_edit->get_line(0) == "");
+ CHECK(code_edit->get_line(1) == "test:");
+
+ /* Whitespace between symbol and caret is okay. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test: ");
+ SEND_GUI_ACTION(code_edit, "ui_text_newline");
+ CHECK(code_edit->get_line(0) == "test: ");
+ CHECK(code_edit->get_line(1) == "\t");
+
+ /* Comment between symbol and caret is okay. */
+ code_edit->add_comment_delimiter("#", "");
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test: # comment");
+ SEND_GUI_ACTION(code_edit, "ui_text_newline");
+ CHECK(code_edit->get_line(0) == "test: # comment");
+ CHECK(code_edit->get_line(1) == "\t");
+ code_edit->remove_comment_delimiter("#");
+
+ /* Strings between symbol and caret are not okay. */
+ code_edit->add_string_delimiter("#", "");
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test: # string");
+ SEND_GUI_ACTION(code_edit, "ui_text_newline");
+ CHECK(code_edit->get_line(0) == "test: # string");
+ CHECK(code_edit->get_line(1) == "");
+ code_edit->remove_comment_delimiter("#");
+
+ /* If between brace pairs an extra line is added. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test{}");
+ code_edit->set_caret_column(5);
+ SEND_GUI_ACTION(code_edit, "ui_text_newline");
+ CHECK(code_edit->get_line(0) == "test{");
+ CHECK(code_edit->get_line(1) == "\t");
+ CHECK(code_edit->get_line(2) == "}");
+
+ /* Except when we are going above. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test{}");
+ code_edit->set_caret_column(5);
+ SEND_GUI_ACTION(code_edit, "ui_text_newline_above");
+ CHECK(code_edit->get_line(0) == "");
+ CHECK(code_edit->get_line(1) == "test{}");
+
+ /* or below. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test{}");
+ code_edit->set_caret_column(5);
+ SEND_GUI_ACTION(code_edit, "ui_text_newline_blank");
+ CHECK(code_edit->get_line(0) == "test{}");
+ CHECK(code_edit->get_line(1) == "");
+ }
+
+ SUBCASE("[CodeEdit] auto indent spaces") {
+ code_edit->set_indent_size(4);
+ code_edit->set_auto_indent_enabled(true);
+ code_edit->set_indent_using_spaces(true);
+
+ /* Simple indent on new line. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test:");
+ SEND_GUI_ACTION(code_edit, "ui_text_newline");
+ CHECK(code_edit->get_line(0) == "test:");
+ CHECK(code_edit->get_line(1) == " ");
+
+ /* new blank line should still indent. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test:");
+ SEND_GUI_ACTION(code_edit, "ui_text_newline_blank");
+ CHECK(code_edit->get_line(0) == "test:");
+ CHECK(code_edit->get_line(1) == " ");
+
+ /* new line above should not indent. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test:");
+ SEND_GUI_ACTION(code_edit, "ui_text_newline_above");
+ CHECK(code_edit->get_line(0) == "");
+ CHECK(code_edit->get_line(1) == "test:");
+
+ /* Whitespace between symbol and caret is okay. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test: ");
+ SEND_GUI_ACTION(code_edit, "ui_text_newline");
+ CHECK(code_edit->get_line(0) == "test: ");
+ CHECK(code_edit->get_line(1) == " ");
+
+ /* Comment between symbol and caret is okay. */
+ code_edit->add_comment_delimiter("#", "");
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test: # comment");
+ SEND_GUI_ACTION(code_edit, "ui_text_newline");
+ CHECK(code_edit->get_line(0) == "test: # comment");
+ CHECK(code_edit->get_line(1) == " ");
+ code_edit->remove_comment_delimiter("#");
+
+ /* Strings between symbol and caret are not okay. */
+ code_edit->add_string_delimiter("#", "");
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test: # string");
+ SEND_GUI_ACTION(code_edit, "ui_text_newline");
+ CHECK(code_edit->get_line(0) == "test: # string");
+ CHECK(code_edit->get_line(1) == "");
+ code_edit->remove_comment_delimiter("#");
+
+ /* If between brace pairs an extra line is added. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test{}");
+ code_edit->set_caret_column(5);
+ SEND_GUI_ACTION(code_edit, "ui_text_newline");
+ CHECK(code_edit->get_line(0) == "test{");
+ CHECK(code_edit->get_line(1) == " ");
+ CHECK(code_edit->get_line(2) == "}");
+
+ /* Except when we are going above. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test{}");
+ code_edit->set_caret_column(5);
+ SEND_GUI_ACTION(code_edit, "ui_text_newline_above");
+ CHECK(code_edit->get_line(0) == "");
+ CHECK(code_edit->get_line(1) == "test{}");
+
+ /* or below. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test{}");
+ code_edit->set_caret_column(5);
+ SEND_GUI_ACTION(code_edit, "ui_text_newline_blank");
+ CHECK(code_edit->get_line(0) == "test{}");
+ CHECK(code_edit->get_line(1) == "");
+ }
+ }
+
+ memdelete(code_edit);
+}
+
+TEST_CASE("[SceneTree][CodeEdit] folding") {
+ CodeEdit *code_edit = memnew(CodeEdit);
+ SceneTree::get_singleton()->get_root()->add_child(code_edit);
+
+ SUBCASE("[CodeEdit] folding settings") {
+ code_edit->set_line_folding_enabled(true);
+ CHECK(code_edit->is_line_folding_enabled());
+
+ code_edit->set_line_folding_enabled(false);
+ CHECK_FALSE(code_edit->is_line_folding_enabled());
+ }
+
+ SUBCASE("[CodeEdit] folding") {
+ code_edit->set_line_folding_enabled(true);
+
+ // No indent.
+ code_edit->set_text("line1\nline2\nline3");
+ for (int i = 0; i < 2; i++) {
+ CHECK_FALSE(code_edit->can_fold_line(i));
+ code_edit->fold_line(i);
+ CHECK_FALSE(code_edit->is_line_folded(i));
+ }
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ // Indented lines.
+ code_edit->set_text("\tline1\n\tline2\n\tline3");
+ for (int i = 0; i < 2; i++) {
+ CHECK_FALSE(code_edit->can_fold_line(i));
+ code_edit->fold_line(i);
+ CHECK_FALSE(code_edit->is_line_folded(i));
+ }
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ // Indent.
+ code_edit->set_text("line1\n\tline2\nline3");
+ CHECK(code_edit->can_fold_line(0));
+ for (int i = 1; i < 2; i++) {
+ CHECK_FALSE(code_edit->can_fold_line(i));
+ code_edit->fold_line(i);
+ CHECK_FALSE(code_edit->is_line_folded(i));
+ }
+ code_edit->fold_line(0);
+ CHECK(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK_FALSE(code_edit->is_line_folded(2));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 2);
+
+ // Nested indents.
+ code_edit->set_text("line1\n\tline2\n\t\tline3\nline4");
+ CHECK(code_edit->can_fold_line(0));
+ CHECK(code_edit->can_fold_line(1));
+ for (int i = 2; i < 3; i++) {
+ CHECK_FALSE(code_edit->can_fold_line(i));
+ code_edit->fold_line(i);
+ CHECK_FALSE(code_edit->is_line_folded(i));
+ }
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(0));
+ CHECK(code_edit->is_line_folded(1));
+ CHECK_FALSE(code_edit->is_line_folded(2));
+ CHECK_FALSE(code_edit->is_line_folded(3));
+ CHECK(code_edit->get_next_visible_line_offset_from(2, 1) == 2);
+
+ code_edit->fold_line(0);
+ CHECK(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK_FALSE(code_edit->is_line_folded(2));
+ CHECK_FALSE(code_edit->is_line_folded(3));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 3);
+
+ // Check metadata.
+ CHECK(code_edit->get_folded_lines().size() == 1);
+ CHECK((int)code_edit->get_folded_lines()[0] == 0);
+
+ // Cannot unfold nested.
+ code_edit->unfold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ // (un)Fold all / toggle.
+ code_edit->unfold_line(0);
+ CHECK_FALSE(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ // Check metadata.
+ CHECK(code_edit->get_folded_lines().size() == 0);
+
+ code_edit->fold_all_lines();
+ CHECK(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 3);
+
+ code_edit->unfold_all_lines();
+ CHECK_FALSE(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ code_edit->toggle_foldable_line(0);
+ CHECK(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 3);
+
+ // Can also unfold from hidden line.
+ code_edit->unfold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ // Blank lines.
+ code_edit->set_text("line1\n\tline2\n\n\n\ttest\n\nline3");
+ CHECK(code_edit->can_fold_line(0));
+ for (int i = 1; i < code_edit->get_line_count(); i++) {
+ CHECK_FALSE(code_edit->can_fold_line(i));
+ code_edit->fold_line(i);
+ CHECK_FALSE(code_edit->is_line_folded(i));
+ }
+ code_edit->fold_line(0);
+ CHECK(code_edit->is_line_folded(0));
+ for (int i = 1; i < code_edit->get_line_count(); i++) {
+ CHECK_FALSE(code_edit->is_line_folded(i));
+ }
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 6);
+
+ // End of file.
+ code_edit->set_text("line1\n\tline2");
+ CHECK(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ // Comment & string blocks.
+ // Single line block
+ code_edit->add_comment_delimiter("#", "", true);
+ code_edit->set_text("#line1\n#\tline2");
+ CHECK(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ // Has to be full line.
+ code_edit->set_text("test #line1\n#\tline2");
+ CHECK_FALSE(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK_FALSE(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ code_edit->set_text("#line1\ntest #\tline2");
+ CHECK_FALSE(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK_FALSE(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ // String.
+ code_edit->add_string_delimiter("^", "", true);
+ code_edit->set_text("^line1\n^\tline2");
+ CHECK(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ // Has to be full line.
+ code_edit->set_text("test ^line1\n^\tline2");
+ CHECK_FALSE(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK_FALSE(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ code_edit->set_text("^line1\ntest ^\tline2");
+ CHECK_FALSE(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK_FALSE(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ // Multiline blocks.
+ code_edit->add_comment_delimiter("&", "&", false);
+ code_edit->set_text("&line1\n\tline2&");
+ CHECK(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ // Has to be full line.
+ code_edit->set_text("test &line1\n\tline2&");
+ CHECK_FALSE(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK_FALSE(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ code_edit->set_text("&line1\n\tline2& test");
+ CHECK_FALSE(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK_FALSE(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ // Strings.
+ code_edit->add_string_delimiter("$", "$", false);
+ code_edit->set_text("$line1\n\tline2$");
+ CHECK(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ // Has to be full line.
+ code_edit->set_text("test $line1\n\tline2$");
+ CHECK_FALSE(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK_FALSE(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ code_edit->set_text("$line1\n\tline2$ test");
+ CHECK_FALSE(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK_FALSE(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ // Non-indented comments/ strings.
+ // Single line
+ code_edit->set_text("test\n\tline1\n#line1\n#line2\n\ttest");
+ CHECK(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 4);
+
+ code_edit->set_text("test\n\tline1\n^line1\n^line2\n\ttest");
+ CHECK(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 4);
+
+ // Multiline
+ code_edit->set_text("test\n\tline1\n&line1\nline2&\n\ttest");
+ CHECK(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 4);
+
+ code_edit->set_text("test\n\tline1\n$line1\nline2$\n\ttest");
+ CHECK(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 4);
+ }
+
+ memdelete(code_edit);
+}
+
+TEST_CASE("[SceneTree][CodeEdit] completion") {
+ CodeEdit *code_edit = memnew(CodeEdit);
+ SceneTree::get_singleton()->get_root()->add_child(code_edit);
+
+ SUBCASE("[CodeEdit] auto brace completion") {
+ code_edit->set_auto_brace_completion_enabled(true);
+ CHECK(code_edit->is_auto_brace_completion_enabled());
+
+ code_edit->set_highlight_matching_braces_enabled(true);
+ CHECK(code_edit->is_highlight_matching_braces_enabled());
+
+ /* Try setters, any length. */
+ Dictionary auto_brace_completion_pairs;
+ auto_brace_completion_pairs["["] = "]";
+ auto_brace_completion_pairs["'"] = "'";
+ auto_brace_completion_pairs[";"] = "'";
+ auto_brace_completion_pairs["'''"] = "'''";
+ code_edit->set_auto_brace_completion_pairs(auto_brace_completion_pairs);
+ CHECK(code_edit->get_auto_brace_completion_pairs().size() == 4);
+ CHECK(code_edit->get_auto_brace_completion_pairs()["["] == "]");
+ CHECK(code_edit->get_auto_brace_completion_pairs()["'"] == "'");
+ CHECK(code_edit->get_auto_brace_completion_pairs()[";"] == "'");
+ CHECK(code_edit->get_auto_brace_completion_pairs()["'''"] == "'''");
+
+ ERR_PRINT_OFF;
+
+ /* No duplicate start keys. */
+ code_edit->add_auto_brace_completion_pair("[", "]");
+ CHECK(code_edit->get_auto_brace_completion_pairs().size() == 4);
+
+ /* No empty keys. */
+ code_edit->add_auto_brace_completion_pair("[", "");
+ CHECK(code_edit->get_auto_brace_completion_pairs().size() == 4);
+
+ code_edit->add_auto_brace_completion_pair("", "]");
+ CHECK(code_edit->get_auto_brace_completion_pairs().size() == 4);
+
+ code_edit->add_auto_brace_completion_pair("", "");
+ CHECK(code_edit->get_auto_brace_completion_pairs().size() == 4);
+
+ /* Must be a symbol. */
+ code_edit->add_auto_brace_completion_pair("a", "]");
+ CHECK(code_edit->get_auto_brace_completion_pairs().size() == 4);
+
+ code_edit->add_auto_brace_completion_pair("[", "a");
+ CHECK(code_edit->get_auto_brace_completion_pairs().size() == 4);
+
+ code_edit->add_auto_brace_completion_pair("a", "a");
+ CHECK(code_edit->get_auto_brace_completion_pairs().size() == 4);
+
+ ERR_PRINT_ON;
+
+ /* Check metadata. */
+ CHECK(code_edit->has_auto_brace_completion_open_key("["));
+ CHECK(code_edit->has_auto_brace_completion_open_key("'"));
+ CHECK(code_edit->has_auto_brace_completion_open_key(";"));
+ CHECK(code_edit->has_auto_brace_completion_open_key("'''"));
+ CHECK_FALSE(code_edit->has_auto_brace_completion_open_key("("));
+
+ CHECK(code_edit->has_auto_brace_completion_close_key("]"));
+ CHECK(code_edit->has_auto_brace_completion_close_key("'"));
+ CHECK(code_edit->has_auto_brace_completion_close_key("'''"));
+ CHECK_FALSE(code_edit->has_auto_brace_completion_close_key(")"));
+
+ CHECK(code_edit->get_auto_brace_completion_close_key("[") == "]");
+ CHECK(code_edit->get_auto_brace_completion_close_key("'") == "'");
+ CHECK(code_edit->get_auto_brace_completion_close_key(";") == "'");
+ CHECK(code_edit->get_auto_brace_completion_close_key("'''") == "'''");
+ CHECK(code_edit->get_auto_brace_completion_close_key("(").is_empty());
+
+ /* Check typing inserts closing pair. */
+ code_edit->clear();
+ SEND_GUI_KEY_EVENT(code_edit, KEY_BRACKETLEFT);
+ CHECK(code_edit->get_line(0) == "[]");
+
+ /* Should first match and insert smaller key. */
+ code_edit->clear();
+ SEND_GUI_KEY_EVENT(code_edit, KEY_APOSTROPHE);
+ CHECK(code_edit->get_line(0) == "''");
+ CHECK(code_edit->get_caret_column() == 1);
+
+ /* Move out from centre, Should match and insert larger key. */
+ SEND_GUI_ACTION(code_edit, "ui_text_caret_right");
+ SEND_GUI_KEY_EVENT(code_edit, KEY_APOSTROPHE);
+ CHECK(code_edit->get_line(0) == "''''''");
+ CHECK(code_edit->get_caret_column() == 3);
+
+ /* Backspace should remove all. */
+ SEND_GUI_ACTION(code_edit, "ui_text_backspace");
+ CHECK(code_edit->get_line(0).is_empty());
+
+ /* If in between and typing close key should "skip". */
+ SEND_GUI_KEY_EVENT(code_edit, KEY_BRACKETLEFT);
+ CHECK(code_edit->get_line(0) == "[]");
+ CHECK(code_edit->get_caret_column() == 1);
+ SEND_GUI_KEY_EVENT(code_edit, KEY_BRACKETRIGHT);
+ CHECK(code_edit->get_line(0) == "[]");
+ CHECK(code_edit->get_caret_column() == 2);
+
+ /* If current is char and inserting a string, do not autocomplete. */
+ code_edit->clear();
+ SEND_GUI_KEY_EVENT(code_edit, KEY_A);
+ SEND_GUI_KEY_EVENT(code_edit, KEY_APOSTROPHE);
+ CHECK(code_edit->get_line(0) == "A'");
+
+ /* If in comment, do not complete. */
+ code_edit->add_comment_delimiter("#", "");
+ code_edit->clear();
+ SEND_GUI_KEY_EVENT(code_edit, KEY_NUMBERSIGN);
+ SEND_GUI_KEY_EVENT(code_edit, KEY_APOSTROPHE);
+ CHECK(code_edit->get_line(0) == "#'");
+
+ /* If in string, and inserting string do not complete. */
+ code_edit->clear();
+ SEND_GUI_KEY_EVENT(code_edit, KEY_APOSTROPHE);
+ SEND_GUI_KEY_EVENT(code_edit, KEY_QUOTEDBL);
+ CHECK(code_edit->get_line(0) == "'\"'");
+ }
+
+ SUBCASE("[CodeEdit] autocomplete") {
+ code_edit->set_code_completion_enabled(true);
+ CHECK(code_edit->is_code_completion_enabled());
+
+ /* Set prefixes, single char only, disallow empty. */
+ TypedArray<String> completion_prefixes;
+ completion_prefixes.push_back("");
+ completion_prefixes.push_back(".");
+ completion_prefixes.push_back(".");
+ completion_prefixes.push_back(",,");
+
+ ERR_PRINT_OFF;
+ code_edit->set_code_completion_prefixes(completion_prefixes);
+ ERR_PRINT_ON;
+ completion_prefixes = code_edit->get_code_completion_prefixes();
+ CHECK(completion_prefixes.size() == 2);
+ CHECK(completion_prefixes.has("."));
+ CHECK(completion_prefixes.has(","));
+
+ code_edit->set_text("test\ntest");
+ CHECK(code_edit->get_text_for_code_completion() == String::chr(0xFFFF) + "test\ntest");
+ }
+
+ SUBCASE("[CodeEdit] autocomplete request") {
+ SIGNAL_WATCH(code_edit, "request_code_completion");
+ code_edit->set_code_completion_enabled(true);
+
+ Array signal_args;
+ signal_args.push_back(Array());
+
+ /* Force request. */
+ code_edit->request_code_completion();
+ SIGNAL_CHECK_FALSE("request_code_completion");
+ code_edit->request_code_completion(true);
+ SIGNAL_CHECK("request_code_completion", signal_args);
+
+ /* Manual request should force. */
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_query");
+ SIGNAL_CHECK("request_code_completion", signal_args);
+
+ /* Insert prefix. */
+ TypedArray<String> completion_prefixes;
+ completion_prefixes.push_back(".");
+ code_edit->set_code_completion_prefixes(completion_prefixes);
+
+ code_edit->insert_text_at_caret(".");
+ code_edit->request_code_completion();
+ SIGNAL_CHECK("request_code_completion", signal_args);
+
+ /* Should work with space too. */
+ code_edit->insert_text_at_caret(" ");
+ code_edit->request_code_completion();
+ SIGNAL_CHECK("request_code_completion", signal_args);
+
+ /* Should work when complete ends with prefix. */
+ code_edit->clear();
+ code_edit->insert_text_at_caret("t");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "test.", "test.");
+ code_edit->update_code_completion_options();
+ code_edit->confirm_code_completion();
+ CHECK(code_edit->get_line(0) == "test.");
+ SIGNAL_CHECK("request_code_completion", signal_args);
+
+ SIGNAL_UNWATCH(code_edit, "request_code_completion");
+ }
+
+ SUBCASE("[CodeEdit] autocomplete completion") {
+ CHECK(code_edit->get_code_completion_selected_index() == -1);
+ code_edit->set_code_completion_enabled(true);
+ CHECK(code_edit->get_code_completion_selected_index() == -1);
+
+ code_edit->update_code_completion_options();
+ code_edit->set_code_completion_selected_index(1);
+ CHECK(code_edit->get_code_completion_selected_index() == -1);
+ CHECK(code_edit->get_code_completion_option(0).size() == 0);
+ CHECK(code_edit->get_code_completion_options().size() == 0);
+
+ /* Adding does not update the list. */
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "item_0.", "item_0");
+
+ code_edit->set_code_completion_selected_index(1);
+ CHECK(code_edit->get_code_completion_selected_index() == -1);
+ CHECK(code_edit->get_code_completion_option(0).size() == 0);
+ CHECK(code_edit->get_code_completion_options().size() == 0);
+
+ /* After update, pending add should not be counted, */
+ /* also does not work on col 0 */
+ code_edit->insert_text_at_caret("i");
+ code_edit->update_code_completion_options();
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0.", "item_0", Color(1, 0, 0), RES(), Color(1, 0, 0));
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "item_1.", "item_1");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "item_2.", "item_2");
+
+ ERR_PRINT_OFF;
+ code_edit->set_code_completion_selected_index(1);
+ ERR_PRINT_ON;
+ CHECK(code_edit->get_code_completion_selected_index() == 0);
+ CHECK(code_edit->get_code_completion_option(0).size() == 6);
+ CHECK(code_edit->get_code_completion_options().size() == 1);
+
+ /* Check cancel closes completion. */
+ SEND_GUI_ACTION(code_edit, "ui_cancel");
+ CHECK(code_edit->get_code_completion_selected_index() == -1);
+
+ code_edit->update_code_completion_options();
+ CHECK(code_edit->get_code_completion_selected_index() == 0);
+ code_edit->set_code_completion_selected_index(1);
+ CHECK(code_edit->get_code_completion_selected_index() == 1);
+ CHECK(code_edit->get_code_completion_option(0).size() == 6);
+ CHECK(code_edit->get_code_completion_options().size() == 3);
+
+ /* Check data. */
+ Dictionary option = code_edit->get_code_completion_option(0);
+ CHECK((int)option["kind"] == (int)CodeEdit::CodeCompletionKind::KIND_CLASS);
+ CHECK(option["display_text"] == "item_0.");
+ CHECK(option["insert_text"] == "item_0");
+ CHECK(option["font_color"] == Color(1, 0, 0));
+ CHECK(option["icon"] == RES());
+ CHECK(option["default_value"] == Color(1, 0, 0));
+
+ /* Set size for mouse input. */
+ code_edit->set_size(Size2(100, 100));
+
+ /* Check input. */
+ SEND_GUI_ACTION(code_edit, "ui_end");
+ CHECK(code_edit->get_code_completion_selected_index() == 2);
+
+ SEND_GUI_ACTION(code_edit, "ui_home");
+ CHECK(code_edit->get_code_completion_selected_index() == 0);
+
+ SEND_GUI_ACTION(code_edit, "ui_page_down");
+ CHECK(code_edit->get_code_completion_selected_index() == 2);
+
+ SEND_GUI_ACTION(code_edit, "ui_page_up");
+ CHECK(code_edit->get_code_completion_selected_index() == 0);
+
+ SEND_GUI_ACTION(code_edit, "ui_up");
+ CHECK(code_edit->get_code_completion_selected_index() == 2);
+
+ SEND_GUI_ACTION(code_edit, "ui_down");
+ CHECK(code_edit->get_code_completion_selected_index() == 0);
+
+ SEND_GUI_KEY_EVENT(code_edit, KEY_T);
+ CHECK(code_edit->get_code_completion_selected_index() == 0);
+
+ SEND_GUI_ACTION(code_edit, "ui_left");
+ CHECK(code_edit->get_code_completion_selected_index() == 0);
+
+ SEND_GUI_ACTION(code_edit, "ui_right");
+ CHECK(code_edit->get_code_completion_selected_index() == 0);
+
+ SEND_GUI_ACTION(code_edit, "ui_text_backspace");
+ CHECK(code_edit->get_code_completion_selected_index() == 0);
+
+ Point2 caret_pos = code_edit->get_caret_draw_pos();
+ caret_pos.y -= code_edit->get_line_height();
+ SEND_GUI_MOUSE_EVENT(code_edit, caret_pos, MOUSE_BUTTON_WHEEL_DOWN, MOUSE_BUTTON_NONE);
+ CHECK(code_edit->get_code_completion_selected_index() == 1);
+
+ SEND_GUI_MOUSE_EVENT(code_edit, caret_pos, MOUSE_BUTTON_WHEEL_UP, MOUSE_BUTTON_NONE);
+ CHECK(code_edit->get_code_completion_selected_index() == 0);
+
+ /* Single click selects. */
+ SEND_GUI_MOUSE_EVENT(code_edit, caret_pos, MOUSE_BUTTON_LEFT, MOUSE_BUTTON_MASK_LEFT);
+ CHECK(code_edit->get_code_completion_selected_index() == 2);
+
+ /* Double click inserts. */
+ SEND_GUI_DOUBLE_CLICK(code_edit, caret_pos);
+ CHECK(code_edit->get_code_completion_selected_index() == -1);
+ CHECK(code_edit->get_line(0) == "item_2");
+
+ code_edit->set_auto_brace_completion_enabled(false);
+
+ /* Does nothing in readonly. */
+ code_edit->undo();
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0.", "item_0");
+ code_edit->update_code_completion_options();
+ code_edit->set_editable(false);
+ code_edit->confirm_code_completion();
+ code_edit->set_editable(true);
+ CHECK(code_edit->get_line(0) == "i");
+
+ /* Replace */
+ code_edit->clear();
+ code_edit->insert_text_at_caret("item_1 test");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0.", "item_0");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_replace");
+ CHECK(code_edit->get_line(0) == "item_0 test");
+
+ /* Replace string. */
+ code_edit->clear();
+ code_edit->insert_text_at_caret("\"item_1 test\"");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0.", "item_0");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_replace");
+ CHECK(code_edit->get_line(0) == "\"item_0\"");
+
+ /* Normal replace if no end is given. */
+ code_edit->clear();
+ code_edit->insert_text_at_caret("\"item_1 test");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0.", "item_0");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_replace");
+ CHECK(code_edit->get_line(0) == "\"item_0\" test");
+
+ /* Insert at completion. */
+ code_edit->clear();
+ code_edit->insert_text_at_caret("item_1 test");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0.", "item_0");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_accept");
+ CHECK(code_edit->get_line(0) == "item_01 test");
+
+ /* Insert at completion with string should have same output. */
+ code_edit->clear();
+ code_edit->insert_text_at_caret("\"item_1 test\"");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0.", "item_0");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_accept");
+ CHECK(code_edit->get_line(0) == "\"item_0\"1 test\"");
+
+ /* Merge symbol at end on insert text. */
+ /* End on completion entry. */
+ code_edit->clear();
+ code_edit->insert_text_at_caret("item_1 test");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0(", "item_0(");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_replace");
+ CHECK(code_edit->get_line(0) == "item_0( test");
+ CHECK(code_edit->get_caret_column() == 7);
+
+ /* End of text*/
+ code_edit->clear();
+ code_edit->insert_text_at_caret("item_1( test");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0", "item_0");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_replace");
+ CHECK(code_edit->get_line(0) == "item_0( test");
+ CHECK(code_edit->get_caret_column() == 6);
+
+ /* End of both. */
+ code_edit->clear();
+ code_edit->insert_text_at_caret("item_1( test");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0(", "item_0(");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_replace");
+ CHECK(code_edit->get_line(0) == "item_0( test");
+ CHECK(code_edit->get_caret_column() == 7);
+
+ /* Full set. */
+ /* End on completion entry. */
+ code_edit->clear();
+ code_edit->insert_text_at_caret("item_1 test");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0()", "item_0()");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_replace");
+ CHECK(code_edit->get_line(0) == "item_0() test");
+ CHECK(code_edit->get_caret_column() == 8);
+
+ /* End of text*/
+ code_edit->clear();
+ code_edit->insert_text_at_caret("item_1() test");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0", "item_0");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_replace");
+ CHECK(code_edit->get_line(0) == "item_0() test");
+ CHECK(code_edit->get_caret_column() == 6);
+
+ /* End of both. */
+ code_edit->clear();
+ code_edit->insert_text_at_caret("item_1() test");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0()", "item_0()");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_replace");
+ CHECK(code_edit->get_line(0) == "item_0() test");
+ CHECK(code_edit->get_caret_column() == 8);
+
+ /* Autobrace completion. */
+ code_edit->set_auto_brace_completion_enabled(true);
+
+ /* End on completion entry. */
+ code_edit->clear();
+ code_edit->insert_text_at_caret("item_1 test");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0(", "item_0(");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_replace");
+ CHECK(code_edit->get_line(0) == "item_0() test");
+ CHECK(code_edit->get_caret_column() == 7);
+
+ /* End of text*/
+ code_edit->clear();
+ code_edit->insert_text_at_caret("item_1( test");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0", "item_0");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_replace");
+ CHECK(code_edit->get_line(0) == "item_0( test");
+ CHECK(code_edit->get_caret_column() == 6);
+
+ /* End of both. */
+ code_edit->clear();
+ code_edit->insert_text_at_caret("item_1( test");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0(", "item_0(");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_replace");
+ CHECK(code_edit->get_line(0) == "item_0( test");
+ CHECK(code_edit->get_caret_column() == 7);
+
+ /* Full set. */
+ /* End on completion entry. */
+ code_edit->clear();
+ code_edit->insert_text_at_caret("item_1 test");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0()", "item_0()");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_replace");
+ CHECK(code_edit->get_line(0) == "item_0() test");
+ CHECK(code_edit->get_caret_column() == 8);
+
+ /* End of text*/
+ code_edit->clear();
+ code_edit->insert_text_at_caret("item_1() test");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0", "item_0");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_replace");
+ CHECK(code_edit->get_line(0) == "item_0() test");
+ CHECK(code_edit->get_caret_column() == 6);
+
+ /* End of both. */
+ code_edit->clear();
+ code_edit->insert_text_at_caret("item_1() test");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0()", "item_0()");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_replace");
+ CHECK(code_edit->get_line(0) == "item_0() test");
+ CHECK(code_edit->get_caret_column() == 8);
+ }
+
+ memdelete(code_edit);
+}
+
+TEST_CASE("[SceneTree][CodeEdit] symbol lookup") {
+ CodeEdit *code_edit = memnew(CodeEdit);
+ SceneTree::get_singleton()->get_root()->add_child(code_edit);
+
+ code_edit->set_symbol_lookup_on_click_enabled(true);
+ CHECK(code_edit->is_symbol_lookup_on_click_enabled());
+
+ /* Set size for mouse input. */
+ code_edit->set_size(Size2(100, 100));
+
+ code_edit->set_text("this is some text");
+
+ Point2 caret_pos = code_edit->get_caret_draw_pos();
+ caret_pos.x += 55;
+ SEND_GUI_MOUSE_EVENT(code_edit, caret_pos, MOUSE_BUTTON_NONE, MOUSE_BUTTON_NONE);
+ CHECK(code_edit->get_text_for_symbol_lookup() == "this is s" + String::chr(0xFFFF) + "ome text");
+
+ SIGNAL_WATCH(code_edit, "symbol_validate");
+
+#ifdef OSX_ENABLED
+ SEND_GUI_KEY_EVENT(code_edit, KEY_META);
+#else
+ SEND_GUI_KEY_EVENT(code_edit, KEY_CTRL);
+#endif
+
+ Array signal_args;
+ Array arg;
+ arg.push_back("some");
+ signal_args.push_back(arg);
+ SIGNAL_CHECK("symbol_validate", signal_args);
+
+ SIGNAL_UNWATCH(code_edit, "symbol_validate");
+
+ memdelete(code_edit);
+}
+
+TEST_CASE("[SceneTree][CodeEdit] line length guidelines") {
+ CodeEdit *code_edit = memnew(CodeEdit);
+ SceneTree::get_singleton()->get_root()->add_child(code_edit);
+
+ TypedArray<int> guide_lines;
+
+ code_edit->set_line_length_guidelines(guide_lines);
+ CHECK(code_edit->get_line_length_guidelines().size() == 0);
+
+ guide_lines.push_back(80);
+ guide_lines.push_back(120);
+
+ /* Order should be preserved. */
+ code_edit->set_line_length_guidelines(guide_lines);
+ CHECK((int)code_edit->get_line_length_guidelines()[0] == 80);
+ CHECK((int)code_edit->get_line_length_guidelines()[1] == 120);
+
+ memdelete(code_edit);
+}
+
+TEST_CASE("[SceneTree][CodeEdit] Backspace delete") {
+ CodeEdit *code_edit = memnew(CodeEdit);
+ SceneTree::get_singleton()->get_root()->add_child(code_edit);
+
+ /* Backspace with selection on first line. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test backspace");
+ code_edit->select(0, 0, 0, 5);
+ code_edit->backspace();
+ CHECK(code_edit->get_line(0) == "backspace");
+
+ /* Backspace with selection on first line and caret at the beginning of file. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test backspace");
+ code_edit->select(0, 0, 0, 5);
+ code_edit->set_caret_column(0);
+ code_edit->backspace();
+ CHECK(code_edit->get_line(0) == "backspace");
+
+ /* Move caret up to the previous line on backspace if carret is at the first column. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("line 1\nline 2");
+ code_edit->set_caret_line(1);
+ code_edit->set_caret_column(0);
+ code_edit->backspace();
+ CHECK(code_edit->get_line(0) == "line 1line 2");
+ CHECK(code_edit->get_caret_line() == 0);
+ CHECK(code_edit->get_caret_column() == 6);
+
+ /* Backspace delete all text if all text is selected. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("line 1\nline 2\nline 3");
+ code_edit->select_all();
+ code_edit->backspace();
+ CHECK(code_edit->get_text() == "");
+
+ /* Backspace at the beginning without selection has no effect. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("line 1\nline 2\nline 3");
+ code_edit->set_caret_line(0);
+ code_edit->set_caret_column(0);
+ code_edit->backspace();
+ CHECK(code_edit->get_text() == "line 1\nline 2\nline 3");
+
+ memdelete(code_edit);
+}
+
+} // namespace TestCodeEdit
+
+#endif // TEST_CODE_EDIT_H
diff --git a/tests/test_macros.h b/tests/test_macros.h
index a1f1932db4..2f0bc6dcfa 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,221 @@ int register_test_command(String p_command, TestFunc p_function);
register_test_command(m_command, m_function); \
DOCTEST_GLOBAL_NO_WARNINGS_END()
+// Utility macros to send an event actions to a given object
+// Requires Message Queue and InputMap to be setup.
+// SEND_GUI_ACTION - takes an object and a input map key. e.g SEND_GUI_ACTION(code_edit, "ui_text_newline").
+// SEND_GUI_KEY_EVENT - takes an object and a keycode set. e.g SEND_GUI_KEY_EVENT(code_edit, KEY_A | KEY_MASK_CMD).
+// SEND_GUI_MOUSE_EVENT - takes an object, position, mouse button and mouse mask e.g SEND_GUI_MOUSE_EVENT(code_edit, Vector2(50, 50), MOUSE_BUTTON_NONE, MOUSE_BUTTON_NONE);
+// SEND_GUI_DOUBLE_CLICK - takes an object and a postion. e.g SEND_GUI_DOUBLE_CLICK(code_edit, Vector2(50, 50));
+
+#define SEND_GUI_ACTION(m_object, m_action) \
+ { \
+ const List<Ref<InputEvent>> *events = InputMap::get_singleton()->action_get_events(m_action); \
+ const List<Ref<InputEvent>>::Element *first_event = events->front(); \
+ Ref<InputEventKey> event = first_event->get(); \
+ event->set_pressed(true); \
+ m_object->gui_input(event); \
+ MessageQueue::get_singleton()->flush(); \
+ }
+
+#define SEND_GUI_KEY_EVENT(m_object, m_input) \
+ { \
+ Ref<InputEventKey> event = InputEventKey::create_reference(m_input); \
+ event->set_pressed(true); \
+ m_object->gui_input(event); \
+ MessageQueue::get_singleton()->flush(); \
+ }
+
+#define _CREATE_GUI_MOUSE_EVENT(m_object, m_local_pos, m_input, m_mask) \
+ Ref<InputEventMouseButton> event; \
+ event.instantiate(); \
+ event->set_position(m_local_pos); \
+ event->set_button_index(m_input); \
+ event->set_button_mask(m_mask); \
+ event->set_pressed(true);
+
+#define SEND_GUI_MOUSE_EVENT(m_object, m_local_pos, m_input, m_mask) \
+ { \
+ _CREATE_GUI_MOUSE_EVENT(m_object, m_local_pos, m_input, m_mask); \
+ m_object->get_viewport()->push_input(event); \
+ MessageQueue::get_singleton()->flush(); \
+ }
+
+#define SEND_GUI_DOUBLE_CLICK(m_object, m_local_pos) \
+ { \
+ _CREATE_GUI_MOUSE_EVENT(m_object, m_local_pos, MOUSE_BUTTON_LEFT, MOUSE_BUTTON_LEFT); \
+ event->set_double_click(true); \
+ m_object->get_viewport()->push_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<Vector<Variant>>), 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<String, Vector<Vector<Variant>>> */
+ Map<String, Array> _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<Variant> 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..341fb1af61 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,147 @@ 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);
+
+ Error err = OK;
+ 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<Font>());
+
+ 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 (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);
diff --git a/tests/test_math.cpp b/tests/test_math.cpp
index a44040dfe2..72272382ce 100644
--- a/tests/test_math.cpp
+++ b/tests/test_math.cpp
@@ -296,8 +296,8 @@ public:
if (tk == TK_IDENTIFIER) {
String name = value;
if (use_next_class || p_known_class_name == name) {
- for (Map<int, String>::Element *E = namespace_stack.front(); E; E = E->next()) {
- class_name += E->get() + ".";
+ for (const KeyValue<int, String> &E : namespace_stack) {
+ class_name += E.value + ".";
}
class_name += String(value);
break;
diff --git a/tests/test_physics_2d.cpp b/tests/test_physics_2d.cpp
index 61b8951afa..f6619db8fd 100644
--- a/tests/test_physics_2d.cpp
+++ b/tests/test_physics_2d.cpp
@@ -199,7 +199,7 @@ protected:
if (mb.is_valid()) {
if (mb->is_pressed()) {
- Point2 p(mb->get_position().x, mb->get_position().y);
+ Point2 p = mb->get_position();
if (mb->get_button_index() == 1) {
ray_to = p;
@@ -248,20 +248,20 @@ protected:
return body;
}
- void _add_plane(const Vector2 &p_normal, real_t p_d) {
+ void _add_world_boundary(const Vector2 &p_normal, real_t p_d) {
PhysicsServer2D *ps = PhysicsServer2D::get_singleton();
Array arr;
arr.push_back(p_normal);
arr.push_back(p_d);
- RID plane = ps->world_margin_shape_create();
- ps->shape_set_data(plane, arr);
+ RID world_boundary = ps->world_boundary_shape_create();
+ ps->shape_set_data(world_boundary, arr);
RID plane_body = ps->body_create();
ps->body_set_mode(plane_body, PhysicsServer2D::BODY_MODE_STATIC);
ps->body_set_space(plane_body, space);
- ps->body_add_shape(plane_body, plane);
+ ps->body_add_shape(plane_body, world_boundary);
}
void _add_concave(const Vector<Vector2> &p_points, const Transform2D &p_xform = Transform2D()) {
@@ -381,9 +381,9 @@ public:
}
_add_concave(parr);
- //_add_plane(Vector2(0.0,-1).normalized(),-300);
- //_add_plane(Vector2(1,0).normalized(),50);
- //_add_plane(Vector2(-1,0).normalized(),-600);
+ //_add_world_boundary(Vector2(0.0,-1).normalized(),-300);
+ //_add_world_boundary(Vector2(1,0).normalized(),50);
+ //_add_world_boundary(Vector2(-1,0).normalized(),-600);
}
virtual bool process(double p_time) override {
diff --git a/tests/test_physics_3d.cpp b/tests/test_physics_3d.cpp
index ed49b60c71..d839ae26b7 100644
--- a/tests/test_physics_3d.cpp
+++ b/tests/test_physics_3d.cpp
@@ -100,18 +100,17 @@ protected:
return body;
}
- RID create_static_plane(const Plane &p_plane) {
+ RID create_world_boundary(const Plane &p_plane) {
PhysicsServer3D *ps = PhysicsServer3D::get_singleton();
- RID world_margin_shape = ps->shape_create(PhysicsServer3D::SHAPE_PLANE);
- ps->shape_set_data(world_margin_shape, p_plane);
+ RID world_boundary_shape = ps->shape_create(PhysicsServer3D::SHAPE_WORLD_BOUNDARY);
+ ps->shape_set_data(world_boundary_shape, p_plane);
RID b = ps->body_create();
ps->body_set_mode(b, PhysicsServer3D::BODY_MODE_STATIC);
ps->body_set_space(b, space);
- //todo set space
- ps->body_add_shape(b, world_margin_shape);
+ ps->body_add_shape(b, world_boundary_shape);
return b;
}
@@ -362,7 +361,7 @@ public:
RID mesh_instance = vs->instance_create2(capsule_mesh, scenario);
character = ps->body_create();
- ps->body_set_mode(character, PhysicsServer3D::BODY_MODE_DYNAMIC_LOCKED);
+ ps->body_set_mode(character, PhysicsServer3D::BODY_MODE_DYNAMIC_LINEAR);
ps->body_set_space(character, space);
//todo add space
ps->body_add_shape(character, capsule_shape);
@@ -391,12 +390,12 @@ public:
create_body(type, PhysicsServer3D::BODY_MODE_DYNAMIC, t);
}
- create_static_plane(Plane(Vector3(0, 1, 0), -1));
+ create_world_boundary(Plane(Vector3(0, 1, 0), -1));
}
void test_activate() {
create_body(PhysicsServer3D::SHAPE_BOX, PhysicsServer3D::BODY_MODE_DYNAMIC, Transform3D(Basis(), Vector3(0, 2, 0)), true);
- create_static_plane(Plane(Vector3(0, 1, 0), -1));
+ create_world_boundary(Plane(Vector3(0, 1, 0), -1));
}
virtual bool process(double p_time) override {
diff --git a/tests/test_rect2.h b/tests/test_rect2.h
index c5740167db..3d9fe5a32e 100644
--- a/tests/test_rect2.h
+++ b/tests/test_rect2.h
@@ -76,6 +76,12 @@ TEST_CASE("[Rect2] Basic getters") {
CHECK_MESSAGE(
rect.get_end().is_equal_approx(Vector2(1280, 820)),
"get_end() should return the expected value.");
+ CHECK_MESSAGE(
+ rect.get_center().is_equal_approx(Vector2(640, 460)),
+ "get_center() should return the expected value.");
+ CHECK_MESSAGE(
+ Rect2(0, 100, 1281, 721).get_center().is_equal_approx(Vector2(640.5, 460.5)),
+ "get_center() should return the expected value.");
}
TEST_CASE("[Rect2] Basic setters") {
@@ -288,6 +294,12 @@ TEST_CASE("[Rect2i] Basic getters") {
CHECK_MESSAGE(
rect.get_end() == Vector2i(1280, 820),
"get_end() should return the expected value.");
+ CHECK_MESSAGE(
+ rect.get_center() == Vector2i(640, 460),
+ "get_center() should return the expected value.");
+ CHECK_MESSAGE(
+ Rect2i(0, 100, 1281, 721).get_center() == Vector2i(640, 460),
+ "get_center() should return the expected value.");
}
TEST_CASE("[Rect2i] Basic setters") {
diff --git a/tests/test_shader_lang.cpp b/tests/test_shader_lang.cpp
index ad763b344e..5598852f29 100644
--- a/tests/test_shader_lang.cpp
+++ b/tests/test_shader_lang.cpp
@@ -120,38 +120,43 @@ static String dump_node_code(SL::Node *p_node, int p_level) {
case SL::Node::TYPE_SHADER: {
SL::ShaderNode *pnode = (SL::ShaderNode *)p_node;
- for (Map<StringName, SL::ShaderNode::Uniform>::Element *E = pnode->uniforms.front(); E; E = E->next()) {
+ for (const KeyValue<StringName, SL::ShaderNode::Uniform> &E : pnode->uniforms) {
String ucode = "uniform ";
- ucode += _prestr(E->get().precision);
- ucode += _typestr(E->get().type);
- ucode += " " + String(E->key());
-
- if (E->get().default_value.size()) {
- ucode += " = " + get_constant_text(E->get().type, E->get().default_value);
- }
+ ucode += _prestr(E.value.precision);
+ ucode += _typestr(E.value.type);
+ ucode += " " + String(E.key);
+ if (E.value.array_size > 0) {
+ ucode += "[";
+ ucode += itos(E.value.array_size);
+ ucode += "]";
+ } else {
+ if (E.value.default_value.size()) {
+ ucode += " = " + get_constant_text(E.value.type, E.value.default_value);
+ }
- static const char *hint_name[SL::ShaderNode::Uniform::HINT_MAX] = {
- "",
- "color",
- "range",
- "albedo",
- "normal",
- "black",
- "white"
- };
-
- if (E->get().hint) {
- ucode += " : " + String(hint_name[E->get().hint]);
+ static const char *hint_name[SL::ShaderNode::Uniform::HINT_MAX] = {
+ "",
+ "color",
+ "range",
+ "albedo",
+ "normal",
+ "black",
+ "white"
+ };
+
+ if (E.value.hint) {
+ ucode += " : " + String(hint_name[E.value.hint]);
+ }
}
code += ucode + "\n";
}
- for (Map<StringName, SL::ShaderNode::Varying>::Element *E = pnode->varyings.front(); E; E = E->next()) {
+ for (const KeyValue<StringName, SL::ShaderNode::Varying> &E : pnode->varyings) {
String vcode = "varying ";
- vcode += _prestr(E->get().precision);
- vcode += _typestr(E->get().type);
- vcode += " " + String(E->key());
+ vcode += _prestr(E.value.precision);
+ vcode += _typestr(E.value.type);
+ vcode += " " + String(E.key);
code += vcode + "\n";
}
@@ -183,8 +188,8 @@ static String dump_node_code(SL::Node *p_node, int p_level) {
//variables
code += _mktab(p_level - 1) + "{\n";
- for (Map<StringName, SL::BlockNode::Variable>::Element *E = bnode->variables.front(); E; E = E->next()) {
- code += _mktab(p_level) + _prestr(E->get().precision) + _typestr(E->get().type) + " " + E->key() + ";\n";
+ for (const KeyValue<StringName, SL::BlockNode::Variable> &E : bnode->variables) {
+ code += _mktab(p_level) + _prestr(E.value.precision) + _typestr(E.value.type) + " " + E.key + ";\n";
}
for (int i = 0; i < bnode->statements.size(); i++) {
diff --git a/tests/test_string.h b/tests/test_string.h
index 82b23d8a00..28d1089d2f 100644
--- a/tests/test_string.h
+++ b/tests/test_string.h
@@ -39,6 +39,7 @@
#include "core/os/main_loop.h"
#include "core/os/os.h"
#include "core/string/ustring.h"
+#include "core/variant/variant.h"
#include "tests/test_macros.h"
@@ -355,13 +356,23 @@ TEST_CASE("[String] Number to string") {
CHECK(String::num(-0.0) == "-0"); // Includes sign even for zero.
CHECK(String::num(3.141593) == "3.141593");
CHECK(String::num(3.141593, 3) == "3.142");
- CHECK(String::num_real(3.141593) == "3.141593");
CHECK(String::num_scientific(30000000) == "3e+07");
CHECK(String::num_int64(3141593) == "3141593");
CHECK(String::num_int64(0xA141593, 16) == "a141593");
CHECK(String::num_int64(0xA141593, 16, true) == "A141593");
CHECK(String::num(42.100023, 4) == "42.1"); // No trailing zeros.
+ // String::num_real tests.
+ CHECK(String::num_real(3.141593) == "3.141593");
+ CHECK(String::num_real(3.141) == "3.141"); // No trailing zeros.
+#ifdef REAL_T_IS_DOUBLE
+ CHECK_MESSAGE(String::num_real(Math_PI) == "3.14159265358979", "Prints the appropriate amount of digits for real_t = double.");
+ CHECK_MESSAGE(String::num_real(3.1415f) == "3.14149999618530", "Prints more digits of 32-bit float when real_t = double (ones that would be reliable for double).");
+#else
+ CHECK_MESSAGE(String::num_real(Math_PI) == "3.141593", "Prints the appropriate amount of digits for real_t = float.");
+ CHECK_MESSAGE(String::num_real(3.1415f) == "3.1415", "Prints only reliable digits of 32-bit float when real_t = float.");
+#endif // REAL_T_IS_DOUBLE
+
// Checks doubles with many decimal places.
CHECK(String::num(0.0000012345432123454321, -1) == "0.00000123454321"); // -1 uses 14 as sane default.
CHECK(String::num(0.0000012345432123454321) == "0.00000123454321"); // -1 is the default value.
@@ -1142,14 +1153,14 @@ TEST_CASE("[String] dedent") {
}
TEST_CASE("[String] Path functions") {
- static const char *path[4] = { "C:\\Godot\\project\\test.tscn", "/Godot/project/test.xscn", "../Godot/project/test.scn", "Godot\\test.doc" };
- static const char *base_dir[4] = { "C:\\Godot\\project", "/Godot/project", "../Godot/project", "Godot" };
- static const char *base_name[4] = { "C:\\Godot\\project\\test", "/Godot/project/test", "../Godot/project/test", "Godot\\test" };
- static const char *ext[4] = { "tscn", "xscn", "scn", "doc" };
- static const char *file[4] = { "test.tscn", "test.xscn", "test.scn", "test.doc" };
- static const bool abs[4] = { true, true, false, false };
-
- for (int i = 0; i < 4; i++) {
+ static const char *path[7] = { "C:\\Godot\\project\\test.tscn", "/Godot/project/test.xscn", "../Godot/project/test.scn", "Godot\\test.doc", "C:\\test.", "res://test", "/.test" };
+ static const char *base_dir[7] = { "C:\\Godot\\project", "/Godot/project", "../Godot/project", "Godot", "C:\\", "res://", "/" };
+ static const char *base_name[7] = { "C:\\Godot\\project\\test", "/Godot/project/test", "../Godot/project/test", "Godot\\test", "C:\\test", "res://test", "/" };
+ static const char *ext[7] = { "tscn", "xscn", "scn", "doc", "", "", "test" };
+ static const char *file[7] = { "test.tscn", "test.xscn", "test.scn", "test.doc", "test.", "test", ".test" };
+ static const bool abs[7] = { true, true, false, false, true, true, true };
+
+ for (int i = 0; i < 7; i++) {
CHECK(String(path[i]).get_base_dir() == base_dir[i]);
CHECK(String(path[i]).get_basename() == base_name[i]);
CHECK(String(path[i]).get_extension() == ext[i]);
@@ -1370,6 +1381,78 @@ TEST_CASE("[String] validate_node_name") {
String name_with_invalid_chars = "Name with invalid characters :.@removed!";
CHECK(name_with_invalid_chars.validate_node_name() == "Name with invalid characters removed!");
}
+
+TEST_CASE("[String] Variant indexed get") {
+ Variant s = String("abcd");
+ bool valid = false;
+ bool oob = true;
+
+ String r = s.get_indexed(1, valid, oob);
+
+ CHECK(valid);
+ CHECK_FALSE(oob);
+ CHECK_EQ(r, String("b"));
+}
+
+TEST_CASE("[String] Variant validated indexed get") {
+ Variant s = String("abcd");
+
+ Variant::ValidatedIndexedGetter getter = Variant::get_member_validated_indexed_getter(Variant::STRING);
+
+ Variant r;
+ bool oob = true;
+ getter(&s, 1, &r, &oob);
+
+ CHECK_FALSE(oob);
+ CHECK_EQ(r, String("b"));
+}
+
+TEST_CASE("[String] Variant ptr indexed get") {
+ String s("abcd");
+
+ Variant::PTRIndexedGetter getter = Variant::get_member_ptr_indexed_getter(Variant::STRING);
+
+ String r;
+ getter(&s, 1, &r);
+
+ CHECK_EQ(r, String("b"));
+}
+
+TEST_CASE("[String] Variant indexed set") {
+ Variant s = String("abcd");
+ bool valid = false;
+ bool oob = true;
+
+ s.set_indexed(1, String("z"), valid, oob);
+
+ CHECK(valid);
+ CHECK_FALSE(oob);
+ CHECK_EQ(s, String("azcd"));
+}
+
+TEST_CASE("[String] Variant validated indexed set") {
+ Variant s = String("abcd");
+
+ Variant::ValidatedIndexedSetter setter = Variant::get_member_validated_indexed_setter(Variant::STRING);
+
+ Variant v = String("z");
+ bool oob = true;
+ setter(&s, 1, &v, &oob);
+
+ CHECK_FALSE(oob);
+ CHECK_EQ(s, String("azcd"));
+}
+
+TEST_CASE("[String] Variant ptr indexed set") {
+ String s("abcd");
+
+ Variant::PTRIndexedSetter setter = Variant::get_member_ptr_indexed_setter(Variant::STRING);
+
+ String v("z");
+ setter(&s, 1, &v);
+
+ CHECK_EQ(s, String("azcd"));
+}
} // namespace TestString
#endif // TEST_STRING_H
diff --git a/tests/test_text_server.h b/tests/test_text_server.h
index b8ed4f89d0..af3df7bc79 100644
--- a/tests/test_text_server.h
+++ b/tests/test_text_server.h
@@ -41,19 +41,10 @@ namespace TestTextServer {
TEST_SUITE("[[TextServer]") {
TEST_CASE("[TextServer] Init, font loading and shaping") {
- TextServerManager *tsman = memnew(TextServerManager);
- Error err = OK;
-
- SUBCASE("[TextServer] Init") {
- for (int i = 0; i < TextServerManager::get_interface_count(); i++) {
- TextServer *ts = TextServerManager::initialize(i, err);
- TEST_FAIL_COND((err != OK || ts == nullptr), "Text server ", TextServerManager::get_interface_name(i), " init failed.");
- }
- }
-
SUBCASE("[TextServer] Loading fonts") {
- for (int i = 0; i < TextServerManager::get_interface_count(); i++) {
- TextServer *ts = TextServerManager::initialize(i, err);
+ for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {
+ Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);
+ TEST_FAIL_COND(ts.is_null(), "Invalid TS interface.");
RID font = ts->create_font();
ts->font_set_data_ptr(font, _font_NotoSans_Regular, _font_NotoSans_Regular_size);
@@ -63,8 +54,9 @@ TEST_SUITE("[[TextServer]") {
}
SUBCASE("[TextServer] Text layout: Font fallback") {
- for (int i = 0; i < TextServerManager::get_interface_count(); i++) {
- TextServer *ts = TextServerManager::initialize(i, err);
+ for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {
+ Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);
+ TEST_FAIL_COND(ts.is_null(), "Invalid TS interface.");
RID font1 = ts->create_font();
ts->font_set_data_ptr(font1, _font_NotoSans_Regular, _font_NotoSans_Regular_size);
@@ -83,9 +75,10 @@ TEST_SUITE("[[TextServer]") {
bool ok = ts->shaped_text_add_string(ctx, test, font, 16);
TEST_FAIL_COND(!ok, "Adding text to the buffer failed.");
- Vector<TextServer::Glyph> glyphs = ts->shaped_text_get_glyphs(ctx);
- TEST_FAIL_COND(glyphs.size() == 0, "Shaping failed");
- for (int j = 0; j < glyphs.size(); j++) {
+ const Glyph *glyphs = ts->shaped_text_get_glyphs(ctx);
+ int gl_size = ts->shaped_text_get_glyph_count(ctx);
+ TEST_FAIL_COND(gl_size == 0, "Shaping failed");
+ for (int j = 0; j < gl_size; j++) {
if (glyphs[j].start < 6) {
TEST_FAIL_COND(glyphs[j].font_rid != font[1], "Incorrect font selected.");
}
@@ -110,8 +103,9 @@ TEST_SUITE("[[TextServer]") {
}
SUBCASE("[TextServer] Text layout: BiDi") {
- for (int i = 0; i < TextServerManager::get_interface_count(); i++) {
- TextServer *ts = TextServerManager::initialize(i, err);
+ for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {
+ Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);
+ TEST_FAIL_COND(ts.is_null(), "Invalid TS interface.");
if (!ts->has_feature(TextServer::FEATURE_BIDI_LAYOUT)) {
continue;
@@ -134,9 +128,10 @@ TEST_SUITE("[[TextServer]") {
bool ok = ts->shaped_text_add_string(ctx, test, font, 16);
TEST_FAIL_COND(!ok, "Adding text to the buffer failed.");
- Vector<TextServer::Glyph> glyphs = ts->shaped_text_get_glyphs(ctx);
- TEST_FAIL_COND(glyphs.size() == 0, "Shaping failed");
- for (int j = 0; j < glyphs.size(); j++) {
+ const Glyph *glyphs = ts->shaped_text_get_glyphs(ctx);
+ int gl_size = ts->shaped_text_get_glyph_count(ctx);
+ TEST_FAIL_COND(gl_size == 0, "Shaping failed");
+ for (int j = 0; j < gl_size; j++) {
if (glyphs[j].count > 0) {
if (glyphs[j].start < 7) {
TEST_FAIL_COND(((glyphs[j].flags & TextServer::GRAPHEME_IS_RTL) == TextServer::GRAPHEME_IS_RTL), "Incorrect direction.");
@@ -160,8 +155,9 @@ TEST_SUITE("[[TextServer]") {
}
SUBCASE("[TextServer] Text layout: Line breaking") {
- for (int i = 0; i < TextServerManager::get_interface_count(); i++) {
- TextServer *ts = TextServerManager::initialize(i, err);
+ for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {
+ Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);
+ TEST_FAIL_COND(ts.is_null(), "Invalid TS interface.");
String test_1 = U"test test test";
// 5^ 10^
@@ -180,12 +176,17 @@ TEST_SUITE("[[TextServer]") {
bool ok = ts->shaped_text_add_string(ctx, test_1, font, 16);
TEST_FAIL_COND(!ok, "Adding text to the buffer failed.");
- Vector<Vector2i> brks = ts->shaped_text_get_line_breaks(ctx, 1);
- TEST_FAIL_COND(brks.size() != 3, "Invalid line breaks number.");
- if (brks.size() == 3) {
- TEST_FAIL_COND(brks[0] != Vector2i(0, 5), "Invalid line break position.");
- TEST_FAIL_COND(brks[1] != Vector2i(5, 10), "Invalid line break position.");
- TEST_FAIL_COND(brks[2] != Vector2i(10, 14), "Invalid line break position.");
+ PackedInt32Array brks = ts->shaped_text_get_line_breaks(ctx, 1);
+ TEST_FAIL_COND(brks.size() != 6, "Invalid line breaks number.");
+ if (brks.size() == 6) {
+ TEST_FAIL_COND(brks[0] != 0, "Invalid line break position.");
+ TEST_FAIL_COND(brks[1] != 5, "Invalid line break position.");
+
+ TEST_FAIL_COND(brks[2] != 5, "Invalid line break position.");
+ TEST_FAIL_COND(brks[3] != 10, "Invalid line break position.");
+
+ TEST_FAIL_COND(brks[4] != 10, "Invalid line break position.");
+ TEST_FAIL_COND(brks[5] != 14, "Invalid line break position.");
}
ts->free(ctx);
@@ -198,8 +199,9 @@ TEST_SUITE("[[TextServer]") {
}
SUBCASE("[TextServer] Text layout: Justification") {
- for (int i = 0; i < TextServerManager::get_interface_count(); i++) {
- TextServer *ts = TextServerManager::initialize(i, err);
+ for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {
+ Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);
+ TEST_FAIL_COND(ts.is_null(), "Invalid TS interface.");
RID font1 = ts->create_font();
ts->font_set_data_ptr(font1, _font_NotoSans_Regular, _font_NotoSans_Regular_size);
@@ -263,7 +265,6 @@ TEST_SUITE("[[TextServer]") {
font.clear();
}
}
- memdelete(tsman);
}
}
}; // namespace TestTextServer
diff --git a/tests/test_tools.h b/tests/test_tools.h
new file mode 100644
index 0000000000..3ea953cb07
--- /dev/null
+++ b/tests/test_tools.h
@@ -0,0 +1,61 @@
+/*************************************************************************/
+/* test_tools.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_TOOLS_H
+#define TEST_TOOLS_H
+
+#include "core/error/error_macros.h"
+
+struct ErrorDetector {
+ ErrorDetector() {
+ eh.errfunc = _detect_error;
+ eh.userdata = this;
+
+ add_error_handler(&eh);
+ }
+
+ ~ErrorDetector() {
+ remove_error_handler(&eh);
+ }
+
+ void clear() {
+ has_error = false;
+ }
+
+ static void _detect_error(void *p_self, const char *p_func, const char *p_file, int p_line, const char *p_error, const char *p_errorexp, ErrorHandlerType p_type) {
+ ErrorDetector *self = (ErrorDetector *)p_self;
+ self->has_error = true;
+ }
+
+ ErrorHandlerList eh;
+ bool has_error = false;
+};
+
+#endif // TEST_TOOLS_H
diff --git a/tests/test_translation.h b/tests/test_translation.h
index 52ff49bf9b..93c53bbbc9 100644
--- a/tests/test_translation.h
+++ b/tests/test_translation.h
@@ -35,6 +35,10 @@
#include "core/string/translation.h"
#include "core/string/translation_po.h"
+#ifdef TOOLS_ENABLED
+#include "editor/import/resource_importer_csv_translation.h"
+#endif
+
#include "thirdparty/doctest/doctest.h"
namespace TestTranslation {
@@ -145,6 +149,33 @@ TEST_CASE("[OptimizedTranslation] Generate from Translation and read messages")
CHECK(messages.size() == 0);
}
+#ifdef TOOLS_ENABLED
+TEST_CASE("[Translation] CSV import") {
+ Ref<ResourceImporterCSVTranslation> import_csv_translation = memnew(ResourceImporterCSVTranslation);
+
+ Map<StringName, Variant> options;
+ options["compress"] = false;
+ options["delimiter"] = 0;
+
+ List<String> gen_files;
+
+ Error result = import_csv_translation->import(TestUtils::get_data_path("translations.csv"),
+ "", options, nullptr, &gen_files);
+ CHECK(result == OK);
+ CHECK(gen_files.size() == 2);
+
+ for (const String &file : gen_files) {
+ Ref<Translation> translation = ResourceLoader::load(file);
+ CHECK(translation.is_valid());
+ TranslationServer::get_singleton()->add_translation(translation);
+ }
+
+ TranslationServer::get_singleton()->set_locale("de");
+
+ CHECK(Object().tr("GOOD_MORNING", "") == "Guten Morgen");
+}
+#endif // TOOLS_ENABLED
+
} // namespace TestTranslation
#endif // TEST_TRANSLATION_H
diff --git a/tests/test_validate_testing.h b/tests/test_validate_testing.h
index f301047509..40b255e18a 100644
--- a/tests/test_validate_testing.h
+++ b/tests/test_validate_testing.h
@@ -34,6 +34,7 @@
#include "core/os/os.h"
#include "tests/test_macros.h"
+#include "tests/test_tools.h"
TEST_SUITE("Validate tests") {
TEST_CASE("Always pass") {
@@ -182,6 +183,17 @@ TEST_SUITE("Validate tests") {
// doctest string concatenation.
CHECK_MESSAGE(true, var, " ", vec2, " ", rect2, " ", color);
}
+ TEST_CASE("Detect error messages") {
+ ErrorDetector ed;
+
+ REQUIRE_FALSE(ed.has_error);
+
+ ERR_PRINT_OFF;
+ ERR_PRINT("Still waiting for Godot!");
+ ERR_PRINT_ON;
+
+ REQUIRE(ed.has_error);
+ }
}
#endif // TEST_VALIDATE_TESTING_H
diff --git a/tests/test_vector.h b/tests/test_vector.h
index 02c56e59f6..bfdf389aa7 100644
--- a/tests/test_vector.h
+++ b/tests/test_vector.h
@@ -472,6 +472,19 @@ TEST_CASE("[Vector] Sort custom") {
CHECK(vector[7] == "World");
}
+TEST_CASE("[Vector] Search") {
+ Vector<int> vector;
+ vector.push_back(1);
+ vector.push_back(2);
+ vector.push_back(3);
+ vector.push_back(5);
+ vector.push_back(8);
+ CHECK(vector.bsearch(2, true) == 1);
+ CHECK(vector.bsearch(2, false) == 2);
+ CHECK(vector.bsearch(5, true) == 3);
+ CHECK(vector.bsearch(5, false) == 4);
+}
+
TEST_CASE("[Vector] Operators") {
Vector<int> vector;
vector.push_back(2);