diff options
Diffstat (limited to 'tests')
29 files changed, 1524 insertions, 244 deletions
diff --git a/tests/core/config/test_project_settings.h b/tests/core/config/test_project_settings.h index 8fc2489f8b..0e1058a626 100644 --- a/tests/core/config/test_project_settings.h +++ b/tests/core/config/test_project_settings.h @@ -126,10 +126,9 @@ TEST_CASE("[ProjectSettings] localize_path") { CHECK_EQ(ProjectSettings::get_singleton()->localize_path("path\\.\\filename"), "res://path/filename"); #endif - // FIXME?: These checks pass, but that doesn't seems correct - CHECK_EQ(ProjectSettings::get_singleton()->localize_path("../filename"), "res://filename"); - CHECK_EQ(ProjectSettings::get_singleton()->localize_path("../path/filename"), "res://path/filename"); - CHECK_EQ(ProjectSettings::get_singleton()->localize_path("..\\path\\filename"), "res://path/filename"); + CHECK_EQ(ProjectSettings::get_singleton()->localize_path("../filename"), "../filename"); + CHECK_EQ(ProjectSettings::get_singleton()->localize_path("../path/filename"), "../path/filename"); + CHECK_EQ(ProjectSettings::get_singleton()->localize_path("..\\path\\filename"), "../path/filename"); CHECK_EQ(ProjectSettings::get_singleton()->localize_path("/testroot/filename"), "/testroot/filename"); CHECK_EQ(ProjectSettings::get_singleton()->localize_path("/testroot/path/filename"), "/testroot/path/filename"); diff --git a/tests/core/io/test_http_client.h b/tests/core/io/test_http_client.h index 96a7735d03..961c653a0a 100644 --- a/tests/core/io/test_http_client.h +++ b/tests/core/io/test_http_client.h @@ -35,6 +35,8 @@ #include "tests/test_macros.h" +#include "modules/modules_enabled.gen.h" + namespace TestHTTPClient { TEST_CASE("[HTTPClient] Instantiation") { @@ -90,6 +92,7 @@ TEST_CASE("[HTTPClient] verify_headers") { ERR_PRINT_ON; } +#if defined(MODULE_MBEDTLS_ENABLED) || defined(WEB_ENABLED) TEST_CASE("[HTTPClient] connect_to_host") { Ref<HTTPClient> client = HTTPClient::create(); String host = "https://www.example.com"; @@ -100,6 +103,7 @@ TEST_CASE("[HTTPClient] connect_to_host") { Error err = client->connect_to_host(host, port, tls_options); CHECK_MESSAGE(err == OK, "Expected OK for successful connection"); } +#endif // MODULE_MBEDTLS_ENABLED || WEB_ENABLED } // namespace TestHTTPClient diff --git a/tests/core/io/test_image.h b/tests/core/io/test_image.h index 7a0cbb13f9..0698c60f2a 100644 --- a/tests/core/io/test_image.h +++ b/tests/core/io/test_image.h @@ -37,6 +37,8 @@ #include "tests/test_utils.h" #include "thirdparty/doctest/doctest.h" +#include "modules/modules_enabled.gen.h" + namespace TestImage { TEST_CASE("[Image] Instantiation") { @@ -78,8 +80,8 @@ TEST_CASE("[Image] Instantiation") { TEST_CASE("[Image] Saving and loading") { Ref<Image> image = memnew(Image(4, 4, false, Image::FORMAT_RGBA8)); - const String save_path_png = OS::get_singleton()->get_cache_path().path_join("image.png"); - const String save_path_exr = OS::get_singleton()->get_cache_path().path_join("image.exr"); + const String save_path_png = TestUtils::get_temp_path("image.png"); + const String save_path_exr = TestUtils::get_temp_path("image.exr"); // Save PNG Error err; @@ -107,6 +109,7 @@ TEST_CASE("[Image] Saving and loading") { image->get_data() == image_load->get_data(), "The loaded image should have the same data as the one that got saved."); +#ifdef MODULE_BMP_ENABLED // Load BMP Ref<Image> image_bmp = memnew(Image()); Ref<FileAccess> f_bmp = FileAccess::open(TestUtils::get_data_path("images/icon.bmp"), FileAccess::READ, &err); @@ -117,7 +120,9 @@ TEST_CASE("[Image] Saving and loading") { CHECK_MESSAGE( image_bmp->load_bmp_from_buffer(data_bmp) == OK, "The BMP image should load successfully."); +#endif // MODULE_BMP_ENABLED +#ifdef MODULE_JPG_ENABLED // Load JPG Ref<Image> image_jpg = memnew(Image()); Ref<FileAccess> f_jpg = FileAccess::open(TestUtils::get_data_path("images/icon.jpg"), FileAccess::READ, &err); @@ -128,7 +133,9 @@ TEST_CASE("[Image] Saving and loading") { CHECK_MESSAGE( image_jpg->load_jpg_from_buffer(data_jpg) == OK, "The JPG image should load successfully."); +#endif // MODULE_JPG_ENABLED +#ifdef MODULE_WEBP_ENABLED // Load WebP Ref<Image> image_webp = memnew(Image()); Ref<FileAccess> f_webp = FileAccess::open(TestUtils::get_data_path("images/icon.webp"), FileAccess::READ, &err); @@ -139,6 +146,7 @@ TEST_CASE("[Image] Saving and loading") { CHECK_MESSAGE( image_webp->load_webp_from_buffer(data_webp) == OK, "The WebP image should load successfully."); +#endif // MODULE_WEBP_ENABLED // Load PNG Ref<Image> image_png = memnew(Image()); @@ -151,6 +159,7 @@ TEST_CASE("[Image] Saving and loading") { image_png->load_png_from_buffer(data_png) == OK, "The PNG image should load successfully."); +#ifdef MODULE_TGA_ENABLED // Load TGA Ref<Image> image_tga = memnew(Image()); Ref<FileAccess> f_tga = FileAccess::open(TestUtils::get_data_path("images/icon.tga"), FileAccess::READ, &err); @@ -161,6 +170,7 @@ TEST_CASE("[Image] Saving and loading") { CHECK_MESSAGE( image_tga->load_tga_from_buffer(data_tga) == OK, "The TGA image should load successfully."); +#endif // MODULE_TGA_ENABLED } TEST_CASE("[Image] Basic getters") { @@ -345,8 +355,8 @@ TEST_CASE("[Image] Custom mipmaps") { uint8_t *data_ptr = data.ptrw(); for (int mip = 0; mip < mipmaps; mip++) { - int mip_offset = 0; - int mip_size = 0; + int64_t mip_offset = 0; + int64_t mip_size = 0; image->get_mipmap_offset_and_size(mip, mip_offset, mip_size); for (int i = 0; i < mip_size; i++) { @@ -368,8 +378,8 @@ TEST_CASE("[Image] Custom mipmaps") { const uint8_t *data_ptr = data.ptr(); for (int mip = 0; mip < mipmaps; mip++) { - int mip_offset = 0; - int mip_size = 0; + int64_t mip_offset = 0; + int64_t mip_size = 0; image_bytes->get_mipmap_offset_and_size(mip, mip_offset, mip_size); for (int i = 0; i < mip_size; i++) { @@ -392,8 +402,8 @@ TEST_CASE("[Image] Custom mipmaps") { const uint8_t *data_ptr = data.ptr(); for (int mip = 0; mip < mipmaps; mip++) { - int mip_offset = 0; - int mip_size = 0; + int64_t mip_offset = 0; + int64_t mip_size = 0; image_rgbaf->get_mipmap_offset_and_size(mip, mip_offset, mip_size); for (int i = 0; i < mip_size; i += 4) { diff --git a/tests/core/io/test_pck_packer.h b/tests/core/io/test_pck_packer.h index fc4534a949..7ef9451963 100644 --- a/tests/core/io/test_pck_packer.h +++ b/tests/core/io/test_pck_packer.h @@ -42,7 +42,7 @@ namespace TestPCKPacker { TEST_CASE("[PCKPacker] Pack an empty PCK file") { PCKPacker pck_packer; - const String output_pck_path = OS::get_singleton()->get_cache_path().path_join("output_empty.pck"); + const String output_pck_path = TestUtils::get_temp_path("output_empty.pck"); CHECK_MESSAGE( pck_packer.pck_start(output_pck_path) == OK, "Starting a PCK file should return an OK error code."); @@ -66,7 +66,7 @@ TEST_CASE("[PCKPacker] Pack an empty PCK file") { TEST_CASE("[PCKPacker] Pack empty with zero alignment invalid") { PCKPacker pck_packer; - const String output_pck_path = OS::get_singleton()->get_cache_path().path_join("output_empty.pck"); + const String output_pck_path = TestUtils::get_temp_path("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; @@ -74,7 +74,7 @@ TEST_CASE("[PCKPacker] Pack empty with zero alignment invalid") { TEST_CASE("[PCKPacker] Pack empty with invalid key") { PCKPacker pck_packer; - const String output_pck_path = OS::get_singleton()->get_cache_path().path_join("output_empty.pck"); + const String output_pck_path = TestUtils::get_temp_path("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; @@ -82,7 +82,7 @@ TEST_CASE("[PCKPacker] Pack empty with invalid key") { 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().path_join("output_with_files.pck"); + const String output_pck_path = TestUtils::get_temp_path("output_with_files.pck"); CHECK_MESSAGE( pck_packer.pck_start(output_pck_path) == OK, "Starting a PCK file should return an OK error code."); diff --git a/tests/core/io/test_resource.h b/tests/core/io/test_resource.h index a83e7f88ba..cb1fa290b3 100644 --- a/tests/core/io/test_resource.h +++ b/tests/core/io/test_resource.h @@ -76,8 +76,8 @@ TEST_CASE("[Resource] Saving and loading") { Ref<Resource> child_resource = memnew(Resource); child_resource->set_name("I'm a child resource"); resource->set_meta("other_resource", child_resource); - const String save_path_binary = OS::get_singleton()->get_cache_path().path_join("resource.res"); - const String save_path_text = OS::get_singleton()->get_cache_path().path_join("resource.tres"); + const String save_path_binary = TestUtils::get_temp_path("resource.res"); + const String save_path_text = TestUtils::get_temp_path("resource.tres"); ResourceSaver::save(resource, save_path_binary); ResourceSaver::save(resource, save_path_text); @@ -123,8 +123,8 @@ TEST_CASE("[Resource] Breaking circular references on save") { resource_b->set_meta("next", resource_c); resource_c->set_meta("next", resource_b); - const String save_path_binary = OS::get_singleton()->get_cache_path().path_join("resource.res"); - const String save_path_text = OS::get_singleton()->get_cache_path().path_join("resource.tres"); + const String save_path_binary = TestUtils::get_temp_path("resource.res"); + const String save_path_text = TestUtils::get_temp_path("resource.tres"); ResourceSaver::save(resource_a, save_path_binary); // Suppress expected errors caused by the resources above being uncached. ERR_PRINT_OFF; diff --git a/tests/core/math/test_basis.h b/tests/core/math/test_basis.h index a9bc2e9b99..f8c5ef279d 100644 --- a/tests/core/math/test_basis.h +++ b/tests/core/math/test_basis.h @@ -93,9 +93,9 @@ void test_rotation(Vector3 deg_original_euler, EulerOrder rot_order) { Basis res = to_rotation.inverse() * rotation_from_computed_euler; - CHECK_MESSAGE((res.get_column(0) - Vector3(1.0, 0.0, 0.0)).length() <= 0.1, vformat("Fail due to X %s\n", String(res.get_column(0))).utf8().ptr()); - CHECK_MESSAGE((res.get_column(1) - Vector3(0.0, 1.0, 0.0)).length() <= 0.1, vformat("Fail due to Y %s\n", String(res.get_column(1))).utf8().ptr()); - CHECK_MESSAGE((res.get_column(2) - Vector3(0.0, 0.0, 1.0)).length() <= 0.1, vformat("Fail due to Z %s\n", String(res.get_column(2))).utf8().ptr()); + CHECK_MESSAGE((res.get_column(0) - Vector3(1.0, 0.0, 0.0)).length() <= 0.1, vformat("Fail due to X %s\n", String(res.get_column(0)))); + CHECK_MESSAGE((res.get_column(1) - Vector3(0.0, 1.0, 0.0)).length() <= 0.1, vformat("Fail due to Y %s\n", String(res.get_column(1)))); + CHECK_MESSAGE((res.get_column(2) - Vector3(0.0, 0.0, 1.0)).length() <= 0.1, vformat("Fail due to Z %s\n", String(res.get_column(2)))); // Double check `to_rotation` decomposing with XYZ rotation order. const Vector3 euler_xyz_from_rotation = to_rotation.get_euler(EulerOrder::XYZ); @@ -103,13 +103,13 @@ void test_rotation(Vector3 deg_original_euler, EulerOrder rot_order) { res = to_rotation.inverse() * rotation_from_xyz_computed_euler; - CHECK_MESSAGE((res.get_column(0) - Vector3(1.0, 0.0, 0.0)).length() <= 0.1, vformat("Double check with XYZ rot order failed, due to X %s\n", String(res.get_column(0))).utf8().ptr()); - CHECK_MESSAGE((res.get_column(1) - Vector3(0.0, 1.0, 0.0)).length() <= 0.1, vformat("Double check with XYZ rot order failed, due to Y %s\n", String(res.get_column(1))).utf8().ptr()); - CHECK_MESSAGE((res.get_column(2) - Vector3(0.0, 0.0, 1.0)).length() <= 0.1, vformat("Double check with XYZ rot order failed, due to Z %s\n", String(res.get_column(2))).utf8().ptr()); + CHECK_MESSAGE((res.get_column(0) - Vector3(1.0, 0.0, 0.0)).length() <= 0.1, vformat("Double check with XYZ rot order failed, due to X %s\n", String(res.get_column(0)))); + CHECK_MESSAGE((res.get_column(1) - Vector3(0.0, 1.0, 0.0)).length() <= 0.1, vformat("Double check with XYZ rot order failed, due to Y %s\n", String(res.get_column(1)))); + CHECK_MESSAGE((res.get_column(2) - Vector3(0.0, 0.0, 1.0)).length() <= 0.1, vformat("Double check with XYZ rot order failed, due to Z %s\n", String(res.get_column(2)))); - INFO(vformat("Rotation order: %s\n.", get_rot_order_name(rot_order)).utf8().ptr()); - INFO(vformat("Original Rotation: %s\n", String(deg_original_euler)).utf8().ptr()); - INFO(vformat("Quaternion to rotation order: %s\n", String(rad2deg(euler_from_rotation))).utf8().ptr()); + INFO(vformat("Rotation order: %s\n.", get_rot_order_name(rot_order))); + INFO(vformat("Original Rotation: %s\n", String(deg_original_euler))); + INFO(vformat("Quaternion to rotation order: %s\n", String(rad2deg(euler_from_rotation)))); } TEST_CASE("[Basis] Euler conversions") { diff --git a/tests/core/math/test_transform_2d.h b/tests/core/math/test_transform_2d.h index 36d27ce7a9..6d3c80e5ca 100644 --- a/tests/core/math/test_transform_2d.h +++ b/tests/core/math/test_transform_2d.h @@ -45,48 +45,132 @@ Transform2D identity() { return Transform2D(); } +TEST_CASE("[Transform2D] Default constructor") { + Transform2D default_constructor = Transform2D(); + CHECK(default_constructor == Transform2D(Vector2(1, 0), Vector2(0, 1), Vector2(0, 0))); +} + +TEST_CASE("[Transform2D] Copy constructor") { + Transform2D T = create_dummy_transform(); + Transform2D copy_constructor = Transform2D(T); + CHECK(T == copy_constructor); +} + +TEST_CASE("[Transform2D] Constructor from angle and position") { + constexpr float ROTATION = Math_PI / 4; + const Vector2 TRANSLATION = Vector2(20, -20); + + const Transform2D test = Transform2D(ROTATION, TRANSLATION); + const Transform2D expected = Transform2D().rotated(ROTATION).translated(TRANSLATION); + CHECK(test == expected); +} + +TEST_CASE("[Transform2D] Constructor from angle, scale, skew and position") { + constexpr float ROTATION = Math_PI / 2; + const Vector2 SCALE = Vector2(2, 0.5); + constexpr float SKEW = Math_PI / 4; + const Vector2 TRANSLATION = Vector2(30, 0); + + const Transform2D test = Transform2D(ROTATION, SCALE, SKEW, TRANSLATION); + Transform2D expected = Transform2D().scaled(SCALE).rotated(ROTATION).translated(TRANSLATION); + expected.set_skew(SKEW); + + CHECK(test.is_equal_approx(expected)); +} + +TEST_CASE("[Transform2D] Constructor from raw values") { + const Transform2D test = Transform2D(1, 2, 3, 4, 5, 6); + const Transform2D expected = Transform2D(Vector2(1, 2), Vector2(3, 4), Vector2(5, 6)); + CHECK(test == expected); +} + +TEST_CASE("[Transform2D] xform") { + const Vector2 v = Vector2(2, 3); + const Transform2D T = Transform2D(Vector2(1, 2), Vector2(3, 4), Vector2(5, 6)); + const Vector2 expected = Vector2(1 * 2 + 3 * 3 + 5 * 1, 2 * 2 + 4 * 3 + 6 * 1); + CHECK(T.xform(v) == expected); +} + +TEST_CASE("[Transform2D] Basis xform") { + const Vector2 v = Vector2(2, 2); + const Transform2D T1 = Transform2D(Vector2(1, 2), Vector2(3, 4), Vector2(0, 0)); + + // Both versions should be the same when the origin is (0,0). + CHECK(T1.basis_xform(v) == T1.xform(v)); + + const Transform2D T2 = Transform2D(Vector2(1, 2), Vector2(3, 4), Vector2(5, 6)); + + // Each version should be different when the origin is not (0,0). + CHECK_FALSE(T2.basis_xform(v) == T2.xform(v)); +} + +TEST_CASE("[Transform2D] Affine inverse") { + const Transform2D orig = create_dummy_transform(); + const Transform2D affine_inverted = orig.affine_inverse(); + const Transform2D affine_inverted_again = affine_inverted.affine_inverse(); + CHECK(affine_inverted_again == orig); +} + +TEST_CASE("[Transform2D] Orthonormalized") { + const Transform2D T = create_dummy_transform(); + const Transform2D orthonormalized_T = T.orthonormalized(); + + // Check each basis has length 1. + CHECK(Math::is_equal_approx(orthonormalized_T[0].length_squared(), 1)); + CHECK(Math::is_equal_approx(orthonormalized_T[1].length_squared(), 1)); + + const Vector2 vx = Vector2(orthonormalized_T[0].x, orthonormalized_T[1].x); + const Vector2 vy = Vector2(orthonormalized_T[0].y, orthonormalized_T[1].y); + + // Check the basis are orthogonal. + CHECK(Math::is_equal_approx(orthonormalized_T.tdotx(vx), 1)); + CHECK(Math::is_equal_approx(orthonormalized_T.tdotx(vy), 0)); + CHECK(Math::is_equal_approx(orthonormalized_T.tdoty(vx), 0)); + CHECK(Math::is_equal_approx(orthonormalized_T.tdoty(vy), 1)); +} + TEST_CASE("[Transform2D] translation") { - Vector2 offset = Vector2(1, 2); + const Vector2 offset = Vector2(1, 2); // Both versions should give the same result applied to identity. CHECK(identity().translated(offset) == identity().translated_local(offset)); // Check both versions against left and right multiplications. - Transform2D orig = create_dummy_transform(); - Transform2D T = identity().translated(offset); + const Transform2D orig = create_dummy_transform(); + const Transform2D T = identity().translated(offset); CHECK(orig.translated(offset) == T * orig); CHECK(orig.translated_local(offset) == orig * T); } TEST_CASE("[Transform2D] scaling") { - Vector2 scaling = Vector2(1, 2); + const Vector2 scaling = Vector2(1, 2); // Both versions should give the same result applied to identity. CHECK(identity().scaled(scaling) == identity().scaled_local(scaling)); // Check both versions against left and right multiplications. - Transform2D orig = create_dummy_transform(); - Transform2D S = identity().scaled(scaling); + const Transform2D orig = create_dummy_transform(); + const Transform2D S = identity().scaled(scaling); CHECK(orig.scaled(scaling) == S * orig); CHECK(orig.scaled_local(scaling) == orig * S); } TEST_CASE("[Transform2D] rotation") { - real_t phi = 1.0; + constexpr real_t phi = 1.0; // Both versions should give the same result applied to identity. CHECK(identity().rotated(phi) == identity().rotated_local(phi)); // Check both versions against left and right multiplications. - Transform2D orig = create_dummy_transform(); - Transform2D R = identity().rotated(phi); + const Transform2D orig = create_dummy_transform(); + const Transform2D R = identity().rotated(phi); CHECK(orig.rotated(phi) == R * orig); CHECK(orig.rotated_local(phi) == orig * R); } TEST_CASE("[Transform2D] Interpolation") { - Transform2D rotate_scale_skew_pos = Transform2D(Math::deg_to_rad(170.0), Vector2(3.6, 8.0), Math::deg_to_rad(20.0), Vector2(2.4, 6.8)); - Transform2D rotate_scale_skew_pos_halfway = Transform2D(Math::deg_to_rad(85.0), Vector2(2.3, 4.5), Math::deg_to_rad(10.0), Vector2(1.2, 3.4)); + const Transform2D rotate_scale_skew_pos = Transform2D(Math::deg_to_rad(170.0), Vector2(3.6, 8.0), Math::deg_to_rad(20.0), Vector2(2.4, 6.8)); + const Transform2D rotate_scale_skew_pos_halfway = Transform2D(Math::deg_to_rad(85.0), Vector2(2.3, 4.5), Math::deg_to_rad(10.0), Vector2(1.2, 3.4)); Transform2D interpolated = Transform2D().interpolate_with(rotate_scale_skew_pos, 0.5); CHECK(interpolated.get_origin().is_equal_approx(rotate_scale_skew_pos_halfway.get_origin())); CHECK(interpolated.get_rotation() == doctest::Approx(rotate_scale_skew_pos_halfway.get_rotation())); @@ -98,8 +182,8 @@ TEST_CASE("[Transform2D] Interpolation") { } TEST_CASE("[Transform2D] Finite number checks") { - const Vector2 x(0, 1); - const Vector2 infinite(NAN, NAN); + const Vector2 x = Vector2(0, 1); + const Vector2 infinite = Vector2(NAN, NAN); CHECK_MESSAGE( Transform2D(x, x, x).is_finite(), diff --git a/tests/core/math/test_vector2.h b/tests/core/math/test_vector2.h index fc3fd6a87d..7bd494ec80 100644 --- a/tests/core/math/test_vector2.h +++ b/tests/core/math/test_vector2.h @@ -353,7 +353,6 @@ TEST_CASE("[Vector2] Plane methods") { const Vector2 vector = Vector2(1.2, 3.4); const Vector2 vector_y = Vector2(0, 1); const Vector2 vector_normal = Vector2(0.95879811270838721622267, 0.2840883296913739899919); - const Vector2 vector_non_normal = Vector2(5.4, 1.6); const real_t p_d = 99.1; CHECK_MESSAGE( vector.bounce(vector_y) == Vector2(1.2, -3.4), @@ -383,6 +382,8 @@ TEST_CASE("[Vector2] Plane methods") { vector.slide(vector_normal).is_equal_approx(Vector2(-0.8292559899117276166456, 2.798738965952080706179)), "Vector2 slide with normal should return expected value."); // There's probably a better way to test these ones? +#ifdef MATH_CHECKS + const Vector2 vector_non_normal = Vector2(5.4, 1.6); ERR_PRINT_OFF; CHECK_MESSAGE( vector.bounce(vector_non_normal).is_equal_approx(Vector2()), @@ -394,6 +395,7 @@ TEST_CASE("[Vector2] Plane methods") { vector.slide(vector_non_normal).is_equal_approx(Vector2()), "Vector2 slide should return empty Vector2 with non-normalized input."); ERR_PRINT_ON; +#endif // MATH_CHECKS } TEST_CASE("[Vector2] Rounding methods") { diff --git a/tests/core/math/test_vector3.h b/tests/core/math/test_vector3.h index ca0aa02882..4cab753d6f 100644 --- a/tests/core/math/test_vector3.h +++ b/tests/core/math/test_vector3.h @@ -368,7 +368,6 @@ TEST_CASE("[Vector3] Plane methods") { const Vector3 vector = Vector3(1.2, 3.4, 5.6); const Vector3 vector_y = Vector3(0, 1, 0); const Vector3 vector_normal = Vector3(0.88763458893247992491, 0.26300284116517923701, 0.37806658417494515320); - const Vector3 vector_non_normal = Vector3(5.4, 1.6, 2.3); 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."); @@ -394,6 +393,8 @@ TEST_CASE("[Vector3] Plane methods") { vector.slide(vector_normal).is_equal_approx(Vector3(-2.41848149148878681437, 2.32785733585517427722237, 4.0587949202918130235)), "Vector3 slide with normal should return expected value."); // There's probably a better way to test these ones? +#ifdef MATH_CHECKS + const Vector3 vector_non_normal = Vector3(5.4, 1.6, 2.3); ERR_PRINT_OFF; CHECK_MESSAGE( vector.bounce(vector_non_normal).is_equal_approx(Vector3()), @@ -405,6 +406,7 @@ TEST_CASE("[Vector3] Plane methods") { vector.slide(vector_non_normal).is_equal_approx(Vector3()), "Vector3 slide should return empty Vector3 with non-normalized input."); ERR_PRINT_ON; +#endif // MATH_CHECKS } TEST_CASE("[Vector3] Rounding methods") { diff --git a/tests/core/object/test_class_db.h b/tests/core/object/test_class_db.h index 381d759e5b..d2d7b6a8b2 100644 --- a/tests/core/object/test_class_db.h +++ b/tests/core/object/test_class_db.h @@ -139,6 +139,7 @@ struct NamesCache { StringName vector2_type = StaticCString::create("Vector2"); StringName rect2_type = StaticCString::create("Rect2"); StringName vector3_type = StaticCString::create("Vector3"); + StringName vector4_type = StaticCString::create("Vector4"); // Object not included as it must be checked for all derived classes static constexpr int nullable_types_count = 18; @@ -247,6 +248,8 @@ bool arg_default_value_is_assignable_to_type(const Context &p_context, const Var case Variant::VECTOR2: case Variant::RECT2: case Variant::VECTOR3: + case Variant::VECTOR4: + case Variant::PROJECTION: case Variant::RID: case Variant::ARRAY: case Variant::DICTIONARY: @@ -274,13 +277,15 @@ bool arg_default_value_is_assignable_to_type(const Context &p_context, const Var 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()); - default: - if (r_err_msg) { - *r_err_msg = "Unexpected Variant type: " + itos(p_val.get_type()); - } + case Variant::VECTOR4I: + return p_arg_type.name == p_context.names_cache.vector4_type || + p_arg_type.name == Variant::get_type_name(p_val.get_type()); + case Variant::VARIANT_MAX: break; } - + if (r_err_msg) { + *r_err_msg = "Unexpected Variant type: " + itos(p_val.get_type()); + } return false; } @@ -375,8 +380,10 @@ void validate_property(const Context &p_context, const ExposedClass &p_class, co } void validate_argument(const Context &p_context, const ExposedClass &p_class, const String &p_owner_name, const String &p_owner_type, const ArgumentData &p_arg) { +#ifdef DEBUG_METHODS_ENABLED TEST_COND((p_arg.name.is_empty() || p_arg.name.begins_with("_unnamed_arg")), vformat("Unnamed argument in position %d of %s '%s.%s'.", p_arg.position, p_owner_type, p_class.name, p_owner_name)); +#endif // DEBUG_METHODS_ENABLED const ExposedClass *arg_class = p_context.find_exposed_class(p_arg.type); if (arg_class) { @@ -403,7 +410,7 @@ void validate_argument(const Context &p_context, const ExposedClass &p_class, co err_msg += " " + type_error_msg; } - TEST_COND(!arg_defval_assignable_to_type, err_msg.utf8().get_data()); + TEST_COND(!arg_defval_assignable_to_type, err_msg); } } @@ -588,7 +595,7 @@ void add_exposed_classes(Context &r_context) { exposed_class.name, method.name); TEST_FAIL_COND_WARN( (exposed_class.name != r_context.names_cache.object_class || String(method.name) != "free"), - warn_msg.utf8().get_data()); + warn_msg); } else if (return_info.type == Variant::INT && return_info.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) { method.return_type.name = return_info.class_name; @@ -718,7 +725,7 @@ void add_exposed_classes(Context &r_context) { "Signal name conflicts with %s: '%s.%s.", method_conflict ? "method" : "property", class_name, signal.name); TEST_FAIL_COND((method_conflict || exposed_class.find_method_by_name(signal.name)), - warn_msg.utf8().get_data()); + warn_msg); exposed_class.signals_.push_back(signal); } diff --git a/tests/core/os/test_os.h b/tests/core/os/test_os.h index 6ee0ff82e7..1e2f5e222b 100644 --- a/tests/core/os/test_os.h +++ b/tests/core/os/test_os.h @@ -163,12 +163,14 @@ TEST_CASE("[OS] Processor count and memory information") { CHECK_MESSAGE( OS::get_singleton()->get_processor_count() >= 1, "The returned processor count should be greater than zero."); +#ifdef DEBUG_ENABLED CHECK_MESSAGE( OS::get_singleton()->get_static_memory_usage() >= 1, "The returned static memory usage should be greater than zero."); CHECK_MESSAGE( OS::get_singleton()->get_static_memory_peak_usage() >= 1, "The returned static memory peak usage should be greater than zero."); +#endif // DEBUG_ENABLED } TEST_CASE("[OS] Execute") { diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h index cf57183a02..933eeff524 100644 --- a/tests/core/string/test_string.h +++ b/tests/core/string/test_string.h @@ -638,64 +638,90 @@ TEST_CASE("[String] Ends with") { } TEST_CASE("[String] Splitting") { - String s = "Mars,Jupiter,Saturn,Uranus"; - const char *slices_l[3] = { "Mars", "Jupiter", "Saturn,Uranus" }; - MULTICHECK_SPLIT(s, split, ",", true, 2, slices_l, 3); - - const char *slices_r[3] = { "Mars,Jupiter", "Saturn", "Uranus" }; - MULTICHECK_SPLIT(s, rsplit, ",", true, 2, slices_r, 3); - - s = "test"; - const char *slices_3[4] = { "t", "e", "s", "t" }; - MULTICHECK_SPLIT(s, split, "", true, 0, slices_3, 4); - - s = ""; - const char *slices_4[1] = { "" }; - MULTICHECK_SPLIT(s, split, "", true, 0, slices_4, 1); - MULTICHECK_SPLIT(s, split, "", false, 0, slices_4, 0); - - s = "Mars Jupiter Saturn Uranus"; - const char *slices_s[4] = { "Mars", "Jupiter", "Saturn", "Uranus" }; - Vector<String> l = s.split_spaces(); - for (int i = 0; i < l.size(); i++) { - CHECK(l[i] == slices_s[i]); + { + const String s = "Mars,Jupiter,Saturn,Uranus"; + + const char *slices_l[3] = { "Mars", "Jupiter", "Saturn,Uranus" }; + MULTICHECK_SPLIT(s, split, ",", true, 2, slices_l, 3); + + const char *slices_r[3] = { "Mars,Jupiter", "Saturn", "Uranus" }; + MULTICHECK_SPLIT(s, rsplit, ",", true, 2, slices_r, 3); } - s = "1.2;2.3 4.5"; - const double slices_d[3] = { 1.2, 2.3, 4.5 }; + { + const String s = "test"; + const char *slices[4] = { "t", "e", "s", "t" }; + MULTICHECK_SPLIT(s, split, "", true, 0, slices, 4); + } - Vector<double> d_arr; - d_arr = s.split_floats(";"); - CHECK(d_arr.size() == 2); - for (int i = 0; i < d_arr.size(); i++) { - CHECK(ABS(d_arr[i] - slices_d[i]) <= 0.00001); + { + const String s = ""; + const char *slices[1] = { "" }; + MULTICHECK_SPLIT(s, split, "", true, 0, slices, 1); + MULTICHECK_SPLIT(s, split, "", false, 0, slices, 0); } - Vector<String> keys; - keys.push_back(";"); - keys.push_back(" "); - - Vector<float> f_arr; - f_arr = s.split_floats_mk(keys); - CHECK(f_arr.size() == 3); - for (int i = 0; i < f_arr.size(); i++) { - CHECK(ABS(f_arr[i] - slices_d[i]) <= 0.00001); + { + const String s = "Mars Jupiter Saturn Uranus"; + const char *slices[4] = { "Mars", "Jupiter", "Saturn", "Uranus" }; + Vector<String> l = s.split_spaces(); + for (int i = 0; i < l.size(); i++) { + CHECK(l[i] == slices[i]); + } } - s = "1;2 4"; - const int slices_i[3] = { 1, 2, 4 }; + { + const String s = "1.2;2.3 4.5"; + const double slices[3] = { 1.2, 2.3, 4.5 }; + + const Vector<double> d_arr = s.split_floats(";"); + CHECK(d_arr.size() == 2); + for (int i = 0; i < d_arr.size(); i++) { + CHECK(ABS(d_arr[i] - slices[i]) <= 0.00001); + } - Vector<int> ii; - ii = s.split_ints(";"); - CHECK(ii.size() == 2); - for (int i = 0; i < ii.size(); i++) { - CHECK(ii[i] == slices_i[i]); + const Vector<String> keys = { ";", " " }; + const Vector<float> f_arr = s.split_floats_mk(keys); + CHECK(f_arr.size() == 3); + for (int i = 0; i < f_arr.size(); i++) { + CHECK(ABS(f_arr[i] - slices[i]) <= 0.00001); + } } - ii = s.split_ints_mk(keys); - CHECK(ii.size() == 3); - for (int i = 0; i < ii.size(); i++) { - CHECK(ii[i] == slices_i[i]); + { + const String s = " -2.0 5"; + const double slices[10] = { 0, -2, 0, 0, 0, 0, 0, 0, 0, 5 }; + + const Vector<double> arr = s.split_floats(" "); + CHECK(arr.size() == 10); + for (int i = 0; i < arr.size(); i++) { + CHECK(ABS(arr[i] - slices[i]) <= 0.00001); + } + + const Vector<String> keys = { ";", " " }; + const Vector<float> mk = s.split_floats_mk(keys); + CHECK(mk.size() == 10); + for (int i = 0; i < mk.size(); i++) { + CHECK(mk[i] == slices[i]); + } + } + + { + const String s = "1;2 4"; + const int slices[3] = { 1, 2, 4 }; + + const Vector<int> arr = s.split_ints(";"); + CHECK(arr.size() == 2); + for (int i = 0; i < arr.size(); i++) { + CHECK(arr[i] == slices[i]); + } + + const Vector<String> keys = { ";", " " }; + const Vector<int> mk = s.split_ints_mk(keys); + CHECK(mk.size() == 3); + for (int i = 0; i < mk.size(); i++) { + CHECK(mk[i] == slices[i]); + } } } @@ -1594,7 +1620,7 @@ TEST_CASE("[String] Path functions") { static const char *base_name[8] = { "C:\\Godot\\project\\test", "/Godot/project/test", "../Godot/project/test", "Godot\\test", "C:\\test", "res://test", "user://test", "/" }; static const char *ext[8] = { "tscn", "xscn", "scn", "doc", "", "", "", "test" }; static const char *file[8] = { "test.tscn", "test.xscn", "test.scn", "test.doc", "test.", "test", "test", ".test" }; - static const char *simplified[8] = { "C:/Godot/project/test.tscn", "/Godot/project/test.xscn", "Godot/project/test.scn", "Godot/test.doc", "C:/test.", "res://test", "user://test", "/.test" }; + static const char *simplified[8] = { "C:/Godot/project/test.tscn", "/Godot/project/test.xscn", "../Godot/project/test.scn", "Godot/test.doc", "C:/test.", "res://test", "user://test", "/.test" }; static const bool abs[8] = { true, true, false, false, true, true, true, true }; for (int i = 0; i < 8; i++) { diff --git a/tests/core/string/test_translation.h b/tests/core/string/test_translation.h index acdd851b29..7c389191e3 100644 --- a/tests/core/string/test_translation.h +++ b/tests/core/string/test_translation.h @@ -34,6 +34,7 @@ #include "core/string/optimized_translation.h" #include "core/string/translation.h" #include "core/string/translation_po.h" +#include "core/string/translation_server.h" #ifdef TOOLS_ENABLED #include "editor/import/resource_importer_csv_translation.h" diff --git a/tests/core/string/test_translation_server.h b/tests/core/string/test_translation_server.h index 2c20574309..ac1599f2e8 100644 --- a/tests/core/string/test_translation_server.h +++ b/tests/core/string/test_translation_server.h @@ -31,7 +31,7 @@ #ifndef TEST_TRANSLATION_SERVER_H #define TEST_TRANSLATION_SERVER_H -#include "core/string/translation.h" +#include "core/string/translation_server.h" #include "tests/test_macros.h" diff --git a/tests/core/variant/test_array.h b/tests/core/variant/test_array.h index c54854e4d7..787b8f39d9 100644 --- a/tests/core/variant/test_array.h +++ b/tests/core/variant/test_array.h @@ -597,6 +597,43 @@ TEST_CASE("[Array] Iteration and modification") { a4.clear(); } +TEST_CASE("[Array] Typed copying") { + TypedArray<int> a1; + a1.push_back(1); + + TypedArray<double> a2; + a2.push_back(1.0); + + Array a3 = a1; + TypedArray<int> a4 = a3; + + Array a5 = a2; + TypedArray<int> a6 = a5; + + a3[0] = 2; + a4[0] = 3; + + // Same typed TypedArray should be shared. + CHECK_EQ(a1[0], Variant(3)); + CHECK_EQ(a3[0], Variant(3)); + CHECK_EQ(a4[0], Variant(3)); + + a5[0] = 2.0; + a6[0] = 3.0; + + // Different typed TypedArray should not be shared. + CHECK_EQ(a2[0], Variant(2.0)); + CHECK_EQ(a5[0], Variant(2.0)); + CHECK_EQ(a6[0], Variant(3.0)); + + a1.clear(); + a2.clear(); + a3.clear(); + a4.clear(); + a5.clear(); + a6.clear(); +} + } // namespace TestArray #endif // TEST_ARRAY_H diff --git a/tests/display_server_mock.h b/tests/display_server_mock.h index e4946995a7..b44ff06b35 100644 --- a/tests/display_server_mock.h +++ b/tests/display_server_mock.h @@ -36,7 +36,7 @@ #include "servers/rendering/dummy/rasterizer_dummy.h" // Specialized DisplayServer for unittests based on DisplayServerHeadless, that -// additionally supports rudimentary InputEvent handling and mouse position. +// additionally supports things like mouse enter/exit events and clipboard. class DisplayServerMock : public DisplayServerHeadless { private: friend class DisplayServer; @@ -45,7 +45,6 @@ private: CursorShape cursor_shape = CursorShape::CURSOR_ARROW; bool window_over = false; Callable event_callback; - Callable input_event_callback; String clipboard_text; String primary_clipboard_text; @@ -62,16 +61,6 @@ private: return memnew(DisplayServerMock()); } - static void _dispatch_input_events(const Ref<InputEvent> &p_event) { - static_cast<DisplayServerMock *>(get_singleton())->_dispatch_input_event(p_event); - } - - void _dispatch_input_event(const Ref<InputEvent> &p_event) { - if (input_event_callback.is_valid()) { - input_event_callback.call(p_event); - } - } - void _set_mouse_position(const Point2i &p_position) { if (mouse_position == p_position) { return; @@ -153,18 +142,9 @@ public: event_callback = p_callable; } - virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override { - input_event_callback = p_callable; - } - static void register_mock_driver() { register_create_function("mock", create_func, get_rendering_drivers_func); } - - DisplayServerMock() { - Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events); - } - ~DisplayServerMock() {} }; #endif // DISPLAY_SERVER_MOCK_H diff --git a/tests/scene/test_audio_stream_wav.h b/tests/scene/test_audio_stream_wav.h index ed1697929e..e8f3c9e8f5 100644 --- a/tests/scene/test_audio_stream_wav.h +++ b/tests/scene/test_audio_stream_wav.h @@ -115,7 +115,7 @@ Vector<uint8_t> gen_pcm16_test(float wav_rate, int wav_count, bool stereo) { } void run_test(String file_name, AudioStreamWAV::Format data_format, bool stereo, float wav_rate, float wav_count) { - String save_path = OS::get_singleton()->get_cache_path().path_join(file_name); + String save_path = TestUtils::get_temp_path(file_name); Vector<uint8_t> test_data; if (data_format == AudioStreamWAV::FORMAT_8_BITS) { @@ -200,7 +200,7 @@ TEST_CASE("[AudioStreamWAV] Alternate mix rate") { } TEST_CASE("[AudioStreamWAV] save_to_wav() adds '.wav' file extension automatically") { - String save_path = OS::get_singleton()->get_cache_path().path_join("test_wav_extension"); + String save_path = TestUtils::get_temp_path("test_wav_extension"); Vector<uint8_t> test_data = gen_pcm8_test(WAV_RATE, WAV_COUNT, false); Ref<AudioStreamWAV> stream = memnew(AudioStreamWAV); stream->set_data(test_data); @@ -230,7 +230,7 @@ TEST_CASE("[AudioStreamWAV] Save empty file") { } TEST_CASE("[AudioStreamWAV] Saving IMA ADPCM is not supported") { - String save_path = OS::get_singleton()->get_cache_path().path_join("test_adpcm.wav"); + String save_path = TestUtils::get_temp_path("test_adpcm.wav"); Ref<AudioStreamWAV> stream = memnew(AudioStreamWAV); stream->set_format(AudioStreamWAV::FORMAT_IMA_ADPCM); ERR_PRINT_OFF; diff --git a/tests/scene/test_code_edit.h b/tests/scene/test_code_edit.h index 87d5015451..9ec1b812df 100644 --- a/tests/scene/test_code_edit.h +++ b/tests/scene/test_code_edit.h @@ -3331,6 +3331,45 @@ TEST_CASE("[SceneTree][CodeEdit] folding") { CHECK_FALSE(code_edit->is_line_folded(1)); } + SUBCASE("[CodeEdit] actions unfold") { + // add_selection_for_next_occurrence unfolds. + code_edit->set_text("test\n\tline1 test\n\t\tline 2\ntest2"); + code_edit->select(0, 0, 0, 4); + code_edit->fold_line(0); + CHECK(code_edit->is_line_folded(0)); + code_edit->add_selection_for_next_occurrence(); + + CHECK(code_edit->get_caret_count() == 2); + CHECK(code_edit->has_selection(0)); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_caret_column() == 4); + CHECK(code_edit->get_selection_origin_column() == 0); + CHECK(code_edit->has_selection(1)); + CHECK(code_edit->get_caret_line(1) == 1); + CHECK(code_edit->get_selection_origin_line(1) == 1); + CHECK(code_edit->get_caret_column(1) == 11); + CHECK(code_edit->get_selection_origin_column(1) == 7); + CHECK_FALSE(code_edit->is_line_folded(0)); + code_edit->remove_secondary_carets(); + + // skip_selection_for_next_occurrence unfolds. + code_edit->select(0, 0, 0, 4); + code_edit->fold_line(0); + CHECK(code_edit->is_line_folded(0)); + code_edit->skip_selection_for_next_occurrence(); + + CHECK(code_edit->get_caret_count() == 1); + CHECK(code_edit->has_selection(0)); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_selection_origin_line() == 1); + CHECK(code_edit->get_caret_column() == 11); + CHECK(code_edit->get_selection_origin_column() == 7); + CHECK_FALSE(code_edit->is_line_folded(0)); + code_edit->remove_secondary_carets(); + code_edit->deselect(); + } + SUBCASE("[CodeEdit] toggle folding carets") { code_edit->set_text("test\n\tline1\ntest2\n\tline2"); @@ -4870,6 +4909,17 @@ TEST_CASE("[SceneTree][CodeEdit] text manipulation") { CHECK(code_edit->get_caret_line() == 0); CHECK(code_edit->get_caret_column() == 1); + // Does nothing at the first line when selection ends at column 0. + code_edit->set_text("test\nlines\nto\n\nmove\naround"); + code_edit->select(0, 0, 1, 0); + code_edit->move_lines_up(); + CHECK(code_edit->get_text() == "test\nlines\nto\n\nmove\naround"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_selection_origin_column() == 0); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 0); + // Works on empty line. code_edit->set_text("test\nlines\nto\n\nmove\naround"); code_edit->set_caret_line(3); @@ -4931,9 +4981,9 @@ TEST_CASE("[SceneTree][CodeEdit] text manipulation") { CHECK_FALSE(code_edit->has_selection(2)); CHECK(code_edit->get_caret_line(2) == 3); CHECK(code_edit->get_caret_column(2) == 4); + code_edit->remove_secondary_carets(); // Move multiple separate lines with multiple selections. - code_edit->remove_secondary_carets(); code_edit->set_text("test\nlines\nto\n\nmove\naround"); code_edit->select(2, 2, 1, 4); code_edit->add_caret(5, 0); @@ -4951,6 +5001,44 @@ TEST_CASE("[SceneTree][CodeEdit] text manipulation") { CHECK(code_edit->get_selection_origin_column(1) == 0); CHECK(code_edit->get_caret_line(1) == 4); CHECK(code_edit->get_caret_column(1) == 1); + code_edit->remove_secondary_carets(); + + // Move lines with adjacent selections that end at column 0. + code_edit->set_text("test\nlines\nto\n\nmove\naround"); + code_edit->select(1, 2, 2, 0); + code_edit->add_caret(2, 2); + code_edit->select(2, 2, 3, 0, 1); + code_edit->move_lines_up(); + CHECK(code_edit->get_text() == "lines\nto\ntest\n\nmove\naround"); + CHECK(code_edit->get_caret_count() == 2); + CHECK(code_edit->has_selection(0)); + CHECK(code_edit->get_selection_origin_line(0) == 0); + CHECK(code_edit->get_selection_origin_column(0) == 2); + CHECK(code_edit->get_caret_line(0) == 1); + CHECK(code_edit->get_caret_column(0) == 0); + CHECK(code_edit->has_selection(1)); + CHECK(code_edit->get_selection_origin_line(1) == 1); + CHECK(code_edit->get_selection_origin_column(1) == 2); + CHECK(code_edit->get_caret_line(1) == 2); + CHECK(code_edit->get_caret_column(1) == 0); + code_edit->remove_secondary_carets(); + code_edit->deselect(); + + code_edit->set_line_folding_enabled(true); + + // Move line up into a folded region unfolds it. + code_edit->set_text("test\n\tline1 test\n\t\tline 2\ntest2"); + code_edit->set_caret_line(3); + code_edit->set_caret_column(0); + code_edit->fold_line(0); + CHECK(code_edit->is_line_folded(0)); + code_edit->move_lines_up(); + CHECK(code_edit->get_caret_count() == 1); + CHECK_FALSE(code_edit->has_selection(0)); + CHECK(code_edit->get_caret_line() == 2); + CHECK(code_edit->get_caret_column() == 0); + CHECK(code_edit->get_text() == "test\n\tline1 test\ntest2\n\t\tline 2"); + CHECK_FALSE(code_edit->is_line_folded(0)); } SUBCASE("[SceneTree][CodeEdit] move lines down") { @@ -4985,6 +5073,17 @@ TEST_CASE("[SceneTree][CodeEdit] text manipulation") { CHECK(code_edit->get_caret_line() == 5); CHECK(code_edit->get_caret_column() == 1); + // Does nothing at the last line when selection ends at column 0. + code_edit->set_text("test\nlines\nto\n\nmove\naround"); + code_edit->select(4, 0, 5, 0); + code_edit->move_lines_down(); + CHECK(code_edit->get_text() == "test\nlines\nto\n\nmove\naround"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 4); + CHECK(code_edit->get_selection_origin_column() == 0); + CHECK(code_edit->get_caret_line() == 5); + CHECK(code_edit->get_caret_column() == 0); + // Works on empty line. code_edit->set_text("test\nlines\nto\n\nmove\naround"); code_edit->set_caret_line(3); @@ -5066,6 +5165,64 @@ TEST_CASE("[SceneTree][CodeEdit] text manipulation") { CHECK(code_edit->get_selection_origin_column(1) == 0); CHECK(code_edit->get_caret_line(1) == 5); CHECK(code_edit->get_caret_column(1) == 2); + + // Move lines with adjacent selections that end at column 0. + code_edit->set_text("test\nlines\nto\n\nmove\naround"); + code_edit->select(1, 2, 2, 0); + code_edit->add_caret(2, 2); + code_edit->select(2, 2, 3, 0, 1); + code_edit->move_lines_down(); + CHECK(code_edit->get_text() == "test\n\nlines\nto\nmove\naround"); + CHECK(code_edit->get_caret_count() == 2); + CHECK(code_edit->has_selection(0)); + CHECK(code_edit->get_selection_origin_line(0) == 2); + CHECK(code_edit->get_selection_origin_column(0) == 2); + CHECK(code_edit->get_caret_line(0) == 3); + CHECK(code_edit->get_caret_column(0) == 0); + CHECK(code_edit->has_selection(1)); + CHECK(code_edit->get_selection_origin_line(1) == 3); + CHECK(code_edit->get_selection_origin_column(1) == 2); + CHECK(code_edit->get_caret_line(1) == 4); + CHECK(code_edit->get_caret_column(1) == 0); + code_edit->remove_secondary_carets(); + + // Move lines with disconnected adjacent selections that end at column 0. + code_edit->set_text("test\nlines\nto\n\nmove\naround"); + code_edit->select(0, 2, 1, 0); + code_edit->add_caret(2, 2); + code_edit->select(2, 0, 3, 0, 1); + code_edit->move_lines_down(); + CHECK(code_edit->get_text() == "lines\ntest\n\nto\nmove\naround"); + CHECK(code_edit->get_caret_count() == 2); + CHECK(code_edit->has_selection(0)); + CHECK(code_edit->get_selection_origin_line(0) == 1); + CHECK(code_edit->get_selection_origin_column(0) == 2); + CHECK(code_edit->get_caret_line(0) == 2); + CHECK(code_edit->get_caret_column(0) == 0); + CHECK(code_edit->has_selection(1)); + CHECK(code_edit->get_selection_origin_line(1) == 3); + CHECK(code_edit->get_selection_origin_column(1) == 0); + CHECK(code_edit->get_caret_line(1) == 4); + CHECK(code_edit->get_caret_column(1) == 0); + code_edit->remove_secondary_carets(); + code_edit->deselect(); + + code_edit->set_line_folding_enabled(true); + + // Move line down into a folded region unfolds it. + code_edit->set_text("test\ntest2\n\tline1 test\n\t\tline 2\ntest2"); + code_edit->set_caret_line(0); + code_edit->set_caret_column(0); + code_edit->fold_line(1); + CHECK(code_edit->is_line_folded(1)); + code_edit->move_lines_down(); + CHECK(code_edit->get_caret_count() == 1); + CHECK_FALSE(code_edit->has_selection(0)); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 0); + CHECK(code_edit->get_text() == "test2\ntest\n\tline1 test\n\t\tline 2\ntest2"); + CHECK_FALSE(code_edit->is_line_folded(0)); + CHECK_FALSE(code_edit->is_line_folded(1)); } SUBCASE("[SceneTree][CodeEdit] delete lines") { diff --git a/tests/scene/test_graph_node.h b/tests/scene/test_graph_node.h index 72b8b682c9..7973ac1444 100644 --- a/tests/scene/test_graph_node.h +++ b/tests/scene/test_graph_node.h @@ -42,14 +42,16 @@ TEST_CASE("[GraphNode][SceneTree]") { SUBCASE("[GraphNode] Graph Node only child on delete should not cause error.") { // Setup. GraphNode *test_node = memnew(GraphNode); - test_child->set_name("Graph Node"); + test_node->set_name("Graph Node"); Control *test_child = memnew(Control); test_child->set_name("child"); test_node->add_child(test_child); // Test. - CHECK_NOTHROW_MESSAGE(test_node->remove_child(test_child)); + test_node->remove_child(test_child); + CHECK(test_node->get_child_count(false) == 0); + memdelete(test_child); memdelete(test_node); } } diff --git a/tests/scene/test_image_texture_3d.h b/tests/scene/test_image_texture_3d.h new file mode 100644 index 0000000000..f2a7abcf69 --- /dev/null +++ b/tests/scene/test_image_texture_3d.h @@ -0,0 +1,101 @@ +/**************************************************************************/ +/* test_image_texture_3d.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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_IMAGE_TEXTURE_3D_H +#define TEST_IMAGE_TEXTURE_3D_H + +#include "core/io/image.h" +#include "scene/resources/image_texture.h" + +#include "tests/test_macros.h" +#include "tests/test_utils.h" + +namespace TestImageTexture3D { + +// [SceneTree] in a test case name enables initializing a mock render server, +// which ImageTexture3D is dependent on. +TEST_CASE("[SceneTree][ImageTexture3D] Constructor") { + Ref<ImageTexture3D> image_texture_3d = memnew(ImageTexture3D); + CHECK(image_texture_3d->get_format() == Image::FORMAT_L8); + CHECK(image_texture_3d->get_width() == 1); + CHECK(image_texture_3d->get_height() == 1); + CHECK(image_texture_3d->get_depth() == 1); + CHECK(image_texture_3d->has_mipmaps() == false); +} + +TEST_CASE("[SceneTree][ImageTexture3D] get_format") { + Ref<ImageTexture3D> image_texture_3d = memnew(ImageTexture3D); + CHECK(image_texture_3d->get_format() == Image::FORMAT_L8); +} + +TEST_CASE("[SceneTree][ImageTexture3D] get_width") { + Ref<ImageTexture3D> image_texture_3d = memnew(ImageTexture3D); + CHECK(image_texture_3d->get_width() == 1); +} + +TEST_CASE("[SceneTree][ImageTexture3D] get_height") { + Ref<ImageTexture3D> image_texture_3d = memnew(ImageTexture3D); + CHECK(image_texture_3d->get_height() == 1); +} + +TEST_CASE("[SceneTree][ImageTexture3D] get_depth") { + Ref<ImageTexture3D> image_texture_3d = memnew(ImageTexture3D); + CHECK(image_texture_3d->get_depth() == 1); +} + +TEST_CASE("[SceneTree][ImageTexture3D] has_mipmaps") { + const Vector<Ref<Image>> images = { memnew(Image(8, 8, false, Image::FORMAT_RGBA8)), memnew(Image(8, 8, false, Image::FORMAT_RGBA8)) }; + Ref<ImageTexture3D> image_texture_3d = memnew(ImageTexture3D); + CHECK(image_texture_3d->has_mipmaps() == false); // No mipmaps. + image_texture_3d->create(Image::FORMAT_RGBA8, 2, 2, 2, true, images); + CHECK(image_texture_3d->has_mipmaps() == true); // Mipmaps. +} + +TEST_CASE("[SceneTree][ImageTexture3D] create") { + const Vector<Ref<Image>> images = { memnew(Image(8, 8, false, Image::FORMAT_RGBA8)), memnew(Image(8, 8, false, Image::FORMAT_RGBA8)) }; + Ref<ImageTexture3D> image_texture_3d = memnew(ImageTexture3D); + CHECK(image_texture_3d->create(Image::FORMAT_RGBA8, 2, 2, 2, true, images) == OK); // Run create and check return value simultaneously. + CHECK(image_texture_3d->get_format() == Image::FORMAT_RGBA8); + CHECK(image_texture_3d->get_width() == 2); + CHECK(image_texture_3d->get_height() == 2); + CHECK(image_texture_3d->get_depth() == 2); + CHECK(image_texture_3d->has_mipmaps() == true); +} + +TEST_CASE("[SceneTree][ImageTexture3D] set_path") { + Ref<ImageTexture3D> image_texture_3d = memnew(ImageTexture3D); + String path = TestUtils::get_data_path("images/icon.png"); + image_texture_3d->set_path(path, true); + CHECK(image_texture_3d->get_path() == path); +} + +} //namespace TestImageTexture3D + +#endif // TEST_IMAGE_TEXTURE_3D_H diff --git a/tests/scene/test_instance_placeholder.h b/tests/scene/test_instance_placeholder.h new file mode 100644 index 0000000000..17f2151d54 --- /dev/null +++ b/tests/scene/test_instance_placeholder.h @@ -0,0 +1,534 @@ +/**************************************************************************/ +/* test_instance_placeholder.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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_INSTANCE_PLACEHOLDER_H +#define TEST_INSTANCE_PLACEHOLDER_H + +#include "scene/main/instance_placeholder.h" +#include "scene/resources/packed_scene.h" + +#include "tests/test_macros.h" + +class _TestInstancePlaceholderNode : public Node { + GDCLASS(_TestInstancePlaceholderNode, Node); + +protected: + static void _bind_methods() { + ClassDB::bind_method(D_METHOD("set_int_property", "int_property"), &_TestInstancePlaceholderNode::set_int_property); + ClassDB::bind_method(D_METHOD("get_int_property"), &_TestInstancePlaceholderNode::get_int_property); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "int_property"), "set_int_property", "get_int_property"); + + ClassDB::bind_method(D_METHOD("set_reference_property", "reference_property"), &_TestInstancePlaceholderNode::set_reference_property); + ClassDB::bind_method(D_METHOD("get_reference_property"), &_TestInstancePlaceholderNode::get_reference_property); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "reference_property", PROPERTY_HINT_NODE_TYPE), "set_reference_property", "get_reference_property"); + + ClassDB::bind_method(D_METHOD("set_reference_array_property", "reference_array_property"), &_TestInstancePlaceholderNode::set_reference_array_property); + ClassDB::bind_method(D_METHOD("get_reference_array_property"), &_TestInstancePlaceholderNode::get_reference_array_property); + + // The hint string value "24/34:Node" is determined from existing PackedScenes with typed Array properties. + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "reference_array_property", PROPERTY_HINT_TYPE_STRING, "24/34:Node"), "set_reference_array_property", "get_reference_array_property"); + } + +public: + int int_property = 0; + + void set_int_property(int p_int) { + int_property = p_int; + } + + int get_int_property() const { + return int_property; + } + + Variant reference_property; + + void set_reference_property(const Variant &p_node) { + reference_property = p_node; + } + + Variant get_reference_property() const { + return reference_property; + } + + Array reference_array_property; + + void set_reference_array_property(const Array &p_array) { + reference_array_property = p_array; + } + + Array get_reference_array_property() const { + return reference_array_property; + } + + _TestInstancePlaceholderNode() { + reference_array_property.set_typed(Variant::OBJECT, "Node", Variant()); + } +}; + +namespace TestInstancePlaceholder { + +TEST_CASE("[SceneTree][InstancePlaceholder] Instantiate from placeholder with no overrides") { + GDREGISTER_CLASS(_TestInstancePlaceholderNode); + + SUBCASE("with non-node values") { + InstancePlaceholder *ip = memnew(InstancePlaceholder); + ip->set_name("TestScene"); + Node *root = memnew(Node); + SceneTree::get_singleton()->get_root()->add_child(root); + + root->add_child(ip); + // Create a scene to instance. + _TestInstancePlaceholderNode *scene = memnew(_TestInstancePlaceholderNode); + scene->set_int_property(12); + + // Pack the scene. + PackedScene *packed_scene = memnew(PackedScene); + const Error err = packed_scene->pack(scene); + REQUIRE(err == OK); + + // Instantiate the scene. + _TestInstancePlaceholderNode *created = Object::cast_to<_TestInstancePlaceholderNode>(ip->create_instance(true, packed_scene)); + REQUIRE(created != nullptr); + CHECK(created->get_name() == "TestScene"); + CHECK(created->get_int_property() == 12); + + root->queue_free(); + memdelete(scene); + } + + SUBCASE("with node value") { + InstancePlaceholder *ip = memnew(InstancePlaceholder); + ip->set_name("TestScene"); + Node *root = memnew(Node); + SceneTree::get_singleton()->get_root()->add_child(root); + + root->add_child(ip); + // Create a scene to instance. + _TestInstancePlaceholderNode *scene = memnew(_TestInstancePlaceholderNode); + Node *referenced = memnew(Node); + scene->add_child(referenced); + referenced->set_owner(scene); + scene->set_reference_property(referenced); + // Pack the scene. + PackedScene *packed_scene = memnew(PackedScene); + const Error err = packed_scene->pack(scene); + REQUIRE(err == OK); + + // Instantiate the scene. + _TestInstancePlaceholderNode *created = Object::cast_to<_TestInstancePlaceholderNode>(ip->create_instance(true, packed_scene)); + REQUIRE(created != nullptr); + CHECK(created->get_name() == "TestScene"); + CHECK(created->get_child_count() == 1); + CHECK(created->get_reference_property().identity_compare(created->get_child(0, false))); + CHECK_FALSE(created->get_reference_property().identity_compare(referenced)); + + root->queue_free(); + memdelete(scene); + } + + SUBCASE("with node-array value") { + InstancePlaceholder *ip = memnew(InstancePlaceholder); + ip->set_name("TestScene"); + Node *root = memnew(Node); + SceneTree::get_singleton()->get_root()->add_child(root); + + root->add_child(ip); + // Create a scene to instance. + _TestInstancePlaceholderNode *scene = memnew(_TestInstancePlaceholderNode); + Node *referenced1 = memnew(Node); + Node *referenced2 = memnew(Node); + scene->add_child(referenced1); + scene->add_child(referenced2); + referenced1->set_owner(scene); + referenced2->set_owner(scene); + Array node_array; + node_array.set_typed(Variant::OBJECT, "Node", Variant()); + node_array.push_back(referenced1); + node_array.push_back(referenced2); + scene->set_reference_array_property(node_array); + // Pack the scene. + PackedScene *packed_scene = memnew(PackedScene); + const Error err = packed_scene->pack(scene); + REQUIRE(err == OK); + + // Instantiate the scene. + _TestInstancePlaceholderNode *created = Object::cast_to<_TestInstancePlaceholderNode>(ip->create_instance(true, packed_scene)); + REQUIRE(created != nullptr); + CHECK(created->get_name() == "TestScene"); + CHECK(created->get_child_count() == 2); + Array created_array = created->get_reference_array_property(); + REQUIRE(created_array.size() == node_array.size()); + REQUIRE(created_array.size() == created->get_child_count()); + + // Iterate over all nodes, since the ordering is not guaranteed. + for (int i = 0; i < node_array.size(); i++) { + bool node_found = false; + for (int j = 0; j < created->get_child_count(); j++) { + if (created_array[i].identity_compare(created->get_child(j, true))) { + node_found = true; + } + } + CHECK(node_found); + } + root->queue_free(); + memdelete(scene); + } +} + +TEST_CASE("[SceneTree][InstancePlaceholder] Instantiate from placeholder with overrides") { + GDREGISTER_CLASS(_TestInstancePlaceholderNode); + + SUBCASE("with non-node values") { + InstancePlaceholder *ip = memnew(InstancePlaceholder); + Node *root = memnew(Node); + SceneTree::get_singleton()->get_root()->add_child(root); + + root->add_child(ip); + ip->set_name("TestScene"); + ip->set("int_property", 45); + // Create a scene to pack. + _TestInstancePlaceholderNode *scene = memnew(_TestInstancePlaceholderNode); + scene->set_int_property(12); + + // Pack the scene. + PackedScene *packed_scene = memnew(PackedScene); + packed_scene->pack(scene); + + // Instantiate the scene. + _TestInstancePlaceholderNode *created = Object::cast_to<_TestInstancePlaceholderNode>(ip->create_instance(true, packed_scene)); + REQUIRE(created != nullptr); + CHECK(created->get_int_property() == 45); + + root->queue_free(); + memdelete(scene); + } + + SUBCASE("with node values") { + InstancePlaceholder *ip = memnew(InstancePlaceholder); + ip->set_name("TestScene"); + Node *root = memnew(Node); + Node *overriding = memnew(Node); + SceneTree::get_singleton()->get_root()->add_child(root); + + root->add_child(ip); + root->add_child(overriding); + ip->set("reference_property", overriding); + // Create a scene to instance. + _TestInstancePlaceholderNode *scene = memnew(_TestInstancePlaceholderNode); + Node *referenced = memnew(Node); + scene->add_child(referenced); + referenced->set_owner(scene); + scene->set_reference_property(referenced); + // Pack the scene. + PackedScene *packed_scene = memnew(PackedScene); + const Error err = packed_scene->pack(scene); + REQUIRE(err == OK); + + // Instantiate the scene. + _TestInstancePlaceholderNode *created = Object::cast_to<_TestInstancePlaceholderNode>(ip->create_instance(true, packed_scene)); + REQUIRE(created != nullptr); + CHECK(created->get_name() == "TestScene"); + CHECK(created->get_child_count() == 1); + CHECK(created->get_reference_property().identity_compare(overriding)); + CHECK_FALSE(created->get_reference_property().identity_compare(referenced)); + + root->queue_free(); + memdelete(scene); + } + + SUBCASE("with node-array value") { + InstancePlaceholder *ip = memnew(InstancePlaceholder); + ip->set_name("TestScene"); + Node *root = memnew(Node); + SceneTree::get_singleton()->get_root()->add_child(root); + + Node *override1 = memnew(Node); + Node *override2 = memnew(Node); + Node *override3 = memnew(Node); + root->add_child(ip); + root->add_child(override1); + root->add_child(override2); + root->add_child(override3); + + Array override_node_array; + override_node_array.set_typed(Variant::OBJECT, "Node", Variant()); + override_node_array.push_back(override1); + override_node_array.push_back(override2); + override_node_array.push_back(override3); + + ip->set("reference_array_property", override_node_array); + + // Create a scene to instance. + _TestInstancePlaceholderNode *scene = memnew(_TestInstancePlaceholderNode); + Node *referenced1 = memnew(Node); + Node *referenced2 = memnew(Node); + + scene->add_child(referenced1); + scene->add_child(referenced2); + + referenced1->set_owner(scene); + referenced2->set_owner(scene); + Array referenced_array; + referenced_array.set_typed(Variant::OBJECT, "Node", Variant()); + referenced_array.push_back(referenced1); + referenced_array.push_back(referenced2); + + scene->set_reference_array_property(referenced_array); + // Pack the scene. + PackedScene *packed_scene = memnew(PackedScene); + const Error err = packed_scene->pack(scene); + REQUIRE(err == OK); + + // Instantiate the scene. + _TestInstancePlaceholderNode *created = Object::cast_to<_TestInstancePlaceholderNode>(ip->create_instance(true, packed_scene)); + REQUIRE(created != nullptr); + CHECK(created->get_name() == "TestScene"); + CHECK(created->get_child_count() == 2); + Array created_array = created->get_reference_array_property(); + REQUIRE_FALSE(created_array.size() == referenced_array.size()); + REQUIRE(created_array.size() == override_node_array.size()); + REQUIRE_FALSE(created_array.size() == created->get_child_count()); + + // Iterate over all nodes, since the ordering is not guaranteed. + for (int i = 0; i < override_node_array.size(); i++) { + bool node_found = false; + for (int j = 0; j < created_array.size(); j++) { + if (override_node_array[i].identity_compare(created_array[j])) { + node_found = true; + } + } + CHECK(node_found); + } + root->queue_free(); + memdelete(scene); + } +} + +#ifdef TOOLS_ENABLED +TEST_CASE("[SceneTree][InstancePlaceholder] Instance a PackedScene containing an InstancePlaceholder with no overrides") { + GDREGISTER_CLASS(_TestInstancePlaceholderNode); + + // Create the internal scene. + _TestInstancePlaceholderNode *internal = memnew(_TestInstancePlaceholderNode); + internal->set_name("InternalNode"); + Node *referenced = memnew(Node); + referenced->set_name("OriginalReference"); + internal->add_child(referenced); + referenced->set_owner(internal); + internal->set_reference_property(referenced); + + // Pack the internal scene. + PackedScene *internal_scene = memnew(PackedScene); + Error err = internal_scene->pack(internal); + REQUIRE(err == OK); + + const String internal_path = TestUtils::get_temp_path("instance_placeholder_test_internal.tscn"); + err = ResourceSaver::save(internal_scene, internal_path); + REQUIRE(err == OK); + + Ref<PackedScene> internal_scene_loaded = ResourceLoader::load(internal_path, "PackedScene", ResourceFormatLoader::CacheMode::CACHE_MODE_IGNORE, &err); + REQUIRE(err == OK); + + // Create the main scene. + Node *root = memnew(Node); + root->set_name("MainNode"); + Node *overriding = memnew(Node); + overriding->set_name("OverridingReference"); + + _TestInstancePlaceholderNode *internal_created = Object::cast_to<_TestInstancePlaceholderNode>(internal_scene_loaded->instantiate(PackedScene::GEN_EDIT_STATE_MAIN_INHERITED)); + internal_created->set_scene_instance_load_placeholder(true); + root->add_child(internal_created); + internal_created->set_owner(root); + + root->add_child(overriding); + overriding->set_owner(root); + // Here we introduce an error, we override the property with an internal node to the instance placeholder. + // The InstancePlaceholder is now forced to properly resolve the Node. + internal_created->set("reference_property", NodePath("OriginalReference")); + + // Pack the main scene. + PackedScene *main_scene = memnew(PackedScene); + err = main_scene->pack(root); + REQUIRE(err == OK); + + const String main_path = TestUtils::get_temp_path("instance_placeholder_test_main.tscn"); + err = ResourceSaver::save(main_scene, main_path); + REQUIRE(err == OK); + + // // Instantiate the scene. + Ref<PackedScene> main_scene_loaded = ResourceLoader::load(main_path, "PackedScene", ResourceFormatLoader::CacheMode::CACHE_MODE_IGNORE, &err); + REQUIRE(err == OK); + + Node *instanced_main_node = main_scene_loaded->instantiate(); + REQUIRE(instanced_main_node != nullptr); + SceneTree::get_singleton()->get_root()->add_child(instanced_main_node); + CHECK(instanced_main_node->get_name() == "MainNode"); + REQUIRE(instanced_main_node->get_child_count() == 2); + InstancePlaceholder *instanced_placeholder = Object::cast_to<InstancePlaceholder>(instanced_main_node->get_child(0, true)); + REQUIRE(instanced_placeholder != nullptr); + + _TestInstancePlaceholderNode *final_node = Object::cast_to<_TestInstancePlaceholderNode>(instanced_placeholder->create_instance(true)); + REQUIRE(final_node != nullptr); + REQUIRE(final_node->get_child_count() == 1); + REQUIRE(final_node->get_reference_property().identity_compare(final_node->get_child(0, true))); + + instanced_main_node->queue_free(); + memdelete(overriding); + memdelete(root); + memdelete(internal); + DirAccess::remove_file_or_error(internal_path); + DirAccess::remove_file_or_error(main_path); +} + +TEST_CASE("[SceneTree][InstancePlaceholder] Instance a PackedScene containing an InstancePlaceholder with overrides") { + GDREGISTER_CLASS(_TestInstancePlaceholderNode); + + // Create the internal scene. + _TestInstancePlaceholderNode *internal = memnew(_TestInstancePlaceholderNode); + internal->set_name("InternalNode"); + Node *referenced = memnew(Node); + referenced->set_name("OriginalReference"); + internal->add_child(referenced); + referenced->set_owner(internal); + internal->set_reference_property(referenced); + + Node *array_ref1 = memnew(Node); + array_ref1->set_name("ArrayRef1"); + internal->add_child(array_ref1); + array_ref1->set_owner(internal); + Node *array_ref2 = memnew(Node); + array_ref2->set_name("ArrayRef2"); + internal->add_child(array_ref2); + array_ref2->set_owner(internal); + Array referenced_array; + referenced_array.set_typed(Variant::OBJECT, "Node", Variant()); + referenced_array.push_back(array_ref1); + referenced_array.push_back(array_ref2); + internal->set_reference_array_property(referenced_array); + + // Pack the internal scene. + PackedScene *internal_scene = memnew(PackedScene); + Error err = internal_scene->pack(internal); + REQUIRE(err == OK); + + const String internal_path = TestUtils::get_temp_path("instance_placeholder_test_internal_override.tscn"); + err = ResourceSaver::save(internal_scene, internal_path); + REQUIRE(err == OK); + + Ref<PackedScene> internal_scene_loaded = ResourceLoader::load(internal_path, "PackedScene", ResourceFormatLoader::CacheMode::CACHE_MODE_IGNORE, &err); + REQUIRE(err == OK); + + // Create the main scene. + Node *root = memnew(Node); + root->set_name("MainNode"); + Node *overriding = memnew(Node); + overriding->set_name("OverridingReference"); + Node *array_ext = memnew(Node); + array_ext->set_name("ExternalArrayMember"); + + _TestInstancePlaceholderNode *internal_created = Object::cast_to<_TestInstancePlaceholderNode>(internal_scene_loaded->instantiate(PackedScene::GEN_EDIT_STATE_MAIN_INHERITED)); + internal_created->set_scene_instance_load_placeholder(true); + root->add_child(internal_created); + internal_created->set_owner(root); + + root->add_child(overriding); + overriding->set_owner(root); + root->add_child(array_ext); + array_ext->set_owner(root); + // Here we introduce an error, we override the property with an internal node to the instance placeholder. + // The InstancePlaceholder is now forced to properly resolve the Node. + internal_created->set_reference_property(overriding); + Array internal_array = internal_created->get_reference_array_property(); + Array override_array; + override_array.set_typed(Variant::OBJECT, "Node", Variant()); + for (int i = 0; i < internal_array.size(); i++) { + override_array.push_back(internal_array[i]); + } + override_array.push_back(array_ext); + internal_created->set_reference_array_property(override_array); + + // Pack the main scene. + PackedScene *main_scene = memnew(PackedScene); + err = main_scene->pack(root); + REQUIRE(err == OK); + + const String main_path = TestUtils::get_temp_path("instance_placeholder_test_main_override.tscn"); + err = ResourceSaver::save(main_scene, main_path); + REQUIRE(err == OK); + + // // Instantiate the scene. + Ref<PackedScene> main_scene_loaded = ResourceLoader::load(main_path, "PackedScene", ResourceFormatLoader::CacheMode::CACHE_MODE_IGNORE, &err); + REQUIRE(err == OK); + + Node *instanced_main_node = main_scene_loaded->instantiate(); + REQUIRE(instanced_main_node != nullptr); + SceneTree::get_singleton()->get_root()->add_child(instanced_main_node); + CHECK(instanced_main_node->get_name() == "MainNode"); + REQUIRE(instanced_main_node->get_child_count() == 3); + InstancePlaceholder *instanced_placeholder = Object::cast_to<InstancePlaceholder>(instanced_main_node->get_child(0, true)); + REQUIRE(instanced_placeholder != nullptr); + + _TestInstancePlaceholderNode *final_node = Object::cast_to<_TestInstancePlaceholderNode>(instanced_placeholder->create_instance(true)); + REQUIRE(final_node != nullptr); + REQUIRE(final_node->get_child_count() == 3); + REQUIRE(final_node->get_reference_property().identity_compare(instanced_main_node->get_child(1, true))); + Array final_array = final_node->get_reference_array_property(); + REQUIRE(final_array.size() == 3); + Array wanted_node_array; + wanted_node_array.push_back(instanced_main_node->get_child(2, true)); // ExternalArrayMember + wanted_node_array.push_back(final_node->get_child(1, true)); // ArrayRef1 + wanted_node_array.push_back(final_node->get_child(2, true)); // ArrayRef2 + + // Iterate over all nodes, since the ordering is not guaranteed. + for (int i = 0; i < wanted_node_array.size(); i++) { + bool node_found = false; + for (int j = 0; j < final_array.size(); j++) { + if (wanted_node_array[i].identity_compare(final_array[j])) { + node_found = true; + } + } + CHECK(node_found); + } + + instanced_main_node->queue_free(); + memdelete(array_ext); + memdelete(overriding); + memdelete(root); + memdelete(internal); + DirAccess::remove_file_or_error(internal_path); + DirAccess::remove_file_or_error(main_path); +} +#endif // TOOLS_ENABLED + +} //namespace TestInstancePlaceholder + +#endif // TEST_INSTANCE_PLACEHOLDER_H diff --git a/tests/scene/test_node.h b/tests/scene/test_node.h index b3362b02a8..e387c73f9f 100644 --- a/tests/scene/test_node.h +++ b/tests/scene/test_node.h @@ -31,7 +31,9 @@ #ifndef TEST_NODE_H #define TEST_NODE_H +#include "core/object/class_db.h" #include "scene/main/node.h" +#include "scene/resources/packed_scene.h" #include "tests/test_macros.h" @@ -62,6 +64,16 @@ protected: } } + static void _bind_methods() { + ClassDB::bind_method(D_METHOD("set_exported_node", "node"), &TestNode::set_exported_node); + ClassDB::bind_method(D_METHOD("get_exported_node"), &TestNode::get_exported_node); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "exported_node", PROPERTY_HINT_NODE_TYPE, "Node"), "set_exported_node", "get_exported_node"); + + ClassDB::bind_method(D_METHOD("set_exported_nodes", "node"), &TestNode::set_exported_nodes); + ClassDB::bind_method(D_METHOD("get_exported_nodes"), &TestNode::get_exported_nodes); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "exported_nodes", PROPERTY_HINT_TYPE_STRING, "24/34:Node"), "set_exported_nodes", "get_exported_nodes"); + } + private: void push_self() { if (callback_list) { @@ -75,7 +87,16 @@ public: int process_counter = 0; int physics_process_counter = 0; + Node *exported_node = nullptr; + Array exported_nodes; + List<Node *> *callback_list = nullptr; + + void set_exported_node(Node *p_node) { exported_node = p_node; } + Node *get_exported_node() const { return exported_node; } + + void set_exported_nodes(const Array &p_nodes) { exported_nodes = p_nodes; } + Array get_exported_nodes() const { return exported_nodes; } }; TEST_CASE("[SceneTree][Node] Testing node operations with a very simple scene tree") { @@ -478,6 +499,115 @@ TEST_CASE("[SceneTree][Node] Testing node operations with a more complex simple memdelete(node2); } +TEST_CASE("[SceneTree][Node]Exported node checks") { + TestNode *node = memnew(TestNode); + SceneTree::get_singleton()->get_root()->add_child(node); + + Node *child = memnew(Node); + child->set_name("Child"); + node->add_child(child); + child->set_owner(node); + + Node *child2 = memnew(Node); + child2->set_name("Child2"); + node->add_child(child2); + child2->set_owner(node); + + Array children; + children.append(child); + + node->set("exported_node", child); + node->set("exported_nodes", children); + + SUBCASE("Property of duplicated node should point to duplicated child") { + GDREGISTER_CLASS(TestNode); + + TestNode *dup = Object::cast_to<TestNode>(node->duplicate()); + Node *new_exported = Object::cast_to<Node>(dup->get("exported_node")); + CHECK(new_exported == dup->get_child(0)); + + memdelete(dup); + } + +#ifdef TOOLS_ENABLED + SUBCASE("Saving instance with exported nodes should not store the unchanged property") { + Ref<PackedScene> ps; + ps.instantiate(); + ps->pack(node); + + String scene_path = TestUtils::get_temp_path("test_scene.tscn"); + ps->set_path(scene_path); + + Node *root = memnew(Node); + + Node *sub_child = ps->instantiate(PackedScene::GEN_EDIT_STATE_MAIN); + root->add_child(sub_child); + sub_child->set_owner(root); + + Ref<PackedScene> ps2; + ps2.instantiate(); + ps2->pack(root); + + scene_path = TestUtils::get_temp_path("new_test_scene.tscn"); + ResourceSaver::save(ps2, scene_path); + memdelete(root); + + bool is_wrong = false; + Ref<FileAccess> fa = FileAccess::open(scene_path, FileAccess::READ); + while (!fa->eof_reached()) { + const String line = fa->get_line(); + if (line.begins_with("exported_node")) { + // The property was saved, while it shouldn't. + is_wrong = true; + break; + } + } + CHECK_FALSE(is_wrong); + } + + SUBCASE("Saving instance with exported nodes should store property if changed") { + Ref<PackedScene> ps; + ps.instantiate(); + ps->pack(node); + + String scene_path = TestUtils::get_temp_path("test_scene.tscn"); + ps->set_path(scene_path); + + Node *root = memnew(Node); + + Node *sub_child = ps->instantiate(PackedScene::GEN_EDIT_STATE_MAIN); + root->add_child(sub_child); + sub_child->set_owner(root); + + sub_child->set("exported_node", sub_child->get_child(1)); + + children = Array(); + children.append(sub_child->get_child(1)); + sub_child->set("exported_nodes", children); + + Ref<PackedScene> ps2; + ps2.instantiate(); + ps2->pack(root); + + scene_path = TestUtils::get_temp_path("new_test_scene2.tscn"); + ResourceSaver::save(ps2, scene_path); + memdelete(root); + + int stored_properties = 0; + Ref<FileAccess> fa = FileAccess::open(scene_path, FileAccess::READ); + while (!fa->eof_reached()) { + const String line = fa->get_line(); + if (line.begins_with("exported_node")) { + stored_properties++; + } + } + CHECK_EQ(stored_properties, 2); + } +#endif // TOOLS_ENABLED + + memdelete(node); +} + TEST_CASE("[Node] Processing checks") { Node *node = memnew(Node); diff --git a/tests/scene/test_path_follow_2d.h b/tests/scene/test_path_follow_2d.h index 1958befa18..45ae0dff5d 100644 --- a/tests/scene/test_path_follow_2d.h +++ b/tests/scene/test_path_follow_2d.h @@ -32,205 +32,223 @@ #define TEST_PATH_FOLLOW_2D_H #include "scene/2d/path_2d.h" +#include "scene/main/window.h" #include "tests/test_macros.h" namespace TestPathFollow2D { -TEST_CASE("[PathFollow2D] Sampling with progress ratio") { - const Ref<Curve2D> &curve = memnew(Curve2D()); +bool is_equal_approx(const Vector2 &p_a, const Vector2 &p_b) { + const real_t tolerance = 0.001; + return Math::is_equal_approx(p_a.x, p_b.x, tolerance) && + Math::is_equal_approx(p_a.y, p_b.y, tolerance); +} + +TEST_CASE("[SceneTree][PathFollow2D] Sampling with progress ratio") { + Ref<Curve2D> curve = memnew(Curve2D); + curve->set_bake_interval(1); curve->add_point(Vector2(0, 0)); curve->add_point(Vector2(100, 0)); curve->add_point(Vector2(100, 100)); curve->add_point(Vector2(0, 100)); curve->add_point(Vector2(0, 0)); - const Path2D *path = memnew(Path2D); + Path2D *path = memnew(Path2D); path->set_curve(curve); - const PathFollow2D *path_follow_2d = memnew(PathFollow2D); + PathFollow2D *path_follow_2d = memnew(PathFollow2D); + path_follow_2d->set_loop(false); path->add_child(path_follow_2d); + SceneTree::get_singleton()->get_root()->add_child(path); path_follow_2d->set_progress_ratio(0); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(0, 0))); + CHECK(is_equal_approx(Vector2(0, 0), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress_ratio(0.125); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(50, 0))); + CHECK(is_equal_approx(Vector2(50, 0), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress_ratio(0.25); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(100, 0))); + CHECK(is_equal_approx(Vector2(100, 0), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress_ratio(0.375); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(100, 50))); + CHECK(is_equal_approx(Vector2(100, 50), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress_ratio(0.5); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(100, 100))); + CHECK(is_equal_approx(Vector2(100, 100), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress_ratio(0.625); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(50, 100))); + CHECK(is_equal_approx(Vector2(50, 100), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress_ratio(0.75); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(0, 100))); + CHECK(is_equal_approx(Vector2(0, 100), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress_ratio(0.875); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(0, 50))); + CHECK(is_equal_approx(Vector2(0, 50), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress_ratio(1); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(0, 0))); + CHECK(is_equal_approx(Vector2(0, 0), path_follow_2d->get_transform().get_origin())); memdelete(path); } -TEST_CASE("[PathFollow2D] Sampling with progress") { - const Ref<Curve2D> &curve = memnew(Curve2D()); +TEST_CASE("[SceneTree][PathFollow2D] Sampling with progress") { + Ref<Curve2D> curve = memnew(Curve2D); + curve->set_bake_interval(1); curve->add_point(Vector2(0, 0)); curve->add_point(Vector2(100, 0)); curve->add_point(Vector2(100, 100)); curve->add_point(Vector2(0, 100)); curve->add_point(Vector2(0, 0)); - const Path2D *path = memnew(Path2D); + Path2D *path = memnew(Path2D); path->set_curve(curve); - const PathFollow2D *path_follow_2d = memnew(PathFollow2D); + PathFollow2D *path_follow_2d = memnew(PathFollow2D); + path_follow_2d->set_loop(false); path->add_child(path_follow_2d); + SceneTree::get_singleton()->get_root()->add_child(path); path_follow_2d->set_progress(0); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(0, 0))); + CHECK(is_equal_approx(Vector2(0, 0), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress(50); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(50, 0))); + CHECK(is_equal_approx(Vector2(50, 0), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress(100); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(100, 0))); + CHECK(is_equal_approx(Vector2(100, 0), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress(150); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(100, 50))); + CHECK(is_equal_approx(Vector2(100, 50), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress(200); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(100, 100))); + CHECK(is_equal_approx(Vector2(100, 100), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress(250); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(50, 100))); + CHECK(is_equal_approx(Vector2(50, 100), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress(300); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(0, 100))); + CHECK(is_equal_approx(Vector2(0, 100), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress(350); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(0, 50))); + CHECK(is_equal_approx(Vector2(0, 50), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress(400); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(0, 0))); + CHECK(is_equal_approx(Vector2(0, 0), path_follow_2d->get_transform().get_origin())); memdelete(path); } -TEST_CASE("[PathFollow2D] Removal of a point in curve") { - const Ref<Curve2D> &curve = memnew(Curve2D()); +TEST_CASE("[SceneTree][PathFollow2D] Removal of a point in curve") { + Ref<Curve2D> curve = memnew(Curve2D); curve->add_point(Vector2(0, 0)); curve->add_point(Vector2(100, 0)); curve->add_point(Vector2(100, 100)); - const Path2D *path = memnew(Path2D); + Path2D *path = memnew(Path2D); path->set_curve(curve); - const PathFollow2D *path_follow_2d = memnew(PathFollow2D); + PathFollow2D *path_follow_2d = memnew(PathFollow2D); path->add_child(path_follow_2d); + SceneTree::get_singleton()->get_root()->add_child(path); path_follow_2d->set_progress_ratio(0.5); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(100, 0))); + CHECK(is_equal_approx(Vector2(100, 0), path_follow_2d->get_transform().get_origin())); curve->remove_point(1); + path_follow_2d->set_progress_ratio(0.5); CHECK_MESSAGE( - path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(50, 50)), + is_equal_approx(Vector2(50, 50), path_follow_2d->get_transform().get_origin()), "Path follow's position should be updated after removing a point from the curve"); memdelete(path); } -TEST_CASE("[PathFollow2D] Setting h_offset and v_offset") { - const Ref<Curve2D> &curve = memnew(Curve2D()); +TEST_CASE("[SceneTree][PathFollow2D] Setting h_offset and v_offset") { + Ref<Curve2D> curve = memnew(Curve2D); curve->add_point(Vector2(0, 0)); curve->add_point(Vector2(100, 0)); - const Path2D *path = memnew(Path2D); + Path2D *path = memnew(Path2D); path->set_curve(curve); - const PathFollow2D *path_follow_2d = memnew(PathFollow2D); + PathFollow2D *path_follow_2d = memnew(PathFollow2D); path->add_child(path_follow_2d); + SceneTree::get_singleton()->get_root()->add_child(path); path_follow_2d->set_progress_ratio(0.5); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(50, 0))); + CHECK(is_equal_approx(Vector2(50, 0), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_h_offset(25); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(75, 0))); + CHECK(is_equal_approx(Vector2(75, 0), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_v_offset(25); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(75, 25))); + CHECK(is_equal_approx(Vector2(75, 25), path_follow_2d->get_transform().get_origin())); memdelete(path); } -TEST_CASE("[PathFollow2D] Unit offset out of range") { - const Ref<Curve2D> &curve = memnew(Curve2D()); +TEST_CASE("[SceneTree][PathFollow2D] Progress ratio out of range") { + Ref<Curve2D> curve = memnew(Curve2D); curve->add_point(Vector2(0, 0)); curve->add_point(Vector2(100, 0)); - const Path2D *path = memnew(Path2D); + Path2D *path = memnew(Path2D); path->set_curve(curve); - const PathFollow2D *path_follow_2d = memnew(PathFollow2D); + PathFollow2D *path_follow_2d = memnew(PathFollow2D); path->add_child(path_follow_2d); + SceneTree::get_singleton()->get_root()->add_child(path); path_follow_2d->set_loop(true); path_follow_2d->set_progress_ratio(-0.3); CHECK_MESSAGE( - path_follow_2d->get_progress_ratio() == 0.7, + Math::is_equal_approx(path_follow_2d->get_progress_ratio(), (real_t)0.7), "Progress Ratio should loop back from the end in the opposite direction"); path_follow_2d->set_progress_ratio(1.3); CHECK_MESSAGE( - path_follow_2d->get_progress_ratio() == 0.3, + Math::is_equal_approx(path_follow_2d->get_progress_ratio(), (real_t)0.3), "Progress Ratio should loop back from the end in the opposite direction"); path_follow_2d->set_loop(false); path_follow_2d->set_progress_ratio(-0.3); CHECK_MESSAGE( - path_follow_2d->get_progress_ratio() == 0, + Math::is_equal_approx(path_follow_2d->get_progress_ratio(), 0), "Progress Ratio should be clamped at 0"); path_follow_2d->set_progress_ratio(1.3); CHECK_MESSAGE( - path_follow_2d->get_progress_ratio() == 1, + Math::is_equal_approx(path_follow_2d->get_progress_ratio(), 1), "Progress Ratio should be clamped at 1"); memdelete(path); } -TEST_CASE("[PathFollow2D] Progress out of range") { - const Ref<Curve2D> &curve = memnew(Curve2D()); +TEST_CASE("[SceneTree][PathFollow2D] Progress out of range") { + Ref<Curve2D> curve = memnew(Curve2D); curve->add_point(Vector2(0, 0)); curve->add_point(Vector2(100, 0)); - const Path2D *path = memnew(Path2D); + Path2D *path = memnew(Path2D); path->set_curve(curve); - const PathFollow2D *path_follow_2d = memnew(PathFollow2D); + PathFollow2D *path_follow_2d = memnew(PathFollow2D); path->add_child(path_follow_2d); + SceneTree::get_singleton()->get_root()->add_child(path); path_follow_2d->set_loop(true); path_follow_2d->set_progress(-50); CHECK_MESSAGE( - path_follow_2d->get_progress() == 50, + Math::is_equal_approx(path_follow_2d->get_progress(), 50), "Progress should loop back from the end in the opposite direction"); path_follow_2d->set_progress(150); CHECK_MESSAGE( - path_follow_2d->get_progress() == 50, + Math::is_equal_approx(path_follow_2d->get_progress(), 50), "Progress should loop back from the end in the opposite direction"); path_follow_2d->set_loop(false); path_follow_2d->set_progress(-50); CHECK_MESSAGE( - path_follow_2d->get_progress() == 0, + Math::is_equal_approx(path_follow_2d->get_progress(), 0), "Progress should be clamped at 0"); path_follow_2d->set_progress(150); CHECK_MESSAGE( - path_follow_2d->get_progress() == 100, + Math::is_equal_approx(path_follow_2d->get_progress(), 100), "Progress should be clamped at 1"); memdelete(path); diff --git a/tests/scene/test_path_follow_3d.h b/tests/scene/test_path_follow_3d.h index 7595fddd2f..d08af3a70c 100644 --- a/tests/scene/test_path_follow_3d.h +++ b/tests/scene/test_path_follow_3d.h @@ -32,188 +32,289 @@ #define TEST_PATH_FOLLOW_3D_H #include "scene/3d/path_3d.h" +#include "scene/main/window.h" #include "tests/test_macros.h" namespace TestPathFollow3D { -TEST_CASE("[PathFollow3D] Sampling with progress ratio") { - const Ref<Curve3D> &curve = memnew(Curve3D()); +bool is_equal_approx(const Vector3 &p_a, const Vector3 &p_b) { + const real_t tolerance = 0.001; + return Math::is_equal_approx(p_a.x, p_b.x, tolerance) && + Math::is_equal_approx(p_a.y, p_b.y, tolerance) && + Math::is_equal_approx(p_a.z, p_b.z, tolerance); +} + +TEST_CASE("[SceneTree][PathFollow3D] Sampling with progress ratio") { + Ref<Curve3D> curve = memnew(Curve3D); curve->add_point(Vector3(0, 0, 0)); curve->add_point(Vector3(100, 0, 0)); curve->add_point(Vector3(100, 100, 0)); curve->add_point(Vector3(100, 100, 100)); curve->add_point(Vector3(100, 0, 100)); - const Path3D *path = memnew(Path3D); + Path3D *path = memnew(Path3D); path->set_curve(curve); - const PathFollow3D *path_follow_3d = memnew(PathFollow3D); + PathFollow3D *path_follow_3d = memnew(PathFollow3D); + path_follow_3d->set_loop(false); path->add_child(path_follow_3d); + SceneTree::get_singleton()->get_root()->add_child(path); path_follow_3d->set_progress_ratio(0); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(0, 0, 0)); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(0, 0, 0), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress_ratio(0.125); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(50, 0, 0)); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(50, 0, 0), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress_ratio(0.25); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(100, 0, 0); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(100, 0, 0), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress_ratio(0.375); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(100, 50, 0))); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(100, 50, 0), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress_ratio(0.5); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(100, 100, 0))); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(100, 100, 0), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress_ratio(0.625); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(100, 100, 50))); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(100, 100, 50), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress_ratio(0.75); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(100, 100, 100))); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(100, 100, 100), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress_ratio(0.875); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(100, 50, 100))); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(100, 50, 100), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress_ratio(1); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(100, 0, 100))); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(100, 0, 100), path_follow_3d->get_transform().get_origin())); memdelete(path); } -TEST_CASE("[PathFollow3D] Sampling with progress") { - const Ref<Curve3D> &curve = memnew(Curve3D()); +TEST_CASE("[SceneTree][PathFollow3D] Sampling with progress") { + Ref<Curve3D> curve = memnew(Curve3D); curve->add_point(Vector3(0, 0, 0)); curve->add_point(Vector3(100, 0, 0)); curve->add_point(Vector3(100, 100, 0)); curve->add_point(Vector3(100, 100, 100)); curve->add_point(Vector3(100, 0, 100)); - const Path3D *path = memnew(Path3D); + Path3D *path = memnew(Path3D); path->set_curve(curve); - const PathFollow3D *path_follow_3d = memnew(PathFollow3D); + PathFollow3D *path_follow_3d = memnew(PathFollow3D); + path_follow_3d->set_loop(false); path->add_child(path_follow_3d); + SceneTree::get_singleton()->get_root()->add_child(path); path_follow_3d->set_progress(0); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(0, 0, 0)); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(0, 0, 0), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress(50); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(50, 0, 0)); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(50, 0, 0), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress(100); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(100, 0, 0); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(100, 0, 0), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress(150); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(100, 50, 0))); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(100, 50, 0), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress(200); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(100, 100, 0))); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(100, 100, 0), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress(250); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(100, 100, 50))); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(100, 100, 50), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress(300); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(100, 100, 100))); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(100, 100, 100), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress(350); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(100, 50, 100))); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(100, 50, 100), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress(400); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(100, 0, 100))); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(100, 0, 100), path_follow_3d->get_transform().get_origin())); memdelete(path); } -TEST_CASE("[PathFollow3D] Removal of a point in curve") { - const Ref<Curve3D> &curve = memnew(Curve3D()); +TEST_CASE("[SceneTree][PathFollow3D] Removal of a point in curve") { + Ref<Curve3D> curve = memnew(Curve3D); curve->add_point(Vector3(0, 0, 0)); curve->add_point(Vector3(100, 0, 0)); curve->add_point(Vector3(100, 100, 0)); - const Path3D *path = memnew(Path3D); + Path3D *path = memnew(Path3D); path->set_curve(curve); - const PathFollow3D *path_follow_3d = memnew(PathFollow3D); + PathFollow3D *path_follow_3d = memnew(PathFollow3D); path->add_child(path_follow_3d); + SceneTree::get_singleton()->get_root()->add_child(path); path_follow_3d->set_progress_ratio(0.5); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector2(100, 0, 0))); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(100, 0, 0), path_follow_3d->get_transform().get_origin())); curve->remove_point(1); + path_follow_3d->set_progress_ratio(0.5); + path_follow_3d->update_transform(true); CHECK_MESSAGE( - path_follow_3d->get_transform().get_origin().is_equal_approx(Vector2(50, 50, 0)), + is_equal_approx(Vector3(50, 50, 0), path_follow_3d->get_transform().get_origin()), "Path follow's position should be updated after removing a point from the curve"); memdelete(path); } -TEST_CASE("[PathFollow3D] Progress ratio out of range") { - const Ref<Curve3D> &curve = memnew(Curve3D()); +TEST_CASE("[SceneTree][PathFollow3D] Progress ratio out of range") { + Ref<Curve3D> curve = memnew(Curve3D); curve->add_point(Vector3(0, 0, 0)); curve->add_point(Vector3(100, 0, 0)); - const Path3D *path = memnew(Path3D); + Path3D *path = memnew(Path3D); path->set_curve(curve); - const PathFollow3D *path_follow_3d = memnew(PathFollow3D); + PathFollow3D *path_follow_3d = memnew(PathFollow3D); path->add_child(path_follow_3d); + SceneTree::get_singleton()->get_root()->add_child(path); path_follow_3d->set_loop(true); path_follow_3d->set_progress_ratio(-0.3); CHECK_MESSAGE( - path_follow_3d->get_progress_ratio() == 0.7, + Math::is_equal_approx(path_follow_3d->get_progress_ratio(), (real_t)0.7), "Progress Ratio should loop back from the end in the opposite direction"); path_follow_3d->set_progress_ratio(1.3); CHECK_MESSAGE( - path_follow_3d->get_progress_ratio() == 0.3, + Math::is_equal_approx(path_follow_3d->get_progress_ratio(), (real_t)0.3), "Progress Ratio should loop back from the end in the opposite direction"); path_follow_3d->set_loop(false); path_follow_3d->set_progress_ratio(-0.3); CHECK_MESSAGE( - path_follow_3d->get_progress_ratio() == 0, + Math::is_equal_approx(path_follow_3d->get_progress_ratio(), 0), "Progress Ratio should be clamped at 0"); path_follow_3d->set_progress_ratio(1.3); CHECK_MESSAGE( - path_follow_3d->get_progress_ratio() == 1, + Math::is_equal_approx(path_follow_3d->get_progress_ratio(), 1), "Progress Ratio should be clamped at 1"); memdelete(path); } -TEST_CASE("[PathFollow3D] Progress out of range") { - const Ref<Curve3D> &curve = memnew(Curve3D()); +TEST_CASE("[SceneTree][PathFollow3D] Progress out of range") { + Ref<Curve3D> curve = memnew(Curve3D); curve->add_point(Vector3(0, 0, 0)); curve->add_point(Vector3(100, 0, 0)); - const Path3D *path = memnew(Path3D); + Path3D *path = memnew(Path3D); path->set_curve(curve); - const PathFollow3D *path_follow_3d = memnew(PathFollow3D); + PathFollow3D *path_follow_3d = memnew(PathFollow3D); path->add_child(path_follow_3d); + SceneTree::get_singleton()->get_root()->add_child(path); path_follow_3d->set_loop(true); path_follow_3d->set_progress(-50); CHECK_MESSAGE( - path_follow_3d->get_progress() == 50, + Math::is_equal_approx(path_follow_3d->get_progress(), 50), "Progress should loop back from the end in the opposite direction"); path_follow_3d->set_progress(150); CHECK_MESSAGE( - path_follow_3d->get_progress() == 50, + Math::is_equal_approx(path_follow_3d->get_progress(), 50), "Progress should loop back from the end in the opposite direction"); path_follow_3d->set_loop(false); path_follow_3d->set_progress(-50); CHECK_MESSAGE( - path_follow_3d->get_progress() == 0, + Math::is_equal_approx(path_follow_3d->get_progress(), 0), "Progress should be clamped at 0"); path_follow_3d->set_progress(150); CHECK_MESSAGE( - path_follow_3d->get_progress() == 100, + Math::is_equal_approx(path_follow_3d->get_progress(), 100), "Progress should be clamped at max value of curve"); memdelete(path); } + +TEST_CASE("[SceneTree][PathFollow3D] Calculate forward vector") { + const real_t dist_cube_100 = 100 * Math::sqrt(3.0); + Ref<Curve3D> curve = memnew(Curve3D); + curve->add_point(Vector3(0, 0, 0)); + curve->add_point(Vector3(100, 0, 0)); + curve->add_point(Vector3(200, 100, -100)); + curve->add_point(Vector3(200, 100, 200)); + curve->add_point(Vector3(100, 0, 100)); + curve->add_point(Vector3(0, 0, 100)); + Path3D *path = memnew(Path3D); + path->set_curve(curve); + PathFollow3D *path_follow_3d = memnew(PathFollow3D); + path->add_child(path_follow_3d); + SceneTree::get_singleton()->get_root()->add_child(path); + + path_follow_3d->set_loop(false); + path_follow_3d->set_rotation_mode(PathFollow3D::RotationMode::ROTATION_ORIENTED); + + path_follow_3d->set_progress(-50); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(-1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2))); + + path_follow_3d->set_progress(0); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(-1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2))); + + path_follow_3d->set_progress(50); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(-1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2))); + + path_follow_3d->set_progress(100); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(-1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2))); + + path_follow_3d->set_progress(100 + dist_cube_100 / 2); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(-0.577348, -0.577348, 0.577348), path_follow_3d->get_transform().get_basis().get_column(2))); + + path_follow_3d->set_progress(100 + dist_cube_100 - 0.01); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(-0.577348, -0.577348, 0.577348), path_follow_3d->get_transform().get_basis().get_column(2))); + + path_follow_3d->set_progress(250 + dist_cube_100); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(0, 0, -1), path_follow_3d->get_transform().get_basis().get_column(2))); + + path_follow_3d->set_progress(400 + dist_cube_100 - 0.01); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(0, 0, -1), path_follow_3d->get_transform().get_basis().get_column(2))); + + path_follow_3d->set_progress(400 + 1.5 * dist_cube_100); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(0.577348, 0.577348, 0.577348), path_follow_3d->get_transform().get_basis().get_column(2))); + + path_follow_3d->set_progress(400 + 2 * dist_cube_100 - 0.01); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(0.577348, 0.577348, 0.577348), path_follow_3d->get_transform().get_basis().get_column(2))); + + path_follow_3d->set_progress(500 + 2 * dist_cube_100); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2))); + + memdelete(path); +} } // namespace TestPathFollow3D #endif // TEST_PATH_FOLLOW_3D_H diff --git a/tests/scene/test_text_edit.h b/tests/scene/test_text_edit.h index b2d9f5100e..69e27fe7a0 100644 --- a/tests/scene/test_text_edit.h +++ b/tests/scene/test_text_edit.h @@ -1763,6 +1763,28 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK_FALSE(text_edit->has_selection()); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 4); + + // Wrapped lines. + text_edit->set_line_wrapping_mode(TextEdit::LineWrappingMode::LINE_WRAPPING_BOUNDARY); + text_edit->set_text("this is some text\nfor selection"); + text_edit->set_size(Size2(110, 100)); + MessageQueue::get_singleton()->flush(); + + // Line 0 wraps: 'this is ', 'some text'. + // Line 1 wraps: 'for ', 'selection'. + CHECK(text_edit->is_line_wrapped(0)); + + // Select to the first character of a wrapped line. + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(0, 11).get_center(), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(0, 8).get_center(), MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text() == "so"); + CHECK(text_edit->get_selection_mode() == TextEdit::SELECTION_MODE_POINTER); + CHECK(text_edit->get_selection_origin_line() == 0); + CHECK(text_edit->get_selection_origin_column() == 10); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 8); + CHECK(text_edit->is_dragging_cursor()); } SUBCASE("[TextEdit] mouse word select") { @@ -5713,6 +5735,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->get_caret_count() == 2); MessageQueue::get_singleton()->flush(); + // Lines 0 and 4 are wrapped into 2 parts: 'this is ' and 'some'. CHECK(text_edit->is_line_wrapped(0)); SIGNAL_DISCARD("text_set"); SIGNAL_DISCARD("text_changed"); @@ -5762,9 +5785,9 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); - text_edit->set_caret_column(12, false); // Normal up over wrapped line to line 0. + text_edit->set_caret_column(12, false); SEND_GUI_ACTION("ui_text_caret_up"); CHECK(text_edit->get_viewport()->is_input_handled()); CHECK(text_edit->get_caret_line() == 0); @@ -5777,6 +5800,23 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); + + // Normal up from column 0 to a wrapped line. + text_edit->remove_secondary_carets(); + text_edit->set_caret_line(5); + text_edit->set_caret_column(0); + SEND_GUI_ACTION("ui_text_caret_up"); + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK(text_edit->get_caret_line() == 4); + CHECK(text_edit->get_caret_column() == 8); + CHECK_FALSE(text_edit->has_selection(0)); + + // Normal up to column 0 of a wrapped line. + SEND_GUI_ACTION("ui_text_caret_up"); + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK(text_edit->get_caret_line() == 4); + CHECK(text_edit->get_caret_column() == 0); + CHECK_FALSE(text_edit->has_selection(0)); } SUBCASE("[TextEdit] ui_text_caret_down") { @@ -5792,6 +5832,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { MessageQueue::get_singleton()->flush(); + // Lines 3 and 7 are wrapped into 2 parts: 'this is ' and 'some'. CHECK(text_edit->is_line_wrapped(3)); SIGNAL_DISCARD("text_set"); SIGNAL_DISCARD("text_changed"); @@ -5841,9 +5882,9 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); - text_edit->set_caret_column(7, false); // Normal down over wrapped line to last wrapped line. + text_edit->set_caret_column(7, false); SEND_GUI_ACTION("ui_text_caret_down"); CHECK(text_edit->get_viewport()->is_input_handled()); CHECK(text_edit->get_caret_line() == 3); @@ -5856,6 +5897,23 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); + + // Normal down to column 0 of a wrapped line. + text_edit->remove_secondary_carets(); + text_edit->set_caret_line(3); + text_edit->set_caret_column(0); + SEND_GUI_ACTION("ui_text_caret_down"); + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK(text_edit->get_caret_line() == 3); + CHECK(text_edit->get_caret_column() == 8); + CHECK_FALSE(text_edit->has_selection(0)); + + // Normal down out of visual column 0 of a wrapped line moves to start of next line. + SEND_GUI_ACTION("ui_text_caret_down"); + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK(text_edit->get_caret_line() == 4); + CHECK(text_edit->get_caret_column() == 0); + CHECK_FALSE(text_edit->has_selection(0)); } SUBCASE("[TextEdit] ui_text_caret_document_start") { @@ -7162,7 +7220,7 @@ TEST_CASE("[SceneTree][TextEdit] multicaret") { CHECK(text_edit->get_caret_line(0) == 2); CHECK(text_edit->get_caret_column(0) == 5); CHECK(text_edit->get_caret_line(1) == 2); - CHECK(text_edit->get_caret_column(1) == 10); + CHECK(text_edit->get_caret_column(1) == 6); // Cannot add caret below from last line last line wrap. text_edit->add_caret_at_carets(true); @@ -7171,7 +7229,7 @@ TEST_CASE("[SceneTree][TextEdit] multicaret") { CHECK(text_edit->get_caret_line(0) == 2); CHECK(text_edit->get_caret_column(0) == 5); CHECK(text_edit->get_caret_line(1) == 2); - CHECK(text_edit->get_caret_column(1) == 10); + CHECK(text_edit->get_caret_column(1) == 6); // Add caret above from not first line wrap. text_edit->remove_secondary_carets(); diff --git a/tests/servers/test_navigation_server_3d.h b/tests/servers/test_navigation_server_3d.h index 8778ea86a6..cf6b89c330 100644 --- a/tests/servers/test_navigation_server_3d.h +++ b/tests/servers/test_navigation_server_3d.h @@ -697,12 +697,16 @@ TEST_SUITE("[Navigation]") { CHECK_NE(navigation_server->map_get_closest_point(map, Vector3(0, 0, 0)), Vector3(0, 0, 0)); CHECK_NE(navigation_server->map_get_closest_point_normal(map, Vector3(0, 0, 0)), Vector3()); CHECK(navigation_server->map_get_closest_point_owner(map, Vector3(0, 0, 0)).is_valid()); - // TODO: Test map_get_closest_point_to_segment() with p_use_collision=true as well. CHECK_NE(navigation_server->map_get_closest_point_to_segment(map, Vector3(0, 0, 0), Vector3(1, 1, 1), false), Vector3()); + CHECK_NE(navigation_server->map_get_closest_point_to_segment(map, Vector3(0, 0, 0), Vector3(1, 1, 1), true), Vector3()); CHECK_NE(navigation_server->map_get_path(map, Vector3(0, 0, 0), Vector3(10, 0, 10), true).size(), 0); CHECK_NE(navigation_server->map_get_path(map, Vector3(0, 0, 0), Vector3(10, 0, 10), false).size(), 0); } + SUBCASE("'map_get_closest_point_to_segment' with 'use_collision' should return default if segment doesn't intersect map") { + CHECK_EQ(navigation_server->map_get_closest_point_to_segment(map, Vector3(1, 2, 1), Vector3(1, 1, 1), true), Vector3()); + } + SUBCASE("Elaborate query with 'CORRIDORFUNNEL' post-processing should yield non-empty result") { Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D); query_parameters->set_map(map); diff --git a/tests/test_main.cpp b/tests/test_main.cpp index dd30003c01..edadc52a16 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -30,6 +30,8 @@ #include "test_main.h" +#include "modules/modules_enabled.gen.h" + #ifdef TOOLS_ENABLED #include "editor/editor_paths.h" #include "editor/editor_settings.h" @@ -103,20 +105,20 @@ #include "tests/scene/test_audio_stream_wav.h" #include "tests/scene/test_bit_map.h" #include "tests/scene/test_camera_2d.h" -#include "tests/scene/test_code_edit.h" -#include "tests/scene/test_color_picker.h" #include "tests/scene/test_control.h" #include "tests/scene/test_curve.h" #include "tests/scene/test_curve_2d.h" #include "tests/scene/test_curve_3d.h" #include "tests/scene/test_gradient.h" #include "tests/scene/test_image_texture.h" +#include "tests/scene/test_image_texture_3d.h" +#include "tests/scene/test_instance_placeholder.h" #include "tests/scene/test_node.h" #include "tests/scene/test_node_2d.h" #include "tests/scene/test_packed_scene.h" #include "tests/scene/test_path_2d.h" +#include "tests/scene/test_path_follow_2d.h" #include "tests/scene/test_sprite_frames.h" -#include "tests/scene/test_text_edit.h" #include "tests/scene/test_theme.h" #include "tests/scene/test_timer.h" #include "tests/scene/test_viewport.h" @@ -126,19 +128,30 @@ #include "tests/servers/test_text_server.h" #include "tests/test_validate_testing.h" +#ifndef ADVANCED_GUI_DISABLED +#include "tests/scene/test_code_edit.h" +#include "tests/scene/test_color_picker.h" +#include "tests/scene/test_graph_node.h" +#include "tests/scene/test_text_edit.h" +#endif // ADVANCED_GUI_DISABLED + #ifndef _3D_DISABLED -#include "tests/scene/test_arraymesh.h" -#include "tests/scene/test_camera_3d.h" +#ifdef MODULE_NAVIGATION_ENABLED #include "tests/scene/test_navigation_agent_2d.h" #include "tests/scene/test_navigation_agent_3d.h" #include "tests/scene/test_navigation_obstacle_2d.h" #include "tests/scene/test_navigation_obstacle_3d.h" #include "tests/scene/test_navigation_region_2d.h" #include "tests/scene/test_navigation_region_3d.h" -#include "tests/scene/test_path_3d.h" -#include "tests/scene/test_primitives.h" #include "tests/servers/test_navigation_server_2d.h" #include "tests/servers/test_navigation_server_3d.h" +#endif // MODULE_NAVIGATION_ENABLED + +#include "tests/scene/test_arraymesh.h" +#include "tests/scene/test_camera_3d.h" +#include "tests/scene/test_path_3d.h" +#include "tests/scene/test_path_follow_3d.h" +#include "tests/scene/test_primitives.h" #endif // _3D_DISABLED #include "modules/modules_tests.gen.h" diff --git a/tests/test_utils.cpp b/tests/test_utils.cpp index cbd6d1ffbb..9d41e74020 100644 --- a/tests/test_utils.cpp +++ b/tests/test_utils.cpp @@ -30,6 +30,7 @@ #include "tests/test_utils.h" +#include "core/io/dir_access.h" #include "core/os/os.h" String TestUtils::get_data_path(const String &p_file) { @@ -40,3 +41,9 @@ String TestUtils::get_data_path(const String &p_file) { String TestUtils::get_executable_dir() { return OS::get_singleton()->get_executable_path().get_base_dir(); } + +String TestUtils::get_temp_path(const String &p_suffix) { + const String temp_base = OS::get_singleton()->get_cache_path().path_join("godot_test"); + DirAccess::make_dir_absolute(temp_base); // Ensure the directory exists. + return temp_base.path_join(p_suffix); +} diff --git a/tests/test_utils.h b/tests/test_utils.h index 48abe75c06..876a59ee7b 100644 --- a/tests/test_utils.h +++ b/tests/test_utils.h @@ -37,6 +37,7 @@ namespace TestUtils { String get_data_path(const String &p_file); String get_executable_dir(); +String get_temp_path(const String &p_suffix); } // namespace TestUtils #endif // TEST_UTILS_H |