diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/SCsub | 9 | ||||
| -rw-r--r-- | tests/core/io/test_config_file.h (renamed from tests/test_config_file.h) | 20 | ||||
| -rw-r--r-- | tests/core/io/test_file_access.h (renamed from tests/test_file_access.h) | 40 | ||||
| -rw-r--r-- | tests/core/io/test_image.h (renamed from tests/test_image.h) | 78 | ||||
| -rw-r--r-- | tests/core/io/test_json.h (renamed from tests/test_json.h) | 67 | ||||
| -rw-r--r-- | tests/core/io/test_marshalls.h (renamed from tests/test_marshalls.h) | 4 | ||||
| -rw-r--r-- | tests/core/io/test_pck_packer.h (renamed from tests/test_pck_packer.h) | 42 | ||||
| -rw-r--r-- | tests/core/io/test_resource.h (renamed from tests/test_resource.h) | 4 | ||||
| -rw-r--r-- | tests/core/io/test_xml_parser.h (renamed from tests/test_xml_parser.h) | 7 | ||||
| -rw-r--r-- | tests/core/math/test_aabb.h (renamed from tests/test_aabb.h) | 82 | ||||
| -rw-r--r-- | tests/core/math/test_astar.h (renamed from tests/test_astar.h) | 11 | ||||
| -rw-r--r-- | tests/core/math/test_basis.h (renamed from tests/test_basis.h) | 35 | ||||
| -rw-r--r-- | tests/core/math/test_color.h (renamed from tests/test_color.h) | 20 | ||||
| -rw-r--r-- | tests/core/math/test_expression.h (renamed from tests/test_expression.h) | 28 | ||||
| -rw-r--r-- | tests/core/math/test_geometry_2d.h (renamed from tests/test_geometry_2d.h) | 46 | ||||
| -rw-r--r-- | tests/core/math/test_geometry_3d.h (renamed from tests/test_geometry_3d.h) | 44 | ||||
| -rw-r--r-- | tests/core/math/test_math.cpp (renamed from tests/test_math.cpp) | 52 | ||||
| -rw-r--r-- | tests/core/math/test_math.h (renamed from tests/test_math.h) | 6 | ||||
| -rw-r--r-- | tests/core/math/test_random_number_generator.h (renamed from tests/test_random_number_generator.h) | 4 | ||||
| -rw-r--r-- | tests/core/math/test_rect2.h (renamed from tests/test_rect2.h) | 298 | ||||
| -rw-r--r-- | tests/core/math/test_rect2i.h | 311 | ||||
| -rw-r--r-- | tests/core/math/test_vector2.h | 389 | ||||
| -rw-r--r-- | tests/core/math/test_vector2i.h | 145 | ||||
| -rw-r--r-- | tests/core/math/test_vector3.h | 417 | ||||
| -rw-r--r-- | tests/core/math/test_vector3i.h | 145 | ||||
| -rw-r--r-- | tests/core/object/test_class_db.h (renamed from tests/test_class_db.h) | 121 | ||||
| -rw-r--r-- | tests/core/object/test_method_bind.h (renamed from tests/test_method_bind.h) | 24 | ||||
| -rw-r--r-- | tests/core/object/test_object.h (renamed from tests/test_object.h) | 53 | ||||
| -rw-r--r-- | tests/core/string/test_node_path.h (renamed from tests/test_node_path.h) | 6 | ||||
| -rw-r--r-- | tests/core/string/test_string.h (renamed from tests/test_string.h) | 244 | ||||
| -rw-r--r-- | tests/core/string/test_translation.h | 182 | ||||
| -rw-r--r-- | tests/core/templates/test_command_queue.h (renamed from tests/test_command_queue.h) | 70 | ||||
| -rw-r--r-- | tests/core/templates/test_list.h (renamed from tests/test_list.h) | 6 | ||||
| -rw-r--r-- | tests/core/templates/test_local_vector.h (renamed from tests/test_local_vector.h) | 39 | ||||
| -rw-r--r-- | tests/core/templates/test_lru.h (renamed from tests/test_lru.h) | 5 | ||||
| -rw-r--r-- | tests/core/templates/test_oa_hash_map.cpp (renamed from tests/test_oa_hash_map.cpp) | 9 | ||||
| -rw-r--r-- | tests/core/templates/test_oa_hash_map.h (renamed from tests/test_oa_hash_map.h) | 6 | ||||
| -rw-r--r-- | tests/core/templates/test_ordered_hash_map.h (renamed from tests/test_ordered_hash_map.h) | 7 | ||||
| -rw-r--r-- | tests/core/templates/test_paged_array.h (renamed from tests/test_paged_array.h) | 4 | ||||
| -rw-r--r-- | tests/core/templates/test_vector.h | 535 | ||||
| -rw-r--r-- | tests/core/test_crypto.h (renamed from tests/test_crypto.h) | 5 | ||||
| -rw-r--r-- | tests/core/test_hashing_context.h (renamed from tests/test_hashing_context.h) | 4 | ||||
| -rw-r--r-- | tests/core/test_time.h | 148 | ||||
| -rw-r--r-- | tests/core/variant/test_array.h | 521 | ||||
| -rw-r--r-- | tests/core/variant/test_dictionary.h | 505 | ||||
| -rw-r--r-- | tests/core/variant/test_variant.h (renamed from tests/test_variant.h) | 254 | ||||
| -rw-r--r-- | tests/data/translations.csv | 5 | ||||
| -rw-r--r-- | tests/scene/test_animation.h | 314 | ||||
| -rw-r--r-- | tests/scene/test_code_edit.h | 3348 | ||||
| -rw-r--r-- | tests/scene/test_curve.h (renamed from tests/test_curve.h) | 75 | ||||
| -rw-r--r-- | tests/scene/test_gradient.h (renamed from tests/test_gradient.h) | 6 | ||||
| -rw-r--r-- | tests/scene/test_gui.cpp (renamed from tests/test_gui.cpp) | 21 | ||||
| -rw-r--r-- | tests/scene/test_gui.h (renamed from tests/test_gui.h) | 6 | ||||
| -rw-r--r-- | tests/scene/test_path_3d.h (renamed from tests/test_path_3d.h) | 5 | ||||
| -rw-r--r-- | tests/scene/test_path_follow_2d.h (renamed from tests/test_path_follow_2d.h) | 5 | ||||
| -rw-r--r-- | tests/scene/test_path_follow_3d.h (renamed from tests/test_path_follow_3d.h) | 5 | ||||
| -rw-r--r-- | tests/servers/test_physics_2d.cpp (renamed from tests/test_physics_2d.cpp) | 66 | ||||
| -rw-r--r-- | tests/servers/test_physics_2d.h (renamed from tests/test_physics_2d.h) | 6 | ||||
| -rw-r--r-- | tests/servers/test_physics_3d.cpp (renamed from tests/test_physics_3d.cpp) | 78 | ||||
| -rw-r--r-- | tests/servers/test_physics_3d.h (renamed from tests/test_physics_3d.h) | 6 | ||||
| -rw-r--r-- | tests/servers/test_render.cpp (renamed from tests/test_render.cpp) | 35 | ||||
| -rw-r--r-- | tests/servers/test_render.h (renamed from tests/test_render.h) | 8 | ||||
| -rw-r--r-- | tests/servers/test_shader_lang.cpp (renamed from tests/test_shader_lang.cpp) | 82 | ||||
| -rw-r--r-- | tests/servers/test_shader_lang.h (renamed from tests/test_shader_lang.h) | 6 | ||||
| -rw-r--r-- | tests/servers/test_text_server.h | 502 | ||||
| -rw-r--r-- | tests/test_array.h | 186 | ||||
| -rw-r--r-- | tests/test_dictionary.h | 159 | ||||
| -rw-r--r-- | tests/test_macros.cpp | 4 | ||||
| -rw-r--r-- | tests/test_macros.h | 228 | ||||
| -rw-r--r-- | tests/test_main.cpp | 286 | ||||
| -rw-r--r-- | tests/test_main.h | 4 | ||||
| -rw-r--r-- | tests/test_text_server.h | 252 | ||||
| -rw-r--r-- | tests/test_tools.h | 59 | ||||
| -rw-r--r-- | tests/test_utils.cpp | 6 | ||||
| -rw-r--r-- | tests/test_utils.h | 6 | ||||
| -rw-r--r-- | tests/test_validate_testing.h | 20 |
76 files changed, 9196 insertions, 1645 deletions
diff --git a/tests/SCsub b/tests/SCsub index 0f3c14f0bd..25b06f2312 100644 --- a/tests/SCsub +++ b/tests/SCsub @@ -6,10 +6,6 @@ env.tests_sources = [] env_tests = env.Clone() -# Include GDNative headers. -if env["module_gdnative_enabled"]: - env_tests.Append(CPPPATH=["#modules/gdnative/include"]) - # We must disable the THREAD_LOCAL entirely in doctest to prevent crashes on debugging # Since we link with /MT thread_local is always expired when the header is used # So the debugger crashes the engine and it causes weird errors @@ -22,6 +18,11 @@ if env_tests["platform"] == "windows": if env_tests.msvc: env_tests.Append(CCFLAGS=["/bigobj"]) +env_tests.add_source_files(env.tests_sources, "core/*.cpp") +env_tests.add_source_files(env.tests_sources, "core/math/*.cpp") +env_tests.add_source_files(env.tests_sources, "core/templates/*.cpp") +env_tests.add_source_files(env.tests_sources, "scene/*.cpp") +env_tests.add_source_files(env.tests_sources, "servers/*.cpp") env_tests.add_source_files(env.tests_sources, "*.cpp") lib = env_tests.add_library("tests", env.tests_sources) diff --git a/tests/test_config_file.h b/tests/core/io/test_config_file.h index 958341018b..6e393c7a2d 100644 --- a/tests/test_config_file.h +++ b/tests/core/io/test_config_file.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -32,6 +32,7 @@ #define TEST_CONFIG_FILE_H #include "core/io/config_file.h" +#include "core/os/os.h" #include "tests/test_macros.h" @@ -122,6 +123,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,20 +135,25 @@ 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 for Godot" -color=Color( 0, 0.5, 1, 1 ) -position=Vector2( 3, 4 ) +color=Color(0, 0.5, 1, 1) +position=Vector2(3, 4) [graphics] 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_file_access.h b/tests/core/io/test_file_access.h index 00a314644c..eee57048cf 100644 --- a/tests/test_file_access.h +++ b/tests/core/io/test_file_access.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -31,18 +31,19 @@ #ifndef TEST_FILE_ACCESS_H #define TEST_FILE_ACCESS_H -#include "core/os/file_access.h" -#include "test_utils.h" +#include "core/io/file_access.h" +#include "tests/test_macros.h" +#include "tests/test_utils.h" 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"); @@ -52,13 +53,32 @@ TEST_CASE("[FileAccess] CSV read") { REQUIRE(row2.size() == 3); CHECK(row2[0] == "GOOD_EVENING"); CHECK(row2[1] == "Good Evening"); - CHECK(row2[2] == ""); // Use case: not yet translated! - + CHECK(row2[2].is_empty()); // 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_image.h b/tests/core/io/test_image.h index d73717f5b7..dcf21dd7b0 100644 --- a/tests/test_image.h +++ b/tests/core/io/test_image.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -31,10 +31,10 @@ #ifndef TEST_IMAGE_H #define TEST_IMAGE_H -#include "core/io/file_access_pack.h" #include "core/io/image.h" -#include "test_utils.h" +#include "core/os/os.h" +#include "tests/test_utils.h" #include "thirdparty/doctest/doctest.h" namespace TestImage { @@ -52,6 +52,13 @@ TEST_CASE("[Image] Instantiation") { "A newly created image should not be compressed."); CHECK(!image->has_mipmaps()); + PackedByteArray image_data = image->get_data(); + for (int i = 0; i < image_data.size(); i++) { + CHECK_MESSAGE( + image_data[i] == 0, + "An image created without data specified should have its data zeroed out."); + } + Ref<Image> image_copy = memnew(Image()); CHECK_MESSAGE( image_copy->is_empty(), @@ -62,7 +69,7 @@ TEST_CASE("[Image] Instantiation") { image->get_data() == image_copy->get_data(), "Duplicated images should have the same data."); - PackedByteArray image_data = image->get_data(); + image_data = image->get_data(); Ref<Image> image_from_data = memnew(Image(8, 4, false, Image::FORMAT_RGBA8, image_data)); CHECK_MESSAGE( image->get_data() == image_from_data->get_data(), @@ -101,8 +108,8 @@ TEST_CASE("[Image] Saving and loading") { Ref<Image> image_bmp = memnew(Image()); FileAccessRef f_bmp = FileAccess::open(TestUtils::get_data_path("images/icon.bmp"), FileAccess::READ, &err); PackedByteArray data_bmp; - data_bmp.resize(f_bmp->get_len() + 1); - f_bmp->get_buffer(data_bmp.ptrw(), f_bmp->get_len()); + data_bmp.resize(f_bmp->get_length() + 1); + f_bmp->get_buffer(data_bmp.ptrw(), f_bmp->get_length()); CHECK_MESSAGE( image_bmp->load_bmp_from_buffer(data_bmp) == OK, "The BMP image should load successfully."); @@ -111,8 +118,8 @@ TEST_CASE("[Image] Saving and loading") { Ref<Image> image_jpg = memnew(Image()); FileAccessRef f_jpg = FileAccess::open(TestUtils::get_data_path("images/icon.jpg"), FileAccess::READ, &err); PackedByteArray data_jpg; - data_jpg.resize(f_jpg->get_len() + 1); - f_jpg->get_buffer(data_jpg.ptrw(), f_jpg->get_len()); + data_jpg.resize(f_jpg->get_length() + 1); + f_jpg->get_buffer(data_jpg.ptrw(), f_jpg->get_length()); CHECK_MESSAGE( image_jpg->load_jpg_from_buffer(data_jpg) == OK, "The JPG image should load successfully."); @@ -121,8 +128,8 @@ TEST_CASE("[Image] Saving and loading") { Ref<Image> image_webp = memnew(Image()); FileAccessRef f_webp = FileAccess::open(TestUtils::get_data_path("images/icon.webp"), FileAccess::READ, &err); PackedByteArray data_webp; - data_webp.resize(f_webp->get_len() + 1); - f_webp->get_buffer(data_webp.ptrw(), f_webp->get_len()); + data_webp.resize(f_webp->get_length() + 1); + f_webp->get_buffer(data_webp.ptrw(), f_webp->get_length()); CHECK_MESSAGE( image_webp->load_webp_from_buffer(data_webp) == OK, "The WEBP image should load successfully."); @@ -131,8 +138,8 @@ TEST_CASE("[Image] Saving and loading") { Ref<Image> image_png = memnew(Image()); FileAccessRef f_png = FileAccess::open(TestUtils::get_data_path("images/icon.png"), FileAccess::READ, &err); PackedByteArray data_png; - data_png.resize(f_png->get_len() + 1); - f_png->get_buffer(data_png.ptrw(), f_png->get_len()); + data_png.resize(f_png->get_length() + 1); + f_png->get_buffer(data_png.ptrw(), f_png->get_length()); CHECK_MESSAGE( image_png->load_png_from_buffer(data_png) == OK, "The PNG image should load successfully."); @@ -141,8 +148,8 @@ TEST_CASE("[Image] Saving and loading") { Ref<Image> image_tga = memnew(Image()); FileAccessRef f_tga = FileAccess::open(TestUtils::get_data_path("images/icon.tga"), FileAccess::READ, &err); PackedByteArray data_tga; - data_tga.resize(f_tga->get_len() + 1); - f_tga->get_buffer(data_tga.ptrw(), f_tga->get_len()); + data_tga.resize(f_tga->get_length() + 1); + f_tga->get_buffer(data_tga.ptrw(), f_tga->get_length()); CHECK_MESSAGE( image_tga->load_tga_from_buffer(data_tga) == OK, "The TGA image should load successfully."); @@ -214,14 +221,51 @@ TEST_CASE("[Image] Modifying pixels of an image") { // Fill image with color image2->fill(Color(0.5, 0.5, 0.5, 0.5)); - for (int x = 0; x < image2->get_width(); x++) { - for (int y = 0; y < image2->get_height(); y++) { + for (int y = 0; y < image2->get_height(); y++) { + for (int x = 0; x < image2->get_width(); x++) { CHECK_MESSAGE( image2->get_pixel(x, y).r > 0.49, "fill() should colorize all pixels of the image."); } } + // Fill rect with color + { + const int img_width = 3; + const int img_height = 3; + Vector<Rect2> rects; + rects.push_back(Rect2()); + rects.push_back(Rect2(-5, -5, 3, 3)); + rects.push_back(Rect2(img_width, 0, 12, 12)); + rects.push_back(Rect2(0, img_height, 12, 12)); + rects.push_back(Rect2(img_width + 1, img_height + 2, 12, 12)); + rects.push_back(Rect2(1, 1, 1, 1)); + rects.push_back(Rect2(0, 1, 2, 3)); + rects.push_back(Rect2(-5, 0, img_width + 10, 2)); + rects.push_back(Rect2(0, -5, 2, img_height + 10)); + rects.push_back(Rect2(-1, -1, img_width + 1, img_height + 1)); + + for (const Rect2 &rect : rects) { + Ref<Image> img = memnew(Image(img_width, img_height, false, Image::FORMAT_RGBA8)); + CHECK_NOTHROW_MESSAGE( + img->fill_rect(rect, Color(1, 1, 1, 1)), + "fill_rect() shouldn't throw for any rect."); + for (int y = 0; y < img->get_height(); y++) { + for (int x = 0; x < img->get_width(); x++) { + if (rect.abs().has_point(Point2(x, y))) { + CHECK_MESSAGE( + img->get_pixel(x, y).is_equal_approx(Color(1, 1, 1, 1)), + "fill_rect() should colorize all image pixels within rect bounds."); + } else { + CHECK_MESSAGE( + !img->get_pixel(x, y).is_equal_approx(Color(1, 1, 1, 1)), + "fill_rect() shouldn't colorize any image pixel out of rect bounds."); + } + } + } + } + } + // Blend two images together image->blend_rect(image2, Rect2(Vector2(0, 0), image2->get_size()), Vector2(0, 0)); CHECK_MESSAGE( diff --git a/tests/test_json.h b/tests/core/io/test_json.h index e652a8fced..478cf1766e 100644 --- a/tests/test_json.h +++ b/tests/core/io/test_json.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -45,75 +45,65 @@ TEST_CASE("[JSON] Parsing single data types") { // Parsing a single data type as JSON is valid per the JSON specification. JSON json; - Variant result; - String err_str; - int err_line; - json.parse("null", result, err_str, err_line); + json.parse("null"); CHECK_MESSAGE( - err_line == 0, + json.get_error_line() == 0, "Parsing `null` as JSON should parse successfully."); CHECK_MESSAGE( - result == Variant(), + json.get_data() == Variant(), "Parsing a double quoted string as JSON should return the expected value."); - json.parse("true", result, err_str, err_line); + json.parse("true"); CHECK_MESSAGE( - err_line == 0, + json.get_error_line() == 0, "Parsing boolean `true` as JSON should parse successfully."); CHECK_MESSAGE( - result, + json.get_data(), "Parsing boolean `true` as JSON should return the expected value."); - json.parse("false", result, err_str, err_line); + json.parse("false"); CHECK_MESSAGE( - err_line == 0, + json.get_error_line() == 0, "Parsing boolean `false` as JSON should parse successfully."); CHECK_MESSAGE( - !result, + !json.get_data(), "Parsing boolean `false` as JSON should return the expected value."); - // JSON only has a floating-point number type, no integer type. - // This is why we use `is_equal_approx()` for the comparison. - json.parse("123456", result, err_str, err_line); + json.parse("123456"); CHECK_MESSAGE( - err_line == 0, + json.get_error_line() == 0, "Parsing an integer number as JSON should parse successfully."); CHECK_MESSAGE( - Math::is_equal_approx(result, 123'456), + (int)(json.get_data()) == 123456, "Parsing an integer number as JSON should return the expected value."); - json.parse("0.123456", result, err_str, err_line); + json.parse("0.123456"); CHECK_MESSAGE( - err_line == 0, + json.get_error_line() == 0, "Parsing a floating-point number as JSON should parse successfully."); CHECK_MESSAGE( - Math::is_equal_approx(result, 0.123456), + Math::is_equal_approx(double(json.get_data()), 0.123456), "Parsing a floating-point number as JSON should return the expected value."); - json.parse("\"hello\"", result, err_str, err_line); + json.parse("\"hello\""); CHECK_MESSAGE( - err_line == 0, + json.get_error_line() == 0, "Parsing a double quoted string as JSON should parse successfully."); CHECK_MESSAGE( - result == "hello", + json.get_data() == "hello", "Parsing a double quoted string as JSON should return the expected value."); } TEST_CASE("[JSON] Parsing arrays") { JSON json; - Variant result; - String err_str; - int err_line; // JSON parsing fails if it's split over several lines (even if leading indentation is removed). - json.parse( - R"(["Hello", "world.", "This is",["a","json","array.",[]], "Empty arrays ahoy:", [[["Gotcha!"]]]])", - result, err_str, err_line); + json.parse(R"(["Hello", "world.", "This is",["a","json","array.",[]], "Empty arrays ahoy:", [[["Gotcha!"]]]])"); - const Array array = result; + const Array array = json.get_data(); CHECK_MESSAGE( - err_line == 0, + json.get_error_line() == 0, "Parsing a JSON array should parse successfully."); CHECK_MESSAGE( array[0] == "Hello", @@ -136,15 +126,10 @@ TEST_CASE("[JSON] Parsing arrays") { TEST_CASE("[JSON] Parsing objects (dictionaries)") { JSON json; - Variant result; - String err_str; - int err_line; - json.parse( - R"({"name": "Godot Engine", "is_free": true, "bugs": null, "apples": {"red": 500, "green": 0, "blue": -20}, "empty_object": {}})", - result, err_str, err_line); + json.parse(R"({"name": "Godot Engine", "is_free": true, "bugs": null, "apples": {"red": 500, "green": 0, "blue": -20}, "empty_object": {}})"); - const Dictionary dictionary = result; + const Dictionary dictionary = json.get_data(); CHECK_MESSAGE( dictionary["name"] == "Godot Engine", "The parsed JSON should contain the expected values."); @@ -155,7 +140,7 @@ TEST_CASE("[JSON] Parsing objects (dictionaries)") { dictionary["bugs"] == Variant(), "The parsed JSON should contain the expected values."); CHECK_MESSAGE( - Math::is_equal_approx(Dictionary(dictionary["apples"])["blue"], -20), + (int)Dictionary(dictionary["apples"])["blue"] == -20, "The parsed JSON should contain the expected values."); CHECK_MESSAGE( dictionary["empty_object"].hash() == Dictionary().hash(), diff --git a/tests/test_marshalls.h b/tests/core/io/test_marshalls.h index 6bd916164e..546a2e9358 100644 --- a/tests/test_marshalls.h +++ b/tests/core/io/test_marshalls.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ diff --git a/tests/test_pck_packer.h b/tests/core/io/test_pck_packer.h index 8e4721b821..95adca6d68 100644 --- a/tests/test_pck_packer.h +++ b/tests/core/io/test_pck_packer.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -35,21 +35,16 @@ #include "core/io/pck_packer.h" #include "core/os/os.h" +#include "tests/test_utils.h" #include "thirdparty/doctest/doctest.h" namespace TestPCKPacker { -// Dummy 64-character encryption key (since it's required). -constexpr const char *ENCRYPTION_KEY = "0000000000000000000000000000000000000000000000000000000000000000"; - TEST_CASE("[PCKPacker] Pack an empty PCK file") { PCKPacker pck_packer; const String output_pck_path = OS::get_singleton()->get_cache_path().plus_file("output_empty.pck"); CHECK_MESSAGE( - pck_packer.pck_start( - output_pck_path, - 32, - ENCRYPTION_KEY) == OK, + pck_packer.pck_start(output_pck_path) == OK, "Starting a PCK file should return an OK error code."); CHECK_MESSAGE( @@ -62,21 +57,34 @@ TEST_CASE("[PCKPacker] Pack an empty PCK file") { err == OK, "The generated empty PCK file should be opened successfully."); CHECK_MESSAGE( - f->get_len() >= 100, + f->get_length() >= 100, "The generated empty PCK file shouldn't be too small (it should have the PCK header)."); CHECK_MESSAGE( - f->get_len() <= 500, + f->get_length() <= 500, "The generated empty PCK file shouldn't be too large."); } +TEST_CASE("[PCKPacker] Pack empty with zero alignment invalid") { + PCKPacker pck_packer; + const String output_pck_path = OS::get_singleton()->get_cache_path().plus_file("output_empty.pck"); + ERR_PRINT_OFF; + CHECK_MESSAGE(pck_packer.pck_start(output_pck_path, 0) != OK, "PCK with zero alignment should fail."); + ERR_PRINT_ON; +} + +TEST_CASE("[PCKPacker] Pack empty with invalid key") { + PCKPacker pck_packer; + const String output_pck_path = OS::get_singleton()->get_cache_path().plus_file("output_empty.pck"); + ERR_PRINT_OFF; + CHECK_MESSAGE(pck_packer.pck_start(output_pck_path, 32, "") != OK, "PCK with invalid key should fail."); + ERR_PRINT_ON; +} + TEST_CASE("[PCKPacker] Pack a PCK file with some files and directories") { PCKPacker pck_packer; const String output_pck_path = OS::get_singleton()->get_cache_path().plus_file("output_with_files.pck"); CHECK_MESSAGE( - pck_packer.pck_start( - output_pck_path, - 32, - ENCRYPTION_KEY) == OK, + pck_packer.pck_start(output_pck_path) == OK, "Starting a PCK file should return an OK error code."); const String base_dir = OS::get_singleton()->get_executable_path().get_base_dir(); @@ -103,10 +111,10 @@ TEST_CASE("[PCKPacker] Pack a PCK file with some files and directories") { err == OK, "The generated non-empty PCK file should be opened successfully."); CHECK_MESSAGE( - f->get_len() >= 25000, + f->get_length() >= 25000, "The generated non-empty PCK file should be large enough to actually hold the contents specified above."); CHECK_MESSAGE( - f->get_len() <= 35000, + f->get_length() <= 35000, "The generated non-empty PCK file shouldn't be too large."); } } // namespace TestPCKPacker diff --git a/tests/test_resource.h b/tests/core/io/test_resource.h index cee3281995..b3983bb06d 100644 --- a/tests/test_resource.h +++ b/tests/core/io/test_resource.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ diff --git a/tests/test_xml_parser.h b/tests/core/io/test_xml_parser.h index 55de048d6a..87592b56ce 100644 --- a/tests/test_xml_parser.h +++ b/tests/core/io/test_xml_parser.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -31,10 +31,7 @@ #ifndef TEST_XML_PARSER_H #define TEST_XML_PARSER_H -#include <inttypes.h> - #include "core/io/xml_parser.h" -#include "core/string/ustring.h" #include "tests/test_macros.h" diff --git a/tests/test_aabb.h b/tests/core/math/test_aabb.h index 517c4dcefd..526972a82f 100644 --- a/tests/test_aabb.h +++ b/tests/core/math/test_aabb.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -32,10 +32,8 @@ #define TEST_AABB_H #include "core/math/aabb.h" -#include "core/string/print_string.h" -#include "tests/test_macros.h" -#include "thirdparty/doctest/doctest.h" +#include "tests/test_macros.h" namespace TestAABB { @@ -50,8 +48,8 @@ TEST_CASE("[AABB] Constructor methods") { TEST_CASE("[AABB] String conversion") { CHECK_MESSAGE( - String(AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6))) == "-1.5, 2, -2.5 - 4, 5, 6", - "The string representation shouild match the expected value."); + String(AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6))) == "[P: (-1.5, 2, -2.5), S: (4, 5, 6)]", + "The string representation should match the expected value."); } TEST_CASE("[AABB] Basic getters") { @@ -65,6 +63,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") { @@ -87,38 +88,38 @@ TEST_CASE("[AABB] Basic setters") { "set_size() should result in the expected AABB."); } -TEST_CASE("[AABB] Area getters") { +TEST_CASE("[AABB] Volume getters") { AABB aabb = AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6)); CHECK_MESSAGE( - Math::is_equal_approx(aabb.get_area(), 120), - "get_area() should return the expected value with positive size."); + Math::is_equal_approx(aabb.get_volume(), 120), + "get_volume() should return the expected value with positive size."); CHECK_MESSAGE( - !aabb.has_no_area(), - "Non-empty volumetric AABB should have an area."); + !aabb.has_no_volume(), + "Non-empty volumetric AABB should have a volume."); aabb = AABB(Vector3(-1.5, 2, -2.5), Vector3(-4, 5, 6)); CHECK_MESSAGE( - Math::is_equal_approx(aabb.get_area(), -120), - "get_area() should return the expected value with negative size (1 component)."); + Math::is_equal_approx(aabb.get_volume(), -120), + "get_volume() should return the expected value with negative size (1 component)."); aabb = AABB(Vector3(-1.5, 2, -2.5), Vector3(-4, -5, 6)); CHECK_MESSAGE( - Math::is_equal_approx(aabb.get_area(), 120), - "get_area() should return the expected value with negative size (2 components)."); + Math::is_equal_approx(aabb.get_volume(), 120), + "get_volume() should return the expected value with negative size (2 components)."); aabb = AABB(Vector3(-1.5, 2, -2.5), Vector3(-4, -5, -6)); CHECK_MESSAGE( - Math::is_equal_approx(aabb.get_area(), -120), - "get_area() should return the expected value with negative size (3 components)."); + Math::is_equal_approx(aabb.get_volume(), -120), + "get_volume() should return the expected value with negative size (3 components)."); aabb = AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 0, 6)); CHECK_MESSAGE( - aabb.has_no_area(), - "Non-empty flat AABB should not have an area."); + aabb.has_no_volume(), + "Non-empty flat AABB should not have a volume."); CHECK_MESSAGE( - AABB().has_no_area(), - "Empty AABB should not have an area."); + AABB().has_no_volume(), + "Empty AABB should not have a volume."); } TEST_CASE("[AABB] Surface getters") { @@ -278,24 +279,24 @@ TEST_CASE("[AABB] Get endpoints") { TEST_CASE("[AABB] Get longest/shortest axis") { const AABB aabb = AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6)); CHECK_MESSAGE( - aabb.get_longest_axis().is_equal_approx(Vector3(0, 0, 1)), + aabb.get_longest_axis() == Vector3(0, 0, 1), "get_longest_axis() should return the expected value."); CHECK_MESSAGE( aabb.get_longest_axis_index() == Vector3::AXIS_Z, - "get_longest_axis() should return the expected value."); + "get_longest_axis_index() should return the expected value."); CHECK_MESSAGE( - Math::is_equal_approx(aabb.get_longest_axis_size(), 6), - "get_longest_axis() should return the expected value."); + aabb.get_longest_axis_size() == 6, + "get_longest_axis_size() should return the expected value."); CHECK_MESSAGE( - aabb.get_shortest_axis().is_equal_approx(Vector3(1, 0, 0)), + aabb.get_shortest_axis() == Vector3(1, 0, 0), "get_shortest_axis() should return the expected value."); CHECK_MESSAGE( aabb.get_shortest_axis_index() == Vector3::AXIS_X, - "get_shortest_axis() should return the expected value."); + "get_shortest_axis_index() should return the expected value."); CHECK_MESSAGE( - Math::is_equal_approx(aabb.get_shortest_axis_size(), 4), - "get_shortest_axis() should return the expected value."); + aabb.get_shortest_axis_size() == 4, + "get_shortest_axis_size() should return the expected value."); } #ifndef _MSC_VER @@ -348,14 +349,27 @@ TEST_CASE("[AABB] Has point") { aabb.has_point(Vector3(2, 3, 0)), "has_point() with contained point should return the expected value."); CHECK_MESSAGE( + !aabb.has_point(Vector3(-20, 0, 0)), + "has_point() with non-contained point should return the expected value."); + + CHECK_MESSAGE( aabb.has_point(Vector3(-1.5, 3, 0)), - "has_point() with contained point on negative edge should return the expected value."); + "has_point() with positive size should include point on near face (X axis)."); CHECK_MESSAGE( aabb.has_point(Vector3(2.5, 3, 0)), - "has_point() with contained point on positive edge should return the expected value."); + "has_point() with positive size should include point on far face (X axis)."); CHECK_MESSAGE( - !aabb.has_point(Vector3(-20, 0, 0)), - "has_point() with non-contained point should return the expected value."); + aabb.has_point(Vector3(0, 2, 0)), + "has_point() with positive size should include point on near face (Y axis)."); + CHECK_MESSAGE( + aabb.has_point(Vector3(0, 7, 0)), + "has_point() with positive size should include point on far face (Y axis)."); + CHECK_MESSAGE( + aabb.has_point(Vector3(0, 3, -2.5)), + "has_point() with positive size should include point on near face (Z axis)."); + CHECK_MESSAGE( + aabb.has_point(Vector3(0, 3, 3.5)), + "has_point() with positive size should include point on far face (Z axis)."); } TEST_CASE("[AABB] Expanding") { diff --git a/tests/test_astar.h b/tests/core/math/test_astar.h index 12664a5ff1..859172dca3 100644 --- a/tests/test_astar.h +++ b/tests/core/math/test_astar.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -32,11 +32,6 @@ #define TEST_ASTAR_H #include "core/math/a_star.h" -#include "core/math/math_funcs.h" -#include "core/os/os.h" - -#include <math.h> -#include <stdio.h> #include "tests/test_macros.h" @@ -63,7 +58,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_basis.h b/tests/core/math/test_basis.h index 11c68f9eb7..257e41e82c 100644 --- a/tests/test_basis.h +++ b/tests/core/math/test_basis.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -31,9 +31,8 @@ #ifndef TEST_BASIS_H #define TEST_BASIS_H +#include "core/math/basis.h" #include "core/math/random_number_generator.h" -#include "core/os/os.h" -#include "core/string/ustring.h" #include "tests/test_macros.h" @@ -60,27 +59,27 @@ Basis EulerToBasis(RotOrder mode, const Vector3 &p_rotation) { Basis ret; switch (mode) { case EulerXYZ: - ret.set_euler_xyz(p_rotation); + ret.set_euler(p_rotation, Basis::EULER_ORDER_XYZ); break; case EulerXZY: - ret.set_euler_xzy(p_rotation); + ret.set_euler(p_rotation, Basis::EULER_ORDER_XZY); break; case EulerYZX: - ret.set_euler_yzx(p_rotation); + ret.set_euler(p_rotation, Basis::EULER_ORDER_YZX); break; case EulerYXZ: - ret.set_euler_yxz(p_rotation); + ret.set_euler(p_rotation, Basis::EULER_ORDER_YXZ); break; case EulerZXY: - ret.set_euler_zxy(p_rotation); + ret.set_euler(p_rotation, Basis::EULER_ORDER_ZXY); break; case EulerZYX: - ret.set_euler_zyx(p_rotation); + ret.set_euler(p_rotation, Basis::EULER_ORDER_ZYX); break; default: @@ -94,22 +93,22 @@ Basis EulerToBasis(RotOrder mode, const Vector3 &p_rotation) { Vector3 BasisToEuler(RotOrder mode, const Basis &p_rotation) { switch (mode) { case EulerXYZ: - return p_rotation.get_euler_xyz(); + return p_rotation.get_euler(Basis::EULER_ORDER_XYZ); case EulerXZY: - return p_rotation.get_euler_xzy(); + return p_rotation.get_euler(Basis::EULER_ORDER_XZY); case EulerYZX: - return p_rotation.get_euler_yzx(); + return p_rotation.get_euler(Basis::EULER_ORDER_YZX); case EulerYXZ: - return p_rotation.get_euler_yxz(); + return p_rotation.get_euler(Basis::EULER_ORDER_YXZ); case EulerZXY: - return p_rotation.get_euler_zxy(); + return p_rotation.get_euler(Basis::EULER_ORDER_ZXY); case EulerZYX: - return p_rotation.get_euler_zyx(); + return p_rotation.get_euler(Basis::EULER_ORDER_ZYX); default: // If you land here, Please integrate all rotation orders. @@ -170,9 +169,9 @@ void test_rotation(Vector3 deg_original_euler, RotOrder rot_order) { CHECK_MESSAGE((res.get_axis(2) - Vector3(0.0, 0.0, 1.0)).length() <= 0.1, vformat("Fail due to Z %s\n", String(res.get_axis(2))).utf8().ptr()); // Double check `to_rotation` decomposing with XYZ rotation order. - const Vector3 euler_xyz_from_rotation = to_rotation.get_euler_xyz(); + const Vector3 euler_xyz_from_rotation = to_rotation.get_euler(Basis::EULER_ORDER_XYZ); Basis rotation_from_xyz_computed_euler; - rotation_from_xyz_computed_euler.set_euler_xyz(euler_xyz_from_rotation); + rotation_from_xyz_computed_euler.set_euler(euler_xyz_from_rotation, Basis::EULER_ORDER_XYZ); res = to_rotation.inverse() * rotation_from_xyz_computed_euler; diff --git a/tests/test_color.h b/tests/core/math/test_color.h index eb8d7dcbd4..702f17a9cf 100644 --- a/tests/test_color.h +++ b/tests/core/math/test_color.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -33,7 +33,7 @@ #include "core/math/color.h" -#include "thirdparty/doctest/doctest.h" +#include "tests/test_macros.h" namespace TestColor { @@ -101,13 +101,13 @@ TEST_CASE("[Color] Reading methods") { const Color dark_blue = Color(0, 0, 0.5, 0.4); CHECK_MESSAGE( - Math::is_equal_approx(dark_blue.get_h(), 240 / 360.0), + Math::is_equal_approx(dark_blue.get_h(), 240.0f / 360.0f), "The returned HSV hue should match the expected value."); CHECK_MESSAGE( - Math::is_equal_approx(dark_blue.get_s(), 1), + Math::is_equal_approx(dark_blue.get_s(), 1.0f), "The returned HSV saturation should match the expected value."); CHECK_MESSAGE( - Math::is_equal_approx(dark_blue.get_v(), 0.5), + Math::is_equal_approx(dark_blue.get_v(), 0.5f), "The returned HSV value should match the expected value."); } @@ -140,21 +140,21 @@ TEST_CASE("[Color] Conversion methods") { cyan.to_rgba64() == 0x0000'ffff'ffff'ffff, "The returned 64-bit BGR number should match the expected value."); CHECK_MESSAGE( - String(cyan) == "0, 1, 1, 1", + String(cyan) == "(0, 1, 1, 1)", "The string representation should match the expected value."); } TEST_CASE("[Color] Named colors") { CHECK_MESSAGE( - Color::named("red").is_equal_approx(Color(1, 0, 0)), + Color::named("red").is_equal_approx(Color::hex(0xFF0000FF)), "The named color \"red\" should match the expected value."); // Named colors have their names automatically normalized. CHECK_MESSAGE( - Color::named("white_smoke").is_equal_approx(Color(0.96, 0.96, 0.96)), + Color::named("white_smoke").is_equal_approx(Color::hex(0xF5F5F5FF)), "The named color \"white_smoke\" should match the expected value."); CHECK_MESSAGE( - Color::named("Slate Blue").is_equal_approx(Color(0.42, 0.35, 0.80)), + Color::named("Slate Blue").is_equal_approx(Color::hex(0x6A5ACDFF)), "The named color \"Slate Blue\" should match the expected value."); ERR_PRINT_OFF; diff --git a/tests/test_expression.h b/tests/core/math/test_expression.h index 0ef60d1a19..6e3be541b0 100644 --- a/tests/test_expression.h +++ b/tests/core/math/test_expression.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -83,42 +83,42 @@ TEST_CASE("[Expression] Floating-point arithmetic") { expression.parse("-123.456") == OK, "Float identity should parse successfully."); CHECK_MESSAGE( - Math::is_equal_approx(float(expression.execute()), -123.456), + Math::is_equal_approx(double(expression.execute()), -123.456), "Float identity should return the expected result."); CHECK_MESSAGE( expression.parse("2.0 + 3.0") == OK, "Float addition should parse successfully."); CHECK_MESSAGE( - Math::is_equal_approx(float(expression.execute()), 5), + Math::is_equal_approx(double(expression.execute()), 5), "Float addition should return the expected result."); CHECK_MESSAGE( expression.parse("3.0 / 10") == OK, "Float / integer division should parse successfully."); CHECK_MESSAGE( - Math::is_equal_approx(float(expression.execute()), 0.3), + Math::is_equal_approx(double(expression.execute()), 0.3), "Float / integer division should return the expected result."); CHECK_MESSAGE( expression.parse("3 / 10.0") == OK, "Basic integer / float division should parse successfully."); CHECK_MESSAGE( - Math::is_equal_approx(float(expression.execute()), 0.3), + Math::is_equal_approx(double(expression.execute()), 0.3), "Basic integer / float division should return the expected result."); CHECK_MESSAGE( expression.parse("3.0 / 10.0") == OK, "Float / float division should parse successfully."); CHECK_MESSAGE( - Math::is_equal_approx(float(expression.execute()), 0.3), + Math::is_equal_approx(double(expression.execute()), 0.3), "Float / float division should return the expected result."); CHECK_MESSAGE( expression.parse("2.5 * (6.0 + 14.25) / 2.0 - 5.12345") == OK, "Float multiplication-addition-subtraction-division should parse successfully."); CHECK_MESSAGE( - Math::is_equal_approx(float(expression.execute()), 20.18905), + Math::is_equal_approx(double(expression.execute()), 20.18905), "Float multiplication-addition-subtraction-division should return the expected result."); } @@ -129,7 +129,7 @@ TEST_CASE("[Expression] Scientific notation") { expression.parse("2.e5") == OK, "The expression should parse successfully."); CHECK_MESSAGE( - Math::is_equal_approx(float(expression.execute()), 200'000), + Math::is_equal_approx(double(expression.execute()), 200'000), "The expression should return the expected result."); // The middle "e" is ignored here. @@ -137,14 +137,14 @@ TEST_CASE("[Expression] Scientific notation") { expression.parse("2e5") == OK, "The expression should parse successfully."); CHECK_MESSAGE( - Math::is_equal_approx(float(expression.execute()), 25), + Math::is_equal_approx(double(expression.execute()), 2e5), "The expression should return the expected result."); CHECK_MESSAGE( expression.parse("2e.5") == OK, "The expression should parse successfully."); CHECK_MESSAGE( - Math::is_equal_approx(float(expression.execute()), 2), + Math::is_equal_approx(double(expression.execute()), 2), "The expression should return the expected result."); } @@ -176,14 +176,14 @@ TEST_CASE("[Expression] Built-in functions") { expression.parse("snapped(sin(0.5), 0.01)") == OK, "The expression should parse successfully."); CHECK_MESSAGE( - Math::is_equal_approx(float(expression.execute()), 0.48), + Math::is_equal_approx(double(expression.execute()), 0.48), "`snapped(sin(0.5), 0.01)` should return the expected result."); CHECK_MESSAGE( expression.parse("pow(2.0, -2500)") == OK, "The expression should parse successfully."); CHECK_MESSAGE( - Math::is_zero_approx(float(expression.execute())), + Math::is_zero_approx(double(expression.execute())), "`pow(2.0, -2500)` should return the expected result (asymptotically zero)."); } @@ -410,7 +410,7 @@ TEST_CASE("[Expression] Unusual expressions") { "The expression should parse successfully."); ERR_PRINT_OFF; CHECK_MESSAGE( - Math::is_inf(float(expression.execute())), + Math::is_inf(double(expression.execute())), "`-25.4 / 0` should return inf."); ERR_PRINT_ON; diff --git a/tests/test_geometry_2d.h b/tests/core/math/test_geometry_2d.h index ea02d1114f..3487e4d7e8 100644 --- a/tests/test_geometry_2d.h +++ b/tests/core/math/test_geometry_2d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -32,7 +32,6 @@ #define TEST_GEOMETRY_2D_H #include "core/math/geometry_2d.h" -#include "core/templates/vector.h" #include "thirdparty/doctest/doctest.h" @@ -50,9 +49,7 @@ TEST_CASE("[Geometry2D] Point in circle") { CHECK(Geometry2D::is_point_in_circle(Vector2(7, -42), Vector2(4, -40), 3.7)); 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 beeing 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). + // This tests points on the edge of the circle. They are treated as being inside the circle. 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)); } @@ -65,8 +62,8 @@ TEST_CASE("[Geometry2D] Point in triangle") { CHECK(Geometry2D::is_point_in_triangle(Vector2(-3, -2.5), Vector2(-1, -4), Vector2(-3, -2), Vector2(-5, -4))); 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 beeing outside the triangle. - // In `is_point_in_circle` they are treated as being inside, so in order the make + // This tests points on the edge of the triangle. They are treated as being outside the triangle. + // 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 +92,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 beeing 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") { @@ -113,7 +115,7 @@ TEST_CASE("[Geometry2D] Polygon clockwise") { p.push_back(Vector2(1, 5)); CHECK(Geometry2D::is_polygon_clockwise(p)); - p.invert(); + p.reverse(); CHECK_FALSE(Geometry2D::is_polygon_clockwise(p)); } @@ -140,9 +142,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") { @@ -227,7 +241,7 @@ TEST_CASE("[Geometry2D] Polygon intersection") { CHECK(r[0][2].is_equal_approx(Point2(160.52632, 92.63157))); } - SUBCASE("[Geometry2D] Intersection with one polygon beeing completly inside the other polygon") { + SUBCASE("[Geometry2D] Intersection with one polygon being completely inside the other polygon") { b.push_back(Point2(80, 100)); b.push_back(Point2(50, 50)); b.push_back(Point2(150, 50)); diff --git a/tests/test_geometry_3d.h b/tests/core/math/test_geometry_3d.h index 2b2a424b2b..1b8d2eee34 100644 --- a/tests/test_geometry_3d.h +++ b/tests/core/math/test_geometry_3d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -28,17 +28,13 @@ /* 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" -#include "core/math/random_number_generator.h" -#include "core/math/vector3.h" #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 +53,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 +70,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 +88,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 +108,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 +127,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 +146,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 +172,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 +187,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 +198,7 @@ TEST_CASE("[Geometry3D] Clip Polygon") { } } } + TEST_CASE("[Geometry3D] Compute Convex Mesh Points") { struct Case { Vector<Plane> mesh; @@ -215,6 +224,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 +245,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 +265,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 +284,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 +304,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 +325,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 +345,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 +366,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 +385,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 +407,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 +432,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_math.cpp b/tests/core/math/test_math.cpp index 26c2aa2088..4182455b7a 100644 --- a/tests/test_math.cpp +++ b/tests/core/math/test_math.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -30,23 +30,11 @@ #include "test_math.h" -#include "core/math/basis.h" #include "core/math/camera_matrix.h" #include "core/math/delaunay_3d.h" #include "core/math/geometry_2d.h" -#include "core/math/math_funcs.h" -#include "core/math/transform.h" -#include "core/os/file_access.h" -#include "core/os/keyboard.h" +#include "core/os/main_loop.h" #include "core/os/os.h" -#include "core/string/print_string.h" -#include "core/string/ustring.h" -#include "core/templates/vmap.h" -#include "core/variant/method_ptrcall.h" -#include "core/variant/variant.h" -#include "scene/main/node.h" -#include "scene/resources/texture.h" -#include "servers/rendering/shader_language.h" namespace TestMath { @@ -239,7 +227,7 @@ class GetClassAndNamespace { return TK_SYMBOL; } - if (code[idx] == '-' || (code[idx] >= '0' && code[idx] <= '9')) { + if (code[idx] == '-' || is_digit(code[idx])) { //a number const char32_t *rptr; double number = String::to_float(&code[idx], &rptr); @@ -247,10 +235,10 @@ class GetClassAndNamespace { value = number; return TK_NUMBER; - } else if ((code[idx] >= 'A' && code[idx] <= 'Z') || (code[idx] >= 'a' && code[idx] <= 'z') || code[idx] > 127) { + } else if (is_ascii_char(code[idx]) || code[idx] > 127) { String id; - while ((code[idx] >= 'A' && code[idx] <= 'Z') || (code[idx] >= 'a' && code[idx] <= 'z') || code[idx] > 127) { + while (is_ascii_char(code[idx]) || code[idx] > 127) { id += code[idx]; idx++; } @@ -296,8 +284,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; @@ -320,11 +308,11 @@ public: curly_stack++; break; } else { - break; //whathever else + break; //whatever else } } - if (name != String()) { + if (!name.is_empty()) { namespace_stack[at_level] = name; } @@ -529,8 +517,8 @@ MainLoop *test() { ERR_FAIL_COND_V_MSG(!fa, nullptr, "Could not open file: " + test); Vector<uint8_t> buf; - int flen = fa->get_len(); - buf.resize(fa->get_len() + 1); + uint64_t flen = fa->get_length(); + buf.resize(fa->get_length() + 1); fa->get_buffer(buf.ptrw(), flen); buf.write[flen] = 0; @@ -549,8 +537,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,23 +577,23 @@ 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); - Quat q = m; - Quat q2 = m2; + Quaternion q = m; + Quaternion q2 = m2; Basis m3 = m.inverse() * m2; - Quat q3 = (q.inverse() * q2); //.normalized(); + Quaternion q3 = (q.inverse() * q2); //.normalized(); - print_line(Quat(m3)); + print_line(Quaternion(m3)); print_line(q3); print_line("before v: " + v + " a: " + rtos(a)); diff --git a/tests/test_math.h b/tests/core/math/test_math.h index 4375925bd5..a8aa8f6847 100644 --- a/tests/test_math.h +++ b/tests/core/math/test_math.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -31,7 +31,7 @@ #ifndef TEST_MATH_H #define TEST_MATH_H -#include "core/os/main_loop.h" +class MainLoop; namespace TestMath { diff --git a/tests/test_random_number_generator.h b/tests/core/math/test_random_number_generator.h index 39c4771c19..e8cd47b9d7 100644 --- a/tests/test_random_number_generator.h +++ b/tests/core/math/test_random_number_generator.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ diff --git a/tests/test_rect2.h b/tests/core/math/test_rect2.h index b94a8b7d05..0b1106ac3c 100644 --- a/tests/test_rect2.h +++ b/tests/core/math/test_rect2.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -32,15 +32,11 @@ #define TEST_RECT2_H #include "core/math/rect2.h" +#include "core/math/rect2i.h" #include "thirdparty/doctest/doctest.h" namespace TestRect2 { -// We also test Rect2i here, for consistency with the source code where Rect2 -// and Rect2i are defined in the same file. - -// Rect2 - TEST_CASE("[Rect2] Constructor methods") { const Rect2 rect = Rect2(0, 100, 1280, 720); const Rect2 rect_vector = Rect2(Vector2(0, 100), Vector2(1280, 720)); @@ -61,7 +57,7 @@ TEST_CASE("[Rect2] Constructor methods") { TEST_CASE("[Rect2] String conversion") { // Note: This also depends on the Vector2 string representation. CHECK_MESSAGE( - String(Rect2(0, 100, 1280, 720)) == "0, 100, 1280, 720", + String(Rect2(0, 100, 1280, 720)) == "[P: (0, 100), S: (1280, 720)]", "The string representation should match the expected value."); } @@ -76,6 +72,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") { @@ -144,7 +146,7 @@ TEST_CASE("[Rect2] Absolute coordinates") { "abs() should return the expected Rect2."); } -TEST_CASE("[Rect2] Intersecton") { +TEST_CASE("[Rect2] Intersection") { CHECK_MESSAGE( Rect2(0, 100, 1280, 720).intersection(Rect2(0, 300, 100, 100)).is_equal_approx(Rect2(0, 300, 100, 100)), "intersection() with fully enclosed Rect2 should return the expected result."); @@ -205,262 +207,98 @@ TEST_CASE("[Rect2] Growing") { } TEST_CASE("[Rect2] Has point") { + Rect2 rect = Rect2(0, 100, 1280, 720); CHECK_MESSAGE( - Rect2(0, 100, 1280, 720).has_point(Vector2(500, 600)), + rect.has_point(Vector2(500, 600)), "has_point() with contained Vector2 should return the expected result."); CHECK_MESSAGE( - !Rect2(0, 100, 1280, 720).has_point(Vector2(0, 0)), + !rect.has_point(Vector2(0, 0)), "has_point() with non-contained Vector2 should return the expected result."); CHECK_MESSAGE( - Rect2(0, 100, 1280, 720).has_point(Vector2(0, 110)), - "has_point() with positive Vector2 on left edge should return the expected result."); - CHECK_MESSAGE( - !Rect2(0, 100, 1280, 720).has_point(Vector2(1280, 110)), - "has_point() with positive Vector2 on right edge should return the expected result."); - - CHECK_MESSAGE( - Rect2(-4000, 100, 1280, 720).has_point(Vector2(-4000, 110)), - "has_point() with negative Vector2 on left edge should return the expected result."); - CHECK_MESSAGE( - !Rect2(-4000, 100, 1280, 720).has_point(Vector2(-2720, 110)), - "has_point() with negative Vector2 on right edge should return the expected result."); -} - -TEST_CASE("[Rect2] Intersection") { - CHECK_MESSAGE( - Rect2(0, 100, 1280, 720).intersects(Rect2(0, 300, 100, 100)), - "intersects() with fully enclosed Rect2 should return the expected result."); - CHECK_MESSAGE( - Rect2(0, 100, 1280, 720).intersects(Rect2(1200, 700, 100, 100)), - "intersects() with partially enclosed Rect2 should return the expected result."); - CHECK_MESSAGE( - !Rect2(0, 100, 1280, 720).intersects(Rect2(-4000, -4000, 100, 100)), - "intersects() with non-enclosed Rect2 should return the expected result."); -} - -TEST_CASE("[Rect2] Merging") { - CHECK_MESSAGE( - Rect2(0, 100, 1280, 720).merge(Rect2(0, 300, 100, 100)).is_equal_approx(Rect2(0, 100, 1280, 720)), - "merge() with fully enclosed Rect2 should return the expected result."); - CHECK_MESSAGE( - Rect2(0, 100, 1280, 720).merge(Rect2(1200, 700, 100, 100)).is_equal_approx(Rect2(0, 100, 1300, 720)), - "merge() with partially enclosed Rect2 should return the expected result."); - CHECK_MESSAGE( - Rect2(0, 100, 1280, 720).merge(Rect2(-4000, -4000, 100, 100)).is_equal_approx(Rect2(-4000, -4000, 5280, 4820)), - "merge() with non-enclosed Rect2 should return the expected result."); -} - -// Rect2i - -TEST_CASE("[Rect2i] Constructor methods") { - Rect2i recti = Rect2i(0, 100, 1280, 720); - Rect2i recti_vector = Rect2i(Vector2i(0, 100), Vector2i(1280, 720)); - Rect2i recti_copy_recti = Rect2i(recti); - Rect2i recti_copy_rect = Rect2i(Rect2(0, 100, 1280, 720)); - - CHECK_MESSAGE( - recti == recti_vector, - "Rect2is created with the same dimensions but by different methods should be equal."); - CHECK_MESSAGE( - recti == recti_copy_recti, - "Rect2is created with the same dimensions but by different methods should be equal."); - CHECK_MESSAGE( - recti == recti_copy_rect, - "Rect2is created with the same dimensions but by different methods should be equal."); -} - -TEST_CASE("[Rect2i] String conversion") { - // Note: This also depends on the Vector2 string representation. - CHECK_MESSAGE( - String(Rect2i(0, 100, 1280, 720)) == "0, 100, 1280, 720", - "The string representation should match the expected value."); -} - -TEST_CASE("[Rect2i] Basic getters") { - const Rect2i rect = Rect2i(0, 100, 1280, 720); - CHECK_MESSAGE( - rect.get_position() == Vector2i(0, 100), - "get_position() should return the expected value."); - CHECK_MESSAGE( - rect.get_size() == Vector2i(1280, 720), - "get_size() should return the expected value."); - CHECK_MESSAGE( - rect.get_end() == Vector2i(1280, 820), - "get_end() should return the expected value."); -} - -TEST_CASE("[Rect2i] Basic setters") { - Rect2i rect = Rect2i(0, 100, 1280, 720); - rect.set_end(Vector2i(4000, 4000)); - CHECK_MESSAGE( - rect == Rect2i(0, 100, 4000, 3900), - "set_end() should result in the expected Rect2i."); - - rect = Rect2i(0, 100, 1280, 720); - rect.set_position(Vector2i(4000, 4000)); + rect.has_point(rect.position), + "has_point() with positive size should include `position`."); CHECK_MESSAGE( - rect == Rect2i(4000, 4000, 1280, 720), - "set_position() should result in the expected Rect2i."); - - rect = Rect2i(0, 100, 1280, 720); - rect.set_size(Vector2i(4000, 4000)); + rect.has_point(rect.position + Vector2(1, 1)), + "has_point() with positive size should include `position + (1, 1)`."); CHECK_MESSAGE( - rect == Rect2i(0, 100, 4000, 4000), - "set_size() should result in the expected Rect2i."); -} - -TEST_CASE("[Rect2i] Area getters") { + !rect.has_point(rect.position + Vector2(1, -1)), + "has_point() with positive size should not include `position + (1, -1)`."); CHECK_MESSAGE( - Math::is_equal_approx(Rect2i(0, 100, 1280, 720).get_area(), 921'600), - "get_area() should return the expected value."); + !rect.has_point(rect.position + rect.size), + "has_point() with positive size should not include `position + size`."); CHECK_MESSAGE( - Math::is_equal_approx(Rect2i(0, 100, -1280, -720).get_area(), 921'600), - "get_area() should return the expected value."); + !rect.has_point(rect.position + rect.size + Vector2(1, 1)), + "has_point() with positive size should not include `position + size + (1, 1)`."); CHECK_MESSAGE( - Math::is_equal_approx(Rect2i(0, 100, 1280, -720).get_area(), -921'600), - "get_area() should return the expected value."); + rect.has_point(rect.position + rect.size + Vector2(-1, -1)), + "has_point() with positive size should include `position + size + (-1, -1)`."); CHECK_MESSAGE( - Math::is_equal_approx(Rect2i(0, 100, -1280, 720).get_area(), -921'600), - "get_area() should return the expected value."); - CHECK_MESSAGE( - Math::is_zero_approx(Rect2i(0, 100, 0, 720).get_area()), - "get_area() should return the expected value."); + !rect.has_point(rect.position + rect.size + Vector2(-1, 1)), + "has_point() with positive size should not include `position + size + (-1, 1)`."); CHECK_MESSAGE( - !Rect2i(0, 100, 1280, 720).has_no_area(), - "has_no_area() should return the expected value on Rect2i with an area."); + rect.has_point(rect.position + Vector2(0, 10)), + "has_point() with point located on left edge should return true."); CHECK_MESSAGE( - Rect2i(0, 100, 0, 500).has_no_area(), - "has_no_area() should return the expected value on Rect2i with no area."); + !rect.has_point(rect.position + Vector2(rect.size.x, 10)), + "has_point() with point located on right edge should return false."); CHECK_MESSAGE( - Rect2i(0, 100, 500, 0).has_no_area(), - "has_no_area() should return the expected value on Rect2i with no area."); + rect.has_point(rect.position + Vector2(10, 0)), + "has_point() with point located on top edge should return true."); CHECK_MESSAGE( - Rect2i(0, 100, 0, 0).has_no_area(), - "has_no_area() should return the expected value on Rect2i with no area."); -} + !rect.has_point(rect.position + Vector2(10, rect.size.y)), + "has_point() with point located on bottom edge should return false."); -TEST_CASE("[Rect2i] Absolute coordinates") { - CHECK_MESSAGE( - Rect2i(0, 100, 1280, 720).abs() == Rect2i(0, 100, 1280, 720), - "abs() should return the expected Rect2i."); + /* + // FIXME: Disabled for now until GH-37617 is fixed one way or another. + // More tests should then be written like for the positive size case. + rect = Rect2(0, 100, -1280, -720); CHECK_MESSAGE( - Rect2i(0, -100, 1280, 720).abs() == Rect2i(0, -100, 1280, 720), - "abs() should return the expected Rect2i."); + rect.has_point(rect.position), + "has_point() with negative size should include `position`."); CHECK_MESSAGE( - Rect2i(0, -100, -1280, -720).abs() == Rect2i(-1280, -820, 1280, 720), - "abs() should return the expected Rect2i."); - CHECK_MESSAGE( - Rect2i(0, 100, -1280, 720).abs() == Rect2i(-1280, 100, 1280, 720), - "abs() should return the expected Rect2i."); -} + !rect.has_point(rect.position + rect.size), + "has_point() with negative size should not include `position + size`."); + */ -TEST_CASE("[Rect2i] Intersection") { + rect = Rect2(-4000, -200, 1280, 720); CHECK_MESSAGE( - Rect2i(0, 100, 1280, 720).intersection(Rect2i(0, 300, 100, 100)) == Rect2i(0, 300, 100, 100), - "intersection() with fully enclosed Rect2i should return the expected result."); - // The resulting Rect2i is 100 pixels high because the first Rect2i is vertically offset by 100 pixels. + rect.has_point(rect.position + Vector2(0, 10)), + "has_point() with negative position and point located on left edge should return true."); CHECK_MESSAGE( - Rect2i(0, 100, 1280, 720).intersection(Rect2i(1200, 700, 100, 100)) == Rect2i(1200, 700, 80, 100), - "intersection() with partially enclosed Rect2i should return the expected result."); + !rect.has_point(rect.position + Vector2(rect.size.x, 10)), + "has_point() with negative position and point located on right edge should return false."); CHECK_MESSAGE( - Rect2i(0, 100, 1280, 720).intersection(Rect2i(-4000, -4000, 100, 100)) == Rect2i(), - "intersection() with non-enclosed Rect2i should return the expected result."); -} - -TEST_CASE("[Rect2i] Enclosing") { - CHECK_MESSAGE( - Rect2i(0, 100, 1280, 720).encloses(Rect2i(0, 300, 100, 100)), - "encloses() with fully contained Rect2i should return the expected result."); - CHECK_MESSAGE( - !Rect2i(0, 100, 1280, 720).encloses(Rect2i(1200, 700, 100, 100)), - "encloses() with partially contained Rect2i should return the expected result."); - CHECK_MESSAGE( - !Rect2i(0, 100, 1280, 720).encloses(Rect2i(-4000, -4000, 100, 100)), - "encloses() with non-contained Rect2i should return the expected result."); -} - -TEST_CASE("[Rect2i] Expanding") { - CHECK_MESSAGE( - Rect2i(0, 100, 1280, 720).expand(Vector2i(500, 600)) == Rect2i(0, 100, 1280, 720), - "expand() with contained Vector2i should return the expected result."); + rect.has_point(rect.position + Vector2(10, 0)), + "has_point() with negative position and point located on top edge should return true."); CHECK_MESSAGE( - Rect2i(0, 100, 1280, 720).expand(Vector2i(0, 0)) == Rect2i(0, 0, 1280, 820), - "expand() with non-contained Vector2i should return the expected result."); + !rect.has_point(rect.position + Vector2(10, rect.size.y)), + "has_point() with negative position and point located on bottom edge should return false."); } -TEST_CASE("[Rect2i] Growing") { - CHECK_MESSAGE( - Rect2i(0, 100, 1280, 720).grow(100) == Rect2i(-100, 0, 1480, 920), - "grow() with positive value should return the expected Rect2i."); - CHECK_MESSAGE( - Rect2i(0, 100, 1280, 720).grow(-100) == Rect2i(100, 200, 1080, 520), - "grow() with negative value should return the expected Rect2i."); - CHECK_MESSAGE( - Rect2i(0, 100, 1280, 720).grow(-4000) == Rect2i(4000, 4100, -6720, -7280), - "grow() with large negative value should return the expected Rect2i."); - - CHECK_MESSAGE( - Rect2i(0, 100, 1280, 720).grow_individual(100, 200, 300, 400) == Rect2i(-100, -100, 1680, 1320), - "grow_individual() with positive values should return the expected Rect2i."); - CHECK_MESSAGE( - Rect2i(0, 100, 1280, 720).grow_individual(-100, 200, 300, -400) == Rect2i(100, -100, 1480, 520), - "grow_individual() with positive and negative values should return the expected Rect2i."); - - CHECK_MESSAGE( - Rect2i(0, 100, 1280, 720).grow_side(SIDE_TOP, 500) == Rect2i(0, -400, 1280, 1220), - "grow_side() with positive value should return the expected Rect2i."); - CHECK_MESSAGE( - Rect2i(0, 100, 1280, 720).grow_side(SIDE_TOP, -500) == Rect2i(0, 600, 1280, 220), - "grow_side() with negative value should return the expected Rect2i."); -} - -TEST_CASE("[Rect2i] Has point") { - CHECK_MESSAGE( - Rect2i(0, 100, 1280, 720).has_point(Vector2i(500, 600)), - "has_point() with contained Vector2i should return the expected result."); - CHECK_MESSAGE( - !Rect2i(0, 100, 1280, 720).has_point(Vector2i(0, 0)), - "has_point() with non-contained Vector2i should return the expected result."); - - CHECK_MESSAGE( - Rect2i(0, 100, 1280, 720).has_point(Vector2(0, 110)), - "has_point() with positive Vector2 on left edge should return the expected result."); - CHECK_MESSAGE( - !Rect2i(0, 100, 1280, 720).has_point(Vector2(1280, 110)), - "has_point() with positive Vector2 on right edge should return the expected result."); - - CHECK_MESSAGE( - Rect2i(-4000, 100, 1280, 720).has_point(Vector2(-4000, 110)), - "has_point() with negative Vector2 on left edge should return the expected result."); - CHECK_MESSAGE( - !Rect2i(-4000, 100, 1280, 720).has_point(Vector2(-2720, 110)), - "has_point() with negative Vector2 on right edge should return the expected result."); -} - -TEST_CASE("[Rect2i] Intersection") { +TEST_CASE("[Rect2] Intersection") { CHECK_MESSAGE( - Rect2i(0, 100, 1280, 720).intersects(Rect2i(0, 300, 100, 100)), - "intersects() with fully enclosed Rect2i should return the expected result."); + Rect2(0, 100, 1280, 720).intersects(Rect2(0, 300, 100, 100)), + "intersects() with fully enclosed Rect2 should return the expected result."); CHECK_MESSAGE( - Rect2i(0, 100, 1280, 720).intersects(Rect2i(1200, 700, 100, 100)), - "intersects() with partially enclosed Rect2i should return the expected result."); + Rect2(0, 100, 1280, 720).intersects(Rect2(1200, 700, 100, 100)), + "intersects() with partially enclosed Rect2 should return the expected result."); CHECK_MESSAGE( - !Rect2i(0, 100, 1280, 720).intersects(Rect2i(-4000, -4000, 100, 100)), - "intersects() with non-enclosed Rect2i should return the expected result."); + !Rect2(0, 100, 1280, 720).intersects(Rect2(-4000, -4000, 100, 100)), + "intersects() with non-enclosed Rect2 should return the expected result."); } -TEST_CASE("[Rect2i] Merging") { +TEST_CASE("[Rect2] Merging") { CHECK_MESSAGE( - Rect2i(0, 100, 1280, 720).merge(Rect2i(0, 300, 100, 100)) == Rect2i(0, 100, 1280, 720), - "merge() with fully enclosed Rect2i should return the expected result."); + Rect2(0, 100, 1280, 720).merge(Rect2(0, 300, 100, 100)).is_equal_approx(Rect2(0, 100, 1280, 720)), + "merge() with fully enclosed Rect2 should return the expected result."); CHECK_MESSAGE( - Rect2i(0, 100, 1280, 720).merge(Rect2i(1200, 700, 100, 100)) == Rect2i(0, 100, 1300, 720), - "merge() with partially enclosed Rect2i should return the expected result."); + Rect2(0, 100, 1280, 720).merge(Rect2(1200, 700, 100, 100)).is_equal_approx(Rect2(0, 100, 1300, 720)), + "merge() with partially enclosed Rect2 should return the expected result."); CHECK_MESSAGE( - Rect2i(0, 100, 1280, 720).merge(Rect2i(-4000, -4000, 100, 100)) == Rect2i(-4000, -4000, 5280, 4820), - "merge() with non-enclosed Rect2i should return the expected result."); + Rect2(0, 100, 1280, 720).merge(Rect2(-4000, -4000, 100, 100)).is_equal_approx(Rect2(-4000, -4000, 5280, 4820)), + "merge() with non-enclosed Rect2 should return the expected result."); } } // namespace TestRect2 diff --git a/tests/core/math/test_rect2i.h b/tests/core/math/test_rect2i.h new file mode 100644 index 0000000000..0d1a088a66 --- /dev/null +++ b/tests/core/math/test_rect2i.h @@ -0,0 +1,311 @@ +/*************************************************************************/ +/* test_rect2i.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_RECT2I_H +#define TEST_RECT2I_H + +#include "core/math/rect2.h" +#include "core/math/rect2i.h" + +#include "thirdparty/doctest/doctest.h" + +namespace TestRect2i { +TEST_CASE("[Rect2i] Constructor methods") { + Rect2i recti = Rect2i(0, 100, 1280, 720); + Rect2i recti_vector = Rect2i(Vector2i(0, 100), Vector2i(1280, 720)); + Rect2i recti_copy_recti = Rect2i(recti); + Rect2i recti_copy_rect = Rect2i(Rect2(0, 100, 1280, 720)); + + CHECK_MESSAGE( + recti == recti_vector, + "Rect2is created with the same dimensions but by different methods should be equal."); + CHECK_MESSAGE( + recti == recti_copy_recti, + "Rect2is created with the same dimensions but by different methods should be equal."); + CHECK_MESSAGE( + recti == recti_copy_rect, + "Rect2is created with the same dimensions but by different methods should be equal."); +} + +TEST_CASE("[Rect2i] String conversion") { + // Note: This also depends on the Vector2 string representation. + CHECK_MESSAGE( + String(Rect2i(0, 100, 1280, 720)) == "[P: (0, 100), S: (1280, 720)]", + "The string representation should match the expected value."); +} + +TEST_CASE("[Rect2i] Basic getters") { + const Rect2i rect = Rect2i(0, 100, 1280, 720); + CHECK_MESSAGE( + rect.get_position() == Vector2i(0, 100), + "get_position() should return the expected value."); + CHECK_MESSAGE( + rect.get_size() == Vector2i(1280, 720), + "get_size() should return the expected value."); + 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") { + Rect2i rect = Rect2i(0, 100, 1280, 720); + rect.set_end(Vector2i(4000, 4000)); + CHECK_MESSAGE( + rect == Rect2i(0, 100, 4000, 3900), + "set_end() should result in the expected Rect2i."); + + rect = Rect2i(0, 100, 1280, 720); + rect.set_position(Vector2i(4000, 4000)); + CHECK_MESSAGE( + rect == Rect2i(4000, 4000, 1280, 720), + "set_position() should result in the expected Rect2i."); + + rect = Rect2i(0, 100, 1280, 720); + rect.set_size(Vector2i(4000, 4000)); + CHECK_MESSAGE( + rect == Rect2i(0, 100, 4000, 4000), + "set_size() should result in the expected Rect2i."); +} + +TEST_CASE("[Rect2i] Area getters") { + CHECK_MESSAGE( + Rect2i(0, 100, 1280, 720).get_area() == 921'600, + "get_area() should return the expected value."); + CHECK_MESSAGE( + Rect2i(0, 100, -1280, -720).get_area() == 921'600, + "get_area() should return the expected value."); + CHECK_MESSAGE( + Rect2i(0, 100, 1280, -720).get_area() == -921'600, + "get_area() should return the expected value."); + CHECK_MESSAGE( + Rect2i(0, 100, -1280, 720).get_area() == -921'600, + "get_area() should return the expected value."); + CHECK_MESSAGE( + Rect2i(0, 100, 0, 720).get_area() == 0, + "get_area() should return the expected value."); + + CHECK_MESSAGE( + !Rect2i(0, 100, 1280, 720).has_no_area(), + "has_no_area() should return the expected value on Rect2i with an area."); + CHECK_MESSAGE( + Rect2i(0, 100, 0, 500).has_no_area(), + "has_no_area() should return the expected value on Rect2i with no area."); + CHECK_MESSAGE( + Rect2i(0, 100, 500, 0).has_no_area(), + "has_no_area() should return the expected value on Rect2i with no area."); + CHECK_MESSAGE( + Rect2i(0, 100, 0, 0).has_no_area(), + "has_no_area() should return the expected value on Rect2i with no area."); +} + +TEST_CASE("[Rect2i] Absolute coordinates") { + CHECK_MESSAGE( + Rect2i(0, 100, 1280, 720).abs() == Rect2i(0, 100, 1280, 720), + "abs() should return the expected Rect2i."); + CHECK_MESSAGE( + Rect2i(0, -100, 1280, 720).abs() == Rect2i(0, -100, 1280, 720), + "abs() should return the expected Rect2i."); + CHECK_MESSAGE( + Rect2i(0, -100, -1280, -720).abs() == Rect2i(-1280, -820, 1280, 720), + "abs() should return the expected Rect2i."); + CHECK_MESSAGE( + Rect2i(0, 100, -1280, 720).abs() == Rect2i(-1280, 100, 1280, 720), + "abs() should return the expected Rect2i."); +} + +TEST_CASE("[Rect2i] Intersection") { + CHECK_MESSAGE( + Rect2i(0, 100, 1280, 720).intersection(Rect2i(0, 300, 100, 100)) == Rect2i(0, 300, 100, 100), + "intersection() with fully enclosed Rect2i should return the expected result."); + // The resulting Rect2i is 100 pixels high because the first Rect2i is vertically offset by 100 pixels. + CHECK_MESSAGE( + Rect2i(0, 100, 1280, 720).intersection(Rect2i(1200, 700, 100, 100)) == Rect2i(1200, 700, 80, 100), + "intersection() with partially enclosed Rect2i should return the expected result."); + CHECK_MESSAGE( + Rect2i(0, 100, 1280, 720).intersection(Rect2i(-4000, -4000, 100, 100)) == Rect2i(), + "intersection() with non-enclosed Rect2i should return the expected result."); +} + +TEST_CASE("[Rect2i] Enclosing") { + CHECK_MESSAGE( + Rect2i(0, 100, 1280, 720).encloses(Rect2i(0, 300, 100, 100)), + "encloses() with fully contained Rect2i should return the expected result."); + CHECK_MESSAGE( + !Rect2i(0, 100, 1280, 720).encloses(Rect2i(1200, 700, 100, 100)), + "encloses() with partially contained Rect2i should return the expected result."); + CHECK_MESSAGE( + !Rect2i(0, 100, 1280, 720).encloses(Rect2i(-4000, -4000, 100, 100)), + "encloses() with non-contained Rect2i should return the expected result."); + CHECK_MESSAGE( + Rect2i(0, 100, 1280, 720).encloses(Rect2i(0, 100, 1280, 720)), + "encloses() with identical Rect2i should return the expected result."); +} + +TEST_CASE("[Rect2i] Expanding") { + CHECK_MESSAGE( + Rect2i(0, 100, 1280, 720).expand(Vector2i(500, 600)) == Rect2i(0, 100, 1280, 720), + "expand() with contained Vector2i should return the expected result."); + CHECK_MESSAGE( + Rect2i(0, 100, 1280, 720).expand(Vector2i(0, 0)) == Rect2i(0, 0, 1280, 820), + "expand() with non-contained Vector2i should return the expected result."); +} + +TEST_CASE("[Rect2i] Growing") { + CHECK_MESSAGE( + Rect2i(0, 100, 1280, 720).grow(100) == Rect2i(-100, 0, 1480, 920), + "grow() with positive value should return the expected Rect2i."); + CHECK_MESSAGE( + Rect2i(0, 100, 1280, 720).grow(-100) == Rect2i(100, 200, 1080, 520), + "grow() with negative value should return the expected Rect2i."); + CHECK_MESSAGE( + Rect2i(0, 100, 1280, 720).grow(-4000) == Rect2i(4000, 4100, -6720, -7280), + "grow() with large negative value should return the expected Rect2i."); + + CHECK_MESSAGE( + Rect2i(0, 100, 1280, 720).grow_individual(100, 200, 300, 400) == Rect2i(-100, -100, 1680, 1320), + "grow_individual() with positive values should return the expected Rect2i."); + CHECK_MESSAGE( + Rect2i(0, 100, 1280, 720).grow_individual(-100, 200, 300, -400) == Rect2i(100, -100, 1480, 520), + "grow_individual() with positive and negative values should return the expected Rect2i."); + + CHECK_MESSAGE( + Rect2i(0, 100, 1280, 720).grow_side(SIDE_TOP, 500) == Rect2i(0, -400, 1280, 1220), + "grow_side() with positive value should return the expected Rect2i."); + CHECK_MESSAGE( + Rect2i(0, 100, 1280, 720).grow_side(SIDE_TOP, -500) == Rect2i(0, 600, 1280, 220), + "grow_side() with negative value should return the expected Rect2i."); +} + +TEST_CASE("[Rect2i] Has point") { + Rect2i rect = Rect2i(0, 100, 1280, 720); + CHECK_MESSAGE( + rect.has_point(Vector2i(500, 600)), + "has_point() with contained Vector2i should return the expected result."); + CHECK_MESSAGE( + !rect.has_point(Vector2i(0, 0)), + "has_point() with non-contained Vector2i should return the expected result."); + + CHECK_MESSAGE( + rect.has_point(rect.position), + "has_point() with positive size should include `position`."); + CHECK_MESSAGE( + rect.has_point(rect.position + Vector2i(1, 1)), + "has_point() with positive size should include `position + (1, 1)`."); + CHECK_MESSAGE( + !rect.has_point(rect.position + Vector2i(1, -1)), + "has_point() with positive size should not include `position + (1, -1)`."); + CHECK_MESSAGE( + !rect.has_point(rect.position + rect.size), + "has_point() with positive size should not include `position + size`."); + CHECK_MESSAGE( + !rect.has_point(rect.position + rect.size + Vector2i(1, 1)), + "has_point() with positive size should not include `position + size + (1, 1)`."); + CHECK_MESSAGE( + rect.has_point(rect.position + rect.size + Vector2i(-1, -1)), + "has_point() with positive size should include `position + size + (-1, -1)`."); + CHECK_MESSAGE( + !rect.has_point(rect.position + rect.size + Vector2i(-1, 1)), + "has_point() with positive size should not include `position + size + (-1, 1)`."); + + CHECK_MESSAGE( + rect.has_point(rect.position + Vector2i(0, 10)), + "has_point() with point located on left edge should return true."); + CHECK_MESSAGE( + !rect.has_point(rect.position + Vector2i(rect.size.x, 10)), + "has_point() with point located on right edge should return false."); + CHECK_MESSAGE( + rect.has_point(rect.position + Vector2i(10, 0)), + "has_point() with point located on top edge should return true."); + CHECK_MESSAGE( + !rect.has_point(rect.position + Vector2i(10, rect.size.y)), + "has_point() with point located on bottom edge should return false."); + + /* + // FIXME: Disabled for now until GH-37617 is fixed one way or another. + // More tests should then be written like for the positive size case. + rect = Rect2i(0, 100, -1280, -720); + CHECK_MESSAGE( + rect.has_point(rect.position), + "has_point() with negative size should include `position`."); + CHECK_MESSAGE( + !rect.has_point(rect.position + rect.size), + "has_point() with negative size should not include `position + size`."); + */ + + rect = Rect2i(-4000, -200, 1280, 720); + CHECK_MESSAGE( + rect.has_point(rect.position + Vector2i(0, 10)), + "has_point() with negative position and point located on left edge should return true."); + CHECK_MESSAGE( + !rect.has_point(rect.position + Vector2i(rect.size.x, 10)), + "has_point() with negative position and point located on right edge should return false."); + CHECK_MESSAGE( + rect.has_point(rect.position + Vector2i(10, 0)), + "has_point() with negative position and point located on top edge should return true."); + CHECK_MESSAGE( + !rect.has_point(rect.position + Vector2i(10, rect.size.y)), + "has_point() with negative position and point located on bottom edge should return false."); +} + +TEST_CASE("[Rect2i] Intersection") { + CHECK_MESSAGE( + Rect2i(0, 100, 1280, 720).intersects(Rect2i(0, 300, 100, 100)), + "intersects() with fully enclosed Rect2i should return the expected result."); + CHECK_MESSAGE( + Rect2i(0, 100, 1280, 720).intersects(Rect2i(1200, 700, 100, 100)), + "intersects() with partially enclosed Rect2i should return the expected result."); + CHECK_MESSAGE( + !Rect2i(0, 100, 1280, 720).intersects(Rect2i(-4000, -4000, 100, 100)), + "intersects() with non-enclosed Rect2i should return the expected result."); + CHECK_MESSAGE( + !Rect2i(0, 0, 2, 2).intersects(Rect2i(2, 2, 2, 2)), + "intersects() with adjacent Rect2i should return the expected result."); +} + +TEST_CASE("[Rect2i] Merging") { + CHECK_MESSAGE( + Rect2i(0, 100, 1280, 720).merge(Rect2i(0, 300, 100, 100)) == Rect2i(0, 100, 1280, 720), + "merge() with fully enclosed Rect2i should return the expected result."); + CHECK_MESSAGE( + Rect2i(0, 100, 1280, 720).merge(Rect2i(1200, 700, 100, 100)) == Rect2i(0, 100, 1300, 720), + "merge() with partially enclosed Rect2i should return the expected result."); + CHECK_MESSAGE( + Rect2i(0, 100, 1280, 720).merge(Rect2i(-4000, -4000, 100, 100)) == Rect2i(-4000, -4000, 5280, 4820), + "merge() with non-enclosed Rect2i should return the expected result."); +} +} // namespace TestRect2i + +#endif // TEST_RECT2I_H diff --git a/tests/core/math/test_vector2.h b/tests/core/math/test_vector2.h new file mode 100644 index 0000000000..9b7800164a --- /dev/null +++ b/tests/core/math/test_vector2.h @@ -0,0 +1,389 @@ +/*************************************************************************/ +/* test_vector2.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_VECTOR2_H +#define TEST_VECTOR2_H + +#include "core/math/vector2.h" +#include "core/math/vector2i.h" +#include "tests/test_macros.h" + +namespace TestVector2 { + +TEST_CASE("[Vector2] Angle methods") { + const Vector2 vector_x = Vector2(1, 0); + const Vector2 vector_y = Vector2(0, 1); + CHECK_MESSAGE( + Math::is_equal_approx(vector_x.angle_to(vector_y), (real_t)Math_TAU / 4), + "Vector2 angle_to should work as expected."); + CHECK_MESSAGE( + Math::is_equal_approx(vector_y.angle_to(vector_x), (real_t)-Math_TAU / 4), + "Vector2 angle_to should work as expected."); + CHECK_MESSAGE( + Math::is_equal_approx(vector_x.angle_to_point(vector_y), (real_t)Math_TAU * 3 / 8), + "Vector2 angle_to_point should work as expected."); + CHECK_MESSAGE( + Math::is_equal_approx(vector_y.angle_to_point(vector_x), (real_t)-Math_TAU / 8), + "Vector2 angle_to_point should work as expected."); +} + +TEST_CASE("[Vector2] Axis methods") { + Vector2 vector = Vector2(1.2, 3.4); + CHECK_MESSAGE( + vector.max_axis_index() == Vector2::Axis::AXIS_Y, + "Vector2 max_axis_index should work as expected."); + CHECK_MESSAGE( + vector.min_axis_index() == Vector2::Axis::AXIS_X, + "Vector2 min_axis_index should work as expected."); + CHECK_MESSAGE( + vector[vector.min_axis_index()] == (real_t)1.2, + "Vector2 array operator should work as expected."); + vector[Vector2::Axis::AXIS_Y] = 3.7; + CHECK_MESSAGE( + vector[Vector2::Axis::AXIS_Y] == (real_t)3.7, + "Vector2 array operator setter should work as expected."); +} + +TEST_CASE("[Vector2] Interpolation methods") { + const Vector2 vector1 = Vector2(1, 2); + const Vector2 vector2 = Vector2(4, 5); + CHECK_MESSAGE( + vector1.lerp(vector2, 0.5) == Vector2(2.5, 3.5), + "Vector2 lerp should work as expected."); + CHECK_MESSAGE( + vector1.lerp(vector2, 1.0 / 3.0).is_equal_approx(Vector2(2, 3)), + "Vector2 lerp should work as expected."); + CHECK_MESSAGE( + vector1.normalized().slerp(vector2.normalized(), 0.5).is_equal_approx(Vector2(0.538953602313995361, 0.84233558177947998)), + "Vector2 slerp should work as expected."); + CHECK_MESSAGE( + vector1.normalized().slerp(vector2.normalized(), 1.0 / 3.0).is_equal_approx(Vector2(0.508990883827209473, 0.860771894454956055)), + "Vector2 slerp should work as expected."); + CHECK_MESSAGE( + Vector2(5, 0).slerp(Vector2(0, 5), 0.5).is_equal_approx(Vector2(5, 5) * Math_SQRT12), + "Vector2 slerp with non-normalized values should work as expected."); + CHECK_MESSAGE( + Vector2(1, 1).slerp(Vector2(2, 2), 0.5).is_equal_approx(Vector2(1.5, 1.5)), + "Vector2 slerp with colinear inputs should behave as expected."); + CHECK_MESSAGE( + Vector2().slerp(Vector2(), 0.5) == Vector2(), + "Vector2 slerp with both inputs as zero vectors should return a zero vector."); + CHECK_MESSAGE( + Vector2().slerp(Vector2(1, 1), 0.5) == Vector2(0.5, 0.5), + "Vector2 slerp with one input as zero should behave like a regular lerp."); + CHECK_MESSAGE( + Vector2(1, 1).slerp(Vector2(), 0.5) == Vector2(0.5, 0.5), + "Vector2 slerp with one input as zero should behave like a regular lerp."); + CHECK_MESSAGE( + Math::is_equal_approx(vector1.slerp(vector2, 0.5).length(), (real_t)4.31959610746631919), + "Vector2 slerp with different length input should return a vector with an interpolated length."); + CHECK_MESSAGE( + Math::is_equal_approx(vector1.angle_to(vector1.slerp(vector2, 0.5)) * 2, vector1.angle_to(vector2)), + "Vector2 slerp with different length input should return a vector with an interpolated angle."); + CHECK_MESSAGE( + vector1.cubic_interpolate(vector2, Vector2(), Vector2(7, 7), 0.5) == Vector2(2.375, 3.5), + "Vector2 cubic_interpolate should work as expected."); + CHECK_MESSAGE( + vector1.cubic_interpolate(vector2, Vector2(), Vector2(7, 7), 1.0 / 3.0).is_equal_approx(Vector2(1.851851940155029297, 2.962963104248046875)), + "Vector2 cubic_interpolate should work as expected."); + CHECK_MESSAGE( + Vector2(1, 0).move_toward(Vector2(10, 0), 3) == Vector2(4, 0), + "Vector2 move_toward should work as expected."); +} + +TEST_CASE("[Vector2] Length methods") { + const Vector2 vector1 = Vector2(10, 10); + const Vector2 vector2 = Vector2(20, 30); + CHECK_MESSAGE( + vector1.length_squared() == 200, + "Vector2 length_squared should work as expected and return exact result."); + CHECK_MESSAGE( + Math::is_equal_approx(vector1.length(), 10 * (real_t)Math_SQRT2), + "Vector2 length should work as expected."); + CHECK_MESSAGE( + vector2.length_squared() == 1300, + "Vector2 length_squared should work as expected and return exact result."); + CHECK_MESSAGE( + Math::is_equal_approx(vector2.length(), (real_t)36.05551275463989293119), + "Vector2 length should work as expected."); + CHECK_MESSAGE( + vector1.distance_squared_to(vector2) == 500, + "Vector2 distance_squared_to should work as expected and return exact result."); + CHECK_MESSAGE( + Math::is_equal_approx(vector1.distance_to(vector2), (real_t)22.36067977499789696409), + "Vector2 distance_to should work as expected."); +} + +TEST_CASE("[Vector2] Limiting methods") { + const Vector2 vector = Vector2(10, 10); + CHECK_MESSAGE( + vector.limit_length().is_equal_approx(Vector2(Math_SQRT12, Math_SQRT12)), + "Vector2 limit_length should work as expected."); + CHECK_MESSAGE( + vector.limit_length(5).is_equal_approx(5 * Vector2(Math_SQRT12, Math_SQRT12)), + "Vector2 limit_length should work as expected."); + + CHECK_MESSAGE( + Vector2(-5, 15).clamp(Vector2(), vector).is_equal_approx(Vector2(0, 10)), + "Vector2 clamp should work as expected."); + CHECK_MESSAGE( + vector.clamp(Vector2(0, 15), Vector2(5, 20)).is_equal_approx(Vector2(5, 15)), + "Vector2 clamp should work as expected."); +} + +TEST_CASE("[Vector2] Normalization methods") { + CHECK_MESSAGE( + Vector2(1, 0).is_normalized() == true, + "Vector2 is_normalized should return true for a normalized vector."); + CHECK_MESSAGE( + Vector2(1, 1).is_normalized() == false, + "Vector2 is_normalized should return false for a non-normalized vector."); + CHECK_MESSAGE( + Vector2(1, 0).normalized() == Vector2(1, 0), + "Vector2 normalized should return the same vector for a normalized vector."); + CHECK_MESSAGE( + Vector2(1, 1).normalized().is_equal_approx(Vector2(Math_SQRT12, Math_SQRT12)), + "Vector2 normalized should work as expected."); +} + +TEST_CASE("[Vector2] Operators") { + const Vector2 decimal1 = Vector2(2.3, 4.9); + const Vector2 decimal2 = Vector2(1.2, 3.4); + const Vector2 power1 = Vector2(0.75, 1.5); + const Vector2 power2 = Vector2(0.5, 0.125); + const Vector2 int1 = Vector2(4, 5); + const Vector2 int2 = Vector2(1, 2); + + CHECK_MESSAGE( + (decimal1 + decimal2).is_equal_approx(Vector2(3.5, 8.3)), + "Vector2 addition should behave as expected."); + CHECK_MESSAGE( + (power1 + power2) == Vector2(1.25, 1.625), + "Vector2 addition with powers of two should give exact results."); + CHECK_MESSAGE( + (int1 + int2) == Vector2(5, 7), + "Vector2 addition with integers should give exact results."); + + CHECK_MESSAGE( + (decimal1 - decimal2).is_equal_approx(Vector2(1.1, 1.5)), + "Vector2 subtraction should behave as expected."); + CHECK_MESSAGE( + (power1 - power2) == Vector2(0.25, 1.375), + "Vector2 subtraction with powers of two should give exact results."); + CHECK_MESSAGE( + (int1 - int2) == Vector2(3, 3), + "Vector2 subtraction with integers should give exact results."); + + CHECK_MESSAGE( + (decimal1 * decimal2).is_equal_approx(Vector2(2.76, 16.66)), + "Vector2 multiplication should behave as expected."); + CHECK_MESSAGE( + (power1 * power2) == Vector2(0.375, 0.1875), + "Vector2 multiplication with powers of two should give exact results."); + CHECK_MESSAGE( + (int1 * int2) == Vector2(4, 10), + "Vector2 multiplication with integers should give exact results."); + + CHECK_MESSAGE( + (decimal1 / decimal2).is_equal_approx(Vector2(1.91666666666666666, 1.44117647058823529)), + "Vector2 division should behave as expected."); + CHECK_MESSAGE( + (power1 / power2) == Vector2(1.5, 12.0), + "Vector2 division with powers of two should give exact results."); + CHECK_MESSAGE( + (int1 / int2) == Vector2(4, 2.5), + "Vector2 division with integers should give exact results."); + + CHECK_MESSAGE( + (decimal1 * 2).is_equal_approx(Vector2(4.6, 9.8)), + "Vector2 multiplication should behave as expected."); + CHECK_MESSAGE( + (power1 * 2) == Vector2(1.5, 3), + "Vector2 multiplication with powers of two should give exact results."); + CHECK_MESSAGE( + (int1 * 2) == Vector2(8, 10), + "Vector2 multiplication with integers should give exact results."); + + CHECK_MESSAGE( + (decimal1 / 2).is_equal_approx(Vector2(1.15, 2.45)), + "Vector2 division should behave as expected."); + CHECK_MESSAGE( + (power1 / 2) == Vector2(0.375, 0.75), + "Vector2 division with powers of two should give exact results."); + CHECK_MESSAGE( + (int1 / 2) == Vector2(2, 2.5), + "Vector2 division with integers should give exact results."); + + CHECK_MESSAGE( + ((Vector2i)decimal1) == Vector2i(2, 4), + "Vector2 cast to Vector2i should work as expected."); + CHECK_MESSAGE( + ((Vector2i)decimal2) == Vector2i(1, 3), + "Vector2 cast to Vector2i should work as expected."); + CHECK_MESSAGE( + Vector2(Vector2i(1, 2)) == Vector2(1, 2), + "Vector2 constructed from Vector2i should work as expected."); + + CHECK_MESSAGE( + ((String)decimal1) == "(2.3, 4.9)", + "Vector2 cast to String should work as expected."); + CHECK_MESSAGE( + ((String)decimal2) == "(1.2, 3.4)", + "Vector2 cast to String should work as expected."); + CHECK_MESSAGE( + ((String)Vector2(9.8, 9.9)) == "(9.8, 9.9)", + "Vector2 cast to String should work as expected."); +#ifdef REAL_T_IS_DOUBLE + CHECK_MESSAGE( + ((String)Vector2(Math_PI, Math_TAU)) == "(3.14159265358979, 6.28318530717959)", + "Vector2 cast to String should print the correct amount of digits for real_t = double."); +#else + CHECK_MESSAGE( + ((String)Vector2(Math_PI, Math_TAU)) == "(3.141593, 6.283185)", + "Vector2 cast to String should print the correct amount of digits for real_t = float."); +#endif // REAL_T_IS_DOUBLE +} + +TEST_CASE("[Vector2] Other methods") { + const Vector2 vector = Vector2(1.2, 3.4); + CHECK_MESSAGE( + Math::is_equal_approx(vector.aspect(), (real_t)1.2 / (real_t)3.4), + "Vector2 aspect should work as expected."); + CHECK_MESSAGE( + vector.direction_to(Vector2()).is_equal_approx(-vector.normalized()), + "Vector2 direction_to should work as expected."); + CHECK_MESSAGE( + Vector2(1, 1).direction_to(Vector2(2, 2)).is_equal_approx(Vector2(Math_SQRT12, Math_SQRT12)), + "Vector2 direction_to should work as expected."); + CHECK_MESSAGE( + vector.posmod(2).is_equal_approx(Vector2(1.2, 1.4)), + "Vector2 posmod should work as expected."); + CHECK_MESSAGE( + (-vector).posmod(2).is_equal_approx(Vector2(0.8, 0.6)), + "Vector2 posmod should work as expected."); + CHECK_MESSAGE( + vector.posmodv(Vector2(1, 2)).is_equal_approx(Vector2(0.2, 1.4)), + "Vector2 posmodv should work as expected."); + CHECK_MESSAGE( + (-vector).posmodv(Vector2(2, 3)).is_equal_approx(Vector2(0.8, 2.6)), + "Vector2 posmodv should work as expected."); + CHECK_MESSAGE( + vector.rotated(Math_TAU / 4).is_equal_approx(Vector2(-3.4, 1.2)), + "Vector2 rotated should work as expected."); + CHECK_MESSAGE( + vector.snapped(Vector2(1, 1)) == Vector2(1, 3), + "Vector2 snapped to integers should be the same as rounding."); + CHECK_MESSAGE( + Vector2(3.4, 5.6).snapped(Vector2(1, 1)) == Vector2(3, 6), + "Vector2 snapped to integers should be the same as rounding."); + CHECK_MESSAGE( + vector.snapped(Vector2(0.25, 0.25)) == Vector2(1.25, 3.5), + "Vector2 snapped to 0.25 should give exact results."); +} + +TEST_CASE("[Vector2] Plane methods") { + const Vector2 vector = Vector2(1.2, 3.4); + const Vector2 vector_y = Vector2(0, 1); + CHECK_MESSAGE( + vector.bounce(vector_y) == Vector2(1.2, -3.4), + "Vector2 bounce on a plane with normal of the Y axis should."); + CHECK_MESSAGE( + vector.reflect(vector_y) == Vector2(-1.2, 3.4), + "Vector2 reflect on a plane with normal of the Y axis should."); + CHECK_MESSAGE( + vector.project(vector_y) == Vector2(0, 3.4), + "Vector2 projected on the X axis should only give the Y component."); + CHECK_MESSAGE( + vector.slide(vector_y) == Vector2(1.2, 0), + "Vector2 slide on a plane with normal of the Y axis should set the Y to zero."); +} + +TEST_CASE("[Vector2] Rounding methods") { + const Vector2 vector1 = Vector2(1.2, 5.6); + const Vector2 vector2 = Vector2(1.2, -5.6); + CHECK_MESSAGE( + vector1.abs() == vector1, + "Vector2 abs should work as expected."); + CHECK_MESSAGE( + vector2.abs() == vector1, + "Vector2 abs should work as expected."); + + CHECK_MESSAGE( + vector1.ceil() == Vector2(2, 6), + "Vector2 ceil should work as expected."); + CHECK_MESSAGE( + vector2.ceil() == Vector2(2, -5), + "Vector2 ceil should work as expected."); + + CHECK_MESSAGE( + vector1.floor() == Vector2(1, 5), + "Vector2 floor should work as expected."); + CHECK_MESSAGE( + vector2.floor() == Vector2(1, -6), + "Vector2 floor should work as expected."); + + CHECK_MESSAGE( + vector1.round() == Vector2(1, 6), + "Vector2 round should work as expected."); + CHECK_MESSAGE( + vector2.round() == Vector2(1, -6), + "Vector2 round should work as expected."); + + CHECK_MESSAGE( + vector1.sign() == Vector2(1, 1), + "Vector2 sign should work as expected."); + CHECK_MESSAGE( + vector2.sign() == Vector2(1, -1), + "Vector2 sign should work as expected."); +} + +TEST_CASE("[Vector2] Linear algebra methods") { + const Vector2 vector_x = Vector2(1, 0); + const Vector2 vector_y = Vector2(0, 1); + CHECK_MESSAGE( + vector_x.cross(vector_y) == 1, + "Vector2 cross product of X and Y should give 1."); + CHECK_MESSAGE( + vector_y.cross(vector_x) == -1, + "Vector2 cross product of Y and X should give negative 1."); + + CHECK_MESSAGE( + vector_x.dot(vector_y) == 0.0, + "Vector2 dot product of perpendicular vectors should be zero."); + CHECK_MESSAGE( + vector_x.dot(vector_x) == 1.0, + "Vector2 dot product of identical unit vectors should be one."); + CHECK_MESSAGE( + (vector_x * 10).dot(vector_x * 10) == 100.0, + "Vector2 dot product of same direction vectors should behave as expected."); +} +} // namespace TestVector2 + +#endif // TEST_VECTOR2_H diff --git a/tests/core/math/test_vector2i.h b/tests/core/math/test_vector2i.h new file mode 100644 index 0000000000..841bb793a4 --- /dev/null +++ b/tests/core/math/test_vector2i.h @@ -0,0 +1,145 @@ +/*************************************************************************/ +/* test_vector2i.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_VECTOR2I_H +#define TEST_VECTOR2I_H + +#include "core/math/vector2.h" +#include "core/math/vector2i.h" +#include "tests/test_macros.h" + +namespace TestVector2i { + +TEST_CASE("[Vector2i] Axis methods") { + Vector2i vector = Vector2i(2, 3); + CHECK_MESSAGE( + vector.max_axis_index() == Vector2i::Axis::AXIS_Y, + "Vector2i max_axis_index should work as expected."); + CHECK_MESSAGE( + vector.min_axis_index() == Vector2i::Axis::AXIS_X, + "Vector2i min_axis_index should work as expected."); + CHECK_MESSAGE( + vector[vector.min_axis_index()] == 2, + "Vector2i array operator should work as expected."); + vector[Vector2i::Axis::AXIS_Y] = 5; + CHECK_MESSAGE( + vector[Vector2i::Axis::AXIS_Y] == 5, + "Vector2i array operator setter should work as expected."); +} + +TEST_CASE("[Vector2i] Clamp method") { + const Vector2i vector = Vector2i(10, 10); + CHECK_MESSAGE( + Vector2i(-5, 15).clamp(Vector2i(), vector) == Vector2i(0, 10), + "Vector2i clamp should work as expected."); + CHECK_MESSAGE( + vector.clamp(Vector2i(0, 15), Vector2i(5, 20)) == Vector2i(5, 15), + "Vector2i clamp should work as expected."); +} + +TEST_CASE("[Vector2i] Length methods") { + const Vector2i vector1 = Vector2i(10, 10); + const Vector2i vector2 = Vector2i(20, 30); + CHECK_MESSAGE( + vector1.length_squared() == 200, + "Vector2i length_squared should work as expected and return exact result."); + CHECK_MESSAGE( + Math::is_equal_approx(vector1.length(), 10 * Math_SQRT2), + "Vector2i length should work as expected."); + CHECK_MESSAGE( + vector2.length_squared() == 1300, + "Vector2i length_squared should work as expected and return exact result."); + CHECK_MESSAGE( + Math::is_equal_approx(vector2.length(), 36.05551275463989293119), + "Vector2i length should work as expected."); +} + +TEST_CASE("[Vector2i] Operators") { + const Vector2i vector1 = Vector2i(5, 9); + const Vector2i vector2 = Vector2i(2, 3); + + CHECK_MESSAGE( + (vector1 + vector2) == Vector2i(7, 12), + "Vector2i addition with integers should give exact results."); + CHECK_MESSAGE( + (vector1 - vector2) == Vector2i(3, 6), + "Vector2i subtraction with integers should give exact results."); + CHECK_MESSAGE( + (vector1 * vector2) == Vector2i(10, 27), + "Vector2i multiplication with integers should give exact results."); + CHECK_MESSAGE( + (vector1 / vector2) == Vector2i(2, 3), + "Vector2i division with integers should give exact results."); + + CHECK_MESSAGE( + (vector1 * 2) == Vector2i(10, 18), + "Vector2i multiplication with integers should give exact results."); + CHECK_MESSAGE( + (vector1 / 2) == Vector2i(2, 4), + "Vector2i division with integers should give exact results."); + + CHECK_MESSAGE( + ((Vector2)vector1) == Vector2(5, 9), + "Vector2i cast to Vector2 should work as expected."); + CHECK_MESSAGE( + ((Vector2)vector2) == Vector2(2, 3), + "Vector2i cast to Vector2 should work as expected."); + CHECK_MESSAGE( + Vector2i(Vector2(1.1, 2.9)) == Vector2i(1, 2), + "Vector2i constructed from Vector2 should work as expected."); +} + +TEST_CASE("[Vector2i] Other methods") { + const Vector2i vector = Vector2i(1, 3); + CHECK_MESSAGE( + Math::is_equal_approx(vector.aspect(), (real_t)1.0 / (real_t)3.0), + "Vector2i aspect should work as expected."); +} + +TEST_CASE("[Vector2i] Abs and sign methods") { + const Vector2i vector1 = Vector2i(1, 3); + const Vector2i vector2 = Vector2i(1, -3); + CHECK_MESSAGE( + vector1.abs() == vector1, + "Vector2i abs should work as expected."); + CHECK_MESSAGE( + vector2.abs() == vector1, + "Vector2i abs should work as expected."); + + CHECK_MESSAGE( + vector1.sign() == Vector2i(1, 1), + "Vector2i sign should work as expected."); + CHECK_MESSAGE( + vector2.sign() == Vector2i(1, -1), + "Vector2i sign should work as expected."); +} +} // namespace TestVector2i + +#endif // TEST_VECTOR2I_H diff --git a/tests/core/math/test_vector3.h b/tests/core/math/test_vector3.h new file mode 100644 index 0000000000..6f99fada2b --- /dev/null +++ b/tests/core/math/test_vector3.h @@ -0,0 +1,417 @@ +/*************************************************************************/ +/* test_vector3.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_VECTOR3_H +#define TEST_VECTOR3_H + +#include "core/math/vector3.h" +#include "tests/test_macros.h" + +#define Math_SQRT13 0.57735026918962576450914878050196 +#define Math_SQRT3 1.7320508075688772935274463415059 + +namespace TestVector3 { + +TEST_CASE("[Vector3] Angle methods") { + const Vector3 vector_x = Vector3(1, 0, 0); + const Vector3 vector_y = Vector3(0, 1, 0); + const Vector3 vector_yz = Vector3(0, 1, 1); + CHECK_MESSAGE( + Math::is_equal_approx(vector_x.angle_to(vector_y), (real_t)Math_TAU / 4), + "Vector3 angle_to should work as expected."); + CHECK_MESSAGE( + Math::is_equal_approx(vector_x.angle_to(vector_yz), (real_t)Math_TAU / 4), + "Vector3 angle_to should work as expected."); + CHECK_MESSAGE( + Math::is_equal_approx(vector_yz.angle_to(vector_x), (real_t)Math_TAU / 4), + "Vector3 angle_to should work as expected."); + CHECK_MESSAGE( + Math::is_equal_approx(vector_y.angle_to(vector_yz), (real_t)Math_TAU / 8), + "Vector3 angle_to should work as expected."); + + CHECK_MESSAGE( + Math::is_equal_approx(vector_x.signed_angle_to(vector_y, vector_y), (real_t)Math_TAU / 4), + "Vector3 signed_angle_to edge case should be positive."); + CHECK_MESSAGE( + Math::is_equal_approx(vector_x.signed_angle_to(vector_yz, vector_y), (real_t)Math_TAU / -4), + "Vector3 signed_angle_to should work as expected."); + CHECK_MESSAGE( + Math::is_equal_approx(vector_yz.signed_angle_to(vector_x, vector_y), (real_t)Math_TAU / 4), + "Vector3 signed_angle_to should work as expected."); +} + +TEST_CASE("[Vector3] Axis methods") { + Vector3 vector = Vector3(1.2, 3.4, 5.6); + CHECK_MESSAGE( + vector.max_axis_index() == Vector3::Axis::AXIS_Z, + "Vector3 max_axis_index should work as expected."); + CHECK_MESSAGE( + vector.min_axis_index() == Vector3::Axis::AXIS_X, + "Vector3 min_axis_index should work as expected."); + CHECK_MESSAGE( + vector.get_axis(vector.max_axis_index()) == (real_t)5.6, + "Vector3 get_axis should work as expected."); + CHECK_MESSAGE( + vector[vector.min_axis_index()] == (real_t)1.2, + "Vector3 array operator should work as expected."); + + vector.set_axis(Vector3::Axis::AXIS_Y, 4.7); + CHECK_MESSAGE( + vector.get_axis(Vector3::Axis::AXIS_Y) == (real_t)4.7, + "Vector3 set_axis should work as expected."); + vector[Vector3::Axis::AXIS_Y] = 3.7; + CHECK_MESSAGE( + vector[Vector3::Axis::AXIS_Y] == (real_t)3.7, + "Vector3 array operator setter should work as expected."); +} + +TEST_CASE("[Vector3] Interpolation methods") { + const Vector3 vector1 = Vector3(1, 2, 3); + const Vector3 vector2 = Vector3(4, 5, 6); + CHECK_MESSAGE( + vector1.lerp(vector2, 0.5) == Vector3(2.5, 3.5, 4.5), + "Vector3 lerp should work as expected."); + CHECK_MESSAGE( + vector1.lerp(vector2, 1.0 / 3.0).is_equal_approx(Vector3(2, 3, 4)), + "Vector3 lerp should work as expected."); + CHECK_MESSAGE( + vector1.normalized().slerp(vector2.normalized(), 0.5).is_equal_approx(Vector3(0.363866806030273438, 0.555698215961456299, 0.747529566287994385)), + "Vector3 slerp should work as expected."); + CHECK_MESSAGE( + vector1.normalized().slerp(vector2.normalized(), 1.0 / 3.0).is_equal_approx(Vector3(0.332119762897491455, 0.549413740634918213, 0.766707837581634521)), + "Vector3 slerp should work as expected."); + CHECK_MESSAGE( + Vector3(5, 0, 0).slerp(Vector3(0, 3, 4), 0.5).is_equal_approx(Vector3(3.535533905029296875, 2.121320486068725586, 2.828427314758300781)), + "Vector3 slerp with non-normalized values should work as expected."); + CHECK_MESSAGE( + Vector3(1, 1, 1).slerp(Vector3(2, 2, 2), 0.5).is_equal_approx(Vector3(1.5, 1.5, 1.5)), + "Vector3 slerp with colinear inputs should behave as expected."); + CHECK_MESSAGE( + Vector3().slerp(Vector3(), 0.5) == Vector3(), + "Vector3 slerp with both inputs as zero vectors should return a zero vector."); + CHECK_MESSAGE( + Vector3().slerp(Vector3(1, 1, 1), 0.5) == Vector3(0.5, 0.5, 0.5), + "Vector3 slerp with one input as zero should behave like a regular lerp."); + CHECK_MESSAGE( + Vector3(1, 1, 1).slerp(Vector3(), 0.5) == Vector3(0.5, 0.5, 0.5), + "Vector3 slerp with one input as zero should behave like a regular lerp."); + CHECK_MESSAGE( + Math::is_equal_approx(vector1.slerp(vector2, 0.5).length(), (real_t)6.25831088708303172), + "Vector3 slerp with different length input should return a vector with an interpolated length."); + CHECK_MESSAGE( + Math::is_equal_approx(vector1.angle_to(vector1.slerp(vector2, 0.5)) * 2, vector1.angle_to(vector2)), + "Vector3 slerp with different length input should return a vector with an interpolated angle."); + CHECK_MESSAGE( + vector1.cubic_interpolate(vector2, Vector3(), Vector3(7, 7, 7), 0.5) == Vector3(2.375, 3.5, 4.625), + "Vector3 cubic_interpolate should work as expected."); + CHECK_MESSAGE( + vector1.cubic_interpolate(vector2, Vector3(), Vector3(7, 7, 7), 1.0 / 3.0).is_equal_approx(Vector3(1.851851940155029297, 2.962963104248046875, 4.074074268341064453)), + "Vector3 cubic_interpolate should work as expected."); + CHECK_MESSAGE( + Vector3(1, 0, 0).move_toward(Vector3(10, 0, 0), 3) == Vector3(4, 0, 0), + "Vector3 move_toward should work as expected."); +} + +TEST_CASE("[Vector3] Length methods") { + const Vector3 vector1 = Vector3(10, 10, 10); + const Vector3 vector2 = Vector3(20, 30, 40); + CHECK_MESSAGE( + vector1.length_squared() == 300, + "Vector3 length_squared should work as expected and return exact result."); + CHECK_MESSAGE( + Math::is_equal_approx(vector1.length(), 10 * (real_t)Math_SQRT3), + "Vector3 length should work as expected."); + CHECK_MESSAGE( + vector2.length_squared() == 2900, + "Vector3 length_squared should work as expected and return exact result."); + CHECK_MESSAGE( + Math::is_equal_approx(vector2.length(), (real_t)53.8516480713450403125), + "Vector3 length should work as expected."); + CHECK_MESSAGE( + vector1.distance_squared_to(vector2) == 1400, + "Vector3 distance_squared_to should work as expected and return exact result."); + CHECK_MESSAGE( + Math::is_equal_approx(vector1.distance_to(vector2), (real_t)37.41657386773941385584), + "Vector3 distance_to should work as expected."); +} + +TEST_CASE("[Vector3] Limiting methods") { + const Vector3 vector = Vector3(10, 10, 10); + CHECK_MESSAGE( + vector.limit_length().is_equal_approx(Vector3(Math_SQRT13, Math_SQRT13, Math_SQRT13)), + "Vector3 limit_length should work as expected."); + CHECK_MESSAGE( + vector.limit_length(5).is_equal_approx(5 * Vector3(Math_SQRT13, Math_SQRT13, Math_SQRT13)), + "Vector3 limit_length should work as expected."); + + CHECK_MESSAGE( + Vector3(-5, 5, 15).clamp(Vector3(), vector) == Vector3(0, 5, 10), + "Vector3 clamp should work as expected."); + CHECK_MESSAGE( + vector.clamp(Vector3(0, 10, 15), Vector3(5, 10, 20)) == Vector3(5, 10, 15), + "Vector3 clamp should work as expected."); +} + +TEST_CASE("[Vector3] Normalization methods") { + CHECK_MESSAGE( + Vector3(1, 0, 0).is_normalized() == true, + "Vector3 is_normalized should return true for a normalized vector."); + CHECK_MESSAGE( + Vector3(1, 1, 1).is_normalized() == false, + "Vector3 is_normalized should return false for a non-normalized vector."); + CHECK_MESSAGE( + Vector3(1, 0, 0).normalized() == Vector3(1, 0, 0), + "Vector3 normalized should return the same vector for a normalized vector."); + CHECK_MESSAGE( + Vector3(1, 1, 0).normalized().is_equal_approx(Vector3(Math_SQRT12, Math_SQRT12, 0)), + "Vector3 normalized should work as expected."); + CHECK_MESSAGE( + Vector3(1, 1, 1).normalized().is_equal_approx(Vector3(Math_SQRT13, Math_SQRT13, Math_SQRT13)), + "Vector3 normalized should work as expected."); +} + +TEST_CASE("[Vector3] Operators") { + const Vector3 decimal1 = Vector3(2.3, 4.9, 7.8); + const Vector3 decimal2 = Vector3(1.2, 3.4, 5.6); + const Vector3 power1 = Vector3(0.75, 1.5, 0.625); + const Vector3 power2 = Vector3(0.5, 0.125, 0.25); + const Vector3 int1 = Vector3(4, 5, 9); + const Vector3 int2 = Vector3(1, 2, 3); + + CHECK_MESSAGE( + (decimal1 + decimal2).is_equal_approx(Vector3(3.5, 8.3, 13.4)), + "Vector3 addition should behave as expected."); + CHECK_MESSAGE( + (power1 + power2) == Vector3(1.25, 1.625, 0.875), + "Vector3 addition with powers of two should give exact results."); + CHECK_MESSAGE( + (int1 + int2) == Vector3(5, 7, 12), + "Vector3 addition with integers should give exact results."); + + CHECK_MESSAGE( + (decimal1 - decimal2).is_equal_approx(Vector3(1.1, 1.5, 2.2)), + "Vector3 subtraction should behave as expected."); + CHECK_MESSAGE( + (power1 - power2) == Vector3(0.25, 1.375, 0.375), + "Vector3 subtraction with powers of two should give exact results."); + CHECK_MESSAGE( + (int1 - int2) == Vector3(3, 3, 6), + "Vector3 subtraction with integers should give exact results."); + + CHECK_MESSAGE( + (decimal1 * decimal2).is_equal_approx(Vector3(2.76, 16.66, 43.68)), + "Vector3 multiplication should behave as expected."); + CHECK_MESSAGE( + (power1 * power2) == Vector3(0.375, 0.1875, 0.15625), + "Vector3 multiplication with powers of two should give exact results."); + CHECK_MESSAGE( + (int1 * int2) == Vector3(4, 10, 27), + "Vector3 multiplication with integers should give exact results."); + + CHECK_MESSAGE( + (decimal1 / decimal2).is_equal_approx(Vector3(1.91666666666666666, 1.44117647058823529, 1.39285714285714286)), + "Vector3 division should behave as expected."); + CHECK_MESSAGE( + (power1 / power2) == Vector3(1.5, 12.0, 2.5), + "Vector3 division with powers of two should give exact results."); + CHECK_MESSAGE( + (int1 / int2) == Vector3(4, 2.5, 3), + "Vector3 division with integers should give exact results."); + + CHECK_MESSAGE( + (decimal1 * 2).is_equal_approx(Vector3(4.6, 9.8, 15.6)), + "Vector3 multiplication should behave as expected."); + CHECK_MESSAGE( + (power1 * 2) == Vector3(1.5, 3, 1.25), + "Vector3 multiplication with powers of two should give exact results."); + CHECK_MESSAGE( + (int1 * 2) == Vector3(8, 10, 18), + "Vector3 multiplication with integers should give exact results."); + + CHECK_MESSAGE( + (decimal1 / 2).is_equal_approx(Vector3(1.15, 2.45, 3.9)), + "Vector3 division should behave as expected."); + CHECK_MESSAGE( + (power1 / 2) == Vector3(0.375, 0.75, 0.3125), + "Vector3 division with powers of two should give exact results."); + CHECK_MESSAGE( + (int1 / 2) == Vector3(2, 2.5, 4.5), + "Vector3 division with integers should give exact results."); + + CHECK_MESSAGE( + ((Vector3i)decimal1) == Vector3i(2, 4, 7), + "Vector3 cast to Vector3i should work as expected."); + CHECK_MESSAGE( + ((Vector3i)decimal2) == Vector3i(1, 3, 5), + "Vector3 cast to Vector3i should work as expected."); + CHECK_MESSAGE( + Vector3(Vector3i(1, 2, 3)) == Vector3(1, 2, 3), + "Vector3 constructed from Vector3i should work as expected."); + + CHECK_MESSAGE( + ((String)decimal1) == "(2.3, 4.9, 7.8)", + "Vector3 cast to String should work as expected."); + CHECK_MESSAGE( + ((String)decimal2) == "(1.2, 3.4, 5.6)", + "Vector3 cast to String should work as expected."); + CHECK_MESSAGE( + ((String)Vector3(9.7, 9.8, 9.9)) == "(9.7, 9.8, 9.9)", + "Vector3 cast to String should work as expected."); +#ifdef REAL_T_IS_DOUBLE + CHECK_MESSAGE( + ((String)Vector3(Math_E, Math_SQRT2, Math_SQRT3)) == "(2.71828182845905, 1.4142135623731, 1.73205080756888)", + "Vector3 cast to String should print the correct amount of digits for real_t = double."); +#else + CHECK_MESSAGE( + ((String)Vector3(Math_E, Math_SQRT2, Math_SQRT3)) == "(2.718282, 1.414214, 1.732051)", + "Vector3 cast to String should print the correct amount of digits for real_t = float."); +#endif // REAL_T_IS_DOUBLE +} + +TEST_CASE("[Vector3] Other methods") { + const Vector3 vector = Vector3(1.2, 3.4, 5.6); + CHECK_MESSAGE( + vector.direction_to(Vector3()).is_equal_approx(-vector.normalized()), + "Vector3 direction_to should work as expected."); + CHECK_MESSAGE( + Vector3(1, 1, 1).direction_to(Vector3(2, 2, 2)).is_equal_approx(Vector3(Math_SQRT13, Math_SQRT13, Math_SQRT13)), + "Vector3 direction_to should work as expected."); + CHECK_MESSAGE( + vector.inverse().is_equal_approx(Vector3(1 / 1.2, 1 / 3.4, 1 / 5.6)), + "Vector3 inverse should work as expected."); + CHECK_MESSAGE( + vector.posmod(2).is_equal_approx(Vector3(1.2, 1.4, 1.6)), + "Vector3 posmod should work as expected."); + CHECK_MESSAGE( + (-vector).posmod(2).is_equal_approx(Vector3(0.8, 0.6, 0.4)), + "Vector3 posmod should work as expected."); + CHECK_MESSAGE( + vector.posmodv(Vector3(1, 2, 3)).is_equal_approx(Vector3(0.2, 1.4, 2.6)), + "Vector3 posmodv should work as expected."); + CHECK_MESSAGE( + (-vector).posmodv(Vector3(2, 3, 4)).is_equal_approx(Vector3(0.8, 2.6, 2.4)), + "Vector3 posmodv should work as expected."); + CHECK_MESSAGE( + vector.rotated(Vector3(0, 1, 0), Math_TAU / 4).is_equal_approx(Vector3(5.6, 3.4, -1.2)), + "Vector3 rotated should work as expected."); + CHECK_MESSAGE( + vector.snapped(Vector3(1, 1, 1)) == Vector3(1, 3, 6), + "Vector3 snapped to integers should be the same as rounding."); + CHECK_MESSAGE( + vector.snapped(Vector3(0.25, 0.25, 0.25)) == Vector3(1.25, 3.5, 5.5), + "Vector3 snapped to 0.25 should give exact results."); +} + +TEST_CASE("[Vector3] Plane methods") { + const Vector3 vector = Vector3(1.2, 3.4, 5.6); + const Vector3 vector_y = Vector3(0, 1, 0); + CHECK_MESSAGE( + vector.bounce(vector_y) == Vector3(1.2, -3.4, 5.6), + "Vector3 bounce on a plane with normal of the Y axis should."); + CHECK_MESSAGE( + vector.reflect(vector_y) == Vector3(-1.2, 3.4, -5.6), + "Vector3 reflect on a plane with normal of the Y axis should."); + CHECK_MESSAGE( + vector.project(vector_y) == Vector3(0, 3.4, 0), + "Vector3 projected on the X axis should only give the Y component."); + CHECK_MESSAGE( + vector.slide(vector_y) == Vector3(1.2, 0, 5.6), + "Vector3 slide on a plane with normal of the Y axis should set the Y to zero."); +} + +TEST_CASE("[Vector3] Rounding methods") { + const Vector3 vector1 = Vector3(1.2, 3.4, 5.6); + const Vector3 vector2 = Vector3(1.2, -3.4, -5.6); + CHECK_MESSAGE( + vector1.abs() == vector1, + "Vector3 abs should work as expected."); + CHECK_MESSAGE( + vector2.abs() == vector1, + "Vector3 abs should work as expected."); + + CHECK_MESSAGE( + vector1.ceil() == Vector3(2, 4, 6), + "Vector3 ceil should work as expected."); + CHECK_MESSAGE( + vector2.ceil() == Vector3(2, -3, -5), + "Vector3 ceil should work as expected."); + + CHECK_MESSAGE( + vector1.floor() == Vector3(1, 3, 5), + "Vector3 floor should work as expected."); + CHECK_MESSAGE( + vector2.floor() == Vector3(1, -4, -6), + "Vector3 floor should work as expected."); + + CHECK_MESSAGE( + vector1.round() == Vector3(1, 3, 6), + "Vector3 round should work as expected."); + CHECK_MESSAGE( + vector2.round() == Vector3(1, -3, -6), + "Vector3 round should work as expected."); + + CHECK_MESSAGE( + vector1.sign() == Vector3(1, 1, 1), + "Vector3 sign should work as expected."); + CHECK_MESSAGE( + vector2.sign() == Vector3(1, -1, -1), + "Vector3 sign should work as expected."); +} + +TEST_CASE("[Vector3] Linear algebra methods") { + const Vector3 vector_x = Vector3(1, 0, 0); + const Vector3 vector_y = Vector3(0, 1, 0); + const Vector3 vector_z = Vector3(0, 0, 1); + CHECK_MESSAGE( + vector_x.cross(vector_y) == vector_z, + "Vector3 cross product of X and Y should give Z."); + CHECK_MESSAGE( + vector_y.cross(vector_x) == -vector_z, + "Vector3 cross product of Y and X should give negative Z."); + CHECK_MESSAGE( + vector_y.cross(vector_z) == vector_x, + "Vector3 cross product of Y and Z should give X."); + CHECK_MESSAGE( + vector_z.cross(vector_x) == vector_y, + "Vector3 cross product of Z and X should give Y."); + + CHECK_MESSAGE( + vector_x.dot(vector_y) == 0.0, + "Vector3 dot product of perpendicular vectors should be zero."); + CHECK_MESSAGE( + vector_x.dot(vector_x) == 1.0, + "Vector3 dot product of identical unit vectors should be one."); + CHECK_MESSAGE( + (vector_x * 10).dot(vector_x * 10) == 100.0, + "Vector3 dot product of same direction vectors should behave as expected."); +} +} // namespace TestVector3 + +#endif // TEST_VECTOR3_H diff --git a/tests/core/math/test_vector3i.h b/tests/core/math/test_vector3i.h new file mode 100644 index 0000000000..b1c6944eba --- /dev/null +++ b/tests/core/math/test_vector3i.h @@ -0,0 +1,145 @@ +/*************************************************************************/ +/* test_vector3i.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_VECTOR3I_H +#define TEST_VECTOR3I_H + +#include "core/math/vector3i.h" +#include "tests/test_macros.h" + +namespace TestVector3i { + +TEST_CASE("[Vector3i] Axis methods") { + Vector3i vector = Vector3i(1, 2, 3); + CHECK_MESSAGE( + vector.max_axis_index() == Vector3i::Axis::AXIS_Z, + "Vector3i max_axis_index should work as expected."); + CHECK_MESSAGE( + vector.min_axis_index() == Vector3i::Axis::AXIS_X, + "Vector3i min_axis_index should work as expected."); + CHECK_MESSAGE( + vector.get_axis(vector.max_axis_index()) == 3, + "Vector3i get_axis should work as expected."); + CHECK_MESSAGE( + vector[vector.min_axis_index()] == 1, + "Vector3i array operator should work as expected."); + + vector.set_axis(Vector3i::Axis::AXIS_Y, 4); + CHECK_MESSAGE( + vector.get_axis(Vector3i::Axis::AXIS_Y) == 4, + "Vector3i set_axis should work as expected."); + vector[Vector3i::Axis::AXIS_Y] = 5; + CHECK_MESSAGE( + vector[Vector3i::Axis::AXIS_Y] == 5, + "Vector3i array operator setter should work as expected."); +} + +TEST_CASE("[Vector3i] Clamp method") { + const Vector3i vector = Vector3i(10, 10, 10); + CHECK_MESSAGE( + Vector3i(-5, 5, 15).clamp(Vector3i(), vector) == Vector3i(0, 5, 10), + "Vector3i clamp should work as expected."); + CHECK_MESSAGE( + vector.clamp(Vector3i(0, 10, 15), Vector3i(5, 10, 20)) == Vector3i(5, 10, 15), + "Vector3i clamp should work as expected."); +} + +TEST_CASE("[Vector3i] Length methods") { + const Vector3i vector1 = Vector3i(10, 10, 10); + const Vector3i vector2 = Vector3i(20, 30, 40); + CHECK_MESSAGE( + vector1.length_squared() == 300, + "Vector3i length_squared should work as expected and return exact result."); + CHECK_MESSAGE( + Math::is_equal_approx(vector1.length(), 10 * Math_SQRT3), + "Vector3i length should work as expected."); + CHECK_MESSAGE( + vector2.length_squared() == 2900, + "Vector3i length_squared should work as expected and return exact result."); + CHECK_MESSAGE( + Math::is_equal_approx(vector2.length(), 53.8516480713450403125), + "Vector3i length should work as expected."); +} + +TEST_CASE("[Vector3i] Operators") { + const Vector3i vector1 = Vector3i(4, 5, 9); + const Vector3i vector2 = Vector3i(1, 2, 3); + + CHECK_MESSAGE( + (vector1 + vector2) == Vector3i(5, 7, 12), + "Vector3i addition with integers should give exact results."); + CHECK_MESSAGE( + (vector1 - vector2) == Vector3i(3, 3, 6), + "Vector3i subtraction with integers should give exact results."); + CHECK_MESSAGE( + (vector1 * vector2) == Vector3i(4, 10, 27), + "Vector3i multiplication with integers should give exact results."); + CHECK_MESSAGE( + (vector1 / vector2) == Vector3i(4, 2, 3), + "Vector3i division with integers should give exact results."); + + CHECK_MESSAGE( + (vector1 * 2) == Vector3i(8, 10, 18), + "Vector3i multiplication with integers should give exact results."); + CHECK_MESSAGE( + (vector1 / 2) == Vector3i(2, 2, 4), + "Vector3i division with integers should give exact results."); + + CHECK_MESSAGE( + ((Vector3)vector1) == Vector3(4, 5, 9), + "Vector3i cast to Vector3 should work as expected."); + CHECK_MESSAGE( + ((Vector3)vector2) == Vector3(1, 2, 3), + "Vector3i cast to Vector3 should work as expected."); + CHECK_MESSAGE( + Vector3i(Vector3(1.1, 2.9, 3.9)) == Vector3i(1, 2, 3), + "Vector3i constructed from Vector3 should work as expected."); +} + +TEST_CASE("[Vector3i] Abs and sign methods") { + const Vector3i vector1 = Vector3i(1, 3, 5); + const Vector3i vector2 = Vector3i(1, -3, -5); + CHECK_MESSAGE( + vector1.abs() == vector1, + "Vector3i abs should work as expected."); + CHECK_MESSAGE( + vector2.abs() == vector1, + "Vector3i abs should work as expected."); + + CHECK_MESSAGE( + vector1.sign() == Vector3i(1, 1, 1), + "Vector3i sign should work as expected."); + CHECK_MESSAGE( + vector2.sign() == Vector3i(1, -1, -1), + "Vector3i sign should work as expected."); +} +} // namespace TestVector3i + +#endif // TEST_VECTOR3I_H diff --git a/tests/test_class_db.h b/tests/core/object/test_class_db.h index 9ef4569c14..e4145c8408 100644 --- a/tests/test_class_db.h +++ b/tests/core/object/test_class_db.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -31,14 +31,9 @@ #ifndef TEST_CLASS_DB_H #define TEST_CLASS_DB_H -#include "core/register_core_types.h" - +#include "core/core_bind.h" #include "core/core_constants.h" -#include "core/os/os.h" -#include "core/string/string_name.h" -#include "core/string/ustring.h" -#include "core/templates/ordered_hash_map.h" -#include "core/variant/variant.h" +#include "core/object/class_db.h" #include "tests/test_macros.h" @@ -97,7 +92,7 @@ struct ExposedClass { bool is_singleton = false; bool is_instantiable = false; - bool is_reference = false; + bool is_ref_counted = false; ClassDB::APIType api_type; @@ -108,9 +103,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 +113,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; } } @@ -131,7 +126,7 @@ struct ExposedClass { struct NamesCache { StringName variant_type = StaticCString::create("Variant"); StringName object_class = StaticCString::create("Object"); - StringName reference_class = StaticCString::create("Reference"); + StringName ref_counted_class = StaticCString::create("RefCounted"); StringName string_type = StaticCString::create("String"); StringName string_name_type = StaticCString::create("StringName"); StringName node_path_type = StaticCString::create("NodePath"); @@ -224,26 +219,26 @@ bool arg_default_value_is_assignable_to_type(const Context &p_context, const Var switch (p_val.get_type()) { case Variant::NIL: return p_context.find_exposed_class(p_arg_type) || - p_context.names_cache.is_nullable_type(p_arg_type.name); + p_context.names_cache.is_nullable_type(p_arg_type.name); case Variant::BOOL: return p_arg_type.name == p_context.names_cache.bool_type; case Variant::INT: return p_arg_type.name == p_context.names_cache.int_type || - p_arg_type.name == p_context.names_cache.float_type || - p_arg_type.is_enum; + p_arg_type.name == p_context.names_cache.float_type || + p_arg_type.is_enum; case Variant::FLOAT: return p_arg_type.name == p_context.names_cache.float_type; case Variant::STRING: case Variant::STRING_NAME: return p_arg_type.name == p_context.names_cache.string_type || - p_arg_type.name == p_context.names_cache.string_name_type || - p_arg_type.name == p_context.names_cache.node_path_type; + p_arg_type.name == p_context.names_cache.string_name_type || + p_arg_type.name == p_context.names_cache.node_path_type; case Variant::NODE_PATH: return p_arg_type.name == p_context.names_cache.node_path_type; - case Variant::TRANSFORM: + case Variant::TRANSFORM3D: case Variant::TRANSFORM2D: case Variant::BASIS: - case Variant::QUAT: + case Variant::QUATERNION: case Variant::PLANE: case Variant::AABB: case Variant::COLOR: @@ -269,13 +264,13 @@ bool arg_default_value_is_assignable_to_type(const Context &p_context, const Var return p_context.find_exposed_class(p_arg_type); case Variant::VECTOR2I: return p_arg_type.name == p_context.names_cache.vector2_type || - p_arg_type.name == Variant::get_type_name(p_val.get_type()); + p_arg_type.name == Variant::get_type_name(p_val.get_type()); case Variant::RECT2I: return p_arg_type.name == p_context.names_cache.rect2_type || - p_arg_type.name == Variant::get_type_name(p_val.get_type()); + p_arg_type.name == Variant::get_type_name(p_val.get_type()); case Variant::VECTOR3I: return p_arg_type.name == p_context.names_cache.vector3_type || - p_arg_type.name == Variant::get_type_name(p_val.get_type()); + p_arg_type.name == Variant::get_type_name(p_val.get_type()); default: if (r_err_msg) { *r_err_msg = "Unexpected Variant type: " + itos(p_val.get_type()); @@ -327,7 +322,7 @@ void validate_property(const Context &p_context, const ExposedClass &p_class, co if (getter->return_type.name != setter_first_arg.type.name) { // Special case for Node::set_name bool whitelisted = getter->return_type.name == p_context.names_cache.string_name_type && - setter_first_arg.type.name == p_context.names_cache.string_type; + setter_first_arg.type.name == p_context.names_cache.string_type; TEST_FAIL_COND(!whitelisted, "Return type from getter doesn't match first argument of setter, for property: '", p_class.name, ".", String(p_prop.name), "'."); @@ -395,8 +390,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 +422,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 +464,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); } } @@ -516,7 +511,7 @@ void add_exposed_classes(Context &r_context) { exposed_class.api_type = api_type; exposed_class.is_singleton = Engine::get_singleton()->has_singleton(class_name); exposed_class.is_instantiable = class_info->creation_func && !exposed_class.is_singleton; - exposed_class.is_reference = ClassDB::is_parent_class(class_name, "Reference"); + exposed_class.is_ref_counted = ClassDB::is_parent_class(class_name, "RefCounted"); exposed_class.base = ClassDB::get_parent_class(class_name); // Add properties @@ -526,10 +521,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 +554,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(); @@ -611,7 +604,7 @@ void add_exposed_classes(Context &r_context) { method.return_type.name = return_info.class_name; bool bad_reference_hint = !method.is_virtual && return_info.hint != PROPERTY_HINT_RESOURCE_TYPE && - ClassDB::is_parent_class(return_info.class_name, r_context.names_cache.reference_class); + ClassDB::is_parent_class(return_info.class_name, r_context.names_cache.ref_counted_class); TEST_COND(bad_reference_hint, "Return type is reference but hint is not '" _STR(PROPERTY_HINT_RESOURCE_TYPE) "'.", " Are you returning a reference type by pointer? Method: '", exposed_class.name, ".", method.name, "'."); } else if (return_info.hint == PROPERTY_HINT_RESOURCE_TYPE) { @@ -665,10 +658,10 @@ void add_exposed_classes(Context &r_context) { TEST_COND(exposed_class.find_property_by_name(method.name), "Method name conflicts with property: '", String(class_name), ".", String(method.name), "'."); - // Classes starting with an underscore are ignored unless they're used as a property setter or getter + // 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); @@ -678,6 +671,10 @@ void add_exposed_classes(Context &r_context) { } else { exposed_class.methods.push_back(method); } + + if (method.is_virtual) { + TEST_COND(String(method.name)[0] != '_', "Virtual method ", String(method.name), " does not start with underscore."); + } } // Add signals @@ -748,8 +745,11 @@ 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), "'."); int *value = class_info->constant_map.getptr(constant_name); TEST_FAIL_COND(!value, "Missing enum constant value: '", String(class_name), ".", String(enum_.name), ".", String(constant_name), "'."); @@ -767,10 +767,13 @@ 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(); - int *value = class_info->constant_map.getptr(StringName(E->get())); - TEST_FAIL_COND(!value, "Missing enum constant value: '", String(class_name), ".", String(constant_name), "'."); + 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)); + TEST_FAIL_COND(!value, "Missing constant value: '", String(class_name), ".", String(constant_name), "'."); ConstantData constant; constant.name = constant_name; @@ -819,8 +822,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); } } @@ -830,10 +833,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_method_bind.h b/tests/core/object/test_method_bind.h index 879e7949e2..350a08b6e2 100644 --- a/tests/test_method_bind.h +++ b/tests/core/object/test_method_bind.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -35,10 +35,6 @@ #include "tests/test_macros.h" -#include <inttypes.h> -#include <stdio.h> -#include <wchar.h> - namespace TestMethodBind { class MethodBindTester : public Object { @@ -55,9 +51,15 @@ public: TEST_METHODRC, TEST_METHODRC_ARGS, TEST_METHOD_DEFARGS, + TEST_METHOD_OBJECT_CAST, TEST_MAX }; + class ObjectSubclass : public Object { + public: + int value = 1; + }; + int test_num = 0; bool test_valid[TEST_MAX]; @@ -102,6 +104,10 @@ public: test_valid[TEST_METHOD_DEFARGS] = p_arg1 == 1 && p_arg2 == 2 && p_arg3 == 3 && p_arg4 == 4 && p_arg5 == 5; //temporary } + void test_method_object_cast(ObjectSubclass *p_object) { + test_valid[TEST_METHOD_OBJECT_CAST] = p_object->value == 1; + } + static void _bind_methods() { ClassDB::bind_method(D_METHOD("test_method"), &MethodBindTester::test_method); ClassDB::bind_method(D_METHOD("test_method_args"), &MethodBindTester::test_method_args); @@ -112,6 +118,7 @@ public: ClassDB::bind_method(D_METHOD("test_methodrc"), &MethodBindTester::test_methodrc); ClassDB::bind_method(D_METHOD("test_methodrc_args"), &MethodBindTester::test_methodrc_args); ClassDB::bind_method(D_METHOD("test_method_default_args"), &MethodBindTester::test_method_default_args, DEFVAL(9) /* wrong on purpose */, DEFVAL(4), DEFVAL(5)); + ClassDB::bind_method(D_METHOD("test_method_object_cast", "object"), &MethodBindTester::test_method_object_cast); } virtual void run_tests() { @@ -138,6 +145,10 @@ public: test_valid[TEST_METHODRC_ARGS] = int(call("test_methodrc_args", test_num)) == test_num && test_valid[TEST_METHODRC_ARGS]; call("test_method_default_args", 1, 2, 3, 4); + + ObjectSubclass *obj = memnew(ObjectSubclass); + call("test_method_object_cast", obj); + memdelete(obj); } }; @@ -156,6 +167,7 @@ TEST_CASE("[MethodBind] check all method binds") { CHECK(mbt->test_valid[MethodBindTester::TEST_METHODRC]); CHECK(mbt->test_valid[MethodBindTester::TEST_METHODRC_ARGS]); CHECK(mbt->test_valid[MethodBindTester::TEST_METHOD_DEFARGS]); + CHECK(mbt->test_valid[MethodBindTester::TEST_METHOD_OBJECT_CAST]); memdelete(mbt); } diff --git a/tests/test_object.h b/tests/core/object/test_object.h index 142d76553d..e44b93bb66 100644 --- a/tests/test_object.h +++ b/tests/core/object/test_object.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -32,9 +32,11 @@ #define TEST_OBJECT_H #include "core/core_string_names.h" +#include "core/object/class_db.h" #include "core/object/object.h" +#include "core/object/script_language.h" -#include "thirdparty/doctest/doctest.h" +#include "tests/test_macros.h" // Declared in global namespace because of GDCLASS macro warning (Windows): // "Unqualified friend declaration referring to type outside of the nearest enclosing namespace @@ -85,7 +87,7 @@ public: bool has_method(const StringName &p_method) const override { return false; } - Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override { + Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override { return Variant(); } void notification(int p_notification) override { @@ -93,35 +95,8 @@ public: Ref<Script> get_script() const override { return Ref<Script>(); } - Vector<ScriptNetData> get_rpc_methods() const override { - return Vector<ScriptNetData>(); - } - uint16_t get_rpc_method_id(const StringName &p_method) const override { - return 0; - } - StringName get_rpc_method(uint16_t p_id) const override { - return StringName(); - } - MultiplayerAPI::RPCMode get_rpc_mode_by_id(uint16_t p_id) const override { - return MultiplayerAPI::RPC_MODE_PUPPET; - } - MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const override { - return MultiplayerAPI::RPC_MODE_PUPPET; - } - Vector<ScriptNetData> get_rset_properties() const override { - return Vector<ScriptNetData>(); - } - uint16_t get_rset_property_id(const StringName &p_variable) const override { - return 0; - } - StringName get_rset_property(uint16_t p_id) const override { - return StringName(); - } - MultiplayerAPI::RPCMode get_rset_mode_by_id(uint16_t p_id) const override { - return MultiplayerAPI::RPC_MODE_PUPPET; - } - MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const override { - return MultiplayerAPI::RPC_MODE_PUPPET; + const Vector<Multiplayer::RPCConfig> get_rpc_methods() const override { + return Vector<Multiplayer::RPCConfig>(); } ScriptLanguage *get_language() override { return nullptr; @@ -166,7 +141,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, @@ -181,7 +156,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, @@ -192,8 +167,8 @@ TEST_CASE("[Object] Construction") { Object object; CHECK_MESSAGE( - !object.is_reference(), - "Object is not a Reference."); + !object.is_ref_counted(), + "Object is not a RefCounted."); Object *p_db = ObjectDB::get_instance(object.get_instance_id()); CHECK_MESSAGE( @@ -233,7 +208,7 @@ TEST_CASE("[Object] Script instance property getter") { } TEST_CASE("[Object] Built-in property setter") { - ClassDB::register_class<_TestDerivedObject>(); + GDREGISTER_CLASS(_TestDerivedObject); _TestDerivedObject derived_object; bool valid = false; @@ -245,7 +220,7 @@ TEST_CASE("[Object] Built-in property setter") { } TEST_CASE("[Object] Built-in property getter") { - ClassDB::register_class<_TestDerivedObject>(); + GDREGISTER_CLASS(_TestDerivedObject); _TestDerivedObject derived_object; derived_object.set_property(100); diff --git a/tests/test_node_path.h b/tests/core/string/test_node_path.h index f30fe53c5a..d2de766889 100644 --- a/tests/test_node_path.h +++ b/tests/core/string/test_node_path.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -33,7 +33,7 @@ #include "core/string/node_path.h" -#include "thirdparty/doctest/doctest.h" +#include "tests/test_macros.h" namespace TestNodePath { diff --git a/tests/test_string.h b/tests/core/string/test_string.h index 6febf22765..87016dddf6 100644 --- a/tests/test_string.h +++ b/tests/core/string/test_string.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -31,13 +31,6 @@ #ifndef TEST_STRING_H #define TEST_STRING_H -#include <inttypes.h> -#include <stdio.h> -#include <wchar.h> - -#include "core/io/ip_address.h" -#include "core/os/main_loop.h" -#include "core/os/os.h" #include "core/string/ustring.h" #include "tests/test_macros.h" @@ -45,8 +38,9 @@ namespace TestString { int u32scmp(const char32_t *l, const char32_t *r) { - for (; *l == *r && *l && *r; l++, r++) - ; + for (; *l == *r && *l && *r; l++, r++) { + // Continue. + } return *l - *r; } @@ -164,10 +158,10 @@ TEST_CASE("[String] Invalid UTF8") { String s; bool err = s.parse_utf8((const char *)u8str); CHECK(err); - CHECK(s == String()); + CHECK(s.is_empty()); CharString cs = (const char *)u8str; - CHECK(String::utf8(cs) == String()); + CHECK(String::utf8(cs).is_empty()); ERR_PRINT_ON } @@ -177,10 +171,10 @@ TEST_CASE("[String] Invalid UTF16") { String s; bool err = s.parse_utf16(u16str); CHECK(err); - CHECK(s == String()); + CHECK(s.is_empty()); Char16String cs = u16str; - CHECK(String::utf16(cs) == String()); + CHECK(String::utf16(cs).is_empty()); ERR_PRINT_ON } @@ -251,6 +245,19 @@ TEST_CASE("[String] Testing for empty string") { CHECK(String("").is_empty()); } +TEST_CASE("[String] Contains") { + String s = "C:\\Godot\\project\\string_test.tscn"; + CHECK(s.contains(":\\")); + CHECK(s.contains("Godot")); + CHECK(s.contains(String("project\\string_test"))); + CHECK(s.contains(String("\\string_test.tscn"))); + + CHECK(!s.contains("://")); + CHECK(!s.contains("Godoh")); + CHECK(!s.contains(String("project\\string test"))); + CHECK(!s.contains(String("\\char_test.tscn"))); +} + TEST_CASE("[String] Test chr") { CHECK(String::chr('H') == "H"); CHECK(String::chr(0x3012)[0] == 0x3012); @@ -299,6 +306,7 @@ TEST_CASE("[String] hex_encode_buffer") { TEST_CASE("[String] Substr") { String s = "Killer Baby"; CHECK(s.substr(3, 4) == "ler "); + CHECK(s.substr(3) == "ler Baby"); } TEST_CASE("[String] Find") { @@ -349,13 +357,53 @@ 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(1.0) == "1.0"); + CHECK(String::num_real(1.0, false) == "1"); + CHECK(String::num_real(9.9) == "9.9"); + CHECK(String::num_real(9.99) == "9.99"); + CHECK(String::num_real(9.999) == "9.999"); + CHECK(String::num_real(9.9999) == "9.9999"); + 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.1414999961853", "Prints more digits of 32-bit float when real_t = double (ones that would be reliable for double) and no trailing zero."); +#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") { @@ -462,12 +510,6 @@ TEST_CASE("[String] Splitting") { } } -TEST_CASE("[String] Erasing") { - String s = "Josephine is such a cute girl!"; - s.erase(s.find("cute "), String("cute ").length()); - CHECK(s == "Josephine is such a girl!"); -} - struct test_27_data { char const *data; char const *part; @@ -849,7 +891,7 @@ TEST_CASE("[String] is_subsequence_of") { String a = "is subsequence of"; CHECK(String("sub").is_subsequence_of(a)); CHECK(!String("Sub").is_subsequence_of(a)); - CHECK(String("Sub").is_subsequence_ofi(a)); + CHECK(String("Sub").is_subsequence_ofn(a)); } TEST_CASE("[String] match") { @@ -860,10 +902,10 @@ TEST_CASE("[String] match") { } TEST_CASE("[String] IPVX address to string") { - IP_Address ip0("2001:0db8:85a3:0000:0000:8a2e:0370:7334"); - IP_Address ip(0x0123, 0x4567, 0x89ab, 0xcdef, true); - IP_Address ip2("fe80::52e5:49ff:fe93:1baf"); - IP_Address ip3("::ffff:192.168.0.1"); + IPAddress ip0("2001:0db8:85a3:0000:0000:8a2e:0370:7334"); + IPAddress ip(0x0123, 0x4567, 0x89ab, 0xcdef, true); + IPAddress ip2("fe80::52e5:49ff:fe93:1baf"); + IPAddress ip3("::ffff:192.168.0.1"); String ip4 = "192.168.0.1"; CHECK(ip4.is_valid_ip_address()); @@ -1045,7 +1087,7 @@ TEST_CASE("[String] lstrip and rstrip") { TEST_CASE("[String] ensuring empty string into parse_utf8 passes empty string") { String empty; - CHECK(empty.parse_utf8(NULL, -1)); + CHECK(empty.parse_utf8(nullptr, -1)); } TEST_CASE("[String] Cyrillic to_lower()") { @@ -1110,6 +1152,25 @@ TEST_CASE("[String] c-escape/unescape") { CHECK(s.c_escape().c_unescape() == s); } +TEST_CASE("[String] indent") { + static const char *input[] = { + "", + "aaa\nbbb", + "\tcontains\n\tindent", + "empty\n\nline", + }; + static const char *expected[] = { + "", + "\taaa\n\tbbb", + "\t\tcontains\n\t\tindent", + "\tempty\n\n\tline", + }; + + for (int i = 0; i < 3; i++) { + CHECK(String(input[i]).indent("\t") == expected[i]); + } +} + TEST_CASE("[String] dedent") { String s = " aaa\n bbb"; String t = "aaa\nbbb"; @@ -1117,20 +1178,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_abs_path() == abs[i]); - CHECK(String(path[i]).is_rel_path() != abs[i]); + CHECK(String(path[i]).is_absolute_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()); } @@ -1156,6 +1217,17 @@ TEST_CASE("[String] uri_encode/unescape") { String s = "Godot Engine:'docs'"; String t = "Godot%20Engine%3A%27docs%27"; + String x1 = "T%C4%93%C5%A1t"; + static const uint8_t u8str[] = { 0x54, 0xC4, 0x93, 0xC5, 0xA1, 0x74, 0x00 }; + String x2 = String::utf8((const char *)u8str); + String x3 = U"Tēšt"; + + CHECK(x1.uri_decode() == x2); + CHECK(x1.uri_decode() == x3); + CHECK((x1 + x3).uri_decode() == (x2 + x3)); // Mixed unicode and URL encoded string, e.g. GTK+ bookmark. + CHECK(x2.uri_encode() == x1); + CHECK(x3.uri_encode() == x1); + CHECK(s.uri_encode() == t); CHECK(t.uri_decode() == s); } @@ -1241,8 +1313,10 @@ TEST_CASE("[String] Trim") { TEST_CASE("[String] Right/Left") { String s = "aaaTestbbb"; // ^ - CHECK(s.right(6) == "tbbb"); + CHECK(s.right(6) == "estbbb"); + CHECK(s.right(-6) == "tbbb"); CHECK(s.left(6) == "aaaTes"); + CHECK(s.left(-6) == "aaaT"); } TEST_CASE("[String] Repeat") { @@ -1303,7 +1377,7 @@ TEST_CASE("[String] Is_*") { for (int i = 0; i < 12; i++) { String s = String(data[i]); CHECK(s.is_numeric() == isnum[i]); - CHECK(s.is_valid_integer() == isint[i]); + CHECK(s.is_valid_int() == isint[i]); CHECK(s.is_valid_hex_number(false) == ishex[i]); CHECK(s.is_valid_hex_number(true) == ishex_p[i]); CHECK(s.is_valid_float() == isflt[i]); @@ -1332,6 +1406,96 @@ 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")); +} + +TEST_CASE("[Stress][String] Empty via ' == String()'") { + for (int i = 0; i < 100000; ++i) { + String str = "Hello World!"; + if (str.is_empty()) { + continue; + } + } +} + +TEST_CASE("[Stress][String] Empty via `is_empty()`") { + for (int i = 0; i < 100000; ++i) { + String str = "Hello World!"; + if (str.is_empty()) { + continue; + } + } +} } // namespace TestString #endif // TEST_STRING_H diff --git a/tests/core/string/test_translation.h b/tests/core/string/test_translation.h new file mode 100644 index 0000000000..85ac639bec --- /dev/null +++ b/tests/core/string/test_translation.h @@ -0,0 +1,182 @@ +/*************************************************************************/ +/* test_translation.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_TRANSLATION_H +#define TEST_TRANSLATION_H + +#include "core/string/optimized_translation.h" +#include "core/string/translation.h" +#include "core/string/translation_po.h" + +#ifdef TOOLS_ENABLED +#include "editor/import/resource_importer_csv_translation.h" +#endif + +#include "tests/test_macros.h" +#include "tests/test_utils.h" + +namespace TestTranslation { + +TEST_CASE("[Translation] Messages") { + Ref<Translation> translation = memnew(Translation); + translation->set_locale("fr"); + translation->add_message("Hello", "Bonjour"); + CHECK(translation->get_message("Hello") == "Bonjour"); + + translation->erase_message("Hello"); + // The message no longer exists, so it returns an empty string instead. + CHECK(translation->get_message("Hello") == ""); + + List<StringName> messages; + translation->get_message_list(&messages); + CHECK(translation->get_message_count() == 0); + CHECK(messages.size() == 0); + + translation->add_message("Hello2", "Bonjour2"); + translation->add_message("Hello3", "Bonjour3"); + messages.clear(); + translation->get_message_list(&messages); + CHECK(translation->get_message_count() == 2); + CHECK(messages.size() == 2); + // Messages are stored in a Map, don't assume ordering. + CHECK(messages.find("Hello2")); + CHECK(messages.find("Hello3")); +} + +TEST_CASE("[TranslationPO] Messages with context") { + Ref<TranslationPO> translation = memnew(TranslationPO); + translation->set_locale("fr"); + translation->add_message("Hello", "Bonjour"); + translation->add_message("Hello", "Salut", "friendly"); + CHECK(translation->get_message("Hello") == "Bonjour"); + CHECK(translation->get_message("Hello", "friendly") == "Salut"); + CHECK(translation->get_message("Hello", "nonexistent_context") == ""); + + // Only remove the message for the default context, not the "friendly" context. + translation->erase_message("Hello"); + // The message no longer exists, so it returns an empty string instead. + CHECK(translation->get_message("Hello") == ""); + CHECK(translation->get_message("Hello", "friendly") == "Salut"); + CHECK(translation->get_message("Hello", "nonexistent_context") == ""); + + List<StringName> messages; + translation->get_message_list(&messages); + + // `get_message_count()` takes all contexts into account. + CHECK(translation->get_message_count() == 1); + // Only the default context is taken into account. + // Since "Hello" is now only present in a non-default context, it is not counted in the list of messages. + CHECK(messages.size() == 0); + + translation->add_message("Hello2", "Bonjour2"); + translation->add_message("Hello2", "Salut2", "friendly"); + translation->add_message("Hello3", "Bonjour3"); + messages.clear(); + translation->get_message_list(&messages); + + // `get_message_count()` takes all contexts into account. + CHECK(translation->get_message_count() == 4); + // Only the default context is taken into account. + CHECK(messages.size() == 2); + // Messages are stored in a Map, don't assume ordering. + CHECK(messages.find("Hello2")); + CHECK(messages.find("Hello3")); +} + +TEST_CASE("[TranslationPO] Plural messages") { + Ref<TranslationPO> translation = memnew(TranslationPO); + translation->set_locale("fr"); + translation->set_plural_rule("Plural-Forms: nplurals=2; plural=(n >= 2);"); + CHECK(translation->get_plural_forms() == 2); + + PackedStringArray plurals; + plurals.push_back("Il y a %d pomme"); + plurals.push_back("Il y a %d pommes"); + translation->add_plural_message("There are %d apples", plurals); + ERR_PRINT_OFF; + // This is invalid, as the number passed to `get_plural_message()` may not be negative. + CHECK(vformat(translation->get_plural_message("There are %d apples", "", -1), -1) == ""); + ERR_PRINT_ON; + CHECK(vformat(translation->get_plural_message("There are %d apples", "", 0), 0) == "Il y a 0 pomme"); + CHECK(vformat(translation->get_plural_message("There are %d apples", "", 1), 1) == "Il y a 1 pomme"); + CHECK(vformat(translation->get_plural_message("There are %d apples", "", 2), 2) == "Il y a 2 pommes"); +} + +TEST_CASE("[OptimizedTranslation] Generate from Translation and read messages") { + Ref<Translation> translation = memnew(Translation); + translation->set_locale("fr"); + translation->add_message("Hello", "Bonjour"); + translation->add_message("Hello2", "Bonjour2"); + translation->add_message("Hello3", "Bonjour3"); + + Ref<OptimizedTranslation> optimized_translation = memnew(OptimizedTranslation); + optimized_translation->generate(translation); + CHECK(optimized_translation->get_message("Hello") == "Bonjour"); + CHECK(optimized_translation->get_message("Hello2") == "Bonjour2"); + CHECK(optimized_translation->get_message("Hello3") == "Bonjour3"); + CHECK(optimized_translation->get_message("DoesNotExist") == ""); + + List<StringName> messages; + // `get_message_list()` can't return the list of messages stored in an OptimizedTranslation. + optimized_translation->get_message_list(&messages); + CHECK(optimized_translation->get_message_count() == 0); + 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_command_queue.h b/tests/core/templates/test_command_queue.h index b4fa63ad2b..0d016f5d06 100644 --- a/tests/test_command_queue.h +++ b/tests/core/templates/test_command_queue.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -31,14 +31,12 @@ #ifndef TEST_COMMAND_QUEUE_H #define TEST_COMMAND_QUEUE_H -#include "test_command_queue.h" - #include "core/config/project_settings.h" -#include "core/os/mutex.h" +#include "core/math/random_number_generator.h" #include "core/os/os.h" -#include "core/os/semaphore.h" #include "core/os/thread.h" #include "core/templates/command_queue_mt.h" +#include "tests/test_macros.h" #if !defined(NO_THREADS) @@ -127,20 +125,20 @@ public: int func1_count = 0; - void func1(Transform t) { + void func1(Transform3D t) { func1_count++; } - void func2(Transform t, float f) { + void func2(Transform3D t, float f) { func1_count++; } - void func3(Transform t1, Transform t2, Transform t3, Transform t4, Transform t5, Transform t6) { + void func3(Transform3D t1, Transform3D t2, Transform3D t3, Transform3D t4, Transform3D t5, Transform3D t6) { func1_count++; } - Transform func1r(Transform t) { + Transform3D func1r(Transform3D t) { func1_count++; return t; } - Transform func2r(Transform t, float f) { + Transform3D func2r(Transform3D t, float f) { func1_count++; return t; } @@ -156,7 +154,7 @@ public: command_queue.flush_all(); } for (int i = 0; i < message_count_to_read; i++) { - command_queue.wait_and_flush_one(); + command_queue.wait_and_flush(); } message_count_to_read = 0; @@ -175,8 +173,8 @@ public: during_writing = false; writer_threadwork.thread_wait_for_work(); while (!exit_threads) { - Transform tr; - Transform otr; + Transform3D tr; + Transform3D otr; float f = 1; during_writing = true; for (int i = 0; i < message_types_to_write.size(); i++) { @@ -276,50 +274,6 @@ TEST_CASE("[CommandQueue] Test Queue Basics") { ProjectSettings::get_singleton()->property_get_revert(COMMAND_QUEUE_SETTING)); } -TEST_CASE("[CommandQueue] Test Waiting at Queue Full") { - const char *COMMAND_QUEUE_SETTING = "memory/limits/command_queue/multithreading_queue_size_kb"; - ProjectSettings::get_singleton()->set_setting(COMMAND_QUEUE_SETTING, 1); - SharedThreadState sts; - sts.init_threads(); - - int msgs_to_add = 24; // a queue of size 1kB fundamentally cannot fit 24 matrices. - for (int i = 0; i < msgs_to_add; i++) { - sts.add_msg_to_write(SharedThreadState::TEST_MSG_FUNC1_TRANSFORM); - } - sts.writer_threadwork.main_start_work(); - // If we call main_wait_for_done, we will deadlock. So instead... - sts.message_count_to_read = 1; - sts.reader_threadwork.main_start_work(); - sts.reader_threadwork.main_wait_for_done(); - CHECK_MESSAGE(sts.func1_count == 1, - "Reader should have read one message"); - CHECK_MESSAGE(sts.during_writing, - "Writer thread should still be blocked on writing."); - sts.message_count_to_read = msgs_to_add - 3; - sts.reader_threadwork.main_start_work(); - sts.reader_threadwork.main_wait_for_done(); - CHECK_MESSAGE(sts.func1_count >= msgs_to_add - 3, - "Reader should have read most messages"); - sts.writer_threadwork.main_wait_for_done(); - CHECK_MESSAGE(sts.during_writing == false, - "Writer thread should no longer be blocked on writing."); - sts.message_count_to_read = 2; - sts.reader_threadwork.main_start_work(); - sts.reader_threadwork.main_wait_for_done(); - sts.message_count_to_read = -1; - sts.reader_threadwork.main_start_work(); - sts.reader_threadwork.main_wait_for_done(); - CHECK_MESSAGE(sts.func1_count == msgs_to_add, - "Reader should have read all messages"); - - sts.destroy_threads(); - - CHECK_MESSAGE(sts.func1_count == msgs_to_add, - "Reader should have read no additional messages after join"); - ProjectSettings::get_singleton()->set_setting(COMMAND_QUEUE_SETTING, - ProjectSettings::get_singleton()->property_get_revert(COMMAND_QUEUE_SETTING)); -} - TEST_CASE("[CommandQueue] Test Queue Wrapping to same spot.") { const char *COMMAND_QUEUE_SETTING = "memory/limits/command_queue/multithreading_queue_size_kb"; ProjectSettings::get_singleton()->set_setting(COMMAND_QUEUE_SETTING, 1); diff --git a/tests/test_list.h b/tests/core/templates/test_list.h index 1c70b6e961..49da0b8aad 100644 --- a/tests/test_list.h +++ b/tests/core/templates/test_list.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -260,7 +260,7 @@ TEST_CASE("[List] Invert") { List<int>::Element *n[4]; populate_integers(list, n, 4); - list.invert(); + list.reverse(); CHECK(list.front()->get() == 3); CHECK(list.front()->next()->get() == 2); diff --git a/tests/test_local_vector.h b/tests/core/templates/test_local_vector.h index eff2a16abc..b2464c3914 100644 --- a/tests/test_local_vector.h +++ b/tests/core/templates/test_local_vector.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -37,6 +37,17 @@ namespace TestLocalVector { +TEST_CASE("[LocalVector] List Initialization.") { + LocalVector<int> vector{ 0, 1, 2, 3, 4 }; + + CHECK(vector.size() == 5); + CHECK(vector[0] == 0); + CHECK(vector[1] == 1); + CHECK(vector[2] == 2); + CHECK(vector[3] == 3); + CHECK(vector[4] == 4); +} + TEST_CASE("[LocalVector] Push Back.") { LocalVector<int> vector; vector.push_back(0); @@ -84,25 +95,25 @@ TEST_CASE("[LocalVector] Remove.") { vector.push_back(3); vector.push_back(4); - vector.remove(0); + vector.remove_at(0); CHECK(vector[0] == 1); CHECK(vector[1] == 2); CHECK(vector[2] == 3); CHECK(vector[3] == 4); - vector.remove(2); + vector.remove_at(2); CHECK(vector[0] == 1); CHECK(vector[1] == 2); CHECK(vector[2] == 4); - vector.remove(1); + vector.remove_at(1); CHECK(vector[0] == 1); CHECK(vector[1] == 4); - vector.remove(0); + vector.remove_at(0); CHECK(vector[0] == 4); } @@ -117,7 +128,7 @@ TEST_CASE("[LocalVector] Remove Unordered.") { CHECK(vector.size() == 5); - vector.remove_unordered(0); + vector.remove_at_unordered(0); CHECK(vector.size() == 4); @@ -128,7 +139,7 @@ TEST_CASE("[LocalVector] Remove Unordered.") { CHECK(vector.find(4) != -1); // Now the vector is no more ordered. - vector.remove_unordered(vector.find(3)); + vector.remove_at_unordered(vector.find(3)); CHECK(vector.size() == 3); @@ -137,7 +148,7 @@ TEST_CASE("[LocalVector] Remove Unordered.") { CHECK(vector.find(2) != -1); CHECK(vector.find(4) != -1); - vector.remove_unordered(vector.find(2)); + vector.remove_at_unordered(vector.find(2)); CHECK(vector.size() == 2); @@ -145,7 +156,7 @@ TEST_CASE("[LocalVector] Remove Unordered.") { CHECK(vector.find(1) != -1); CHECK(vector.find(4) != -1); - vector.remove_unordered(vector.find(4)); + vector.remove_at_unordered(vector.find(4)); CHECK(vector.size() == 1); @@ -153,7 +164,7 @@ TEST_CASE("[LocalVector] Remove Unordered.") { CHECK(vector.find(1) != -1); // Remove the last one. - vector.remove_unordered(0); + vector.remove_at_unordered(0); CHECK(vector.is_empty()); CHECK(vector.size() == 0); @@ -193,9 +204,9 @@ TEST_CASE("[LocalVector] Size / Resize / Reserve.") { // Capacity is supposed to change only when the size increase. CHECK(vector.get_capacity() >= 10); - vector.remove(0); - vector.remove(0); - vector.remove(0); + vector.remove_at(0); + vector.remove_at(0); + vector.remove_at(0); CHECK(vector.size() == 2); // Capacity is supposed to change only when the size increase. diff --git a/tests/test_lru.h b/tests/core/templates/test_lru.h index 2802754729..354f53e164 100644 --- a/tests/test_lru.h +++ b/tests/core/templates/test_lru.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -32,7 +32,6 @@ #define TEST_LRU_H #include "core/templates/lru.h" -#include "core/templates/vector.h" #include "tests/test_macros.h" diff --git a/tests/test_oa_hash_map.cpp b/tests/core/templates/test_oa_hash_map.cpp index 904c01642d..87bf9feb83 100644 --- a/tests/test_oa_hash_map.cpp +++ b/tests/core/templates/test_oa_hash_map.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -55,7 +55,10 @@ struct CountedItem { count++; } - CountedItem &operator=(const CountedItem &p_other) = default; + void operator=(const CountedItem &p_other) { + id = p_other.id; + count++; + } ~CountedItem() { CRASH_COND(destroyed); diff --git a/tests/test_oa_hash_map.h b/tests/core/templates/test_oa_hash_map.h index 9745802cc0..d4b72af2ac 100644 --- a/tests/test_oa_hash_map.h +++ b/tests/core/templates/test_oa_hash_map.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -31,7 +31,7 @@ #ifndef TEST_OA_HASH_MAP_H #define TEST_OA_HASH_MAP_H -#include "core/os/main_loop.h" +class MainLoop; namespace TestOAHashMap { diff --git a/tests/test_ordered_hash_map.h b/tests/core/templates/test_ordered_hash_map.h index fbaaa224cf..08c5c9b72a 100644 --- a/tests/test_ordered_hash_map.h +++ b/tests/core/templates/test_ordered_hash_map.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -31,10 +31,7 @@ #ifndef TEST_ORDERED_HASH_MAP_H #define TEST_ORDERED_HASH_MAP_H -#include "core/os/os.h" #include "core/templates/ordered_hash_map.h" -#include "core/templates/pair.h" -#include "core/templates/vector.h" #include "tests/test_macros.h" diff --git a/tests/test_paged_array.h b/tests/core/templates/test_paged_array.h index 7efd3799f3..86cf3a2dfc 100644 --- a/tests/test_paged_array.h +++ b/tests/core/templates/test_paged_array.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ diff --git a/tests/core/templates/test_vector.h b/tests/core/templates/test_vector.h new file mode 100644 index 0000000000..f27d6a332e --- /dev/null +++ b/tests/core/templates/test_vector.h @@ -0,0 +1,535 @@ +/*************************************************************************/ +/* test_vector.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_VECTOR_H +#define TEST_VECTOR_H + +#include "core/templates/vector.h" + +#include "tests/test_macros.h" + +namespace TestVector { + +TEST_CASE("[Vector] List initialization") { + Vector<int> vector{ 0, 1, 2, 3, 4 }; + + CHECK(vector.size() == 5); + CHECK(vector[0] == 0); + CHECK(vector[1] == 1); + CHECK(vector[2] == 2); + CHECK(vector[3] == 3); + CHECK(vector[4] == 4); +} + +TEST_CASE("[Vector] Push back and append") { + Vector<int> vector; + vector.push_back(0); + vector.push_back(1); + vector.push_back(2); + vector.push_back(3); + // Alias for `push_back`. + vector.append(4); + + CHECK(vector[0] == 0); + CHECK(vector[1] == 1); + CHECK(vector[2] == 2); + CHECK(vector[3] == 3); + CHECK(vector[4] == 4); +} + +TEST_CASE("[Vector] Append array") { + Vector<int> vector; + vector.push_back(1); + vector.push_back(2); + + Vector<int> vector_other; + vector_other.push_back(128); + vector_other.push_back(129); + vector.append_array(vector_other); + + CHECK(vector.size() == 4); + CHECK(vector[0] == 1); + CHECK(vector[1] == 2); + CHECK(vector[2] == 128); + CHECK(vector[3] == 129); +} + +TEST_CASE("[Vector] Insert") { + Vector<int> vector; + vector.insert(0, 2); + vector.insert(0, 8); + vector.insert(2, 5); + vector.insert(1, 5); + vector.insert(0, -2); + + CHECK(vector.size() == 5); + CHECK(vector[0] == -2); + CHECK(vector[1] == 8); + CHECK(vector[2] == 5); + CHECK(vector[3] == 2); + CHECK(vector[4] == 5); +} + +TEST_CASE("[Vector] Ordered insert") { + Vector<int> vector; + vector.ordered_insert(2); + vector.ordered_insert(8); + vector.ordered_insert(5); + vector.ordered_insert(5); + vector.ordered_insert(-2); + + CHECK(vector.size() == 5); + CHECK(vector[0] == -2); + CHECK(vector[1] == 2); + CHECK(vector[2] == 5); + CHECK(vector[3] == 5); + CHECK(vector[4] == 8); +} + +TEST_CASE("[Vector] Insert + Ordered insert") { + Vector<int> vector; + vector.ordered_insert(2); + vector.ordered_insert(8); + vector.insert(0, 5); + vector.ordered_insert(5); + vector.insert(1, -2); + + CHECK(vector.size() == 5); + CHECK(vector[0] == 5); + CHECK(vector[1] == -2); + CHECK(vector[2] == 2); + CHECK(vector[3] == 5); + CHECK(vector[4] == 8); +} + +TEST_CASE("[Vector] Fill large array and modify it") { + Vector<int> vector; + vector.resize(1'000'000); + vector.fill(0x60d07); + + vector.write[200] = 0; + CHECK(vector.size() == 1'000'000); + CHECK(vector[0] == 0x60d07); + CHECK(vector[200] == 0); + CHECK(vector[499'999] == 0x60d07); + CHECK(vector[999'999] == 0x60d07); + vector.remove_at(200); + CHECK(vector[200] == 0x60d07); + + vector.clear(); + CHECK(vector.size() == 0); +} + +TEST_CASE("[Vector] Copy creation") { + Vector<int> vector; + vector.push_back(0); + vector.push_back(1); + vector.push_back(2); + vector.push_back(3); + vector.push_back(4); + + Vector<int> vector_other = Vector<int>(vector); + vector_other.remove_at(0); + CHECK(vector_other[0] == 1); + CHECK(vector_other[1] == 2); + CHECK(vector_other[2] == 3); + CHECK(vector_other[3] == 4); + + // Make sure the original vector isn't modified. + CHECK(vector[0] == 0); + CHECK(vector[1] == 1); + CHECK(vector[2] == 2); + CHECK(vector[3] == 3); + CHECK(vector[4] == 4); +} + +TEST_CASE("[Vector] Duplicate") { + Vector<int> vector; + vector.push_back(0); + vector.push_back(1); + vector.push_back(2); + vector.push_back(3); + vector.push_back(4); + + Vector<int> vector_other = vector.duplicate(); + vector_other.remove_at(0); + CHECK(vector_other[0] == 1); + CHECK(vector_other[1] == 2); + CHECK(vector_other[2] == 3); + CHECK(vector_other[3] == 4); + + // Make sure the original vector isn't modified. + CHECK(vector[0] == 0); + CHECK(vector[1] == 1); + CHECK(vector[2] == 2); + CHECK(vector[3] == 3); + CHECK(vector[4] == 4); +} + +TEST_CASE("[Vector] Get, set") { + Vector<int> vector; + vector.push_back(0); + vector.push_back(1); + vector.push_back(2); + vector.push_back(3); + vector.push_back(4); + + CHECK(vector.get(0) == 0); + CHECK(vector.get(1) == 1); + vector.set(2, 256); + CHECK(vector.get(2) == 256); + CHECK(vector.get(3) == 3); + + ERR_PRINT_OFF; + // Invalid (but should not crash): setting out of bounds. + vector.set(6, 500); + ERR_PRINT_ON; + + CHECK(vector.get(4) == 4); +} + +TEST_CASE("[Vector] To byte array") { + Vector<int> vector; + vector.push_back(0); + vector.push_back(-1); + vector.push_back(2008); + vector.push_back(999999999); + + Vector<uint8_t> byte_array = vector.to_byte_array(); + CHECK(byte_array.size() == 16); + // vector[0] + CHECK(byte_array[0] == 0); + CHECK(byte_array[1] == 0); + CHECK(byte_array[2] == 0); + CHECK(byte_array[3] == 0); + + // vector[1] + CHECK(byte_array[4] == 255); + CHECK(byte_array[5] == 255); + CHECK(byte_array[6] == 255); + CHECK(byte_array[7] == 255); + + // vector[2] + CHECK(byte_array[8] == 216); + CHECK(byte_array[9] == 7); + CHECK(byte_array[10] == 0); + CHECK(byte_array[11] == 0); + + // vector[3] + CHECK(byte_array[12] == 255); + CHECK(byte_array[13] == 201); + CHECK(byte_array[14] == 154); + CHECK(byte_array[15] == 59); +} + +TEST_CASE("[Vector] Slice") { + Vector<int> vector; + vector.push_back(0); + vector.push_back(1); + vector.push_back(2); + vector.push_back(3); + vector.push_back(4); + + Vector<int> slice0 = vector.slice(0, 0); + CHECK(slice0.size() == 0); + + Vector<int> slice1 = vector.slice(1, 3); + CHECK(slice1.size() == 2); + CHECK(slice1[0] == 1); + CHECK(slice1[1] == 2); + + Vector<int> slice2 = vector.slice(1, -1); + CHECK(slice2.size() == 3); + CHECK(slice2[0] == 1); + CHECK(slice2[1] == 2); + CHECK(slice2[2] == 3); + + Vector<int> slice3 = vector.slice(3); + CHECK(slice3.size() == 2); + CHECK(slice3[0] == 3); + CHECK(slice3[1] == 4); + + Vector<int> slice4 = vector.slice(2, -2); + CHECK(slice4.size() == 1); + CHECK(slice4[0] == 2); + + Vector<int> slice5 = vector.slice(-2); + CHECK(slice5.size() == 2); + CHECK(slice5[0] == 3); + CHECK(slice5[1] == 4); + + Vector<int> slice6 = vector.slice(2, 42); + CHECK(slice6.size() == 3); + CHECK(slice6[0] == 2); + CHECK(slice6[1] == 3); + CHECK(slice6[2] == 4); + + Vector<int> slice7 = vector.slice(5, 1); + CHECK(slice7.size() == 0); +} + +TEST_CASE("[Vector] Find, has") { + Vector<int> vector; + vector.push_back(3); + vector.push_back(1); + vector.push_back(4); + vector.push_back(0); + vector.push_back(2); + + CHECK(vector[0] == 3); + CHECK(vector[1] == 1); + CHECK(vector[2] == 4); + CHECK(vector[3] == 0); + CHECK(vector[4] == 2); + + CHECK(vector.find(0) == 3); + CHECK(vector.find(1) == 1); + CHECK(vector.find(2) == 4); + CHECK(vector.find(3) == 0); + CHECK(vector.find(4) == 2); + + CHECK(vector.find(-1) == -1); + CHECK(vector.find(5) == -1); + + CHECK(vector.has(0)); + CHECK(vector.has(1)); + CHECK(vector.has(2)); + CHECK(vector.has(3)); + CHECK(vector.has(4)); + + CHECK(!vector.has(-1)); + CHECK(!vector.has(5)); +} + +TEST_CASE("[Vector] Remove at") { + Vector<int> vector; + vector.push_back(0); + vector.push_back(1); + vector.push_back(2); + vector.push_back(3); + vector.push_back(4); + + vector.remove_at(0); + + CHECK(vector[0] == 1); + CHECK(vector[1] == 2); + CHECK(vector[2] == 3); + CHECK(vector[3] == 4); + + vector.remove_at(2); + + CHECK(vector[0] == 1); + CHECK(vector[1] == 2); + CHECK(vector[2] == 4); + + vector.remove_at(1); + + CHECK(vector[0] == 1); + CHECK(vector[1] == 4); + + vector.remove_at(0); + + CHECK(vector[0] == 4); +} + +TEST_CASE("[Vector] Remove at and find") { + Vector<int> vector; + vector.push_back(0); + vector.push_back(1); + vector.push_back(2); + vector.push_back(3); + vector.push_back(4); + + CHECK(vector.size() == 5); + + vector.remove_at(0); + + CHECK(vector.size() == 4); + + CHECK(vector.find(0) == -1); + CHECK(vector.find(1) != -1); + CHECK(vector.find(2) != -1); + CHECK(vector.find(3) != -1); + CHECK(vector.find(4) != -1); + + vector.remove_at(vector.find(3)); + + CHECK(vector.size() == 3); + + CHECK(vector.find(3) == -1); + CHECK(vector.find(1) != -1); + CHECK(vector.find(2) != -1); + CHECK(vector.find(4) != -1); + + vector.remove_at(vector.find(2)); + + CHECK(vector.size() == 2); + + CHECK(vector.find(2) == -1); + CHECK(vector.find(1) != -1); + CHECK(vector.find(4) != -1); + + vector.remove_at(vector.find(4)); + + CHECK(vector.size() == 1); + + CHECK(vector.find(4) == -1); + CHECK(vector.find(1) != -1); + + vector.remove_at(0); + + CHECK(vector.is_empty()); + CHECK(vector.size() == 0); +} + +TEST_CASE("[Vector] Erase") { + Vector<int> vector; + vector.push_back(1); + vector.push_back(3); + vector.push_back(0); + vector.push_back(2); + vector.push_back(4); + + CHECK(vector.find(2) == 3); + + vector.erase(2); + + CHECK(vector.find(2) == -1); + CHECK(vector.size() == 4); +} + +TEST_CASE("[Vector] Size, resize, reserve") { + Vector<int> vector; + CHECK(vector.is_empty()); + CHECK(vector.size() == 0); + + vector.resize(10); + + CHECK(vector.size() == 10); + + vector.resize(5); + + CHECK(vector.size() == 5); + + vector.remove_at(0); + vector.remove_at(0); + vector.remove_at(0); + + CHECK(vector.size() == 2); + + vector.clear(); + + CHECK(vector.size() == 0); + CHECK(vector.is_empty()); + + vector.push_back(0); + vector.push_back(0); + vector.push_back(0); + + CHECK(vector.size() == 3); + + vector.push_back(0); + + CHECK(vector.size() == 4); +} + +TEST_CASE("[Vector] Sort") { + Vector<int> vector; + vector.push_back(2); + vector.push_back(8); + vector.push_back(-4); + vector.push_back(5); + vector.sort(); + + CHECK(vector.size() == 4); + CHECK(vector[0] == -4); + CHECK(vector[1] == 2); + CHECK(vector[2] == 5); + CHECK(vector[3] == 8); +} + +TEST_CASE("[Vector] Sort custom") { + Vector<String> vector; + vector.push_back("world"); + vector.push_back("World"); + vector.push_back("Hello"); + vector.push_back("10Hello"); + vector.push_back("12Hello"); + vector.push_back("01Hello"); + vector.push_back("1Hello"); + vector.push_back(".Hello"); + vector.sort_custom<NaturalNoCaseComparator>(); + + CHECK(vector.size() == 8); + CHECK(vector[0] == ".Hello"); + CHECK(vector[1] == "01Hello"); + CHECK(vector[2] == "1Hello"); + CHECK(vector[3] == "10Hello"); + CHECK(vector[4] == "12Hello"); + CHECK(vector[5] == "Hello"); + CHECK(vector[6] == "world"); + 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); + vector.push_back(8); + vector.push_back(-4); + vector.push_back(5); + + Vector<int> vector_other; + vector_other.push_back(2); + vector_other.push_back(8); + vector_other.push_back(-4); + vector_other.push_back(5); + + CHECK(vector == vector_other); + + vector_other.push_back(10); + CHECK(vector != vector_other); +} + +} // namespace TestVector + +#endif // TEST_VECTOR_H diff --git a/tests/test_crypto.h b/tests/core/test_crypto.h index 8da8c75544..ce4edc71ae 100644 --- a/tests/test_crypto.h +++ b/tests/core/test_crypto.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -33,7 +33,6 @@ #include "core/crypto/crypto.h" #include "tests/test_macros.h" -#include <stdio.h> namespace TestCrypto { diff --git a/tests/test_hashing_context.h b/tests/core/test_hashing_context.h index 728a5f2cfa..4795d24103 100644 --- a/tests/test_hashing_context.h +++ b/tests/core/test_hashing_context.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ diff --git a/tests/core/test_time.h b/tests/core/test_time.h new file mode 100644 index 0000000000..bc341c73bd --- /dev/null +++ b/tests/core/test_time.h @@ -0,0 +1,148 @@ +/*************************************************************************/ +/* test_time.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_TIME_H +#define TEST_TIME_H + +#include "core/os/time.h" + +#include "thirdparty/doctest/doctest.h" + +#define YEAR_KEY "year" +#define MONTH_KEY "month" +#define DAY_KEY "day" +#define WEEKDAY_KEY "weekday" +#define HOUR_KEY "hour" +#define MINUTE_KEY "minute" +#define SECOND_KEY "second" +#define DST_KEY "dst" + +namespace TestTime { + +TEST_CASE("[Time] Unix time conversion to/from datetime string") { + const Time *time = Time::get_singleton(); + + CHECK_MESSAGE(time->get_unix_time_from_datetime_string("1970-01-01T00:00:00") == 0, "Time get_unix_time_from_datetime_string: The timestamp for Unix epoch is zero."); + CHECK_MESSAGE(time->get_unix_time_from_datetime_string("1970-01-01 00:00:00") == 0, "Time get_unix_time_from_datetime_string: The timestamp for Unix epoch with space is zero."); + CHECK_MESSAGE(time->get_unix_time_from_datetime_string("1970-01-01") == 0, "Time get_unix_time_from_datetime_string: The timestamp for Unix epoch without time is zero."); + CHECK_MESSAGE(time->get_unix_time_from_datetime_string("00:00:00") == 0, "Time get_unix_time_from_datetime_string: The timestamp for zero time without date is zero."); + CHECK_MESSAGE(time->get_unix_time_from_datetime_string("1969-12-31T23:59:59") == -1, "Time get_unix_time_from_datetime_string: The timestamp for just before Unix epoch is negative one."); + CHECK_MESSAGE(time->get_unix_time_from_datetime_string("1234-05-06T07:08:09") == -23215049511, "Time get_unix_time_from_datetime_string: The timestamp for an arbitrary datetime is as expected."); + CHECK_MESSAGE(time->get_unix_time_from_datetime_string("1234-05-06 07:08:09") == -23215049511, "Time get_unix_time_from_datetime_string: The timestamp for an arbitrary datetime with space is as expected."); + CHECK_MESSAGE(time->get_unix_time_from_datetime_string("1234-05-06") == -23215075200, "Time get_unix_time_from_datetime_string: The timestamp for an arbitrary date without time is as expected."); + CHECK_MESSAGE(time->get_unix_time_from_datetime_string("07:08:09") == 25689, "Time get_unix_time_from_datetime_string: The timestamp for an arbitrary time without date is as expected."); + CHECK_MESSAGE(time->get_unix_time_from_datetime_string("2014-02-09T22:10:30") == 1391983830, "Time get_unix_time_from_datetime_string: The timestamp for GODOT IS OPEN SOURCE is as expected."); + CHECK_MESSAGE(time->get_unix_time_from_datetime_string("2014-02-09 22:10:30") == 1391983830, "Time get_unix_time_from_datetime_string: The timestamp for GODOT IS OPEN SOURCE with space is as expected."); + CHECK_MESSAGE(time->get_unix_time_from_datetime_string("2014-02-09") == 1391904000, "Time get_unix_time_from_datetime_string: The date for GODOT IS OPEN SOURCE without time is as expected."); + CHECK_MESSAGE(time->get_unix_time_from_datetime_string("22:10:30") == 79830, "Time get_unix_time_from_datetime_string: The time for GODOT IS OPEN SOURCE without date is as expected."); + CHECK_MESSAGE(time->get_unix_time_from_datetime_string("-1000000000-01-01T00:00:00") == -31557014167219200, "Time get_unix_time_from_datetime_string: In the year negative a billion, Japan might not have been here."); + CHECK_MESSAGE(time->get_unix_time_from_datetime_string("1000000-01-01T00:00:00") == 31494784780800, "Time get_unix_time_from_datetime_string: The timestamp for the year a million is as expected."); + + CHECK_MESSAGE(time->get_datetime_string_from_unix_time(0) == "1970-01-01T00:00:00", "Time get_datetime_string_from_unix_time: The timestamp string for Unix epoch is zero."); + CHECK_MESSAGE(time->get_datetime_string_from_unix_time(0, true) == "1970-01-01 00:00:00", "Time get_datetime_string_from_unix_time: The timestamp string for Unix epoch with space is zero."); + CHECK_MESSAGE(time->get_date_string_from_unix_time(0) == "1970-01-01", "Time get_date_string_from_unix_time: The date string for zero is Unix epoch date."); + CHECK_MESSAGE(time->get_time_string_from_unix_time(0) == "00:00:00", "Time get_time_string_from_unix_time: The date for zero zero is Unix epoch date."); + CHECK_MESSAGE(time->get_datetime_string_from_unix_time(-1) == "1969-12-31T23:59:59", "Time get_time_string_from_unix_time: The timestamp string for just before Unix epoch is as expected."); + CHECK_MESSAGE(time->get_datetime_string_from_unix_time(-23215049511) == "1234-05-06T07:08:09", "Time get_datetime_string_from_unix_time: The timestamp for an arbitrary datetime is as expected."); + CHECK_MESSAGE(time->get_datetime_string_from_unix_time(-23215049511, true) == "1234-05-06 07:08:09", "Time get_datetime_string_from_unix_time: The timestamp for an arbitrary datetime with space is as expected."); + CHECK_MESSAGE(time->get_date_string_from_unix_time(-23215075200) == "1234-05-06", "Time get_date_string_from_unix_time: The timestamp for an arbitrary date without time is as expected."); + CHECK_MESSAGE(time->get_time_string_from_unix_time(25689) == "07:08:09", "Time get_time_string_from_unix_time: The timestamp for an arbitrary time without date is as expected."); + CHECK_MESSAGE(time->get_datetime_string_from_unix_time(1391983830) == "2014-02-09T22:10:30", "Time get_datetime_string_from_unix_time: The timestamp for GODOT IS OPEN SOURCE is as expected."); + CHECK_MESSAGE(time->get_datetime_string_from_unix_time(1391983830, true) == "2014-02-09 22:10:30", "Time get_datetime_string_from_unix_time: The timestamp for GODOT IS OPEN SOURCE with space is as expected."); + CHECK_MESSAGE(time->get_date_string_from_unix_time(1391904000) == "2014-02-09", "Time get_date_string_from_unix_time: The date for GODOT IS OPEN SOURCE without time is as expected."); + CHECK_MESSAGE(time->get_time_string_from_unix_time(79830) == "22:10:30", "Time get_time_string_from_unix_time: The time for GODOT IS OPEN SOURCE without date is as expected."); + CHECK_MESSAGE(time->get_datetime_string_from_unix_time(31494784780800) == "1000000-01-01T00:00:00", "Time get_datetime_string_from_unix_time: The timestamp for the year a million is as expected."); + CHECK_MESSAGE(time->get_offset_string_from_offset_minutes(0) == "+00:00", "Time get_offset_string_from_offset_minutes: The offset string is as expected."); + CHECK_MESSAGE(time->get_offset_string_from_offset_minutes(-600) == "-10:00", "Time get_offset_string_from_offset_minutes: The offset string is as expected."); + CHECK_MESSAGE(time->get_offset_string_from_offset_minutes(345) == "+05:45", "Time get_offset_string_from_offset_minutes: The offset string is as expected."); +} + +TEST_CASE("[Time] Datetime dictionary conversion methods") { + const Time *time = Time::get_singleton(); + + Dictionary datetime; + datetime[YEAR_KEY] = 2014; + datetime[MONTH_KEY] = 2; + datetime[DAY_KEY] = 9; + datetime[WEEKDAY_KEY] = Time::Weekday::WEEKDAY_SUNDAY; + datetime[HOUR_KEY] = 22; + datetime[MINUTE_KEY] = 10; + datetime[SECOND_KEY] = 30; + + Dictionary date_only; + date_only[YEAR_KEY] = 2014; + date_only[MONTH_KEY] = 2; + date_only[DAY_KEY] = 9; + date_only[WEEKDAY_KEY] = Time::Weekday::WEEKDAY_SUNDAY; + + Dictionary time_only; + time_only[HOUR_KEY] = 22; + time_only[MINUTE_KEY] = 10; + time_only[SECOND_KEY] = 30; + + CHECK_MESSAGE(time->get_unix_time_from_datetime_dict(datetime) == 1391983830, "Time get_unix_time_from_datetime_dict: The datetime dictionary for GODOT IS OPEN SOURCE is converted to a timestamp as expected."); + CHECK_MESSAGE(time->get_unix_time_from_datetime_dict(date_only) == 1391904000, "Time get_unix_time_from_datetime_dict: The date dictionary for GODOT IS OPEN SOURCE is converted to a timestamp as expected."); + CHECK_MESSAGE(time->get_unix_time_from_datetime_dict(time_only) == 79830, "Time get_unix_time_from_datetime_dict: The time dictionary for GODOT IS OPEN SOURCE is converted to a timestamp as expected."); + + CHECK_MESSAGE(time->get_datetime_dict_from_unix_time(1391983830).hash() == datetime.hash(), "Time get_datetime_dict_from_unix_time: The datetime timestamp for GODOT IS OPEN SOURCE is converted to a dictionary as expected."); + CHECK_MESSAGE(time->get_date_dict_from_unix_time(1391904000).hash() == date_only.hash(), "Time get_date_dict_from_unix_time: The date timestamp for GODOT IS OPEN SOURCE is converted to a dictionary as expected."); + CHECK_MESSAGE(time->get_time_dict_from_unix_time(79830).hash() == time_only.hash(), "Time get_time_dict_from_unix_time: The time timestamp for GODOT IS OPEN SOURCE is converted to a dictionary as expected."); + + CHECK_MESSAGE((Time::Weekday)(int)time->get_datetime_dict_from_unix_time(0)[WEEKDAY_KEY] == Time::Weekday::WEEKDAY_THURSDAY, "Time get_datetime_dict_from_unix_time: The weekday for the Unix epoch is a Thursday as expected."); + CHECK_MESSAGE((Time::Weekday)(int)time->get_datetime_dict_from_unix_time(1391983830)[WEEKDAY_KEY] == Time::Weekday::WEEKDAY_SUNDAY, "Time get_datetime_dict_from_unix_time: The weekday for GODOT IS OPEN SOURCE is a Sunday as expected."); + + CHECK_MESSAGE(time->get_datetime_dict_from_string("2014-02-09T22:10:30").hash() == datetime.hash(), "Time get_datetime_dict_from_string: The dictionary from string for GODOT IS OPEN SOURCE works as expected."); + CHECK_MESSAGE(!time->get_datetime_dict_from_string("2014-02-09T22:10:30", false).has(WEEKDAY_KEY), "Time get_datetime_dict_from_string: The dictionary from string for GODOT IS OPEN SOURCE without weekday doesn't contain the weekday key as expected."); + CHECK_MESSAGE(time->get_datetime_string_from_dict(datetime) == "2014-02-09T22:10:30", "Time get_datetime_string_from_dict: The string from dictionary for GODOT IS OPEN SOURCE works as expected."); + CHECK_MESSAGE(time->get_datetime_string_from_dict(time->get_datetime_dict_from_string("2014-02-09T22:10:30")) == "2014-02-09T22:10:30", "Time get_datetime_string_from_dict: The round-trip string to dict to string GODOT IS OPEN SOURCE works as expected."); + CHECK_MESSAGE(time->get_datetime_string_from_dict(time->get_datetime_dict_from_string("2014-02-09 22:10:30"), true) == "2014-02-09 22:10:30", "Time get_datetime_string_from_dict: The round-trip string to dict to string GODOT IS OPEN SOURCE with spaces works as expected."); +} + +TEST_CASE("[Time] System time methods") { + const Time *time = Time::get_singleton(); + + const uint64_t ticks_msec = time->get_ticks_msec(); + const uint64_t ticks_usec = time->get_ticks_usec(); + + CHECK_MESSAGE(time->get_unix_time_from_system() > 1000000000, "Time get_unix_time_from_system: The timestamp from system time doesn't fail and is very positive."); + CHECK_MESSAGE(time->get_unix_time_from_datetime_dict(time->get_datetime_dict_from_system()) > 1000000000, "Time get_datetime_string_from_system: The timestamp from system time doesn't fail and is very positive."); + CHECK_MESSAGE(time->get_unix_time_from_datetime_dict(time->get_date_dict_from_system()) > 1000000000, "Time get_datetime_string_from_system: The date from system time doesn't fail and is very positive."); + CHECK_MESSAGE(time->get_unix_time_from_datetime_dict(time->get_time_dict_from_system()) < 86400, "Time get_datetime_string_from_system: The time from system time doesn't fail and is within the acceptable range."); + CHECK_MESSAGE(time->get_unix_time_from_datetime_string(time->get_datetime_string_from_system()) > 1000000000, "Time get_datetime_string_from_system: The timestamp from system time doesn't fail and is very positive."); + CHECK_MESSAGE(time->get_unix_time_from_datetime_string(time->get_date_string_from_system()) > 1000000000, "Time get_datetime_string_from_system: The date from system time doesn't fail and is very positive."); + CHECK_MESSAGE(time->get_unix_time_from_datetime_string(time->get_time_string_from_system()) < 86400, "Time get_datetime_string_from_system: The time from system time doesn't fail and is within the acceptable range."); + + CHECK_MESSAGE(time->get_ticks_msec() >= ticks_msec, "Time get_ticks_msec: The value has not decreased."); + CHECK_MESSAGE(time->get_ticks_usec() > ticks_usec, "Time get_ticks_usec: The value has increased."); +} + +} // namespace TestTime + +#endif // TEST_TIME_H diff --git a/tests/core/variant/test_array.h b/tests/core/variant/test_array.h new file mode 100644 index 0000000000..6093048307 --- /dev/null +++ b/tests/core/variant/test_array.h @@ -0,0 +1,521 @@ +/*************************************************************************/ +/* test_array.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_ARRAY_H +#define TEST_ARRAY_H + +#include "core/variant/array.h" +#include "tests/test_macros.h" +#include "tests/test_tools.h" + +namespace TestArray { + +static inline Array build_array() { + return Array(); +} +template <typename... Targs> +static inline Array build_array(Variant item, Targs... Fargs) { + Array a = build_array(Fargs...); + a.push_front(item); + return a; +} +static inline Dictionary build_dictionary() { + return Dictionary(); +} +template <typename... Targs> +static inline Dictionary build_dictionary(Variant key, Variant item, Targs... Fargs) { + Dictionary d = build_dictionary(Fargs...); + d[key] = item; + return d; +} + +TEST_CASE("[Array] size(), clear(), and is_empty()") { + Array arr; + CHECK(arr.size() == 0); + CHECK(arr.is_empty()); + arr.push_back(1); + CHECK(arr.size() == 1); + arr.clear(); + CHECK(arr.is_empty()); + CHECK(arr.size() == 0); +} + +TEST_CASE("[Array] Assignment and comparison operators") { + Array arr1; + Array arr2; + arr1.push_back(1); + CHECK(arr1 != arr2); + CHECK(arr1 > arr2); + CHECK(arr1 >= arr2); + arr2.push_back(2); + CHECK(arr1 != arr2); + CHECK(arr1 < arr2); + CHECK(arr1 <= arr2); + CHECK(arr2 > arr1); + CHECK(arr2 >= arr1); + Array arr3 = arr2; + CHECK(arr3 == arr2); +} + +TEST_CASE("[Array] append_array()") { + Array arr1; + Array arr2; + arr1.push_back(1); + arr1.append_array(arr2); + CHECK(arr1.size() == 1); + arr2.push_back(2); + arr1.append_array(arr2); + CHECK(arr1.size() == 2); + CHECK(int(arr1[0]) == 1); + CHECK(int(arr1[1]) == 2); +} + +TEST_CASE("[Array] resize(), insert(), and erase()") { + Array arr; + arr.resize(2); + CHECK(arr.size() == 2); + arr.insert(0, 1); + CHECK(int(arr[0]) == 1); + arr.insert(0, 2); + CHECK(int(arr[0]) == 2); + arr.erase(2); + CHECK(int(arr[0]) == 1); +} + +TEST_CASE("[Array] front() and back()") { + Array arr; + arr.push_back(1); + CHECK(int(arr.front()) == 1); + CHECK(int(arr.back()) == 1); + arr.push_back(3); + CHECK(int(arr.front()) == 1); + CHECK(int(arr.back()) == 3); +} + +TEST_CASE("[Array] has() and count()") { + Array arr; + arr.push_back(1); + arr.push_back(1); + CHECK(arr.has(1)); + CHECK(!arr.has(2)); + CHECK(arr.count(1) == 2); + CHECK(arr.count(2) == 0); +} + +TEST_CASE("[Array] remove_at()") { + Array arr; + arr.push_back(1); + arr.push_back(2); + arr.remove_at(0); + CHECK(arr.size() == 1); + CHECK(int(arr[0]) == 2); + arr.remove_at(0); + CHECK(arr.size() == 0); + + // The array is now empty; try to use `remove_at()` again. + // Normally, this prints an error message so we silence it. + ERR_PRINT_OFF; + arr.remove_at(0); + ERR_PRINT_ON; + + CHECK(arr.size() == 0); +} + +TEST_CASE("[Array] get()") { + Array arr; + arr.push_back(1); + CHECK(int(arr.get(0)) == 1); +} + +TEST_CASE("[Array] sort()") { + Array arr; + + arr.push_back(3); + arr.push_back(4); + arr.push_back(2); + arr.push_back(1); + arr.sort(); + int val = 1; + for (int i = 0; i < arr.size(); i++) { + CHECK(int(arr[i]) == val); + val++; + } +} + +TEST_CASE("[Array] push_front(), pop_front(), pop_back()") { + Array arr; + arr.push_front(1); + arr.push_front(2); + CHECK(int(arr[0]) == 2); + arr.pop_front(); + CHECK(int(arr[0]) == 1); + CHECK(arr.size() == 1); + arr.push_front(2); + arr.push_front(3); + arr.pop_back(); + CHECK(int(arr[1]) == 2); + 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); + arr.push_front(4); + arr.push_back(5); + arr.push_back(2); + int max = int(arr.max()); + int min = int(arr.min()); + CHECK(max == 5); + CHECK(min == 2); +} + +TEST_CASE("[Array] slice()") { + Array array; + array.push_back(0); + array.push_back(1); + array.push_back(2); + array.push_back(3); + array.push_back(4); + + Array slice0 = array.slice(0, 0); + CHECK(slice0.size() == 0); + + Array slice1 = array.slice(1, 3); + CHECK(slice1.size() == 2); + CHECK(slice1[0] == Variant(1)); + CHECK(slice1[1] == Variant(2)); + + Array slice2 = array.slice(1, -1); + CHECK(slice2.size() == 3); + CHECK(slice2[0] == Variant(1)); + CHECK(slice2[1] == Variant(2)); + CHECK(slice2[2] == Variant(3)); + + Array slice3 = array.slice(3); + CHECK(slice3.size() == 2); + CHECK(slice3[0] == Variant(3)); + CHECK(slice3[1] == Variant(4)); + + Array slice4 = array.slice(2, -2); + CHECK(slice4.size() == 1); + CHECK(slice4[0] == Variant(2)); + + Array slice5 = array.slice(-2); + CHECK(slice5.size() == 2); + CHECK(slice5[0] == Variant(3)); + CHECK(slice5[1] == Variant(4)); + + Array slice6 = array.slice(2, 42); + CHECK(slice6.size() == 3); + CHECK(slice6[0] == Variant(2)); + CHECK(slice6[1] == Variant(3)); + CHECK(slice6[2] == Variant(4)); + + Array slice7 = array.slice(4, 0, -2); + CHECK(slice7.size() == 2); + CHECK(slice7[0] == Variant(4)); + CHECK(slice7[1] == Variant(2)); + + ERR_PRINT_OFF; + Array slice8 = array.slice(4, 1); + CHECK(slice8.size() == 0); + + Array slice9 = array.slice(3, -4); + CHECK(slice9.size() == 0); + ERR_PRINT_ON; +} + +TEST_CASE("[Array] Duplicate array") { + // a = [1, [2, 2], {3: 3}] + Array a = build_array(1, build_array(2, 2), build_dictionary(3, 3)); + + // Deep copy + Array deep_a = a.duplicate(true); + CHECK_MESSAGE(deep_a.id() != a.id(), "Should create a new array"); + CHECK_MESSAGE(Array(deep_a[1]).id() != Array(a[1]).id(), "Should clone nested array"); + CHECK_MESSAGE(Dictionary(deep_a[2]).id() != Dictionary(a[2]).id(), "Should clone nested dictionary"); + CHECK_EQ(deep_a, a); + deep_a.push_back(1); + CHECK_NE(deep_a, a); + deep_a.pop_back(); + Array(deep_a[1]).push_back(1); + CHECK_NE(deep_a, a); + Array(deep_a[1]).pop_back(); + CHECK_EQ(deep_a, a); + + // Shallow copy + Array shallow_a = a.duplicate(false); + CHECK_MESSAGE(shallow_a.id() != a.id(), "Should create a new array"); + CHECK_MESSAGE(Array(shallow_a[1]).id() == Array(a[1]).id(), "Should keep nested array"); + CHECK_MESSAGE(Dictionary(shallow_a[2]).id() == Dictionary(a[2]).id(), "Should keep nested dictionary"); + CHECK_EQ(shallow_a, a); + Array(shallow_a).push_back(1); + CHECK_NE(shallow_a, a); +} + +TEST_CASE("[Array] Duplicate recursive array") { + // Self recursive + Array a; + a.push_back(a); + + Array a_shallow = a.duplicate(false); + CHECK_EQ(a, a_shallow); + + // Deep copy of recursive array endup with recursion limit and return + // an invalid result (multiple nested arrays), the point is we should + // not end up with a segfault and an error log should be printed + ERR_PRINT_OFF; + a.duplicate(true); + ERR_PRINT_ON; + + // Nested recursive + Array a1; + Array a2; + a2.push_back(a1); + a1.push_back(a2); + + Array a1_shallow = a1.duplicate(false); + CHECK_EQ(a1, a1_shallow); + + // Same deep copy issue as above + ERR_PRINT_OFF; + a1.duplicate(true); + ERR_PRINT_ON; + + // Break the recursivity otherwise Array teardown will leak memory + a.clear(); + a1.clear(); + a2.clear(); +} + +TEST_CASE("[Array] Hash array") { + // a = [1, [2, 2], {3: 3}] + Array a = build_array(1, build_array(2, 2), build_dictionary(3, 3)); + uint32_t original_hash = a.hash(); + + a.push_back(1); + CHECK_NE(a.hash(), original_hash); + + a.pop_back(); + CHECK_EQ(a.hash(), original_hash); + + Array(a[1]).push_back(1); + CHECK_NE(a.hash(), original_hash); + Array(a[1]).pop_back(); + CHECK_EQ(a.hash(), original_hash); + + (Dictionary(a[2]))[1] = 1; + CHECK_NE(a.hash(), original_hash); + Dictionary(a[2]).erase(1); + CHECK_EQ(a.hash(), original_hash); + + Array a2 = a.duplicate(true); + CHECK_EQ(a2.hash(), a.hash()); +} + +TEST_CASE("[Array] Hash recursive array") { + Array a1; + a1.push_back(a1); + + Array a2; + a2.push_back(a2); + + // Hash should reach recursion limit + ERR_PRINT_OFF; + CHECK_EQ(a1.hash(), a2.hash()); + ERR_PRINT_ON; + + // Break the recursivity otherwise Array teardown will leak memory + a1.clear(); + a2.clear(); +} + +TEST_CASE("[Array] Empty comparison") { + Array a1; + Array a2; + + // test both operator== and operator!= + CHECK_EQ(a1, a2); + CHECK_FALSE(a1 != a2); +} + +TEST_CASE("[Array] Flat comparison") { + Array a1 = build_array(1); + Array a2 = build_array(1); + Array other_a = build_array(2); + + // test both operator== and operator!= + CHECK_EQ(a1, a1); // compare self + CHECK_FALSE(a1 != a1); + CHECK_EQ(a1, a2); // different equivalent arrays + CHECK_FALSE(a1 != a2); + CHECK_NE(a1, other_a); // different arrays with different content + CHECK_FALSE(a1 == other_a); +} + +TEST_CASE("[Array] Nested array comparison") { + // a1 = [[[1], 2], 3] + Array a1 = build_array(build_array(build_array(1), 2), 3); + + Array a2 = a1.duplicate(true); + + // other_a = [[[1, 0], 2], 3] + Array other_a = build_array(build_array(build_array(1, 0), 2), 3); + + // test both operator== and operator!= + CHECK_EQ(a1, a1); // compare self + CHECK_FALSE(a1 != a1); + CHECK_EQ(a1, a2); // different equivalent arrays + CHECK_FALSE(a1 != a2); + CHECK_NE(a1, other_a); // different arrays with different content + CHECK_FALSE(a1 == other_a); +} + +TEST_CASE("[Array] Nested dictionary comparison") { + // a1 = [{1: 2}, 3] + Array a1 = build_array(build_dictionary(1, 2), 3); + + Array a2 = a1.duplicate(true); + + // other_a = [{1: 0}, 3] + Array other_a = build_array(build_dictionary(1, 0), 3); + + // test both operator== and operator!= + CHECK_EQ(a1, a1); // compare self + CHECK_FALSE(a1 != a1); + CHECK_EQ(a1, a2); // different equivalent arrays + CHECK_FALSE(a1 != a2); + CHECK_NE(a1, other_a); // different arrays with different content + CHECK_FALSE(a1 == other_a); +} + +TEST_CASE("[Array] Recursive comparison") { + Array a1; + a1.push_back(a1); + + Array a2; + a2.push_back(a2); + + // Comparison should reach recursion limit + ERR_PRINT_OFF; + CHECK_EQ(a1, a2); + CHECK_FALSE(a1 != a2); + ERR_PRINT_ON; + + a1.push_back(1); + a2.push_back(1); + + // Comparison should reach recursion limit + ERR_PRINT_OFF; + CHECK_EQ(a1, a2); + CHECK_FALSE(a1 != a2); + ERR_PRINT_ON; + + a1.push_back(1); + a2.push_back(2); + + // Comparison should reach recursion limit + ERR_PRINT_OFF; + CHECK_NE(a1, a2); + CHECK_FALSE(a1 == a2); + ERR_PRINT_ON; + + // Break the recursivity otherwise Array tearndown will leak memory + a1.clear(); + a2.clear(); +} + +TEST_CASE("[Array] Recursive self comparison") { + Array a1; + Array a2; + a2.push_back(a1); + a1.push_back(a2); + + CHECK_EQ(a1, a1); + CHECK_FALSE(a1 != a1); + + // Break the recursivity otherwise Array tearndown will leak memory + a1.clear(); + a2.clear(); +} + +} // namespace TestArray + +#endif // TEST_ARRAY_H diff --git a/tests/core/variant/test_dictionary.h b/tests/core/variant/test_dictionary.h new file mode 100644 index 0000000000..729035919d --- /dev/null +++ b/tests/core/variant/test_dictionary.h @@ -0,0 +1,505 @@ +/*************************************************************************/ +/* test_dictionary.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_DICTIONARY_H +#define TEST_DICTIONARY_H + +#include "core/variant/dictionary.h" +#include "tests/test_macros.h" + +namespace TestDictionary { + +static inline Array build_array() { + return Array(); +} +template <typename... Targs> +static inline Array build_array(Variant item, Targs... Fargs) { + Array a = build_array(Fargs...); + a.push_front(item); + return a; +} +static inline Dictionary build_dictionary() { + return Dictionary(); +} +template <typename... Targs> +static inline Dictionary build_dictionary(Variant key, Variant item, Targs... Fargs) { + Dictionary d = build_dictionary(Fargs...); + d[key] = item; + return d; +} + +TEST_CASE("[Dictionary] Assignment using bracket notation ([])") { + Dictionary map; + map["Hello"] = 0; + CHECK(int(map["Hello"]) == 0); + map["Hello"] = 3; + CHECK(int(map["Hello"]) == 3); + map["World!"] = 4; + CHECK(int(map["World!"]) == 4); + + // Test non-string keys, since keys can be of any Variant type. + map[12345] = -5; + CHECK(int(map[12345]) == -5); + map[false] = 128; + CHECK(int(map[false]) == 128); + map[Vector2(10, 20)] = 30; + CHECK(int(map[Vector2(10, 20)]) == 30); + map[0] = 400; + CHECK(int(map[0]) == 400); + // Check that assigning 0 doesn't overwrite the value for `false`. + CHECK(int(map[false]) == 128); +} + +TEST_CASE("[Dictionary] get_key_lists()") { + Dictionary map; + List<Variant> keys; + List<Variant> *ptr = &keys; + map.get_key_list(ptr); + CHECK(keys.is_empty()); + map[1] = 3; + map.get_key_list(ptr); + CHECK(keys.size() == 1); + CHECK(int(keys[0]) == 1); + map[2] = 4; + map.get_key_list(ptr); + CHECK(keys.size() == 3); +} + +TEST_CASE("[Dictionary] get_key_at_index()") { + Dictionary map; + map[4] = 3; + Variant val = map.get_key_at_index(0); + CHECK(int(val) == 4); + map[3] = 1; + val = map.get_key_at_index(0); + CHECK(int(val) == 4); + val = map.get_key_at_index(1); + CHECK(int(val) == 3); +} + +TEST_CASE("[Dictionary] getptr()") { + Dictionary map; + map[1] = 3; + Variant *key = map.getptr(1); + CHECK(int(*key) == 3); + key = map.getptr(2); + CHECK(key == nullptr); +} + +TEST_CASE("[Dictionary] get_valid()") { + Dictionary map; + map[1] = 3; + Variant val = map.get_valid(1); + CHECK(int(val) == 3); +} +TEST_CASE("[Dictionary] get()") { + Dictionary map; + map[1] = 3; + Variant val = map.get(1, -1); + CHECK(int(val) == 3); +} + +TEST_CASE("[Dictionary] size(), empty() and clear()") { + Dictionary map; + CHECK(map.size() == 0); + CHECK(map.is_empty()); + map[1] = 3; + CHECK(map.size() == 1); + CHECK(!map.is_empty()); + map.clear(); + CHECK(map.size() == 0); + CHECK(map.is_empty()); +} + +TEST_CASE("[Dictionary] has() and has_all()") { + Dictionary map; + CHECK(map.has(1) == false); + map[1] = 3; + CHECK(map.has(1)); + Array keys; + keys.push_back(1); + CHECK(map.has_all(keys)); + keys.push_back(2); + CHECK(map.has_all(keys) == false); +} + +TEST_CASE("[Dictionary] keys() and values()") { + Dictionary map; + Array keys = map.keys(); + Array values = map.values(); + CHECK(keys.is_empty()); + CHECK(values.is_empty()); + map[1] = 3; + keys = map.keys(); + values = map.values(); + CHECK(int(keys[0]) == 1); + CHECK(int(values[0]) == 3); +} + +TEST_CASE("[Dictionary] Duplicate dictionary") { + // d = {1: {1: 1}, {2: 2}: [2], [3]: 3} + Dictionary k2 = build_dictionary(2, 2); + Array k3 = build_array(3); + Dictionary d = build_dictionary(1, build_dictionary(1, 1), k2, build_array(2), k3, 3); + + // Deep copy + Dictionary deep_d = d.duplicate(true); + CHECK_MESSAGE(deep_d.id() != d.id(), "Should create a new dictionary"); + CHECK_MESSAGE(Dictionary(deep_d[1]).id() != Dictionary(d[1]).id(), "Should clone nested dictionary"); + CHECK_MESSAGE(Array(deep_d[k2]).id() != Array(d[k2]).id(), "Should clone nested array"); + CHECK_EQ(deep_d, d); + deep_d[0] = 0; + CHECK_NE(deep_d, d); + deep_d.erase(0); + Dictionary(deep_d[1]).operator[](0) = 0; + CHECK_NE(deep_d, d); + Dictionary(deep_d[1]).erase(0); + CHECK_EQ(deep_d, d); + // Keys should also be copied + k2[0] = 0; + CHECK_NE(deep_d, d); + k2.erase(0); + CHECK_EQ(deep_d, d); + k3.push_back(0); + CHECK_NE(deep_d, d); + k3.pop_back(); + CHECK_EQ(deep_d, d); + + // Shallow copy + Dictionary shallow_d = d.duplicate(false); + CHECK_MESSAGE(shallow_d.id() != d.id(), "Should create a new array"); + CHECK_MESSAGE(Dictionary(shallow_d[1]).id() == Dictionary(d[1]).id(), "Should keep nested dictionary"); + CHECK_MESSAGE(Array(shallow_d[k2]).id() == Array(d[k2]).id(), "Should keep nested array"); + CHECK_EQ(shallow_d, d); + shallow_d[0] = 0; + CHECK_NE(shallow_d, d); + shallow_d.erase(0); +#if 0 // TODO: recursion in dict key currently is buggy + // Keys should also be shallowed + k2[0] = 0; + CHECK_EQ(shallow_d, d); + k2.erase(0); + k3.push_back(0); + CHECK_EQ(shallow_d, d); +#endif +} + +TEST_CASE("[Dictionary] Duplicate recursive dictionary") { + // Self recursive + Dictionary d; + d[1] = d; + + Dictionary d_shallow = d.duplicate(false); + CHECK_EQ(d, d_shallow); + + // Deep copy of recursive dictionary endup with recursion limit and return + // an invalid result (multiple nested dictionaries), the point is we should + // not end up with a segfault and an error log should be printed + ERR_PRINT_OFF; + d.duplicate(true); + ERR_PRINT_ON; + + // Nested recursive + Dictionary d1; + Dictionary d2; + d1[2] = d2; + d2[1] = d1; + + Dictionary d1_shallow = d1.duplicate(false); + CHECK_EQ(d1, d1_shallow); + + // Same deep copy issue as above + ERR_PRINT_OFF; + d1.duplicate(true); + ERR_PRINT_ON; + + // Break the recursivity otherwise Dictionary teardown will leak memory + d.clear(); + d1.clear(); + d2.clear(); +} + +#if 0 // TODO: duplicate recursion in dict key is currently buggy +TEST_CASE("[Dictionary] Duplicate recursive dictionary on keys") { + // Self recursive + Dictionary d; + d[d] = d; + + Dictionary d_shallow = d.duplicate(false); + CHECK_EQ(d, d_shallow); + + // Deep copy of recursive dictionary endup with recursion limit and return + // an invalid result (multiple nested dictionaries), the point is we should + // not end up with a segfault and an error log should be printed + ERR_PRINT_OFF; + d.duplicate(true); + ERR_PRINT_ON; + + // Nested recursive + Dictionary d1; + Dictionary d2; + d1[d2] = d2; + d2[d1] = d1; + + Dictionary d1_shallow = d1.duplicate(false); + CHECK_EQ(d1, d1_shallow); + + // Same deep copy issue as above + ERR_PRINT_OFF; + d1.duplicate(true); + ERR_PRINT_ON; + + // Break the recursivity otherwise Dictionary teardown will leak memory + d.clear(); + d1.clear(); + d2.clear(); +} +#endif + +TEST_CASE("[Dictionary] Hash dictionary") { + // d = {1: {1: 1}, {2: 2}: [2], [3]: 3} + Dictionary k2 = build_dictionary(2, 2); + Array k3 = build_array(3); + Dictionary d = build_dictionary(1, build_dictionary(1, 1), k2, build_array(2), k3, 3); + uint32_t original_hash = d.hash(); + + // Modify dict change the hash + d[0] = 0; + CHECK_NE(d.hash(), original_hash); + d.erase(0); + CHECK_EQ(d.hash(), original_hash); + + // Modify nested item change the hash + Dictionary(d[1]).operator[](0) = 0; + CHECK_NE(d.hash(), original_hash); + Dictionary(d[1]).erase(0); + Array(d[k2]).push_back(0); + CHECK_NE(d.hash(), original_hash); + Array(d[k2]).pop_back(); + + // Modify a key change the hash + k2[0] = 0; + CHECK_NE(d.hash(), original_hash); + k2.erase(0); + CHECK_EQ(d.hash(), original_hash); + k3.push_back(0); + CHECK_NE(d.hash(), original_hash); + k3.pop_back(); + CHECK_EQ(d.hash(), original_hash); + + // Duplication doesn't change the hash + Dictionary d2 = d.duplicate(true); + CHECK_EQ(d2.hash(), original_hash); +} + +TEST_CASE("[Dictionary] Hash recursive dictionary") { + Dictionary d; + d[1] = d; + + // Hash should reach recursion limit, we just make sure this doesn't blow up + ERR_PRINT_OFF; + d.hash(); + ERR_PRINT_ON; + + // Break the recursivity otherwise Dictionary teardown will leak memory + d.clear(); +} + +#if 0 // TODO: recursion in dict key is currently buggy +TEST_CASE("[Dictionary] Hash recursive dictionary on keys") { + Dictionary d; + d[d] = 1; + + // Hash should reach recursion limit, we just make sure this doesn't blow up + ERR_PRINT_OFF; + d.hash(); + ERR_PRINT_ON; + + // Break the recursivity otherwise Dictionary teardown will leak memory + d.clear(); +} +#endif + +TEST_CASE("[Dictionary] Empty comparison") { + Dictionary d1; + Dictionary d2; + + // test both operator== and operator!= + CHECK_EQ(d1, d2); + CHECK_FALSE(d1 != d2); +} + +TEST_CASE("[Dictionary] Flat comparison") { + Dictionary d1 = build_dictionary(1, 1); + Dictionary d2 = build_dictionary(1, 1); + Dictionary other_d = build_dictionary(2, 1); + + // test both operator== and operator!= + CHECK_EQ(d1, d1); // compare self + CHECK_FALSE(d1 != d1); + CHECK_EQ(d1, d2); // different equivalent arrays + CHECK_FALSE(d1 != d2); + CHECK_NE(d1, other_d); // different arrays with different content + CHECK_FALSE(d1 == other_d); +} + +TEST_CASE("[Dictionary] Nested dictionary comparison") { + // d1 = {1: {2: {3: 4}}} + Dictionary d1 = build_dictionary(1, build_dictionary(2, build_dictionary(3, 4))); + + Dictionary d2 = d1.duplicate(true); + + // other_d = {1: {2: {3: 0}}} + Dictionary other_d = build_dictionary(1, build_dictionary(2, build_dictionary(3, 0))); + + // test both operator== and operator!= + CHECK_EQ(d1, d1); // compare self + CHECK_FALSE(d1 != d1); + CHECK_EQ(d1, d2); // different equivalent arrays + CHECK_FALSE(d1 != d2); + CHECK_NE(d1, other_d); // different arrays with different content + CHECK_FALSE(d1 == other_d); +} + +TEST_CASE("[Dictionary] Nested array comparison") { + // d1 = {1: [2, 3]} + Dictionary d1 = build_dictionary(1, build_array(2, 3)); + + Dictionary d2 = d1.duplicate(true); + + // other_d = {1: [2, 0]} + Dictionary other_d = build_dictionary(1, build_array(2, 0)); + + // test both operator== and operator!= + CHECK_EQ(d1, d1); // compare self + CHECK_FALSE(d1 != d1); + CHECK_EQ(d1, d2); // different equivalent arrays + CHECK_FALSE(d1 != d2); + CHECK_NE(d1, other_d); // different arrays with different content + CHECK_FALSE(d1 == other_d); +} + +TEST_CASE("[Dictionary] Recursive comparison") { + Dictionary d1; + d1[1] = d1; + + Dictionary d2; + d2[1] = d2; + + // Comparison should reach recursion limit + ERR_PRINT_OFF; + CHECK_EQ(d1, d2); + CHECK_FALSE(d1 != d2); + ERR_PRINT_ON; + + d1[2] = 2; + d2[2] = 2; + + // Comparison should reach recursion limit + ERR_PRINT_OFF; + CHECK_EQ(d1, d2); + CHECK_FALSE(d1 != d2); + ERR_PRINT_ON; + + d1[3] = 3; + d2[3] = 0; + + // Comparison should reach recursion limit + ERR_PRINT_OFF; + CHECK_NE(d1, d2); + CHECK_FALSE(d1 == d2); + ERR_PRINT_ON; + + // Break the recursivity otherwise Dictionary teardown will leak memory + d1.clear(); + d2.clear(); +} + +#if 0 // TODO: recursion in dict key is currently buggy +TEST_CASE("[Dictionary] Recursive comparison on keys") { + Dictionary d1; + // Hash computation should reach recursion limit + ERR_PRINT_OFF; + d1[d1] = 1; + ERR_PRINT_ON; + + Dictionary d2; + // Hash computation should reach recursion limit + ERR_PRINT_OFF; + d2[d2] = 1; + ERR_PRINT_ON; + + // Comparison should reach recursion limit + ERR_PRINT_OFF; + CHECK_EQ(d1, d2); + CHECK_FALSE(d1 != d2); + ERR_PRINT_ON; + + d1[2] = 2; + d2[2] = 2; + + // Comparison should reach recursion limit + ERR_PRINT_OFF; + CHECK_EQ(d1, d2); + CHECK_FALSE(d1 != d2); + ERR_PRINT_ON; + + d1[3] = 3; + d2[3] = 0; + + // Comparison should reach recursion limit + ERR_PRINT_OFF; + CHECK_NE(d1, d2); + CHECK_FALSE(d1 == d2); + ERR_PRINT_ON; + + // Break the recursivity otherwise Dictionary teardown will leak memory + d1.clear(); + d2.clear(); +} +#endif + +TEST_CASE("[Dictionary] Recursive self comparison") { + Dictionary d1; + Dictionary d2; + d1[1] = d2; + d2[1] = d1; + + CHECK_EQ(d1, d1); + CHECK_FALSE(d1 != d1); + + // Break the recursivity otherwise Dictionary teardown will leak memory + d1.clear(); + d2.clear(); +} + +} // namespace TestDictionary + +#endif // TEST_DICTIONARY_H diff --git a/tests/test_variant.h b/tests/core/variant/test_variant.h index dfc72b512c..916686d7c1 100644 --- a/tests/test_variant.h +++ b/tests/core/variant/test_variant.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -38,8 +38,27 @@ namespace TestVariant { +static inline Array build_array() { + return Array(); +} +template <typename... Targs> +static inline Array build_array(Variant item, Targs... Fargs) { + Array a = build_array(Fargs...); + a.push_front(item); + return a; +} +static inline Dictionary build_dictionary() { + return Dictionary(); +} +template <typename... Targs> +static inline Dictionary build_dictionary(Variant key, Variant item, Targs... Fargs) { + Dictionary d = build_dictionary(Fargs...); + d[key] = item; + return d; +} + 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 +95,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") { @@ -699,6 +719,198 @@ TEST_CASE("[Variant] Assignment To Color from Bool,Int,Float,String,Vec2,Vec2i,V vec3i_v = col_v; CHECK(vec3i_v.get_type() == Variant::COLOR); } +TEST_CASE("[Variant] Writer and parser array") { + Array a = build_array(1, String("hello"), build_array(Variant())); + String a_str; + VariantWriter::write_to_string(a, a_str); + + CHECK_EQ(a_str, "[1, \"hello\", [null]]"); + + VariantParser::StreamString ss; + String errs; + int line; + Variant a_parsed; + + ss.s = a_str; + VariantParser::parse(&ss, a_parsed, errs, line); + + CHECK_MESSAGE(a_parsed == Variant(a), "Should parse back."); +} + +TEST_CASE("[Variant] Writer recursive array") { + // There is no way to accurately represent a recursive array, + // the only thing we can do is make sure the writer doesn't blow up + + // Self recursive + Array a; + a.push_back(a); + + // Writer should it recursion limit while visiting the array + ERR_PRINT_OFF; + String a_str; + VariantWriter::write_to_string(a, a_str); + ERR_PRINT_ON; + + // Nested recursive + Array a1; + Array a2; + a1.push_back(a2); + a2.push_back(a1); + + // Writer should it recursion limit while visiting the array + ERR_PRINT_OFF; + String a1_str; + VariantWriter::write_to_string(a1, a1_str); + ERR_PRINT_ON; + + // Break the recursivity otherwise Dictionary tearndown will leak memory + a.clear(); + a1.clear(); + a2.clear(); +} + +TEST_CASE("[Variant] Writer and parser dictionary") { + // d = {{1: 2}: 3, 4: "hello", 5: {null: []}} + Dictionary d = build_dictionary(build_dictionary(1, 2), 3, 4, String("hello"), 5, build_dictionary(Variant(), build_array())); + String d_str; + VariantWriter::write_to_string(d, d_str); + + CHECK_EQ(d_str, "{\n4: \"hello\",\n5: {\nnull: []\n},\n{\n1: 2\n}: 3\n}"); + + VariantParser::StreamString ss; + String errs; + int line; + Variant d_parsed; + + ss.s = d_str; + VariantParser::parse(&ss, d_parsed, errs, line); + + CHECK_MESSAGE(d_parsed == Variant(d), "Should parse back."); +} + +TEST_CASE("[Variant] Writer recursive dictionary") { + // There is no way to accurately represent a recursive dictionary, + // the only thing we can do is make sure the writer doesn't blow up + + // Self recursive + Dictionary d; + d[1] = d; + + // Writer should it recursion limit while visiting the dictionary + ERR_PRINT_OFF; + String d_str; + VariantWriter::write_to_string(d, d_str); + ERR_PRINT_ON; + + // Nested recursive + Dictionary d1; + Dictionary d2; + d1[2] = d2; + d2[1] = d1; + + // Writer should it recursion limit while visiting the dictionary + ERR_PRINT_OFF; + String d1_str; + VariantWriter::write_to_string(d1, d1_str); + ERR_PRINT_ON; + + // Break the recursivity otherwise Dictionary tearndown will leak memory + d.clear(); + d1.clear(); + d2.clear(); +} + +#if 0 // TODO: recursion in dict key is currently buggy +TEST_CASE("[Variant] Writer recursive dictionary on keys") { + // There is no way to accurately represent a recursive dictionary, + // the only thing we can do is make sure the writer doesn't blow up + + // Self recursive + Dictionary d; + d[d] = 1; + + // Writer should it recursion limit while visiting the dictionary + ERR_PRINT_OFF; + String d_str; + VariantWriter::write_to_string(d, d_str); + ERR_PRINT_ON; + + // Nested recursive + Dictionary d1; + Dictionary d2; + d1[d2] = 2; + d2[d1] = 1; + + // Writer should it recursion limit while visiting the dictionary + ERR_PRINT_OFF; + String d1_str; + VariantWriter::write_to_string(d1, d1_str); + ERR_PRINT_ON; + + // Break the recursivity otherwise Dictionary tearndown will leak memory + d.clear(); + d1.clear(); + d2.clear(); +} +#endif + +TEST_CASE("[Variant] Basic comparison") { + CHECK_EQ(Variant(1), Variant(1)); + CHECK_FALSE(Variant(1) != Variant(1)); + CHECK_NE(Variant(1), Variant(2)); + CHECK_EQ(Variant(String("foo")), Variant(String("foo"))); + CHECK_NE(Variant(String("foo")), Variant(String("bar"))); + // Check "empty" version of different types are not equivalents + CHECK_NE(Variant(0), Variant()); + CHECK_NE(Variant(String()), Variant()); + CHECK_NE(Variant(Array()), Variant()); + CHECK_NE(Variant(Dictionary()), Variant()); +} + +TEST_CASE("[Variant] Nested array comparison") { + Array a1 = build_array(1, build_array(2, 3)); + Array a2 = build_array(1, build_array(2, 3)); + Array a_other = build_array(1, build_array(2, 4)); + Variant v_a1 = a1; + Variant v_a1_ref2 = a1; + Variant v_a2 = a2; + Variant v_a_other = a_other; + + // test both operator== and operator!= + CHECK_EQ(v_a1, v_a1); + CHECK_FALSE(v_a1 != v_a1); + CHECK_EQ(v_a1, v_a1_ref2); + CHECK_FALSE(v_a1 != v_a1_ref2); + CHECK_EQ(v_a1, v_a2); + CHECK_FALSE(v_a1 != v_a2); + CHECK_NE(v_a1, v_a_other); + CHECK_FALSE(v_a1 == v_a_other); +} + +TEST_CASE("[Variant] Nested dictionary comparison") { + Dictionary d1 = build_dictionary(build_dictionary(1, 2), build_dictionary(3, 4)); + Dictionary d2 = build_dictionary(build_dictionary(1, 2), build_dictionary(3, 4)); + Dictionary d_other_key = build_dictionary(build_dictionary(1, 0), build_dictionary(3, 4)); + Dictionary d_other_val = build_dictionary(build_dictionary(1, 2), build_dictionary(3, 0)); + Variant v_d1 = d1; + Variant v_d1_ref2 = d1; + Variant v_d2 = d2; + Variant v_d_other_key = d_other_key; + Variant v_d_other_val = d_other_val; + + // test both operator== and operator!= + CHECK_EQ(v_d1, v_d1); + CHECK_FALSE(v_d1 != v_d1); + CHECK_EQ(v_d1, v_d1_ref2); + CHECK_FALSE(v_d1 != v_d1_ref2); + CHECK_EQ(v_d1, v_d2); + CHECK_FALSE(v_d1 != v_d2); + CHECK_NE(v_d1, v_d_other_key); + CHECK_FALSE(v_d1 == v_d_other_key); + CHECK_NE(v_d1, v_d_other_val); + CHECK_FALSE(v_d1 == v_d_other_val); +} + } // namespace TestVariant #endif // TEST_VARIANT_H 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/scene/test_animation.h b/tests/scene/test_animation.h new file mode 100644 index 0000000000..9199713fd9 --- /dev/null +++ b/tests/scene/test_animation.h @@ -0,0 +1,314 @@ +/*************************************************************************/ +/* test_animation.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_ANIMATION_H +#define TEST_ANIMATION_H + +#include "scene/resources/animation.h" + +#include "tests/test_macros.h" + +namespace TestAnimation { + +TEST_CASE("[Animation] Empty animation getters") { + const Ref<Animation> animation = memnew(Animation); + + CHECK(Math::is_equal_approx(animation->get_length(), real_t(1.0))); + CHECK(Math::is_equal_approx(animation->get_step(), real_t(0.1))); +} + +TEST_CASE("[Animation] Create value track") { + // This creates an animation that makes the node "Enemy" move to the right by + // 100 pixels in 0.5 seconds. + Ref<Animation> animation = memnew(Animation); + const int track_index = animation->add_track(Animation::TYPE_VALUE); + CHECK(track_index == 0); + animation->track_set_path(track_index, NodePath("Enemy:position:x")); + animation->track_insert_key(track_index, 0.0, 0); + animation->track_insert_key(track_index, 0.5, 100); + + CHECK(animation->get_track_count() == 1); + CHECK(!animation->track_is_compressed(0)); + CHECK(int(animation->track_get_key_value(0, 0)) == 0); + CHECK(int(animation->track_get_key_value(0, 1)) == 100); + + CHECK(Math::is_equal_approx(animation->value_track_interpolate(0, -0.2), 0.0)); + CHECK(Math::is_equal_approx(animation->value_track_interpolate(0, 0.0), 0.0)); + CHECK(Math::is_equal_approx(animation->value_track_interpolate(0, 0.2), 40.0)); + CHECK(Math::is_equal_approx(animation->value_track_interpolate(0, 0.4), 80.0)); + CHECK(Math::is_equal_approx(animation->value_track_interpolate(0, 0.5), 100.0)); + CHECK(Math::is_equal_approx(animation->value_track_interpolate(0, 0.6), 100.0)); + + CHECK(Math::is_equal_approx(animation->track_get_key_transition(0, 0), real_t(1.0))); + CHECK(Math::is_equal_approx(animation->track_get_key_transition(0, 1), real_t(1.0))); + + ERR_PRINT_OFF; + // Nonexistent keys. + CHECK(animation->track_get_key_value(0, 2).is_null()); + CHECK(animation->track_get_key_value(0, -1).is_null()); + CHECK(Math::is_equal_approx(animation->track_get_key_transition(0, 2), real_t(-1.0))); + // Nonexistent track (and keys). + CHECK(animation->track_get_key_value(1, 0).is_null()); + CHECK(animation->track_get_key_value(1, 1).is_null()); + CHECK(animation->track_get_key_value(1, 2).is_null()); + CHECK(animation->track_get_key_value(1, -1).is_null()); + CHECK(Math::is_equal_approx(animation->track_get_key_transition(1, 0), real_t(-1.0))); + + // This is a value track, so the methods below should return errors. + CHECK(animation->position_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER); + CHECK(animation->rotation_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER); + CHECK(animation->scale_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER); + CHECK(Math::is_zero_approx(animation->bezier_track_interpolate(0, 0.0))); + CHECK(animation->blend_shape_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER); + ERR_PRINT_ON; +} + +TEST_CASE("[Animation] Create 3D position track") { + Ref<Animation> animation = memnew(Animation); + const int track_index = animation->add_track(Animation::TYPE_POSITION_3D); + animation->track_set_path(track_index, NodePath("Enemy:position")); + animation->position_track_insert_key(track_index, 0.0, Vector3(0, 1, 2)); + animation->position_track_insert_key(track_index, 0.5, Vector3(3.5, 4, 5)); + + CHECK(animation->get_track_count() == 1); + CHECK(!animation->track_is_compressed(0)); + CHECK(Vector3(animation->track_get_key_value(0, 0)).is_equal_approx(Vector3(0, 1, 2))); + CHECK(Vector3(animation->track_get_key_value(0, 1)).is_equal_approx(Vector3(3.5, 4, 5))); + + Vector3 r_interpolation; + + CHECK(animation->position_track_interpolate(0, -0.2, &r_interpolation) == OK); + CHECK(r_interpolation.is_equal_approx(Vector3(0, 1, 2))); + + CHECK(animation->position_track_interpolate(0, 0.0, &r_interpolation) == OK); + CHECK(r_interpolation.is_equal_approx(Vector3(0, 1, 2))); + + CHECK(animation->position_track_interpolate(0, 0.2, &r_interpolation) == OK); + CHECK(r_interpolation.is_equal_approx(Vector3(1.4, 2.2, 3.2))); + + CHECK(animation->position_track_interpolate(0, 0.4, &r_interpolation) == OK); + CHECK(r_interpolation.is_equal_approx(Vector3(2.8, 3.4, 4.4))); + + CHECK(animation->position_track_interpolate(0, 0.5, &r_interpolation) == OK); + CHECK(r_interpolation.is_equal_approx(Vector3(3.5, 4, 5))); + + CHECK(animation->position_track_interpolate(0, 0.6, &r_interpolation) == OK); + CHECK(r_interpolation.is_equal_approx(Vector3(3.5, 4, 5))); + + // 3D position tracks always use linear interpolation for performance reasons. + CHECK(Math::is_equal_approx(animation->track_get_key_transition(0, 0), real_t(1.0))); + CHECK(Math::is_equal_approx(animation->track_get_key_transition(0, 1), real_t(1.0))); + + // This is a 3D position track, so the methods below should return errors. + ERR_PRINT_OFF; + CHECK(animation->value_track_interpolate(0, 0.0).is_null()); + CHECK(animation->rotation_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER); + CHECK(animation->scale_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER); + CHECK(Math::is_zero_approx(animation->bezier_track_interpolate(0, 0.0))); + CHECK(animation->blend_shape_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER); + ERR_PRINT_ON; +} + +TEST_CASE("[Animation] Create 3D rotation track") { + Ref<Animation> animation = memnew(Animation); + const int track_index = animation->add_track(Animation::TYPE_ROTATION_3D); + animation->track_set_path(track_index, NodePath("Enemy:rotation")); + animation->rotation_track_insert_key(track_index, 0.0, Quaternion(Vector3(0, 1, 2))); + animation->rotation_track_insert_key(track_index, 0.5, Quaternion(Vector3(3.5, 4, 5))); + + CHECK(animation->get_track_count() == 1); + CHECK(!animation->track_is_compressed(0)); + CHECK(Quaternion(animation->track_get_key_value(0, 0)).is_equal_approx(Quaternion(Vector3(0, 1, 2)))); + CHECK(Quaternion(animation->track_get_key_value(0, 1)).is_equal_approx(Quaternion(Vector3(3.5, 4, 5)))); + + Quaternion r_interpolation; + + CHECK(animation->rotation_track_interpolate(0, -0.2, &r_interpolation) == OK); + CHECK(r_interpolation.is_equal_approx(Quaternion(0.403423, 0.259035, 0.73846, 0.47416))); + + CHECK(animation->rotation_track_interpolate(0, 0.0, &r_interpolation) == OK); + CHECK(r_interpolation.is_equal_approx(Quaternion(0.403423, 0.259035, 0.73846, 0.47416))); + + CHECK(animation->rotation_track_interpolate(0, 0.2, &r_interpolation) == OK); + CHECK(r_interpolation.is_equal_approx(Quaternion(0.336182, 0.30704, 0.751515, 0.477425))); + + CHECK(animation->rotation_track_interpolate(0, 0.4, &r_interpolation) == OK); + CHECK(r_interpolation.is_equal_approx(Quaternion(0.266585, 0.352893, 0.759303, 0.477344))); + + CHECK(animation->rotation_track_interpolate(0, 0.5, &r_interpolation) == OK); + CHECK(r_interpolation.is_equal_approx(Quaternion(0.231055, 0.374912, 0.761204, 0.476048))); + + CHECK(animation->rotation_track_interpolate(0, 0.6, &r_interpolation) == OK); + CHECK(r_interpolation.is_equal_approx(Quaternion(0.231055, 0.374912, 0.761204, 0.476048))); + + // 3D rotation tracks always use linear interpolation for performance reasons. + CHECK(Math::is_equal_approx(animation->track_get_key_transition(0, 0), real_t(1.0))); + CHECK(Math::is_equal_approx(animation->track_get_key_transition(0, 1), real_t(1.0))); + + // This is a 3D rotation track, so the methods below should return errors. + ERR_PRINT_OFF; + CHECK(animation->value_track_interpolate(0, 0.0).is_null()); + CHECK(animation->position_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER); + CHECK(animation->scale_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER); + CHECK(Math::is_zero_approx(animation->bezier_track_interpolate(0, 0.0))); + CHECK(animation->blend_shape_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER); + ERR_PRINT_ON; +} + +TEST_CASE("[Animation] Create 3D scale track") { + Ref<Animation> animation = memnew(Animation); + const int track_index = animation->add_track(Animation::TYPE_SCALE_3D); + animation->track_set_path(track_index, NodePath("Enemy:scale")); + animation->scale_track_insert_key(track_index, 0.0, Vector3(0, 1, 2)); + animation->scale_track_insert_key(track_index, 0.5, Vector3(3.5, 4, 5)); + + CHECK(animation->get_track_count() == 1); + CHECK(!animation->track_is_compressed(0)); + CHECK(Vector3(animation->track_get_key_value(0, 0)).is_equal_approx(Vector3(0, 1, 2))); + CHECK(Vector3(animation->track_get_key_value(0, 1)).is_equal_approx(Vector3(3.5, 4, 5))); + + Vector3 r_interpolation; + + CHECK(animation->scale_track_interpolate(0, -0.2, &r_interpolation) == OK); + CHECK(r_interpolation.is_equal_approx(Vector3(0, 1, 2))); + + CHECK(animation->scale_track_interpolate(0, 0.0, &r_interpolation) == OK); + CHECK(r_interpolation.is_equal_approx(Vector3(0, 1, 2))); + + CHECK(animation->scale_track_interpolate(0, 0.2, &r_interpolation) == OK); + CHECK(r_interpolation.is_equal_approx(Vector3(1.4, 2.2, 3.2))); + + CHECK(animation->scale_track_interpolate(0, 0.4, &r_interpolation) == OK); + CHECK(r_interpolation.is_equal_approx(Vector3(2.8, 3.4, 4.4))); + + CHECK(animation->scale_track_interpolate(0, 0.5, &r_interpolation) == OK); + CHECK(r_interpolation.is_equal_approx(Vector3(3.5, 4, 5))); + + CHECK(animation->scale_track_interpolate(0, 0.6, &r_interpolation) == OK); + CHECK(r_interpolation.is_equal_approx(Vector3(3.5, 4, 5))); + + // 3D scale tracks always use linear interpolation for performance reasons. + CHECK(Math::is_equal_approx(animation->track_get_key_transition(0, 0), real_t(1.0))); + CHECK(Math::is_equal_approx(animation->track_get_key_transition(0, 1), real_t(1.0))); + + // This is a 3D scale track, so the methods below should return errors. + ERR_PRINT_OFF; + CHECK(animation->value_track_interpolate(0, 0.0).is_null()); + CHECK(animation->position_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER); + CHECK(animation->rotation_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER); + CHECK(Math::is_zero_approx(animation->bezier_track_interpolate(0, 0.0))); + CHECK(animation->blend_shape_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER); + ERR_PRINT_ON; +} + +TEST_CASE("[Animation] Create blend shape track") { + Ref<Animation> animation = memnew(Animation); + const int track_index = animation->add_track(Animation::TYPE_BLEND_SHAPE); + animation->track_set_path(track_index, NodePath("Enemy:scale")); + // Negative values for blend shapes should work as expected. + animation->blend_shape_track_insert_key(track_index, 0.0, -1.0); + animation->blend_shape_track_insert_key(track_index, 0.5, 1.0); + + CHECK(animation->get_track_count() == 1); + CHECK(!animation->track_is_compressed(0)); + + float r_blend = 0.0f; + + CHECK(animation->blend_shape_track_get_key(0, 0, &r_blend) == OK); + CHECK(Math::is_equal_approx(r_blend, -1.0f)); + + CHECK(animation->blend_shape_track_get_key(0, 1, &r_blend) == OK); + CHECK(Math::is_equal_approx(r_blend, 1.0f)); + + CHECK(animation->blend_shape_track_interpolate(0, -0.2, &r_blend) == OK); + CHECK(Math::is_equal_approx(r_blend, -1.0f)); + + CHECK(animation->blend_shape_track_interpolate(0, 0.0, &r_blend) == OK); + CHECK(Math::is_equal_approx(r_blend, -1.0f)); + + CHECK(animation->blend_shape_track_interpolate(0, 0.2, &r_blend) == OK); + CHECK(Math::is_equal_approx(r_blend, -0.2f)); + + CHECK(animation->blend_shape_track_interpolate(0, 0.4, &r_blend) == OK); + CHECK(Math::is_equal_approx(r_blend, 0.6f)); + + CHECK(animation->blend_shape_track_interpolate(0, 0.5, &r_blend) == OK); + CHECK(Math::is_equal_approx(r_blend, 1.0f)); + + CHECK(animation->blend_shape_track_interpolate(0, 0.6, &r_blend) == OK); + CHECK(Math::is_equal_approx(r_blend, 1.0f)); + + // Blend shape tracks always use linear interpolation for performance reasons. + CHECK(Math::is_equal_approx(animation->track_get_key_transition(0, 0), real_t(1.0))); + CHECK(Math::is_equal_approx(animation->track_get_key_transition(0, 1), real_t(1.0))); + + // This is a blend shape track, so the methods below should return errors. + ERR_PRINT_OFF; + CHECK(animation->value_track_interpolate(0, 0.0).is_null()); + CHECK(animation->position_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER); + CHECK(animation->rotation_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER); + CHECK(animation->scale_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER); + CHECK(Math::is_zero_approx(animation->bezier_track_interpolate(0, 0.0))); + ERR_PRINT_ON; +} + +TEST_CASE("[Animation] Create Bezier track") { + Ref<Animation> animation = memnew(Animation); + const int track_index = animation->add_track(Animation::TYPE_BEZIER); + animation->track_set_path(track_index, NodePath("Enemy:scale")); + animation->bezier_track_insert_key(track_index, 0.0, -1.0, Vector2(-1, -1), Vector2(1, 1)); + animation->bezier_track_insert_key(track_index, 0.5, 1.0, Vector2(0, 1), Vector2(1, 0.5)); + + CHECK(animation->get_track_count() == 1); + CHECK(!animation->track_is_compressed(0)); + + CHECK(Math::is_equal_approx(animation->bezier_track_get_key_value(0, 0), real_t(-1.0))); + CHECK(Math::is_equal_approx(animation->bezier_track_get_key_value(0, 1), real_t(1.0))); + + CHECK(Math::is_equal_approx(animation->bezier_track_interpolate(0, -0.2), real_t(-1.0))); + CHECK(Math::is_equal_approx(animation->bezier_track_interpolate(0, 0.0), real_t(-1.0))); + CHECK(Math::is_equal_approx(animation->bezier_track_interpolate(0, 0.2), real_t(-0.76057207584381))); + CHECK(Math::is_equal_approx(animation->bezier_track_interpolate(0, 0.4), real_t(-0.39975279569626))); + CHECK(Math::is_equal_approx(animation->bezier_track_interpolate(0, 0.5), real_t(1.0))); + CHECK(Math::is_equal_approx(animation->bezier_track_interpolate(0, 0.6), real_t(1.0))); + + // This is a bezier track, so the methods below should return errors. + ERR_PRINT_OFF; + CHECK(animation->value_track_interpolate(0, 0.0).is_null()); + CHECK(animation->position_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER); + CHECK(animation->rotation_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER); + CHECK(animation->scale_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER); + CHECK(animation->blend_shape_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER); + ERR_PRINT_ON; +} + +} // namespace TestAnimation + +#endif // TEST_ANIMATION_H diff --git a/tests/scene/test_code_edit.h b/tests/scene/test_code_edit.h new file mode 100644 index 0000000000..8bd35df107 --- /dev/null +++ b/tests/scene/test_code_edit.h @@ -0,0 +1,3348 @@ +/*************************************************************************/ +/* test_code_edit.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "scene/gui/code_edit.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 handled 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 returns 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 handled 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 returns 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 returns 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 returns 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 returns 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); + + // Indent with blank lines. + code_edit->set_text("line1\n\tline2\n\n\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) == 5); + + // 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&\nline3"); + 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) == 2); + + // Multiline comment before last line. + code_edit->set_text("&line1\nline2&\ntest"); + CHECK(code_edit->can_fold_line(0)); + CHECK_FALSE(code_edit->can_fold_line(2)); + 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(code_edit->get_next_visible_line_offset_from(1, 1) == 2); + + // 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); + + // Indent level 0->1, comment after lines + code_edit->set_text("line1\n\tline2\n#test"); + 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) == 2); + + // Indent level 0->1, comment between lines + code_edit->set_text("line1\n#test\n\tline2\nline3"); + CHECK(code_edit->can_fold_line(0)); + CHECK_FALSE(code_edit->can_fold_line(2)); + code_edit->fold_line(2); + CHECK_FALSE(code_edit->is_line_folded(2)); + code_edit->fold_line(0); + CHECK(code_edit->is_line_folded(0)); + CHECK_FALSE(code_edit->is_line_folded(2)); + CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 3); + + // Indent level 1->2, comment after lines + code_edit->set_text("\tline1\n\t\tline2\n#test"); + 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) == 2); + + // Indent level 1->2, comment between lines + code_edit->set_text("\tline1\n#test\n\t\tline2\nline3"); + CHECK(code_edit->can_fold_line(0)); + CHECK_FALSE(code_edit->can_fold_line(2)); + code_edit->fold_line(2); + CHECK_FALSE(code_edit->is_line_folded(2)); + code_edit->fold_line(0); + CHECK(code_edit->is_line_folded(0)); + CHECK_FALSE(code_edit->is_line_folded(2)); + CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 3); + + // 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) == "'\"'"); + + /* Wrap single line selection with brackets */ + code_edit->clear(); + code_edit->insert_text_at_caret("abc"); + code_edit->select_all(); + SEND_GUI_KEY_EVENT(code_edit, Key::BRACKETLEFT); + CHECK(code_edit->get_line(0) == "[abc]"); + + /* Caret should be after the last character of the single line selection */ + CHECK(code_edit->get_caret_column() == 4); + + /* Wrap multi line selection with brackets */ + code_edit->clear(); + code_edit->insert_text_at_caret("abc\nabc"); + code_edit->select_all(); + SEND_GUI_KEY_EVENT(code_edit, Key::BRACKETLEFT); + CHECK(code_edit->get_text() == "[abc\nabc]"); + + /* Caret should be after the last character of the multi line selection */ + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 3); + + /* If inserted character is not a auto brace completion open key, replace selected text with the inserted character */ + code_edit->clear(); + code_edit->insert_text_at_caret("abc"); + code_edit->select_all(); + SEND_GUI_KEY_EVENT(code_edit, Key::KEY_1); + CHECK(code_edit->get_text() == "1"); + + /* If potential multichar and single brace completion is matched, it should wrap the single. */ + code_edit->clear(); + code_edit->insert_text_at_caret("\'\'abc"); + code_edit->select(0, 2, 0, 5); + SEND_GUI_KEY_EVENT(code_edit, Key::APOSTROPHE); + CHECK(code_edit->get_text() == "\'\'\'abc\'"); + + /* If only the potential multichar brace completion is matched, it does not wrap or complete. */ + auto_brace_completion_pairs.erase("\'"); + code_edit->set_auto_brace_completion_pairs(auto_brace_completion_pairs); + CHECK_FALSE(code_edit->has_auto_brace_completion_open_key("\'")); + + code_edit->clear(); + code_edit->insert_text_at_caret("\'\'abc"); + code_edit->select(0, 2, 0, 5); + SEND_GUI_KEY_EVENT(code_edit, Key::APOSTROPHE); + CHECK(code_edit->get_text() == "\'\'\'"); + } + + 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, "code_completion_requested"); + 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("code_completion_requested"); + code_edit->request_code_completion(true); + SIGNAL_CHECK("code_completion_requested", signal_args); + + /* Manual request should force. */ + SEND_GUI_ACTION(code_edit, "ui_text_completion_query"); + SIGNAL_CHECK("code_completion_requested", 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("code_completion_requested", signal_args); + + /* Should work with space too. */ + code_edit->insert_text_at_caret(" "); + code_edit->request_code_completion(); + SIGNAL_CHECK("code_completion_requested", 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("code_completion_requested", signal_args); + + SIGNAL_UNWATCH(code_edit, "code_completion_requested"); + } + + 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, MouseButton::WHEEL_DOWN, MouseButton::NONE); + CHECK(code_edit->get_code_completion_selected_index() == 1); + + SEND_GUI_MOUSE_EVENT(code_edit, caret_pos, MouseButton::WHEEL_UP, MouseButton::NONE); + CHECK(code_edit->get_code_completion_selected_index() == 0); + + /* Single click selects. */ + SEND_GUI_MOUSE_EVENT(code_edit, caret_pos, MouseButton::LEFT, MouseButton::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 += 58; + SEND_GUI_MOUSE_EVENT(code_edit, caret_pos, MouseButton::NONE, MouseButton::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 caret 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().is_empty()); + + /* 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); +} + +TEST_CASE("[SceneTree][CodeEdit] New Line") { + CodeEdit *code_edit = memnew(CodeEdit); + SceneTree::get_singleton()->get_root()->add_child(code_edit); + + /* Add a new line. */ + code_edit->set_text(""); + code_edit->insert_text_at_caret("test new line"); + code_edit->set_caret_line(0); + code_edit->set_caret_column(13); + SEND_GUI_ACTION(code_edit, "ui_text_newline"); + CHECK(code_edit->get_line(0) == "test new line"); + CHECK(code_edit->get_line(1) == ""); + + /* Split line with new line. */ + code_edit->set_text(""); + code_edit->insert_text_at_caret("test new line"); + code_edit->set_caret_line(0); + 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) == "new line"); + + /* Delete selection and split with new line. */ + code_edit->set_text(""); + code_edit->insert_text_at_caret("test new line"); + code_edit->select(0, 0, 0, 5); + SEND_GUI_ACTION(code_edit, "ui_text_newline"); + CHECK(code_edit->get_line(0) == ""); + CHECK(code_edit->get_line(1) == "new line"); + + /* Blank new line below with selection should not split. */ + code_edit->set_text(""); + code_edit->insert_text_at_caret("test new line"); + code_edit->select(0, 0, 0, 5); + SEND_GUI_ACTION(code_edit, "ui_text_newline_blank"); + CHECK(code_edit->get_line(0) == "test new line"); + CHECK(code_edit->get_line(1) == ""); + + /* Blank new line above with selection should not split. */ + code_edit->set_text(""); + code_edit->insert_text_at_caret("test new line"); + code_edit->select(0, 0, 0, 5); + SEND_GUI_ACTION(code_edit, "ui_text_newline_above"); + CHECK(code_edit->get_line(0) == ""); + CHECK(code_edit->get_line(1) == "test new line"); + + memdelete(code_edit); +} + +} // namespace TestCodeEdit + +#endif // TEST_CODE_EDIT_H diff --git a/tests/test_curve.h b/tests/scene/test_curve.h index 019941a7ce..0370ab15fd 100644 --- a/tests/test_curve.h +++ b/tests/scene/test_curve.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -33,7 +33,7 @@ #include "scene/resources/curve.h" -#include "thirdparty/doctest/doctest.h" +#include "tests/test_macros.h" namespace TestCurve { @@ -80,16 +80,16 @@ TEST_CASE("[Curve] Custom curve with free tangents") { "Custom free curve should contain the expected number of points."); CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate(-0.1), 0), + Math::is_zero_approx(curve->interpolate(-0.1)), "Custom free curve should return the expected value at offset 0.1."); CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate(0.1), 0.352), + Math::is_equal_approx(curve->interpolate(0.1), (real_t)0.352), "Custom free curve should return the expected value at offset 0.1."); CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate(0.4), 0.352), + Math::is_equal_approx(curve->interpolate(0.4), (real_t)0.352), "Custom free curve should return the expected value at offset 0.1."); CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate(0.7), 0.896), + Math::is_equal_approx(curve->interpolate(0.7), (real_t)0.896), "Custom free curve should return the expected value at offset 0.1."); CHECK_MESSAGE( Math::is_equal_approx(curve->interpolate(1), 1), @@ -99,16 +99,16 @@ TEST_CASE("[Curve] Custom curve with free tangents") { "Custom free curve should return the expected value at offset 0.1."); CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate_baked(-0.1), 0), + Math::is_zero_approx(curve->interpolate_baked(-0.1)), "Custom free curve should return the expected baked value at offset 0.1."); CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate_baked(0.1), 0.352), + Math::is_equal_approx(curve->interpolate_baked(0.1), (real_t)0.352), "Custom free curve should return the expected baked value at offset 0.1."); CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate_baked(0.4), 0.352), + Math::is_equal_approx(curve->interpolate_baked(0.4), (real_t)0.352), "Custom free curve should return the expected baked value at offset 0.1."); CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate_baked(0.7), 0.896), + Math::is_equal_approx(curve->interpolate_baked(0.7), (real_t)0.896), "Custom free curve should return the expected baked value at offset 0.1."); CHECK_MESSAGE( Math::is_equal_approx(curve->interpolate_baked(1), 1), @@ -169,16 +169,16 @@ TEST_CASE("[Curve] Custom curve with linear tangents") { "Custom linear curve should contain the expected number of points."); CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate(-0.1), 0), + Math::is_zero_approx(curve->interpolate(-0.1)), "Custom linear curve should return the expected value at offset -0.1."); CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate(0.1), 0.4), + Math::is_equal_approx(curve->interpolate(0.1), (real_t)0.4), "Custom linear curve should return the expected value at offset 0.1."); CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate(0.4), 0.4), + Math::is_equal_approx(curve->interpolate(0.4), (real_t)0.4), "Custom linear curve should return the expected value at offset 0.4."); CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate(0.7), 0.8), + Math::is_equal_approx(curve->interpolate(0.7), (real_t)0.8), "Custom linear curve should return the expected value at offset 0.7."); CHECK_MESSAGE( Math::is_equal_approx(curve->interpolate(1), 1), @@ -188,16 +188,16 @@ TEST_CASE("[Curve] Custom curve with linear tangents") { "Custom linear curve should return the expected value at offset 2.0."); CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate_baked(-0.1), 0), + Math::is_zero_approx(curve->interpolate_baked(-0.1)), "Custom linear curve should return the expected baked value at offset -0.1."); CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate_baked(0.1), 0.4), + Math::is_equal_approx(curve->interpolate_baked(0.1), (real_t)0.4), "Custom linear curve should return the expected baked value at offset 0.1."); CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate_baked(0.4), 0.4), + Math::is_equal_approx(curve->interpolate_baked(0.4), (real_t)0.4), "Custom linear curve should return the expected baked value at offset 0.4."); CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate_baked(0.7), 0.8), + Math::is_equal_approx(curve->interpolate_baked(0.7), (real_t)0.8), "Custom linear curve should return the expected baked value at offset 0.7."); CHECK_MESSAGE( Math::is_equal_approx(curve->interpolate_baked(1), 1), @@ -210,12 +210,45 @@ TEST_CASE("[Curve] Custom curve with linear tangents") { curve->remove_point(10); ERR_PRINT_ON; CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate(0.7), 0.8), + Math::is_equal_approx(curve->interpolate(0.7), (real_t)0.8), "Custom free curve should return the expected value at offset 0.7 after removing point at invalid index 10."); CHECK_MESSAGE( - Math::is_equal_approx(curve->interpolate_baked(0.7), 0.8), + 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); + real_t len = 2048.0; + + curve->add_point(Vector2(0, 0)); + curve->add_point(Vector2(len, 0)); + + real_t baked_length = curve->get_baked_length(); + CHECK(len == baked_length); + + for (int i = 0; i < len; i++) { + Vector2 pos = curve->interpolate_baked(i); + CHECK_MESSAGE(pos.x == i, "interpolate_baked should return exact value"); + } +} + +TEST_CASE("[Curve3D] Linear sampling should return exact value") { + Ref<Curve3D> curve = memnew(Curve3D); + real_t len = 2048.0; + + curve->add_point(Vector3(0, 0, 0)); + curve->add_point(Vector3(len, 0, 0)); + + real_t baked_length = curve->get_baked_length(); + CHECK(len == baked_length); + + for (int i = 0; i < len; i++) { + Vector3 pos = curve->interpolate_baked(i); + CHECK_MESSAGE(pos.x == i, "interpolate_baked should return exact value"); + } +} + } // namespace TestCurve #endif // TEST_CURVE_H diff --git a/tests/test_gradient.h b/tests/scene/test_gradient.h index 8eaa6b2b64..b0e6128932 100644 --- a/tests/test_gradient.h +++ b/tests/scene/test_gradient.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -31,8 +31,6 @@ #ifndef TEST_GRADIENT_H #define TEST_GRADIENT_H -#include "core/math/color.h" -#include "core/object/class_db.h" #include "scene/resources/gradient.h" #include "thirdparty/doctest/doctest.h" diff --git a/tests/test_gui.cpp b/tests/scene/test_gui.cpp index b83bd10af4..cd5624b70c 100644 --- a/tests/test_gui.cpp +++ b/tests/scene/test_gui.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -32,29 +32,18 @@ #include "test_gui.h" -#include "core/io/image_loader.h" -#include "core/os/os.h" -#include "core/string/print_string.h" -#include "scene/2d/sprite_2d.h" #include "scene/gui/button.h" -#include "scene/gui/control.h" #include "scene/gui/label.h" #include "scene/gui/line_edit.h" #include "scene/gui/menu_button.h" #include "scene/gui/option_button.h" #include "scene/gui/panel.h" -#include "scene/gui/popup_menu.h" #include "scene/gui/progress_bar.h" #include "scene/gui/rich_text_label.h" #include "scene/gui/scroll_bar.h" #include "scene/gui/spin_box.h" #include "scene/gui/tab_container.h" -#include "scene/gui/texture_rect.h" #include "scene/gui/tree.h" -#include "scene/main/scene_tree.h" - -#include "scene/3d/camera_3d.h" -#include "scene/main/window.h" namespace TestGUI { @@ -80,7 +69,7 @@ public: label->set_position(Point2(80, 90)); label->set_size(Point2(170, 80)); - label->set_align(Label::ALIGN_FILL); + label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_FILL); label->set_text("There was once upon a time a beautiful unicorn that loved to play with little girls..."); frame->add_child(label); @@ -217,7 +206,7 @@ public: richtext->add_text("faeries.\n"); richtext->pop(); richtext->add_text("In this new episode, we will attempt to "); - richtext->push_font(richtext->get_theme_font("mono_font", "Fonts")); + richtext->push_font(richtext->get_theme_font(SNAME("mono_font"), SNAME("Fonts"))); richtext->push_color(Color(0.7, 0.5, 1.0)); richtext->add_text("deliver something nice"); richtext->pop(); @@ -267,4 +256,4 @@ MainLoop *test() { } } // namespace TestGUI -#endif +#endif // _3D_DISABLED diff --git a/tests/test_gui.h b/tests/scene/test_gui.h index e5c40de7e8..a1807ed15c 100644 --- a/tests/test_gui.h +++ b/tests/scene/test_gui.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -31,7 +31,7 @@ #ifndef TEST_GUI_H #define TEST_GUI_H -#include "core/os/main_loop.h" +class MainLoop; namespace TestGUI { diff --git a/tests/test_path_3d.h b/tests/scene/test_path_3d.h index 9961ae6e97..78f4e97f03 100644 --- a/tests/test_path_3d.h +++ b/tests/scene/test_path_3d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -32,7 +32,6 @@ #define TEST_PATH_3D_H #include "scene/3d/path_3d.h" -#include "scene/resources/curve.h" #include "tests/test_macros.h" diff --git a/tests/test_path_follow_2d.h b/tests/scene/test_path_follow_2d.h index 388b690060..abd12fe862 100644 --- a/tests/test_path_follow_2d.h +++ b/tests/scene/test_path_follow_2d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -32,7 +32,6 @@ #define TEST_PATH_FOLLOW_2D_H #include "scene/2d/path_2d.h" -#include "scene/resources/curve.h" #include "tests/test_macros.h" diff --git a/tests/test_path_follow_3d.h b/tests/scene/test_path_follow_3d.h index b6b4c88222..9ffe49e3d6 100644 --- a/tests/test_path_follow_3d.h +++ b/tests/scene/test_path_follow_3d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -32,7 +32,6 @@ #define TEST_PATH_FOLLOW_3D_H #include "scene/3d/path_3d.h" -#include "scene/resources/curve.h" #include "tests/test_macros.h" diff --git a/tests/test_physics_2d.cpp b/tests/servers/test_physics_2d.cpp index 047697e314..138412ec09 100644 --- a/tests/test_physics_2d.cpp +++ b/tests/servers/test_physics_2d.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -31,11 +31,6 @@ #include "test_physics_2d.h" #include "core/os/main_loop.h" -#include "core/os/os.h" -#include "core/string/print_string.h" -#include "core/templates/map.h" -#include "scene/resources/texture.h" -#include "servers/display_server.h" #include "servers/physics_server_2d.h" #include "servers/rendering_server.h" @@ -89,6 +84,7 @@ class TestPhysics2DMainLoop : public MainLoop { body_shape_data[PhysicsServer2D::SHAPE_SEGMENT].shape = segment_shape; } + // CIRCLE { @@ -187,10 +183,7 @@ class TestPhysics2DMainLoop : public MainLoop { } void _do_ray_query() { - /* - PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); - ps->query_intersection_segment(ray_query,ray_from,ray_to); - */ + // FIXME: Do something? } protected: @@ -199,12 +192,12 @@ 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) { + if (mb->get_button_index() == MouseButton::LEFT) { ray_to = p; _do_ray_query(); - } else if (mb->get_button_index() == 2) { + } else if (mb->get_button_index() == MouseButton::RIGHT) { ray_from = p; _do_ray_query(); } @@ -216,10 +209,10 @@ protected: if (mm.is_valid()) { Point2 p = mm->get_position(); - if (mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) { + if ((mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE) { ray_to = p; _do_ray_query(); - } else if (mm->get_button_mask() & MOUSE_BUTTON_MASK_RIGHT) { + } else if ((mm->get_button_mask() & MouseButton::MASK_RIGHT) != MouseButton::NONE) { ray_from = p; _do_ray_query(); } @@ -236,34 +229,31 @@ protected: ps->body_set_continuous_collision_detection_mode(body, PhysicsServer2D::CCD_MODE_CAST_SHAPE); ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, p_xform); - //print_line("add body with xform: "+p_xform); RID sprite = vs->canvas_item_create(); vs->canvas_item_set_parent(sprite, canvas); vs->canvas_item_set_transform(sprite, p_xform); - Size2 imgsize(5, 5); //vs->texture_get_width(body_shape_data[p_shape].image), vs->texture_get_height(body_shape_data[p_shape].image)); + Size2 imgsize(5, 5); vs->canvas_item_add_texture_rect(sprite, Rect2(-imgsize / 2.0, imgsize), body_shape_data[p_shape].image); - ps->body_set_force_integration_callback(body, this, "_body_moved", sprite); - //RID q = ps->query_create(this,"_body_moved",sprite); - //ps->query_body_state(q,body); + ps->body_set_force_integration_callback(body, callable_mp(this, &TestPhysics2DMainLoop::_body_moved), sprite); 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()) { @@ -310,7 +300,6 @@ protected: } static void _bind_methods() { - ClassDB::bind_method(D_METHOD("_body_moved"), &TestPhysics2DMainLoop::_body_moved); ClassDB::bind_method(D_METHOD("_ray_query_callback"), &TestPhysics2DMainLoop::_ray_query_callback); } @@ -323,7 +312,7 @@ public: ps->space_set_active(space, true); ps->set_active(true); ps->area_set_param(space, PhysicsServer2D::AREA_PARAM_GRAVITY_VECTOR, Vector2(0, 1)); - ps->area_set_param(space, PhysicsServer2D::AREA_PARAM_GRAVITY, 98); + ps->area_set_param(space, PhysicsServer2D::AREA_PARAM_GRAVITY, 980); { RID vp = vs->viewport_create(); @@ -334,21 +323,11 @@ public: vs->viewport_set_size(vp, screen_size.x, screen_size.y); vs->viewport_attach_to_screen(vp, Rect2(Vector2(), screen_size)); vs->viewport_set_active(vp, true); - - Transform2D smaller; - //smaller.scale(Vector2(0.6,0.6)); - //smaller.elements[2]=Vector2(100,0); - - //view_xform = smaller; vs->viewport_set_canvas_transform(vp, canvas, view_xform); } ray = vs->canvas_item_create(); vs->canvas_item_set_parent(ray, canvas); - //ray_query = ps->query_create(this,"_ray_query_callback",Variant()); - //ps->query_intersection(ray_query,space); - - _create_body_shape_data(); for (int i = 0; i < 32; i++) { PhysicsServer2D::ShapeType types[4] = { @@ -360,17 +339,9 @@ public: }; PhysicsServer2D::ShapeType type = types[i % 4]; - //type=PhysicsServer2D::SHAPE_SEGMENT; _add_body(type, Transform2D(i * 0.8, Point2(152 + i * 40, 100 - 40 * i))); - /* - if (i==0) - ps->body_set_mode(b,PhysicsServer2D::BODY_MODE_STATIC); - */ } - //RID b= _add_body(PhysicsServer2D::SHAPE_CIRCLE,Transform2D(0,Point2(101,140))); - //ps->body_set_mode(b,PhysicsServer2D::BODY_MODE_STATIC); - Point2 prev; Vector<Point2> parr; @@ -384,12 +355,9 @@ public: } _add_concave(parr); - //_add_plane(Vector2(0.0,-1).normalized(),-300); - //_add_plane(Vector2(1,0).normalized(),50); - //_add_plane(Vector2(-1,0).normalized(),-600); } - 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_2d.h b/tests/servers/test_physics_2d.h index 966d49200a..b6c47574cd 100644 --- a/tests/test_physics_2d.h +++ b/tests/servers/test_physics_2d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -31,7 +31,7 @@ #ifndef TEST_PHYSICS_2D_H #define TEST_PHYSICS_2D_H -#include "core/os/main_loop.h" +class MainLoop; namespace TestPhysics2D { diff --git a/tests/test_physics_3d.cpp b/tests/servers/test_physics_3d.cpp index bb324d8ffe..3d38b9d901 100644 --- a/tests/test_physics_3d.cpp +++ b/tests/servers/test_physics_3d.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -30,13 +30,9 @@ #include "test_physics_3d.h" -#include "core/math/math_funcs.h" -#include "core/math/quick_hull.h" +#include "core/math/convex_hull.h" +#include "core/math/geometry_3d.h" #include "core/os/main_loop.h" -#include "core/os/os.h" -#include "core/string/print_string.h" -#include "core/templates/map.h" -#include "servers/display_server.h" #include "servers/physics_server_3d.h" #include "servers/rendering_server.h" @@ -70,18 +66,14 @@ class TestPhysics3DMainLoop : public MainLoop { void body_changed_transform(Object *p_state, RID p_visual_instance) { PhysicsDirectBodyState3D *state = (PhysicsDirectBodyState3D *)p_state; RenderingServer *vs = RenderingServer::get_singleton(); - Transform t = state->get_transform(); + Transform3D t = state->get_transform(); vs->instance_set_transform(p_visual_instance, t); } bool quit; protected: - static void _bind_methods() { - ClassDB::bind_method("body_changed_transform", &TestPhysics3DMainLoop::body_changed_transform); - } - - RID create_body(PhysicsServer3D::ShapeType p_shape, PhysicsServer3D::BodyMode p_body, const Transform p_location, bool p_active_default = true, const Transform &p_shape_xform = Transform()) { + RID create_body(PhysicsServer3D::ShapeType p_shape, PhysicsServer3D::BodyMode p_body, const Transform3D p_location, bool p_active_default = true, const Transform3D &p_shape_xform = Transform3D()) { RenderingServer *vs = RenderingServer::get_singleton(); PhysicsServer3D *ps = PhysicsServer3D::get_singleton(); @@ -93,7 +85,7 @@ protected: ps->body_set_param(body, PhysicsServer3D::BODY_PARAM_BOUNCE, 0.0); //todo set space ps->body_add_shape(body, type_shape_map[p_shape]); - ps->body_set_force_integration_callback(body, this, "body_changed_transform", mesh_instance); + ps->body_set_force_integration_callback(body, callable_mp(this, &TestPhysics3DMainLoop::body_changed_transform), mesh_instance); ps->body_set_state(body, PhysicsServer3D::BODY_STATE_TRANSFORM, p_location); bodies.push_back(body); @@ -104,18 +96,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; } @@ -173,7 +164,7 @@ protected: RID convex_mesh = vs->mesh_create(); Geometry3D::MeshData convex_data = Geometry3D::build_convex_mesh(convex_planes); - QuickHull::build(convex_data.vertices, convex_data); + ConvexHullComputer::convex_hull(convex_data.vertices, convex_data); vs->mesh_add_surface_from_mesh_data(convex_mesh, convex_data); type_mesh_map[PhysicsServer3D::SHAPE_CONVEX_POLYGON] = convex_mesh; @@ -183,7 +174,7 @@ protected: type_shape_map[PhysicsServer3D::SHAPE_CONVEX_POLYGON] = convex_shape; } - void make_trimesh(Vector<Vector3> p_faces, const Transform &p_xform = Transform()) { + void make_trimesh(Vector<Vector3> p_faces, const Transform3D &p_xform = Transform3D()) { RenderingServer *vs = RenderingServer::get_singleton(); PhysicsServer3D *ps = PhysicsServer3D::get_singleton(); RID trimesh_shape = ps->shape_create(PhysicsServer3D::SHAPE_CONCAVE_POLYGON); @@ -213,12 +204,12 @@ protected: ps->body_set_space(tribody, space); //todo set space ps->body_add_shape(tribody, trimesh_shape); - Transform tritrans = p_xform; + Transform3D tritrans = p_xform; ps->body_set_state(tribody, PhysicsServer3D::BODY_STATE_TRANSFORM, tritrans); vs->instance_set_transform(triins, tritrans); } - void make_grid(int p_width, int p_height, real_t p_cellsize, real_t p_cellheight, const Transform &p_xform = Transform()) { + void make_grid(int p_width, int p_height, real_t p_cellsize, real_t p_cellheight, const Transform3D &p_xform = Transform3D()) { Vector<Vector<real_t>> grid; grid.resize(p_width); @@ -254,18 +245,18 @@ protected: public: virtual void input_event(const Ref<InputEvent> &p_event) { Ref<InputEventMouseMotion> mm = p_event; - if (mm.is_valid() && mm->get_button_mask() & 4) { + if (mm.is_valid() && (mm->get_button_mask() & MouseButton::MASK_MIDDLE) != MouseButton::NONE) { ofs_y -= mm->get_relative().y / 200.0; ofs_x += mm->get_relative().x / 200.0; } - if (mm.is_valid() && mm->get_button_mask() & 1) { + if (mm.is_valid() && (mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE) { real_t y = -mm->get_relative().y / 20.0; real_t x = mm->get_relative().x / 20.0; if (mover.is_valid()) { PhysicsServer3D *ps = PhysicsServer3D::get_singleton(); - Transform t = ps->body_get_state(mover, PhysicsServer3D::BODY_STATE_TRANSFORM); + Transform3D t = ps->body_get_state(mover, PhysicsServer3D::BODY_STATE_TRANSFORM); t.origin += Vector3(x, y, 0); ps->body_set_state(mover, PhysicsServer3D::BODY_STATE_TRANSFORM, t); @@ -291,7 +282,7 @@ public: scenario = vs->scenario_create(); vs->light_set_shadow(lightaux, true); light = vs->instance_create2(lightaux, scenario); - Transform t; + Transform3D t; t.rotate(Vector3(1.0, 0, 0), 0.6); vs->instance_set_transform(light, t); @@ -308,25 +299,25 @@ public: vs->viewport_set_scenario(viewport, scenario); vs->camera_set_perspective(camera, 60, 0.1, 40.0); - vs->camera_set_transform(camera, Transform(Basis(), Vector3(0, 9, 12))); + vs->camera_set_transform(camera, Transform3D(Basis(), Vector3(0, 9, 12))); - Transform gxf; + Transform3D gxf; gxf.basis.scale(Vector3(1.4, 0.4, 1.4)); gxf.origin = Vector3(-2, 1, -2); make_grid(5, 5, 2.5, 1, gxf); 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(); - Transform t = ps->body_get_state(mover, PhysicsServer3D::BODY_STATE_TRANSFORM); + Transform3D t = ps->body_get_state(mover, PhysicsServer3D::BODY_STATE_TRANSFORM); t.origin += Vector3(joy_speed * joy_direction.x * p_time, -joy_speed * joy_direction.y * p_time, 0); ps->body_set_state(mover, PhysicsServer3D::BODY_STATE_TRANSFORM, t); }; - Transform cameratr; + Transform3D cameratr; cameratr.rotate(Vector3(0, 1, 0), ofs_x); cameratr.rotate(Vector3(1, 0, 0), -ofs_y); cameratr.translate(Vector3(0, 2, 8)); @@ -359,21 +350,20 @@ public: Dictionary capsule_params; capsule_params["radius"] = 0.5; capsule_params["height"] = 1; - Transform shape_xform; + Transform3D shape_xform; shape_xform.rotate(Vector3(1, 0, 0), Math_PI / 2.0); //shape_xform.origin=Vector3(1,1,1); ps->shape_set_data(capsule_shape, capsule_params); RID mesh_instance = vs->instance_create2(capsule_mesh, scenario); character = ps->body_create(); - ps->body_set_mode(character, PhysicsServer3D::BODY_MODE_CHARACTER); + 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); + ps->body_set_force_integration_callback(character, callable_mp(this, &TestPhysics3DMainLoop::body_changed_transform), mesh_instance); - ps->body_set_force_integration_callback(character, this, "body_changed_transform", mesh_instance); - - ps->body_set_state(character, PhysicsServer3D::BODY_STATE_TRANSFORM, Transform(Basis(), Vector3(-2, 5, -2))); + ps->body_set_state(character, PhysicsServer3D::BODY_STATE_TRANSFORM, Transform3D(Basis(), Vector3(-2, 5, -2))); bodies.push_back(character); } @@ -388,23 +378,23 @@ public: PhysicsServer3D::ShapeType type = shape_idx[i % 4]; - Transform t; + Transform3D t; t.origin = Vector3(0.0 * i, 3.5 + 1.1 * i, 0.7 + 0.0 * i); t.basis.rotate(Vector3(0.2, -1, 0), Math_PI / 2 * 0.6); - create_body(type, PhysicsServer3D::BODY_MODE_RIGID, t); + 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_RIGID, Transform(Basis(), Vector3(0, 2, 0)), true); - create_static_plane(Plane(Vector3(0, 1, 0), -1)); + create_body(PhysicsServer3D::SHAPE_BOX, PhysicsServer3D::BODY_MODE_DYNAMIC, Transform3D(Basis(), Vector3(0, 2, 0)), true); + 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_physics_3d.h b/tests/servers/test_physics_3d.h index b6b66f350e..f618d0fb4f 100644 --- a/tests/test_physics_3d.h +++ b/tests/servers/test_physics_3d.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -31,7 +31,7 @@ #ifndef TEST_PHYSICS_H #define TEST_PHYSICS_H -#include "core/os/main_loop.h" +class MainLoop; namespace TestPhysics3D { diff --git a/tests/test_render.cpp b/tests/servers/test_render.cpp index 72b2840098..44403e3724 100644 --- a/tests/test_render.cpp +++ b/tests/servers/test_render.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -30,13 +30,8 @@ #include "test_render.h" -#include "core/math/math_funcs.h" -#include "core/math/quick_hull.h" -#include "core/os/keyboard.h" +#include "core/math/convex_hull.h" #include "core/os/main_loop.h" -#include "core/os/os.h" -#include "core/string/print_string.h" -#include "servers/display_server.h" #include "servers/rendering_server.h" #define OBJECT_COUNT 50 @@ -53,7 +48,7 @@ class TestMainLoop : public MainLoop { struct InstanceInfo { RID instance; - Transform base; + Transform3D base; Vector3 rot_axis; }; @@ -118,7 +113,7 @@ public: vts.push_back(Vector3(-1, -1, -1)); Geometry3D::MeshData md; - Error err = QuickHull::build(vts, md); + Error err = ConvexHullComputer::convex_hull(vts, md); print_line("ERR: " + itos(err)); test_cube = vs->mesh_create(); vs->mesh_add_surface_from_mesh_data(test_cube, md); @@ -165,7 +160,7 @@ public: vs->viewport_set_active(viewport, true); vs->viewport_attach_camera(viewport, camera); vs->viewport_set_scenario(viewport, scenario); - vs->camera_set_transform(camera, Transform(Basis(), Vector3(0, 3, 30))); + vs->camera_set_transform(camera, Transform3D(Basis(), Vector3(0, 3, 30))); vs->camera_set_perspective(camera, 60, 0.1, 1000); /* @@ -182,9 +177,9 @@ public: vs->light_set_color(lightaux, Color(1.0, 1.0, 1.0)); //vs->light_set_shadow( lightaux, true ); light = vs->instance_create2(lightaux, scenario); - Transform lla; + 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,9 +194,9 @@ public: ofs = 0; quit = false; } - virtual bool iteration(float p_time) { + virtual bool iteration(double p_time) { RenderingServer *vs = RenderingServer::get_singleton(); - //Transform t; + //Transform3D t; //t.rotate(Vector3(0, 1, 0), ofs); //t.translate(Vector3(0,0,20 )); //vs->camera_set_transform(camera, t); @@ -210,12 +205,12 @@ public: //return quit; - for (List<InstanceInfo>::Element *E = instances.front(); E; E = E->next()) { - Transform 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 +218,7 @@ public: return quit; } - virtual bool idle(float p_time) { + virtual bool idle(double p_time) { return quit; } diff --git a/tests/test_render.h b/tests/servers/test_render.h index 35bb383773..d5a3e01ee5 100644 --- a/tests/test_render.h +++ b/tests/servers/test_render.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -31,11 +31,11 @@ #ifndef TEST_RENDER_H #define TEST_RENDER_H -#include "core/os/main_loop.h" +class MainLoop; namespace TestRender { MainLoop *test(); } -#endif +#endif // TEST_RENDER_H diff --git a/tests/test_shader_lang.cpp b/tests/servers/test_shader_lang.cpp index a023f35506..06e28212d2 100644 --- a/tests/test_shader_lang.cpp +++ b/tests/servers/test_shader_lang.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -30,13 +30,8 @@ #include "test_shader_lang.h" -#include "core/os/file_access.h" #include "core/os/main_loop.h" #include "core/os/os.h" - -#include "core/string/print_string.h" -#include "scene/gui/control.h" -#include "scene/gui/text_edit.h" #include "servers/rendering/shader_language.h" typedef ShaderLanguage SL; @@ -120,38 +115,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 +183,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++) { @@ -211,9 +211,6 @@ static String dump_node_code(SL::Node *p_node, int p_level) { SL::ArrayNode *vnode = (SL::ArrayNode *)p_node; code = vnode->name; } break; - case SL::Node::TYPE_ARRAY_DECLARATION: { - // FIXME: Implement - } break; case SL::Node::TYPE_ARRAY_CONSTRUCT: { // FIXME: Implement } break; @@ -261,6 +258,8 @@ static String dump_node_code(SL::Node *p_node, int p_level) { } code += ")"; break; + case SL::OP_EMPTY: + break; default: { code = "(" + dump_node_code(onode->arguments[0], p_level) + _opstr(onode->op) + dump_node_code(onode->arguments[1], p_level) + ")"; break; @@ -339,12 +338,17 @@ MainLoop *test() { dt["fragment"].built_ins["ALBEDO"] = SL::TYPE_VEC3; dt["fragment"].can_discard = true; - Vector<StringName> rm; - rm.push_back("popo"); + Vector<SL::ModeInfo> rm; + rm.push_back({ "popo" }); Set<String> types; types.insert("spatial"); - Error err = sl.compile(code, dt, rm, types, nullptr); + ShaderLanguage::ShaderCompileInfo info; + info.functions = dt; + info.render_modes = rm; + info.shader_types = types; + + Error err = sl.compile(code, info); if (err) { print_line("Error at line: " + rtos(sl.get_error_line()) + ": " + sl.get_error_text()); diff --git a/tests/test_shader_lang.h b/tests/servers/test_shader_lang.h index 46a2e6af35..31e1bfbeea 100644 --- a/tests/test_shader_lang.h +++ b/tests/servers/test_shader_lang.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -31,7 +31,7 @@ #ifndef TEST_SHADER_LANG_H #define TEST_SHADER_LANG_H -#include "core/os/main_loop.h" +class MainLoop; namespace TestShaderLang { diff --git a/tests/servers/test_text_server.h b/tests/servers/test_text_server.h new file mode 100644 index 0000000000..0a64237285 --- /dev/null +++ b/tests/servers/test_text_server.h @@ -0,0 +1,502 @@ +/*************************************************************************/ +/* test_text_server.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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. */ +/*************************************************************************/ + +#ifdef TOOLS_ENABLED + +#ifndef TEST_TEXT_SERVER_H +#define TEST_TEXT_SERVER_H + +#include "editor/builtin_fonts.gen.h" +#include "servers/text_server.h" +#include "tests/test_macros.h" + +namespace TestTextServer { + +TEST_SUITE("[[TextServer]") { + TEST_CASE("[TextServer] Init, font loading and shaping") { + SUBCASE("[TextServer] Loading fonts") { + for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) { + Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i); + TEST_FAIL_COND(ts.is_null(), "Invalid TS interface."); + + RID font = ts->create_font(); + ts->font_set_data_ptr(font, _font_NotoSans_Regular, _font_NotoSans_Regular_size); + TEST_FAIL_COND(font == RID(), "Loading font failed."); + ts->free(font); + } + } + + SUBCASE("[TextServer] Text layout: Font fallback") { + 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(font1); + font.push_back(font2); + + String test = U"คนอ้วน khon uan ראה"; + // 6^ 17^ + + RID ctx = ts->create_shaped_text(); + TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed."); + bool ok = ts->shaped_text_add_string(ctx, test, font, 16); + TEST_FAIL_COND(!ok, "Adding text to the buffer failed."); + + 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."); + } + if ((glyphs[j].start > 6) && (glyphs[j].start < 16)) { + TEST_FAIL_COND(glyphs[j].font_rid != font[0], "Incorrect font selected."); + } + if (glyphs[j].start > 16) { + TEST_FAIL_COND(glyphs[j].font_rid != RID(), "Incorrect font selected."); + TEST_FAIL_COND(glyphs[j].index != test[glyphs[j].start], "Incorrect glyph index."); + } + TEST_FAIL_COND((glyphs[j].start < 0 || glyphs[j].end > test.length()), "Incorrect glyph range."); + TEST_FAIL_COND(glyphs[j].font_size != 16, "Incorrect glyph font size."); + } + + ts->free(ctx); + + for (int j = 0; j < font.size(); j++) { + ts->free(font[j]); + } + font.clear(); + } + } + + SUBCASE("[TextServer] Text layout: BiDi") { + 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(font1); + font.push_back(font2); + + String test = U"Arabic (اَلْعَرَبِيَّةُ, al-ʿarabiyyah)"; + // 7^ 26^ + + RID ctx = ts->create_shaped_text(); + TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed."); + bool ok = ts->shaped_text_add_string(ctx, test, font, 16); + TEST_FAIL_COND(!ok, "Adding text to the buffer failed."); + + 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."); + } + if ((glyphs[j].start > 8) && (glyphs[j].start < 23)) { + TEST_FAIL_COND(((glyphs[j].flags & TextServer::GRAPHEME_IS_RTL) != TextServer::GRAPHEME_IS_RTL), "Incorrect direction."); + } + if (glyphs[j].start > 26) { + TEST_FAIL_COND(((glyphs[j].flags & TextServer::GRAPHEME_IS_RTL) == TextServer::GRAPHEME_IS_RTL), "Incorrect direction."); + } + } + } + + ts->free(ctx); + + for (int j = 0; j < font.size(); j++) { + ts->free(font[j]); + } + font.clear(); + } + } + + SUBCASE("[TextServer] Text layout: Line break and align points") { + 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); + RID font3 = ts->create_font(); + ts->font_set_data_ptr(font3, _font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size); + + Vector<RID> font; + font.push_back(font1); + font.push_back(font2); + font.push_back(font3); + + { + String test = U"Test test long text long text\n"; + RID ctx = ts->create_shaped_text(); + TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed."); + bool ok = ts->shaped_text_add_string(ctx, test, font, 16); + TEST_FAIL_COND(!ok, "Adding text to the buffer failed."); + ts->shaped_text_update_breaks(ctx); + ts->shaped_text_update_justification_ops(ctx); + + const Glyph *glyphs = ts->shaped_text_get_glyphs(ctx); + int gl_size = ts->shaped_text_get_glyph_count(ctx); + + TEST_FAIL_COND(gl_size != 30, "Invalid glyph count."); + for (int j = 0; j < gl_size; j++) { + bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD; + bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT; + bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE; + bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL; + bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION; + if (j == 4 || j == 9 || j == 14 || j == 19 || j == 24) { + TEST_FAIL_COND((!soft || !space || hard || virt || elo), "Invalid glyph flags."); + } else if (j == 29) { + TEST_FAIL_COND((soft || !space || !hard || virt || elo), "Invalid glyph flags."); + } else { + TEST_FAIL_COND((soft || space || hard || virt || elo), "Invalid glyph flags."); + } + } + ts->free(ctx); + } + + { + String test = U"الحمـد"; + RID ctx = ts->create_shaped_text(); + TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed."); + bool ok = ts->shaped_text_add_string(ctx, test, font, 16); + TEST_FAIL_COND(!ok, "Adding text to the buffer failed."); + ts->shaped_text_update_breaks(ctx); + + const Glyph *glyphs = ts->shaped_text_get_glyphs(ctx); + int gl_size = ts->shaped_text_get_glyph_count(ctx); + TEST_FAIL_COND(gl_size != 6, "Invalid glyph count."); + for (int j = 0; j < gl_size; j++) { + bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD; + bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT; + bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE; + bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL; + bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION; + TEST_FAIL_COND((soft || space || hard || virt || elo), "Invalid glyph flags."); + } + + if (ts->has_feature(TextServer::FEATURE_KASHIDA_JUSTIFICATION)) { + ts->shaped_text_update_justification_ops(ctx); + + glyphs = ts->shaped_text_get_glyphs(ctx); + gl_size = ts->shaped_text_get_glyph_count(ctx); + + TEST_FAIL_COND(gl_size != 6, "Invalid glyph count."); + for (int j = 0; j < gl_size; j++) { + bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD; + bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT; + bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE; + bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL; + bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION; + if (j == 1) { + TEST_FAIL_COND((soft || space || hard || virt || !elo), "Invalid glyph flags."); + } else { + TEST_FAIL_COND((soft || space || hard || virt || elo), "Invalid glyph flags."); + } + } + } + ts->free(ctx); + } + + { + String test = U"الحمـد الرياضي العربي"; + RID ctx = ts->create_shaped_text(); + TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed."); + bool ok = ts->shaped_text_add_string(ctx, test, font, 16); + TEST_FAIL_COND(!ok, "Adding text to the buffer failed."); + ts->shaped_text_update_breaks(ctx); + + const Glyph *glyphs = ts->shaped_text_get_glyphs(ctx); + int gl_size = ts->shaped_text_get_glyph_count(ctx); + + TEST_FAIL_COND(gl_size != 21, "Invalid glyph count."); + for (int j = 0; j < gl_size; j++) { + bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD; + bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT; + bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE; + bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL; + bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION; + if (j == 6 || j == 14) { + TEST_FAIL_COND((!soft || !space || hard || virt || elo), "Invalid glyph flags."); + } else { + TEST_FAIL_COND((soft || space || hard || virt || elo), "Invalid glyph flags."); + } + } + + if (ts->has_feature(TextServer::FEATURE_KASHIDA_JUSTIFICATION)) { + ts->shaped_text_update_justification_ops(ctx); + + glyphs = ts->shaped_text_get_glyphs(ctx); + gl_size = ts->shaped_text_get_glyph_count(ctx); + + TEST_FAIL_COND(gl_size != 23, "Invalid glyph count."); + for (int j = 0; j < gl_size; j++) { + bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD; + bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT; + bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE; + bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL; + bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION; + if (j == 7 || j == 16) { + TEST_FAIL_COND((!soft || !space || hard || virt || elo), "Invalid glyph flags."); + } else if (j == 3 || j == 9) { + TEST_FAIL_COND((soft || space || hard || !virt || !elo), "Invalid glyph flags."); + } else if (j == 18) { + TEST_FAIL_COND((soft || space || hard || virt || !elo), "Invalid glyph flags."); + } else { + TEST_FAIL_COND((soft || space || hard || virt || elo), "Invalid glyph flags."); + } + } + } + + ts->free(ctx); + } + + { + String test = U"เป็น ภาษา ราชการ และ ภาษา"; + RID ctx = ts->create_shaped_text(); + TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed."); + bool ok = ts->shaped_text_add_string(ctx, test, font, 16); + TEST_FAIL_COND(!ok, "Adding text to the buffer failed."); + ts->shaped_text_update_breaks(ctx); + ts->shaped_text_update_justification_ops(ctx); + + const Glyph *glyphs = ts->shaped_text_get_glyphs(ctx); + int gl_size = ts->shaped_text_get_glyph_count(ctx); + + TEST_FAIL_COND(gl_size != 25, "Invalid glyph count."); + for (int j = 0; j < gl_size; j++) { + bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD; + bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT; + bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE; + bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL; + bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION; + if (j == 4 || j == 9 || j == 16 || j == 20) { + TEST_FAIL_COND((!soft || !space || hard || virt || elo), "Invalid glyph flags."); + } else { + TEST_FAIL_COND((soft || space || hard || virt || elo), "Invalid glyph flags."); + } + } + ts->free(ctx); + } + + if (ts->has_feature(TextServer::FEATURE_BREAK_ITERATORS)) { + String test = U"เป็นภาษาราชการและภาษา"; + RID ctx = ts->create_shaped_text(); + TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed."); + bool ok = ts->shaped_text_add_string(ctx, test, font, 16); + TEST_FAIL_COND(!ok, "Adding text to the buffer failed."); + ts->shaped_text_update_breaks(ctx); + ts->shaped_text_update_justification_ops(ctx); + + const Glyph *glyphs = ts->shaped_text_get_glyphs(ctx); + int gl_size = ts->shaped_text_get_glyph_count(ctx); + + TEST_FAIL_COND(gl_size != 25, "Invalid glyph count."); + for (int j = 0; j < gl_size; j++) { + bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD; + bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT; + bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE; + bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL; + bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION; + if (j == 4 || j == 9 || j == 16 || j == 20) { + TEST_FAIL_COND((!soft || !space || hard || !virt || elo), "Invalid glyph flags."); + } else { + TEST_FAIL_COND((soft || space || hard || virt || elo), "Invalid glyph flags."); + } + } + ts->free(ctx); + } + + for (int j = 0; j < font.size(); j++) { + ts->free(font[j]); + } + font.clear(); + } + } + + SUBCASE("[TextServer] Text layout: Line breaking") { + 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(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."); + + 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); + + for (int j = 0; j < font.size(); j++) { + ts->free(font[j]); + } + font.clear(); + } + } + + SUBCASE("[TextServer] Text layout: Justification") { + 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(font1); + font.push_back(font2); + + String test_1 = U"الحمد"; + String test_2 = U"الحمد test"; + String test_3 = U"test test"; + // 7^ 26^ + + RID ctx; + bool ok; + float width_old, width; + if (ts->has_feature(TextServer::FEATURE_KASHIDA_JUSTIFICATION)) { + ctx = ts->create_shaped_text(); + TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed."); + ok = ts->shaped_text_add_string(ctx, test_1, font, 16); + TEST_FAIL_COND(!ok, "Adding text to the buffer failed."); + + width_old = ts->shaped_text_get_width(ctx); + width = ts->shaped_text_fit_to_width(ctx, 100, TextServer::JUSTIFICATION_WORD_BOUND); + TEST_FAIL_COND((width != width_old), "Invalid fill width."); + width = ts->shaped_text_fit_to_width(ctx, 100, TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA); + TEST_FAIL_COND((width <= width_old || width > 100), "Invalid fill width."); + + ts->free(ctx); + + ctx = ts->create_shaped_text(); + TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed."); + ok = ts->shaped_text_add_string(ctx, test_2, font, 16); + TEST_FAIL_COND(!ok, "Adding text to the buffer failed."); + + width_old = ts->shaped_text_get_width(ctx); + width = ts->shaped_text_fit_to_width(ctx, 100, TextServer::JUSTIFICATION_WORD_BOUND); + TEST_FAIL_COND((width <= width_old || width > 100), "Invalid fill width."); + width = ts->shaped_text_fit_to_width(ctx, 100, TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA); + TEST_FAIL_COND((width <= width_old || width > 100), "Invalid fill width."); + + ts->free(ctx); + } + + ctx = ts->create_shaped_text(); + TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed."); + ok = ts->shaped_text_add_string(ctx, test_3, font, 16); + TEST_FAIL_COND(!ok, "Adding text to the buffer failed."); + + width_old = ts->shaped_text_get_width(ctx); + width = ts->shaped_text_fit_to_width(ctx, 100, TextServer::JUSTIFICATION_WORD_BOUND); + TEST_FAIL_COND((width <= width_old || width > 100), "Invalid fill width."); + + ts->free(ctx); + + for (int j = 0; j < font.size(); j++) { + ts->free(font[j]); + } + font.clear(); + } + } + + SUBCASE("[TextServer] Strip Diacritics") { + 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_SHAPING)) { + CHECK(ts->strip_diacritics(U"ٱلسَّلَامُ عَلَيْكُمْ") == U"ٱلسلام عليكم"); + } + + CHECK(ts->strip_diacritics(U"pêches épinards tomates fraises") == U"peches epinards tomates fraises"); + CHECK(ts->strip_diacritics(U"ΆΈΉΊΌΎΏΪΫϓϔ") == U"ΑΕΗΙΟΥΩΙΥΥΥ"); + CHECK(ts->strip_diacritics(U"άέήίΐϊΰϋόύώ") == U"αεηιιιυυουω"); + CHECK(ts->strip_diacritics(U"ЀЁЃ ЇЌЍӢӤЙ ЎӮӰӲ ӐӒӖӚӜӞ ӦӪ Ӭ Ӵ Ӹ") == U"ЕЕГ ІКИИИИ УУУУ ААЕӘЖЗ ОӨ Э Ч Ы"); + CHECK(ts->strip_diacritics(U"ѐёѓ їќѝӣӥй ўӯӱӳ ӑӓӗӛӝӟ ӧӫ ӭ ӵ ӹ") == U"еег ікииии уууу ааеәжз оө э ч ы"); + CHECK(ts->strip_diacritics(U"ÀÁÂÃÄÅĀĂĄÇĆĈĊČĎÈÉÊËĒĔĖĘĚĜĞĠĢĤÌÍÎÏĨĪĬĮİĴĶĹĻĽÑŃŅŇŊÒÓÔÕÖØŌŎŐƠŔŖŘŚŜŞŠŢŤÙÚÛÜŨŪŬŮŰŲƯŴÝŶŹŻŽ") == U"AAAAAAAAACCCCCDEEEEEEEEEGGGGHIIIIIIIIIJKLLLNNNNŊOOOOOØOOOORRRSSSSTTUUUUUUUUUUUWYYZZZ"); + CHECK(ts->strip_diacritics(U"àáâãäåāăąçćĉċčďèéêëēĕėęěĝğġģĥìíîïĩīĭįĵķĺļľñńņňŋòóôõöøōŏőơŕŗřśŝşšţťùúûüũūŭůűųưŵýÿŷźżž") == U"aaaaaaaaacccccdeeeeeeeeegggghiiiiiiiijklllnnnnŋoooooøoooorrrssssttuuuuuuuuuuuwyyyzzz"); + CHECK(ts->strip_diacritics(U"ǍǏȈǑǪǬȌȎȪȬȮȰǓǕǗǙǛȔȖǞǠǺȀȂȦǢǼǦǴǨǸȆȐȒȘȚȞȨ Ḁ ḂḄḆ Ḉ ḊḌḎḐḒ ḔḖḘḚḜ Ḟ Ḡ ḢḤḦḨḪ ḬḮ ḰḲḴ ḶḸḺḼ ḾṀṂ ṄṆṈṊ ṌṎṐṒ ṔṖ ṘṚṜṞ ṠṢṤṦṨ ṪṬṮṰ ṲṴṶṸṺ") == U"AIIOOOOOOOOOUUUUUUUAAAAAAÆÆGGKNERRSTHE A BBB C DDDDD EEEEE F G HHHHH II KKK LLLL MMM NNNN OOOO PP RRRR SSSSS TTTT UUUUU"); + CHECK(ts->strip_diacritics(U"ǎǐȉȋǒǫǭȍȏȫȭȯȱǔǖǘǚǜȕȗǟǡǻȁȃȧǣǽǧǵǩǹȇȑȓșțȟȩ ḁ ḃḅḇ ḉ ḋḍḏḑḓ ḟ ḡ ḭḯ ḱḳḵ ḷḹḻḽ ḿṁṃ ṅṇṉṋ ṍṏṑṓ ṗṕ ṙṛṝṟ ṡṣṥṧṩ ṫṭṯṱ ṳṵṷṹṻ") == U"aiiiooooooooouuuuuuuaaaaaaææggknerrsthe a bbb c ddddd f g ii kkk llll mmm nnnn oooo pp rrrr sssss tttt uuuuu"); + CHECK(ts->strip_diacritics(U"ṼṾ ẀẂẄẆẈ ẊẌ Ẏ ẐẒẔ") == U"VV WWWWW XX Y ZZZ"); + CHECK(ts->strip_diacritics(U"ṽṿ ẁẃẅẇẉ ẋẍ ẏ ẑẓẕ ẖ ẗẘẙẛ") == U"vv wwwww xx y zzz h twys"); + } + } + } +} +}; // namespace TestTextServer + +#endif // TEST_TEXT_SERVER_H +#endif // TOOLS_ENABLED diff --git a/tests/test_array.h b/tests/test_array.h deleted file mode 100644 index 52da256860..0000000000 --- a/tests/test_array.h +++ /dev/null @@ -1,186 +0,0 @@ -/*************************************************************************/ -/* test_array.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_ARRAY_H -#define TEST_ARRAY_H - -#include "core/object/class_db.h" -#include "core/object/script_language.h" -#include "core/templates/hashfuncs.h" -#include "core/templates/vector.h" -#include "core/variant/array.h" -#include "core/variant/container_type_validate.h" -#include "core/variant/variant.h" -#include "tests/test_macros.h" - -namespace TestArray { - -TEST_CASE("[Array] size(), clear(), and is_empty()") { - Array arr; - CHECK(arr.size() == 0); - CHECK(arr.is_empty()); - arr.push_back(1); - CHECK(arr.size() == 1); - arr.clear(); - CHECK(arr.is_empty()); - CHECK(arr.size() == 0); -} - -TEST_CASE("[Array] Assignment and comparison operators") { - Array arr1; - Array arr2; - arr1.push_back(1); - CHECK(arr1 != arr2); - CHECK(arr1 > arr2); - CHECK(arr1 >= arr2); - arr2.push_back(2); - CHECK(arr1 != arr2); - CHECK(arr1 < arr2); - CHECK(arr1 <= arr2); - CHECK(arr2 > arr1); - CHECK(arr2 >= arr1); - Array arr3 = arr2; - CHECK(arr3 == arr2); -} - -TEST_CASE("[Array] append_array()") { - Array arr1; - Array arr2; - arr1.push_back(1); - arr1.append_array(arr2); - CHECK(arr1.size() == 1); - arr2.push_back(2); - arr1.append_array(arr2); - CHECK(arr1.size() == 2); - CHECK(int(arr1[0]) == 1); - CHECK(int(arr1[1]) == 2); -} - -TEST_CASE("[Array] resize(), insert(), and erase()") { - Array arr; - arr.resize(2); - CHECK(arr.size() == 2); - arr.insert(0, 1); - CHECK(int(arr[0]) == 1); - arr.insert(0, 2); - CHECK(int(arr[0]) == 2); - arr.erase(2); - CHECK(int(arr[0]) == 1); -} - -TEST_CASE("[Array] front() and back()") { - Array arr; - arr.push_back(1); - CHECK(int(arr.front()) == 1); - CHECK(int(arr.back()) == 1); - arr.push_back(3); - CHECK(int(arr.front()) == 1); - CHECK(int(arr.back()) == 3); -} - -TEST_CASE("[Array] has() and count()") { - Array arr; - arr.push_back(1); - arr.push_back(1); - CHECK(arr.has(1)); - CHECK(!arr.has(2)); - CHECK(arr.count(1) == 2); - CHECK(arr.count(2) == 0); -} - -TEST_CASE("[Array] remove()") { - Array arr; - arr.push_back(1); - arr.push_back(2); - arr.remove(0); - CHECK(arr.size() == 1); - CHECK(int(arr[0]) == 2); - arr.remove(0); - CHECK(arr.size() == 0); - - // The array is now empty; try to use `remove()` again. - // Normally, this prints an error message so we silence it. - ERR_PRINT_OFF; - arr.remove(0); - ERR_PRINT_ON; - - CHECK(arr.size() == 0); -} - -TEST_CASE("[Array] get()") { - Array arr; - arr.push_back(1); - CHECK(int(arr.get(0)) == 1); -} - -TEST_CASE("[Array] sort()") { - Array arr; - - arr.push_back(3); - arr.push_back(4); - arr.push_back(2); - arr.push_back(1); - arr.sort(); - int val = 1; - for (int i = 0; i < arr.size(); i++) { - CHECK(int(arr[i]) == val); - val++; - } -} - -TEST_CASE("[Array] push_front(), pop_front(), pop_back()") { - Array arr; - arr.push_front(1); - arr.push_front(2); - CHECK(int(arr[0]) == 2); - arr.pop_front(); - CHECK(int(arr[0]) == 1); - CHECK(arr.size() == 1); - arr.push_front(2); - arr.push_front(3); - arr.pop_back(); - CHECK(int(arr[1]) == 2); - CHECK(arr.size() == 2); -} - -TEST_CASE("[Array] max() and min()") { - Array arr; - arr.push_back(3); - arr.push_front(4); - arr.push_back(5); - arr.push_back(2); - int max = int(arr.max()); - int min = int(arr.min()); - CHECK(max == 5); - CHECK(min == 2); -} -} // namespace TestArray - -#endif // TEST_ARRAY_H diff --git a/tests/test_dictionary.h b/tests/test_dictionary.h deleted file mode 100644 index b94cf36109..0000000000 --- a/tests/test_dictionary.h +++ /dev/null @@ -1,159 +0,0 @@ -/*************************************************************************/ -/* test_dictionary.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_DICTIONARY_H -#define TEST_DICTIONARY_H - -#include "core/templates/ordered_hash_map.h" -#include "core/templates/safe_refcount.h" -#include "core/variant/dictionary.h" -#include "core/variant/variant.h" -#include "tests/test_macros.h" - -namespace TestDictionary { - -TEST_CASE("[Dictionary] Assignment using bracket notation ([])") { - Dictionary map; - map["Hello"] = 0; - CHECK(int(map["Hello"]) == 0); - map["Hello"] = 3; - CHECK(int(map["Hello"]) == 3); - map["World!"] = 4; - CHECK(int(map["World!"]) == 4); - - // Test non-string keys, since keys can be of any Variant type. - map[12345] = -5; - CHECK(int(map[12345]) == -5); - map[false] = 128; - CHECK(int(map[false]) == 128); - map[Vector2(10, 20)] = 30; - CHECK(int(map[Vector2(10, 20)]) == 30); - map[0] = 400; - CHECK(int(map[0]) == 400); - // Check that assigning 0 doesn't overwrite the value for `false`. - CHECK(int(map[false]) == 128); -} - -TEST_CASE("[Dictionary] == and != operators") { - Dictionary map1; - Dictionary map2; - CHECK(map1 != map2); - map1[1] = 3; - map2 = map1; - CHECK(map1 == map2); -} - -TEST_CASE("[Dictionary] get_key_lists()") { - Dictionary map; - List<Variant> keys; - List<Variant> *ptr = &keys; - map.get_key_list(ptr); - CHECK(keys.is_empty()); - map[1] = 3; - map.get_key_list(ptr); - CHECK(keys.size() == 1); - CHECK(int(keys[0]) == 1); - map[2] = 4; - map.get_key_list(ptr); - CHECK(keys.size() == 3); -} - -TEST_CASE("[Dictionary] get_key_at_index()") { - Dictionary map; - map[4] = 3; - Variant val = map.get_key_at_index(0); - CHECK(int(val) == 4); - map[3] = 1; - val = map.get_key_at_index(0); - CHECK(int(val) == 4); - val = map.get_key_at_index(1); - CHECK(int(val) == 3); -} - -TEST_CASE("[Dictionary] getptr()") { - Dictionary map; - map[1] = 3; - Variant *key = map.getptr(1); - CHECK(int(*key) == 3); - key = map.getptr(2); - CHECK(key == nullptr); -} - -TEST_CASE("[Dictionary] get_valid()") { - Dictionary map; - map[1] = 3; - Variant val = map.get_valid(1); - CHECK(int(val) == 3); -} -TEST_CASE("[Dictionary] get()") { - Dictionary map; - map[1] = 3; - Variant val = map.get(1, -1); - CHECK(int(val) == 3); -} - -TEST_CASE("[Dictionary] size(), empty() and clear()") { - Dictionary map; - CHECK(map.size() == 0); - CHECK(map.is_empty()); - map[1] = 3; - CHECK(map.size() == 1); - CHECK(!map.is_empty()); - map.clear(); - CHECK(map.size() == 0); - CHECK(map.is_empty()); -} - -TEST_CASE("[Dictionary] has() and has_all()") { - Dictionary map; - CHECK(map.has(1) == false); - map[1] = 3; - CHECK(map.has(1)); - Array keys; - keys.push_back(1); - CHECK(map.has_all(keys)); - keys.push_back(2); - CHECK(map.has_all(keys) == false); -} - -TEST_CASE("[Dictionary] keys() and values()") { - Dictionary map; - Array keys = map.keys(); - Array values = map.values(); - CHECK(keys.is_empty()); - CHECK(values.is_empty()); - map[1] = 3; - keys = map.keys(); - values = map.values(); - CHECK(int(keys[0]) == 1); - CHECK(int(values[0]) == 3); -} -} // namespace TestDictionary -#endif // TEST_DICTIONARY_H diff --git a/tests/test_macros.cpp b/tests/test_macros.cpp index b0b28ab374..aa07f8211a 100644 --- a/tests/test_macros.cpp +++ b/tests/test_macros.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ diff --git a/tests/test_macros.h b/tests/test_macros.h index a13f3abbe7..ed8a12f155 100644 --- a/tests/test_macros.h +++ b/tests/test_macros.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -31,7 +31,8 @@ #ifndef TEST_MACROS_H #define TEST_MACROS_H -#include "core/templates/map.h" +#include "core/input/input_map.h" +#include "core/object/message_queue.h" #include "core/variant/variant.h" // See documentation for doctest at: @@ -91,10 +92,10 @@ DOCTEST_STRINGIFY_VARIANT(Vector3); DOCTEST_STRINGIFY_VARIANT(Vector3i); DOCTEST_STRINGIFY_VARIANT(Transform2D); DOCTEST_STRINGIFY_VARIANT(Plane); -DOCTEST_STRINGIFY_VARIANT(Quat); +DOCTEST_STRINGIFY_VARIANT(Quaternion); DOCTEST_STRINGIFY_VARIANT(AABB); DOCTEST_STRINGIFY_VARIANT(Basis); -DOCTEST_STRINGIFY_VARIANT(Transform); +DOCTEST_STRINGIFY_VARIANT(Transform3D); DOCTEST_STRINGIFY_VARIANT(::Color); // Disambiguate from `doctest::Color`. DOCTEST_STRINGIFY_VARIANT(StringName); @@ -129,4 +130,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 | KeyModifierMask::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 position. 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, MouseButton::LEFT, MouseButton::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 automatically discarded 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 d06d604532..830731abcd 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -30,58 +30,73 @@ #include "test_main.h" -#include "core/templates/list.h" - -#include "test_aabb.h" -#include "test_array.h" -#include "test_astar.h" -#include "test_basis.h" -#include "test_class_db.h" -#include "test_color.h" -#include "test_command_queue.h" -#include "test_config_file.h" -#include "test_crypto.h" -#include "test_curve.h" -#include "test_dictionary.h" -#include "test_expression.h" -#include "test_file_access.h" -#include "test_geometry_2d.h" -#include "test_geometry_3d.h" -#include "test_gradient.h" -#include "test_gui.h" -#include "test_hashing_context.h" -#include "test_image.h" -#include "test_json.h" -#include "test_list.h" -#include "test_local_vector.h" -#include "test_lru.h" -#include "test_marshalls.h" -#include "test_math.h" -#include "test_method_bind.h" -#include "test_node_path.h" -#include "test_oa_hash_map.h" -#include "test_object.h" -#include "test_ordered_hash_map.h" -#include "test_paged_array.h" -#include "test_path_3d.h" -#include "test_pck_packer.h" -#include "test_physics_2d.h" -#include "test_physics_3d.h" -#include "test_random_number_generator.h" -#include "test_rect2.h" -#include "test_render.h" -#include "test_resource.h" -#include "test_shader_lang.h" -#include "test_string.h" -#include "test_text_server.h" -#include "test_validate_testing.h" -#include "test_variant.h" -#include "test_xml_parser.h" +#include "tests/core/io/test_config_file.h" +#include "tests/core/io/test_file_access.h" +#include "tests/core/io/test_image.h" +#include "tests/core/io/test_json.h" +#include "tests/core/io/test_marshalls.h" +#include "tests/core/io/test_pck_packer.h" +#include "tests/core/io/test_resource.h" +#include "tests/core/io/test_xml_parser.h" +#include "tests/core/math/test_aabb.h" +#include "tests/core/math/test_astar.h" +#include "tests/core/math/test_basis.h" +#include "tests/core/math/test_color.h" +#include "tests/core/math/test_expression.h" +#include "tests/core/math/test_geometry_2d.h" +#include "tests/core/math/test_geometry_3d.h" +#include "tests/core/math/test_math.h" +#include "tests/core/math/test_random_number_generator.h" +#include "tests/core/math/test_rect2.h" +#include "tests/core/math/test_rect2i.h" +#include "tests/core/math/test_vector2.h" +#include "tests/core/math/test_vector2i.h" +#include "tests/core/math/test_vector3.h" +#include "tests/core/math/test_vector3i.h" +#include "tests/core/object/test_class_db.h" +#include "tests/core/object/test_method_bind.h" +#include "tests/core/object/test_object.h" +#include "tests/core/string/test_node_path.h" +#include "tests/core/string/test_string.h" +#include "tests/core/string/test_translation.h" +#include "tests/core/templates/test_command_queue.h" +#include "tests/core/templates/test_list.h" +#include "tests/core/templates/test_local_vector.h" +#include "tests/core/templates/test_lru.h" +#include "tests/core/templates/test_oa_hash_map.h" +#include "tests/core/templates/test_ordered_hash_map.h" +#include "tests/core/templates/test_paged_array.h" +#include "tests/core/templates/test_vector.h" +#include "tests/core/test_crypto.h" +#include "tests/core/test_hashing_context.h" +#include "tests/core/test_time.h" +#include "tests/core/variant/test_array.h" +#include "tests/core/variant/test_dictionary.h" +#include "tests/core/variant/test_variant.h" +#include "tests/scene/test_animation.h" +#include "tests/scene/test_code_edit.h" +#include "tests/scene/test_curve.h" +#include "tests/scene/test_gradient.h" +#include "tests/scene/test_gui.h" +#include "tests/scene/test_path_3d.h" +#include "tests/servers/test_physics_2d.h" +#include "tests/servers/test_physics_3d.h" +#include "tests/servers/test_render.h" +#include "tests/servers/test_shader_lang.h" +#include "tests/servers/test_text_server.h" +#include "tests/test_validate_testing.h" #include "modules/modules_tests.gen.h" #include "tests/test_macros.h" +#include "scene/resources/default_theme/default_theme.h" +#include "servers/navigation_server_2d.h" +#include "servers/navigation_server_3d.h" +#include "servers/physics_server_2d.h" +#include "servers/physics_server_3d.h" +#include "servers/rendering/rendering_server_default.h" + int test_main(int argc, char *argv[]) { bool run_tests = true; @@ -119,24 +134,167 @@ int test_main(int argc, char *argv[]) { test_args.push_back(arg); } } - // Convert Godot command line arguments back to standard arguments. - char **doctest_args = new char *[test_args.size()]; - for (int x = 0; x < test_args.size(); x++) { - // Operation to convert Godot string to non wchar string. - CharString cs = test_args[x].utf8(); - const char *str = cs.get_data(); - // Allocate the string copy. - doctest_args[x] = new char[strlen(str) + 1]; - // Copy this into memory. - memcpy(doctest_args[x], str, strlen(str) + 1); - } - test_context.applyCommandLine(test_args.size(), doctest_args); + if (test_args.size() > 0) { + // Convert Godot command line arguments back to standard arguments. + char **doctest_args = new char *[test_args.size()]; + for (int x = 0; x < test_args.size(); x++) { + // Operation to convert Godot string to non wchar string. + CharString cs = test_args[x].utf8(); + const char *str = cs.get_data(); + // Allocate the string copy. + doctest_args[x] = new char[strlen(str) + 1]; + // Copy this into memory. + memcpy(doctest_args[x], str, strlen(str) + 1); + } + + test_context.applyCommandLine(test_args.size(), doctest_args); - for (int x = 0; x < test_args.size(); x++) { - delete[] doctest_args[x]; + for (int x = 0; x < test_args.size(); x++) { + delete[] doctest_args[x]; + } + delete[] doctest_args; } - delete[] doctest_args; return test_context.run(); } + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +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(1.0, Ref<Font>(), TextServer::SUBPIXEL_POSITIONING_AUTO, TextServer::HINTING_LIGHT, true); + + 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_main.h b/tests/test_main.h index 8c506a776f..8e6a7361fc 100644 --- a/tests/test_main.h +++ b/tests/test_main.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ diff --git a/tests/test_text_server.h b/tests/test_text_server.h deleted file mode 100644 index 3d700f8ec4..0000000000 --- a/tests/test_text_server.h +++ /dev/null @@ -1,252 +0,0 @@ -/*************************************************************************/ -/* test_text_server.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. */ -/*************************************************************************/ - -#ifdef TOOLS_ENABLED - -#ifndef TEST_TEXT_SERVER_H -#define TEST_TEXT_SERVER_H - -#include "editor/builtin_fonts.gen.h" -#include "servers/text_server.h" -#include "tests/test_macros.h" - -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); - - RID font = ts->create_font_memory(_font_NotoSansUI_Regular, _font_NotoSansUI_Regular_size, "ttf"); - 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); - - Vector<RID> font; - font.push_back(ts->create_font_memory(_font_NotoSansUI_Regular, _font_NotoSansUI_Regular_size, "ttf")); - font.push_back(ts->create_font_memory(_font_NotoSansThaiUI_Regular, _font_NotoSansThaiUI_Regular_size, "ttf")); - - String test = U"คนอ้วน khon uan ראה"; - // 6^ 17^ - - RID ctx = ts->create_shaped_text(); - TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed."); - 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++) { - if (glyphs[j].start < 6) { - TEST_FAIL_COND(glyphs[j].font_rid != font[1], "Incorrect font selected."); - } - if ((glyphs[j].start > 6) && (glyphs[j].start < 16)) { - TEST_FAIL_COND(glyphs[j].font_rid != font[0], "Incorrect font selected."); - } - if (glyphs[j].start > 16) { - TEST_FAIL_COND(glyphs[j].font_rid != RID(), "Incorrect font selected."); - TEST_FAIL_COND(glyphs[j].index != test[glyphs[j].start], "Incorrect glyph index."); - } - TEST_FAIL_COND((glyphs[j].start < 0 || glyphs[j].end > test.length()), "Incorrect glyph range."); - TEST_FAIL_COND(glyphs[j].font_size != 16, "Incorrect glyph font size."); - } - - ts->free(ctx); - - for (int j = 0; j < font.size(); j++) { - ts->free(font[j]); - } - font.clear(); - } - } - - SUBCASE("[TextServer] Text layout: BiDi") { - for (int i = 0; i < TextServerManager::get_interface_count(); i++) { - TextServer *ts = TextServerManager::initialize(i, err); - - if (!ts->has_feature(TextServer::FEATURE_BIDI_LAYOUT)) { - continue; - } - - Vector<RID> font; - font.push_back(ts->create_font_memory(_font_NotoSansUI_Regular, _font_NotoSansUI_Regular_size, "ttf")); - font.push_back(ts->create_font_memory(_font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size, "ttf")); - - String test = U"Arabic (اَلْعَرَبِيَّةُ, al-ʿarabiyyah)"; - // 7^ 26^ - - RID ctx = ts->create_shaped_text(); - TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed."); - 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++) { - 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."); - } - if ((glyphs[j].start > 8) && (glyphs[j].start < 23)) { - TEST_FAIL_COND(((glyphs[j].flags & TextServer::GRAPHEME_IS_RTL) != TextServer::GRAPHEME_IS_RTL), "Incorrect direction."); - } - if (glyphs[j].start > 26) { - TEST_FAIL_COND(((glyphs[j].flags & TextServer::GRAPHEME_IS_RTL) == TextServer::GRAPHEME_IS_RTL), "Incorrect direction."); - } - } - } - - ts->free(ctx); - - for (int j = 0; j < font.size(); j++) { - ts->free(font[j]); - } - font.clear(); - } - } - - SUBCASE("[TextServer] Text layout: Line breaking") { - for (int i = 0; i < TextServerManager::get_interface_count(); i++) { - TextServer *ts = TextServerManager::initialize(i, err); - - String test_1 = U"test test test"; - // 5^ 10^ - - Vector<RID> font; - font.push_back(ts->create_font_memory(_font_NotoSansUI_Regular, _font_NotoSansUI_Regular_size, "ttf")); - font.push_back(ts->create_font_memory(_font_NotoSansThaiUI_Regular, _font_NotoSansThaiUI_Regular_size, "ttf")); - - 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."); - } - - ts->free(ctx); - - for (int j = 0; j < font.size(); j++) { - ts->free(font[j]); - } - font.clear(); - } - } - - SUBCASE("[TextServer] Text layout: Justification") { - for (int i = 0; i < TextServerManager::get_interface_count(); i++) { - TextServer *ts = TextServerManager::initialize(i, err); - - Vector<RID> font; - font.push_back(ts->create_font_memory(_font_NotoSansUI_Regular, _font_NotoSansUI_Regular_size, "ttf")); - font.push_back(ts->create_font_memory(_font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size, "ttf")); - - String test_1 = U"الحمد"; - String test_2 = U"الحمد test"; - String test_3 = U"test test"; - // 7^ 26^ - - RID ctx; - bool ok; - float width_old, width; - if (ts->has_feature(TextServer::FEATURE_KASHIDA_JUSTIFICATION)) { - ctx = ts->create_shaped_text(); - TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed."); - ok = ts->shaped_text_add_string(ctx, test_1, font, 16); - TEST_FAIL_COND(!ok, "Adding text to the buffer failed."); - - width_old = ts->shaped_text_get_width(ctx); - width = ts->shaped_text_fit_to_width(ctx, 100, TextServer::JUSTIFICATION_WORD_BOUND); - TEST_FAIL_COND((width != width_old), "Invalid fill width."); - width = ts->shaped_text_fit_to_width(ctx, 100, TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA); - TEST_FAIL_COND((width <= width_old || width > 100), "Invalid fill width."); - - ts->free(ctx); - - ctx = ts->create_shaped_text(); - TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed."); - ok = ts->shaped_text_add_string(ctx, test_2, font, 16); - TEST_FAIL_COND(!ok, "Adding text to the buffer failed."); - - width_old = ts->shaped_text_get_width(ctx); - width = ts->shaped_text_fit_to_width(ctx, 100, TextServer::JUSTIFICATION_WORD_BOUND); - TEST_FAIL_COND((width <= width_old || width > 100), "Invalid fill width."); - width = ts->shaped_text_fit_to_width(ctx, 100, TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA); - TEST_FAIL_COND((width <= width_old || width > 100), "Invalid fill width."); - - ts->free(ctx); - } - - ctx = ts->create_shaped_text(); - TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed."); - ok = ts->shaped_text_add_string(ctx, test_3, font, 16); - TEST_FAIL_COND(!ok, "Adding text to the buffer failed."); - - width_old = ts->shaped_text_get_width(ctx); - width = ts->shaped_text_fit_to_width(ctx, 100, TextServer::JUSTIFICATION_WORD_BOUND); - TEST_FAIL_COND((width <= width_old || width > 100), "Invalid fill width."); - - ts->free(ctx); - - for (int j = 0; j < font.size(); j++) { - ts->free(font[j]); - } - font.clear(); - } - } - - memdelete(tsman); - } -} -}; // namespace TestTextServer - -#endif // TEST_TEXT_SERVER_H -#endif // TOOLS_ENABLED diff --git a/tests/test_tools.h b/tests/test_tools.h new file mode 100644 index 0000000000..8ee7a4718f --- /dev/null +++ b/tests/test_tools.h @@ -0,0 +1,59 @@ +/*************************************************************************/ +/* test_tools.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 + +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_utils.cpp b/tests/test_utils.cpp index 1666a257a9..11cb6398aa 100644 --- a/tests/test_utils.cpp +++ b/tests/test_utils.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "test_utils.h" +#include "tests/test_utils.h" #include "core/os/os.h" diff --git a/tests/test_utils.h b/tests/test_utils.h index f05ab0bdb1..499ddb84b2 100644 --- a/tests/test_utils.h +++ b/tests/test_utils.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -31,7 +31,7 @@ #ifndef TEST_UTILS_H #define TEST_UTILS_H -#include "core/string/ustring.h" +class String; namespace TestUtils { diff --git a/tests/test_validate_testing.h b/tests/test_validate_testing.h index 6d3eea724c..413a7e351d 100644 --- a/tests/test_validate_testing.h +++ b/tests/test_validate_testing.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 */ @@ -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") { @@ -84,7 +85,7 @@ TEST_SUITE("Validate tests") { Plane plane(Vector3(1, 1, 1), 1.0); INFO(plane); - Quat quat(Vector3(0.5, 1.0, 2.0)); + Quaternion quat(Vector3(0.5, 1.0, 2.0)); INFO(quat); AABB aabb(Vector3(), Vector3(100, 100, 100)); @@ -93,7 +94,7 @@ TEST_SUITE("Validate tests") { Basis basis(quat); INFO(basis); - Transform trans(basis); + Transform3D trans(basis); INFO(trans); Color color(1, 0.5, 0.2, 0.3); @@ -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 |