summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/data/translations.csv5
-rw-r--r--tests/test_aabb.h3
-rw-r--r--tests/test_array.h51
-rw-r--r--tests/test_astar.h2
-rw-r--r--tests/test_class_db.h64
-rw-r--r--tests/test_code_edit.h3190
-rw-r--r--tests/test_config_file.h11
-rw-r--r--tests/test_curve.h35
-rw-r--r--tests/test_file_access.h29
-rw-r--r--tests/test_geometry_2d.h33
-rw-r--r--tests/test_geometry_3d.h36
-rw-r--r--tests/test_macros.h219
-rw-r--r--tests/test_main.cpp145
-rw-r--r--tests/test_math.cpp12
-rw-r--r--tests/test_object.h8
-rw-r--r--tests/test_physics_2d.cpp18
-rw-r--r--tests/test_physics_3d.cpp19
-rw-r--r--tests/test_rect2.h12
-rw-r--r--tests/test_render.cpp14
-rw-r--r--tests/test_shader_lang.cpp57
-rw-r--r--tests/test_string.h127
-rw-r--r--tests/test_text_server.h107
-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_variant.h39
-rw-r--r--tests/test_vector.h13
27 files changed, 4163 insertions, 190 deletions
diff --git a/tests/data/translations.csv b/tests/data/translations.csv
index 4c9ad4996a..8cb7b800c5 100644
--- a/tests/data/translations.csv
+++ b/tests/data/translations.csv
@@ -1,3 +1,8 @@
keys,en,de
GOOD_MORNING,"Good Morning","Guten Morgen"
GOOD_EVENING,"Good Evening",""
+Without quotes,"With, comma","With ""inner"" quotes","With ""inner"", quotes"","" and comma","With ""inner
+split"" quotes and
+line breaks","With \nnewline chars"
+Some other~delimiter~should still work, shouldn't it?
+What about tab separated lines, good?
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_astar.h b/tests/test_astar.h
index 12664a5ff1..137c477946 100644
--- a/tests/test_astar.h
+++ b/tests/test_astar.h
@@ -63,7 +63,7 @@ public:
}
// Disable heuristic completely.
- float _compute_cost(int p_from, int p_to) {
+ real_t _compute_cost(int p_from, int p_to) {
if (p_from == A && p_to == C) {
return 1000;
}
diff --git a/tests/test_class_db.h b/tests/test_class_db.h
index 29edf5a4a0..20397bb144 100644
--- a/tests/test_class_db.h
+++ b/tests/test_class_db.h
@@ -108,9 +108,9 @@ struct ExposedClass {
List<SignalData> signals_;
const PropertyData *find_property_by_name(const StringName &p_name) const {
- for (const List<PropertyData>::Element *E = properties.front(); E; E = E->next()) {
- if (E->get().name == p_name) {
- return &E->get();
+ for (const PropertyData &E : properties) {
+ if (E.name == p_name) {
+ return &E;
}
}
@@ -118,9 +118,9 @@ struct ExposedClass {
}
const MethodData *find_method_by_name(const StringName &p_name) const {
- for (const List<MethodData>::Element *E = methods.front(); E; E = E->next()) {
- if (E->get().name == p_name) {
- return &E->get();
+ for (const MethodData &E : methods) {
+ if (E.name == p_name) {
+ return &E;
}
}
@@ -395,8 +395,8 @@ void validate_method(const Context &p_context, const ExposedClass &p_class, cons
}
}
- for (const List<ArgumentData>::Element *F = p_method.arguments.front(); F; F = F->next()) {
- const ArgumentData &arg = F->get();
+ for (const ArgumentData &F : p_method.arguments) {
+ const ArgumentData &arg = F;
const ExposedClass *arg_class = p_context.find_exposed_class(arg.type);
if (arg_class) {
@@ -427,8 +427,8 @@ void validate_method(const Context &p_context, const ExposedClass &p_class, cons
}
void validate_signal(const Context &p_context, const ExposedClass &p_class, const SignalData &p_signal) {
- for (const List<ArgumentData>::Element *F = p_signal.arguments.front(); F; F = F->next()) {
- const ArgumentData &arg = F->get();
+ for (const ArgumentData &F : p_signal.arguments) {
+ const ArgumentData &arg = F;
const ExposedClass *arg_class = p_context.find_exposed_class(arg.type);
if (arg_class) {
@@ -469,16 +469,16 @@ void validate_class(const Context &p_context, const ExposedClass &p_exposed_clas
TEST_FAIL_COND((is_derived_type && !p_context.exposed_classes.has(p_exposed_class.base)),
"Base type '", p_exposed_class.base.operator String(), "' does not exist, for class '", p_exposed_class.name, "'.");
- for (const List<PropertyData>::Element *F = p_exposed_class.properties.front(); F; F = F->next()) {
- validate_property(p_context, p_exposed_class, F->get());
+ for (const PropertyData &F : p_exposed_class.properties) {
+ validate_property(p_context, p_exposed_class, F);
}
- for (const List<MethodData>::Element *F = p_exposed_class.methods.front(); F; F = F->next()) {
- validate_method(p_context, p_exposed_class, F->get());
+ for (const MethodData &F : p_exposed_class.methods) {
+ validate_method(p_context, p_exposed_class, F);
}
- for (const List<SignalData>::Element *F = p_exposed_class.signals_.front(); F; F = F->next()) {
- validate_signal(p_context, p_exposed_class, F->get());
+ for (const SignalData &F : p_exposed_class.signals_) {
+ validate_signal(p_context, p_exposed_class, F);
}
}
@@ -526,10 +526,8 @@ void add_exposed_classes(Context &r_context) {
Map<StringName, StringName> accessor_methods;
- for (const List<PropertyInfo>::Element *E = property_list.front(); E; E = E->next()) {
- const PropertyInfo &property = E->get();
-
- if (property.usage & PROPERTY_USAGE_GROUP || property.usage & PROPERTY_USAGE_SUBGROUP || property.usage & PROPERTY_USAGE_CATEGORY) {
+ for (const PropertyInfo &property : property_list) {
+ if (property.usage & PROPERTY_USAGE_GROUP || property.usage & PROPERTY_USAGE_SUBGROUP || property.usage & PROPERTY_USAGE_CATEGORY || (property.type == Variant::NIL && property.usage & PROPERTY_USAGE_ARRAY)) {
continue;
}
@@ -561,8 +559,8 @@ void add_exposed_classes(Context &r_context) {
ClassDB::get_method_list(class_name, &method_list, true);
method_list.sort();
- for (List<MethodInfo>::Element *E = method_list.front(); E; E = E->next()) {
- const MethodInfo &method_info = E->get();
+ for (const MethodInfo &E : method_list) {
+ const MethodInfo &method_info = E;
int argc = method_info.arguments.size();
@@ -667,8 +665,8 @@ void add_exposed_classes(Context &r_context) {
// Methods starting with an underscore are ignored unless they're virtual or used as a property setter or getter.
if (!method.is_virtual && String(method.name)[0] == '_') {
- for (const List<PropertyData>::Element *F = exposed_class.properties.front(); F; F = F->next()) {
- const PropertyData &prop = F->get();
+ for (const PropertyData &F : exposed_class.properties) {
+ const PropertyData &prop = F;
if (prop.setter == method.name || prop.getter == method.name) {
exposed_class.methods.push_back(method);
@@ -752,8 +750,8 @@ void add_exposed_classes(Context &r_context) {
enum_.name = *k;
const List<StringName> &enum_constants = enum_map.get(*k);
- for (const List<StringName>::Element *E = enum_constants.front(); E; E = E->next()) {
- const StringName &constant_name = E->get();
+ for (const StringName &E : enum_constants) {
+ const StringName &constant_name = E;
TEST_FAIL_COND(String(constant_name).find("::") != -1,
"Enum constant contains '::', check bindings to remove the scope: '",
String(class_name), ".", String(enum_.name), ".", String(constant_name), "'.");
@@ -774,12 +772,12 @@ void add_exposed_classes(Context &r_context) {
r_context.enum_types.push_back(String(class_name) + "." + String(*k));
}
- for (const List<String>::Element *E = constants.front(); E; E = E->next()) {
- const String &constant_name = E->get();
+ for (const String &E : constants) {
+ const String &constant_name = E;
TEST_FAIL_COND(constant_name.find("::") != -1,
"Constant contains '::', check bindings to remove the scope: '",
String(class_name), ".", constant_name, "'.");
- int *value = class_info->constant_map.getptr(StringName(E->get()));
+ int *value = class_info->constant_map.getptr(StringName(E));
TEST_FAIL_COND(!value, "Missing constant value: '", String(class_name), ".", String(constant_name), "'.");
ConstantData constant;
@@ -829,8 +827,8 @@ void add_global_enums(Context &r_context) {
}
}
- for (List<EnumData>::Element *E = r_context.global_enums.front(); E; E = E->next()) {
- r_context.enum_types.push_back(E->get().name);
+ for (const EnumData &E : r_context.global_enums) {
+ r_context.enum_types.push_back(E.name);
}
}
@@ -840,10 +838,10 @@ void add_global_enums(Context &r_context) {
hardcoded_enums.push_back("Vector2i.Axis");
hardcoded_enums.push_back("Vector3.Axis");
hardcoded_enums.push_back("Vector3i.Axis");
- for (List<StringName>::Element *E = hardcoded_enums.front(); E; E = E->next()) {
+ for (const StringName &E : hardcoded_enums) {
// These enums are not generated and must be written manually (e.g.: Vector3.Axis)
// Here, we assume core types do not begin with underscore
- r_context.enum_types.push_back(E->get());
+ r_context.enum_types.push_back(E);
}
}
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_config_file.h b/tests/test_config_file.h
index 33fd30ffa1..e3f40e16fd 100644
--- a/tests/test_config_file.h
+++ b/tests/test_config_file.h
@@ -122,6 +122,8 @@ TEST_CASE("[ConfigFile] Saving file") {
config_file.set_value("player", "position", Vector2(3, 4));
config_file.set_value("graphics", "antialiasing", true);
config_file.set_value("graphics", "antiAliasing", false);
+ config_file.set_value("quoted", String::utf8("静音"), 42);
+ config_file.set_value("quoted", "a=b", 7);
#ifdef WINDOWS_ENABLED
const String config_path = OS::get_singleton()->get_environment("TEMP").plus_file("config.ini");
@@ -132,7 +134,7 @@ TEST_CASE("[ConfigFile] Saving file") {
config_file.save(config_path);
// Expected contents of the saved ConfigFile.
- const String contents = R"([player]
+ const String contents = String::utf8(R"([player]
name="Unnamed Player"
tagline="Waiting
@@ -145,7 +147,12 @@ position=Vector2(3, 4)
antialiasing=true
antiAliasing=false
-)";
+
+[quoted]
+
+"静音"=42
+"a=b"=7
+)");
FileAccessRef file = FileAccess::open(config_path, FileAccess::READ);
CHECK_MESSAGE(file->get_as_utf8_string() == contents,
diff --git a/tests/test_curve.h b/tests/test_curve.h
index 7eeee86f32..e079905e35 100644
--- a/tests/test_curve.h
+++ b/tests/test_curve.h
@@ -216,6 +216,41 @@ TEST_CASE("[Curve] Custom curve with linear tangents") {
Math::is_equal_approx(curve->interpolate_baked(0.7), (real_t)0.8),
"Custom free curve should return the expected baked value at offset 0.7 after removing point at invalid index 10.");
}
+
+TEST_CASE("[Curve2D] Linear sampling should return exact value") {
+ Ref<Curve2D> curve = memnew(Curve2D);
+ int len = 2048;
+
+ curve->add_point(Vector2(0, 0));
+ curve->add_point(Vector2((float)len, 0));
+
+ float baked_length = curve->get_baked_length();
+ CHECK((float)len == baked_length);
+
+ for (int i = 0; i < len; i++) {
+ float expected = (float)i;
+ Vector2 pos = curve->interpolate_baked(expected);
+ CHECK_MESSAGE(pos.x == expected, "interpolate_baked should return exact value");
+ }
+}
+
+TEST_CASE("[Curve3D] Linear sampling should return exact value") {
+ Ref<Curve3D> curve = memnew(Curve3D);
+ int len = 2048;
+
+ curve->add_point(Vector3(0, 0, 0));
+ curve->add_point(Vector3((float)len, 0, 0));
+
+ float baked_length = curve->get_baked_length();
+ CHECK((float)len == baked_length);
+
+ for (int i = 0; i < len; i++) {
+ float expected = (float)i;
+ Vector3 pos = curve->interpolate_baked(expected);
+ CHECK_MESSAGE(pos.x == expected, "interpolate_baked should return exact value");
+ }
+}
+
} // namespace TestCurve
#endif // TEST_CURVE_H
diff --git a/tests/test_file_access.h b/tests/test_file_access.h
index cb74e08a0d..b3da16c1d1 100644
--- a/tests/test_file_access.h
+++ b/tests/test_file_access.h
@@ -37,12 +37,12 @@
namespace TestFileAccess {
TEST_CASE("[FileAccess] CSV read") {
- FileAccess *f = FileAccess::open(TestUtils::get_data_path("translations.csv"), FileAccess::READ);
+ FileAccessRef f = FileAccess::open(TestUtils::get_data_path("translations.csv"), FileAccess::READ);
- Vector<String> header = f->get_csv_line(); // Default delimiter: ","
+ Vector<String> header = f->get_csv_line(); // Default delimiter: ",".
REQUIRE(header.size() == 3);
- Vector<String> row1 = f->get_csv_line(",");
+ Vector<String> row1 = f->get_csv_line(","); // Explicit delimiter, should be the same.
REQUIRE(row1.size() == 3);
CHECK(row1[0] == "GOOD_MORNING");
CHECK(row1[1] == "Good Morning");
@@ -53,12 +53,31 @@ TEST_CASE("[FileAccess] CSV read") {
CHECK(row2[0] == "GOOD_EVENING");
CHECK(row2[1] == "Good Evening");
CHECK(row2[2] == ""); // Use case: not yet translated!
-
// https://github.com/godotengine/godot/issues/44269
CHECK_MESSAGE(row2[2] != "\"", "Should not parse empty string as a single double quote.");
+ Vector<String> row3 = f->get_csv_line();
+ REQUIRE(row3.size() == 6);
+ CHECK(row3[0] == "Without quotes");
+ CHECK(row3[1] == "With, comma");
+ CHECK(row3[2] == "With \"inner\" quotes");
+ CHECK(row3[3] == "With \"inner\", quotes\",\" and comma");
+ CHECK(row3[4] == "With \"inner\nsplit\" quotes and\nline breaks");
+ CHECK(row3[5] == "With \\nnewline chars"); // Escaped, not an actual newline.
+
+ Vector<String> row4 = f->get_csv_line("~"); // Custom delimiter, makes inline commas easier.
+ REQUIRE(row4.size() == 3);
+ CHECK(row4[0] == "Some other");
+ CHECK(row4[1] == "delimiter");
+ CHECK(row4[2] == "should still work, shouldn't it?");
+
+ Vector<String> row5 = f->get_csv_line("\t"); // Tab separated variables.
+ REQUIRE(row5.size() == 3);
+ CHECK(row5[0] == "What about");
+ CHECK(row5[1] == "tab separated");
+ CHECK(row5[2] == "lines, good?");
+
f->close();
- memdelete(f);
}
} // namespace TestFileAccess
diff --git a/tests/test_geometry_2d.h b/tests/test_geometry_2d.h
index 32d4114a1c..25af8c355e 100644
--- a/tests/test_geometry_2d.h
+++ b/tests/test_geometry_2d.h
@@ -51,8 +51,6 @@ TEST_CASE("[Geometry2D] Point in circle") {
CHECK_FALSE(Geometry2D::is_point_in_circle(Vector2(7, -42), Vector2(4, -40), 3.5));
// This tests points on the edge of the circle. They are treated as being inside the circle.
- // In `is_point_in_triangle` and `is_point_in_polygon` they are treated as being outside, so in order the make
- // the behaviour consistent this may change in the future (see issue #44717 and PR #44274).
CHECK(Geometry2D::is_point_in_circle(Vector2(1.0, 0.0), Vector2(0, 0), 1.0));
CHECK(Geometry2D::is_point_in_circle(Vector2(0.0, -1.0), Vector2(0, 0), 1.0));
}
@@ -66,7 +64,7 @@ TEST_CASE("[Geometry2D] Point in triangle") {
CHECK_FALSE(Geometry2D::is_point_in_triangle(Vector2(0, 0), Vector2(1, 4), Vector2(3, 2), Vector2(5, 4)));
// This tests points on the edge of the triangle. They are treated as being outside the triangle.
- // In `is_point_in_circle` they are treated as being inside, so in order the make
+ // In `is_point_in_circle` and `is_point_in_polygon` they are treated as being inside, so in order the make
// the behaviour consistent this may change in the future (see issue #44717 and PR #44274).
CHECK_FALSE(Geometry2D::is_point_in_triangle(Vector2(1, 1), Vector2(-1, 1), Vector2(0, -1), Vector2(1, 1)));
CHECK_FALSE(Geometry2D::is_point_in_triangle(Vector2(0, 1), Vector2(-1, 1), Vector2(0, -1), Vector2(1, 1)));
@@ -95,11 +93,16 @@ TEST_CASE("[Geometry2D] Point in polygon") {
CHECK(Geometry2D::is_point_in_polygon(Vector2(370, 55), p));
CHECK(Geometry2D::is_point_in_polygon(Vector2(-160, 190), p));
- // This tests points on the edge of the polygon. They are treated as being outside the polygon.
- // In `is_point_in_circle` they are treated as being inside, so in order the make
- // the behaviour consistent this may change in the future (see issue #44717 and PR #44274).
- CHECK_FALSE(Geometry2D::is_point_in_polygon(Vector2(68, 112), p));
- CHECK_FALSE(Geometry2D::is_point_in_polygon(Vector2(-88, 120), p));
+ // This tests points on the edge of the polygon. They are treated as being inside the polygon.
+ int c = p.size();
+ for (int i = 0; i < c; i++) {
+ const Vector2 &p1 = p[i];
+ CHECK(Geometry2D::is_point_in_polygon(p1, p));
+
+ const Vector2 &p2 = p[(i + 1) % c];
+ Vector2 midpoint((p1 + p2) * 0.5);
+ CHECK(Geometry2D::is_point_in_polygon(midpoint, p));
+ }
}
TEST_CASE("[Geometry2D] Polygon clockwise") {
@@ -140,9 +143,21 @@ TEST_CASE("[Geometry2D] Segment intersection.") {
CHECK(r.is_equal_approx(Vector2(0, 0)));
CHECK_FALSE(Geometry2D::segment_intersects_segment(Vector2(-1, 1), Vector2(1, -1), Vector2(1, 1), Vector2(0.1, 0.1), &r));
+ CHECK_FALSE(Geometry2D::segment_intersects_segment(Vector2(-1, 1), Vector2(1, -1), Vector2(0.1, 0.1), Vector2(1, 1), &r));
+
CHECK_FALSE_MESSAGE(
- Geometry2D::segment_intersects_segment(Vector2(-1, 1), Vector2(1, -1), Vector2(0, 1), Vector2(1, -1), &r),
+ Geometry2D::segment_intersects_segment(Vector2(-1, 1), Vector2(1, -1), Vector2(0, 1), Vector2(2, -1), &r),
"Parallel segments should not intersect.");
+
+ CHECK_MESSAGE(
+ Geometry2D::segment_intersects_segment(Vector2(0, 0), Vector2(0, 1), Vector2(0, 0), Vector2(1, 0), &r),
+ "Touching segments should intersect.");
+ CHECK(r.is_equal_approx(Vector2(0, 0)));
+
+ CHECK_MESSAGE(
+ Geometry2D::segment_intersects_segment(Vector2(0, 1), Vector2(0, 0), Vector2(0, 0), Vector2(1, 0), &r),
+ "Touching segments should intersect.");
+ CHECK(r.is_equal_approx(Vector2(0, 0)));
}
TEST_CASE("[Geometry2D] Closest point to segment") {
diff --git a/tests/test_geometry_3d.h b/tests/test_geometry_3d.h
index 2b2a424b2b..ae30737fe2 100644
--- a/tests/test_geometry_3d.h
+++ b/tests/test_geometry_3d.h
@@ -28,8 +28,8 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef TEST_3D_GEOMETRY_H
-#define TEST_3D_GEOMETRY_H
+#ifndef TEST_GEOMETRY_3D_H
+#define TEST_GEOMETRY_3D_H
#include "core/math/geometry_3d.h"
#include "core/math/plane.h"
@@ -38,7 +38,7 @@
#include "tests/test_macros.h"
#include "vector"
-namespace Test3DGeometry {
+namespace TestGeometry3D {
TEST_CASE("[Geometry3D] Closest Points Between Segments") {
struct Case {
Vector3 p_1, p_2, p_3, p_4;
@@ -57,6 +57,7 @@ TEST_CASE("[Geometry3D] Closest Points Between Segments") {
CHECK(current_case.got_2.is_equal_approx(current_case.want_2));
}
}
+
TEST_CASE("[Geometry3D] Closest Distance Between Segments") {
struct Case {
Vector3 p_1, p_2, p_3, p_4;
@@ -73,6 +74,7 @@ TEST_CASE("[Geometry3D] Closest Distance Between Segments") {
CHECK(out == current_case.want);
}
}
+
TEST_CASE("[Geometry3D] Build Box Planes") {
const Vector3 extents = Vector3(5, 5, 20);
Vector<Plane> box = Geometry3D::build_box_planes(extents);
@@ -90,6 +92,7 @@ TEST_CASE("[Geometry3D] Build Box Planes") {
CHECK(extents.z == box[5].d);
CHECK(box[5].normal == Vector3(0, 0, -1));
}
+
TEST_CASE("[Geometry3D] Build Capsule Planes") {
struct Case {
real_t radius, height;
@@ -109,6 +112,7 @@ TEST_CASE("[Geometry3D] Build Capsule Planes") {
CHECK(capsule.size() == current_case.want_size);
}
}
+
TEST_CASE("[Geometry3D] Build Cylinder Planes") {
struct Case {
real_t radius, height;
@@ -127,6 +131,7 @@ TEST_CASE("[Geometry3D] Build Cylinder Planes") {
CHECK(planes.size() == current_case.want_size);
}
}
+
TEST_CASE("[Geometry3D] Build Sphere Planes") {
struct Case {
real_t radius;
@@ -145,6 +150,11 @@ TEST_CASE("[Geometry3D] Build Sphere Planes") {
CHECK(planes.size() == 63);
}
}
+
+#if false
+// This test has been temporarily disabled because it's really fragile and
+// breaks if calculations change very slightly. For example, it breaks when
+// using doubles, and it breaks when making Plane calculations more accurate.
TEST_CASE("[Geometry3D] Build Convex Mesh") {
struct Case {
Vector<Plane> object;
@@ -166,6 +176,8 @@ TEST_CASE("[Geometry3D] Build Convex Mesh") {
CHECK(mesh.vertices.size() == current_case.want_vertices);
}
}
+#endif
+
TEST_CASE("[Geometry3D] Clip Polygon") {
struct Case {
Plane clipping_plane;
@@ -179,7 +191,7 @@ TEST_CASE("[Geometry3D] Clip Polygon") {
Vector<Plane> box_planes = Geometry3D::build_box_planes(Vector3(5, 10, 5));
Vector<Vector3> box = Geometry3D::compute_convex_mesh_points(&box_planes[0], box_planes.size());
tt.push_back(Case(Plane(), box, true));
- tt.push_back(Case(Plane(Vector3(0, 3, 0), Vector3(0, 1, 0)), box, false));
+ tt.push_back(Case(Plane(Vector3(0, 1, 0), Vector3(0, 3, 0)), box, false));
for (int i = 0; i < tt.size(); ++i) {
Case current_case = tt[i];
Vector<Vector3> output = Geometry3D::clip_polygon(current_case.polygon, current_case.clipping_plane);
@@ -190,6 +202,7 @@ TEST_CASE("[Geometry3D] Clip Polygon") {
}
}
}
+
TEST_CASE("[Geometry3D] Compute Convex Mesh Points") {
struct Case {
Vector<Plane> mesh;
@@ -215,6 +228,7 @@ TEST_CASE("[Geometry3D] Compute Convex Mesh Points") {
CHECK(vectors == current_case.want);
}
}
+
TEST_CASE("[Geometry3D] Get Closest Point To Segment") {
struct Case {
Vector3 point;
@@ -235,6 +249,7 @@ TEST_CASE("[Geometry3D] Get Closest Point To Segment") {
CHECK(output.is_equal_approx(current_case.want));
}
}
+
TEST_CASE("[Geometry3D] Plane and Box Overlap") {
struct Case {
Vector3 normal, max_box;
@@ -254,6 +269,7 @@ TEST_CASE("[Geometry3D] Plane and Box Overlap") {
CHECK(overlap == current_case.want);
}
}
+
TEST_CASE("[Geometry3D] Is Point in Projected Triangle") {
struct Case {
Vector3 point, v_1, v_2, v_3;
@@ -272,6 +288,7 @@ TEST_CASE("[Geometry3D] Is Point in Projected Triangle") {
CHECK(output == current_case.want);
}
}
+
TEST_CASE("[Geometry3D] Does Ray Intersect Triangle") {
struct Case {
Vector3 from, direction, v_1, v_2, v_3;
@@ -291,6 +308,7 @@ TEST_CASE("[Geometry3D] Does Ray Intersect Triangle") {
CHECK(output == current_case.want);
}
}
+
TEST_CASE("[Geometry3D] Does Segment Intersect Convex") {
struct Case {
Vector3 from, to;
@@ -311,6 +329,7 @@ TEST_CASE("[Geometry3D] Does Segment Intersect Convex") {
CHECK(output == current_case.want);
}
}
+
TEST_CASE("[Geometry3D] Segment Intersects Cylinder") {
struct Case {
Vector3 from, to;
@@ -330,6 +349,7 @@ TEST_CASE("[Geometry3D] Segment Intersects Cylinder") {
CHECK(output == current_case.want);
}
}
+
TEST_CASE("[Geometry3D] Segment Intersects Cylinder") {
struct Case {
Vector3 from, to, sphere_pos;
@@ -350,6 +370,7 @@ TEST_CASE("[Geometry3D] Segment Intersects Cylinder") {
CHECK(output == current_case.want);
}
}
+
TEST_CASE("[Geometry3D] Segment Intersects Triangle") {
struct Case {
Vector3 from, to, v_1, v_2, v_3, *result;
@@ -368,6 +389,7 @@ TEST_CASE("[Geometry3D] Segment Intersects Triangle") {
CHECK(output == current_case.want);
}
}
+
TEST_CASE("[Geometry3D] Triangle and Box Overlap") {
struct Case {
Vector3 box_centre;
@@ -389,6 +411,7 @@ TEST_CASE("[Geometry3D] Triangle and Box Overlap") {
CHECK(output == current_case.want);
}
}
+
TEST_CASE("[Geometry3D] Triangle and Sphere Intersect") {
struct Case {
Vector<Vector3> triangle;
@@ -413,5 +436,6 @@ TEST_CASE("[Geometry3D] Triangle and Sphere Intersect") {
CHECK(output == current_case.want);
}
}
-} // namespace Test3DGeometry
-#endif
+} // namespace TestGeometry3D
+
+#endif // TEST_GEOMETRY_3D_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 67d9a52539..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;
@@ -549,8 +549,8 @@ MainLoop *test() {
List<StringName> tl;
ClassDB::get_class_list(&tl);
- for (List<StringName>::Element *E = tl.front(); E; E = E->next()) {
- Vector<uint8_t> m5b = E->get().operator String().md5_buffer();
+ for (const StringName &E : tl) {
+ Vector<uint8_t> m5b = E.operator String().md5_buffer();
hashes.push_back(hashes.size());
}
@@ -589,13 +589,13 @@ MainLoop *test() {
{
Vector3 v(1, 2, 3);
v.normalize();
- float a = 0.3;
+ real_t a = 0.3;
Basis m(v, a);
Vector3 v2(7, 3, 1);
v2.normalize();
- float a2 = 0.8;
+ real_t a2 = 0.8;
Basis m2(v2, a2);
diff --git a/tests/test_object.h b/tests/test_object.h
index 36f9ef2a51..8cb7116a20 100644
--- a/tests/test_object.h
+++ b/tests/test_object.h
@@ -93,8 +93,8 @@ public:
Ref<Script> get_script() const override {
return Ref<Script>();
}
- const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const override {
- return Vector<MultiplayerAPI::RPCConfig>();
+ const Vector<Multiplayer::RPCConfig> get_rpc_methods() const override {
+ return Vector<Multiplayer::RPCConfig>();
}
ScriptLanguage *get_language() override {
return nullptr;
@@ -139,7 +139,7 @@ TEST_CASE("[Object] Metadata") {
Color(object.get_meta(meta_path)).is_equal_approx(Color(0, 1, 0)),
"The returned object metadata after setting should match the expected value.");
- List<String> meta_list;
+ List<StringName> meta_list;
object.get_meta_list(&meta_list);
CHECK_MESSAGE(
meta_list.size() == 1,
@@ -154,7 +154,7 @@ TEST_CASE("[Object] Metadata") {
"The returned object metadata after removing should match the expected value.");
ERR_PRINT_ON;
- List<String> meta_list2;
+ List<StringName> meta_list2;
object.get_meta_list(&meta_list2);
CHECK_MESSAGE(
meta_list2.size() == 0,
diff --git a/tests/test_physics_2d.cpp b/tests/test_physics_2d.cpp
index a9e2e92b34..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->line_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,12 +381,12 @@ 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(float p_time) override {
+ virtual bool process(double p_time) override {
return false;
}
virtual void finalize() override {
diff --git a/tests/test_physics_3d.cpp b/tests/test_physics_3d.cpp
index 4488e4bf64..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;
}
@@ -313,7 +312,7 @@ public:
test_fall();
quit = false;
}
- virtual bool physics_process(float p_time) override {
+ virtual bool physics_process(double p_time) override {
if (mover.is_valid()) {
static real_t joy_speed = 10;
PhysicsServer3D *ps = PhysicsServer3D::get_singleton();
@@ -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,15 +390,15 @@ 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(float p_time) override {
+ virtual bool process(double p_time) override {
return false;
}
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_render.cpp b/tests/test_render.cpp
index fe223ca258..21b4da9b3b 100644
--- a/tests/test_render.cpp
+++ b/tests/test_render.cpp
@@ -184,7 +184,7 @@ public:
light = vs->instance_create2(lightaux, scenario);
Transform3D lla;
//lla.set_look_at(Vector3(),Vector3(1, -1, 1));
- lla.set_look_at(Vector3(), Vector3(0.0, -0.836026, -0.548690));
+ lla.basis = Basis::looking_at(Vector3(0.0, -0.836026, -0.548690));
vs->instance_set_transform(light, lla);
@@ -199,7 +199,7 @@ public:
ofs = 0;
quit = false;
}
- virtual bool iteration(float p_time) {
+ virtual bool iteration(double p_time) {
RenderingServer *vs = RenderingServer::get_singleton();
//Transform3D t;
//t.rotate(Vector3(0, 1, 0), ofs);
@@ -210,12 +210,12 @@ public:
//return quit;
- for (List<InstanceInfo>::Element *E = instances.front(); E; E = E->next()) {
- Transform3D pre(Basis(E->get().rot_axis, ofs), Vector3());
- vs->instance_set_transform(E->get().instance, pre * E->get().base);
+ for (const InstanceInfo &E : instances) {
+ Transform3D pre(Basis(E.rot_axis, ofs), Vector3());
+ vs->instance_set_transform(E.instance, pre * E.base);
/*
if( !E->next() ) {
- vs->free( E->get().instance );
+ vs->free( E.instance );
instances.erase(E );
}*/
}
@@ -223,7 +223,7 @@ public:
return quit;
}
- virtual bool idle(float p_time) {
+ virtual bool idle(double p_time) {
return quit;
}
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 1982d8de60..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"
@@ -350,13 +351,47 @@ TEST_CASE("[String] Insertion") {
}
TEST_CASE("[String] Number to string") {
+ CHECK(String::num(0) == "0");
+ CHECK(String::num(0.0) == "0"); // No trailing zeros.
+ 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.
+ CHECK(String::num(-0.0000012345432123454321) == "-0.00000123454321");
+ CHECK(String::num(-10000.0000012345432123454321) == "-10000.0000012345");
+ CHECK(String::num(0.0000000000012345432123454321) == "0.00000000000123");
+ CHECK(String::num(0.0000000000012345432123454321, 3) == "0");
+
+ // Note: When relevant (remainder > 0.5), the last digit gets rounded up,
+ // which can also lead to not include a trailing zero, e.g. "...89" -> "...9".
+ CHECK(String::num(0.0000056789876567898765) == "0.00000567898766"); // Should round last digit.
+ CHECK(String::num(10000.000005678999999999) == "10000.000005679"); // We cut at ...789|99 which is rounded to ...79, so only 13 decimals.
+ CHECK(String::num(42.12999999, 6) == "42.13"); // Also happens with lower decimals count.
+
+ // 32 is MAX_DECIMALS. We can't reliably store that many so we can't compare against a string,
+ // but we can check that the string length is 34 (32 + 2 for "0.").
+ CHECK(String::num(0.00000123456789987654321123456789987654321, 32).length() == 34);
+ CHECK(String::num(0.00000123456789987654321123456789987654321, 42).length() == 34); // Should enforce MAX_DECIMALS.
+ CHECK(String::num(10000.00000123456789987654321123456789987654321, 42).length() == 38); // 32 decimals + "10000.".
}
TEST_CASE("[String] String to integer") {
@@ -1118,20 +1153,20 @@ 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]);
CHECK(String(path[i]).get_file() == file[i]);
CHECK(String(path[i]).is_absolute_path() == abs[i]);
- CHECK(String(path[i]).is_rel_path() != abs[i]);
+ CHECK(String(path[i]).is_relative_path() != abs[i]);
CHECK(String(path[i]).simplify_path().get_base_dir().plus_file(file[i]) == String(path[i]).simplify_path());
}
@@ -1346,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 cac022e33f..af3df7bc79 100644
--- a/tests/test_text_server.h
+++ b/tests/test_text_server.h
@@ -41,33 +41,31 @@ 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_memory(_font_NotoSans_Regular, _font_NotoSans_Regular_size, "ttf");
+ RID font = ts->create_font();
+ ts->font_set_data_ptr(font, _font_NotoSans_Regular, _font_NotoSans_Regular_size);
TEST_FAIL_COND(font == RID(), "Loading font failed.");
ts->free(font);
}
}
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);
+ RID font2 = ts->create_font();
+ ts->font_set_data_ptr(font2, _font_NotoSansThaiUI_Regular, _font_NotoSansThaiUI_Regular_size);
Vector<RID> font;
- font.push_back(ts->create_font_memory(_font_NotoSans_Regular, _font_NotoSans_Regular_size, "ttf"));
- font.push_back(ts->create_font_memory(_font_NotoSansThaiUI_Regular, _font_NotoSansThaiUI_Regular_size, "ttf"));
+ font.push_back(font1);
+ font.push_back(font2);
String test = U"คนอ้วน khon uan ראה";
// 6^ 17^
@@ -77,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.");
}
@@ -104,16 +103,22 @@ 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;
}
+ RID font1 = ts->create_font();
+ ts->font_set_data_ptr(font1, _font_NotoSans_Regular, _font_NotoSans_Regular_size);
+ RID font2 = ts->create_font();
+ ts->font_set_data_ptr(font2, _font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size);
+
Vector<RID> font;
- font.push_back(ts->create_font_memory(_font_NotoSans_Regular, _font_NotoSans_Regular_size, "ttf"));
- font.push_back(ts->create_font_memory(_font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size, "ttf"));
+ font.push_back(font1);
+ font.push_back(font2);
String test = U"Arabic (اَلْعَرَبِيَّةُ, al-ʿarabiyyah)";
// 7^ 26^
@@ -123,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.");
@@ -149,27 +155,38 @@ 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^
+ RID font1 = ts->create_font();
+ ts->font_set_data_ptr(font1, _font_NotoSans_Regular, _font_NotoSans_Regular_size);
+ RID font2 = ts->create_font();
+ ts->font_set_data_ptr(font2, _font_NotoSansThaiUI_Regular, _font_NotoSansThaiUI_Regular_size);
+
Vector<RID> font;
- font.push_back(ts->create_font_memory(_font_NotoSans_Regular, _font_NotoSans_Regular_size, "ttf"));
- font.push_back(ts->create_font_memory(_font_NotoSansThaiUI_Regular, _font_NotoSansThaiUI_Regular_size, "ttf"));
+ font.push_back(font1);
+ font.push_back(font2);
RID ctx = ts->create_shaped_text();
TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed.");
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);
@@ -182,12 +199,18 @@ 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);
+ RID font2 = ts->create_font();
+ ts->font_set_data_ptr(font2, _font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size);
Vector<RID> font;
- font.push_back(ts->create_font_memory(_font_NotoSans_Regular, _font_NotoSans_Regular_size, "ttf"));
- font.push_back(ts->create_font_memory(_font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size, "ttf"));
+ font.push_back(font1);
+ font.push_back(font2);
String test_1 = U"الحمد";
String test_2 = U"الحمد test";
@@ -242,8 +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..ec18610f04
--- /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, bool p_editor_notify, 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_variant.h b/tests/test_variant.h
index dfc72b512c..598fe488d7 100644
--- a/tests/test_variant.h
+++ b/tests/test_variant.h
@@ -39,7 +39,7 @@
namespace TestVariant {
TEST_CASE("[Variant] Writer and parser integer") {
- int64_t a32 = 2147483648; // 2^31, so out of bounds for 32-bit signed int [-2^31,-2^31-1].
+ int64_t a32 = 2147483648; // 2^31, so out of bounds for 32-bit signed int [-2^31, +2^31-1].
String a32_str;
VariantWriter::write_to_string(a32, a32_str);
@@ -76,34 +76,35 @@ TEST_CASE("[Variant] Writer and parser integer") {
CHECK_MESSAGE(b64_int_parsed == 9223372036854775807, "The result should be clamped to max value.");
}
-TEST_CASE("[Variant] Writer and parser float") {
- // Assuming real_t is double.
- real_t a64 = 340282346638528859811704183484516925440.0; // std::numeric_limits<real_t>::max()
+TEST_CASE("[Variant] Writer and parser Variant::FLOAT") {
+ // Variant::FLOAT is always 64-bit (C++ double).
+ // This is the maximum non-infinity double-precision float.
+ double a64 = 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0;
String a64_str;
VariantWriter::write_to_string(a64, a64_str);
- CHECK_MESSAGE(a64_str == "3.40282e+38", "Writes in scientific notation.");
+ CHECK_MESSAGE(a64_str == "1.79769e+308", "Writes in scientific notation.");
CHECK_MESSAGE(a64_str != "inf", "Should not overflow.");
CHECK_MESSAGE(a64_str != "nan", "The result should be defined.");
- VariantParser::StreamString ss;
String errs;
int line;
- Variant b64_parsed;
- real_t b64_float_parsed;
-
- ss.s = a64_str;
- VariantParser::parse(&ss, b64_parsed, errs, line);
- b64_float_parsed = b64_parsed;
+ Variant variant_parsed;
+ double float_parsed;
- CHECK_MESSAGE(b64_float_parsed == 340282001837565597733306976381245063168.0, "Should parse back.");
+ VariantParser::StreamString bss;
+ bss.s = a64_str;
+ VariantParser::parse(&bss, variant_parsed, errs, line);
+ float_parsed = variant_parsed;
// Loses precision, but that's alright.
-
- ss.s = "1.0e+100"; // Float version of Googol!
- VariantParser::parse(&ss, b64_parsed, errs, line);
- b64_float_parsed = b64_parsed;
-
- CHECK_MESSAGE(b64_float_parsed == 340282001837565597733306976381245063168.0, "Should not overflow.");
+ CHECK_MESSAGE(float_parsed == 1.79769e+308, "Should parse back.");
+
+ // Approximation of Googol with a double-precision float.
+ VariantParser::StreamString css;
+ css.s = "1.0e+100";
+ VariantParser::parse(&css, variant_parsed, errs, line);
+ float_parsed = variant_parsed;
+ CHECK_MESSAGE(float_parsed == 1.0e+100, "Should match the double literal.");
}
TEST_CASE("[Variant] Assignment To Bool from Int,Float,String,Vec2,Vec2i,Vec3,Vec3i and Color") {
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);