summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/core/config/test_project_settings.h7
-rw-r--r--tests/core/io/test_http_client.h4
-rw-r--r--tests/core/io/test_image.h26
-rw-r--r--tests/core/io/test_pck_packer.h8
-rw-r--r--tests/core/io/test_resource.h8
-rw-r--r--tests/core/math/test_basis.h18
-rw-r--r--tests/core/math/test_transform_2d.h110
-rw-r--r--tests/core/math/test_vector2.h4
-rw-r--r--tests/core/math/test_vector3.h4
-rw-r--r--tests/core/object/test_class_db.h23
-rw-r--r--tests/core/os/test_os.h2
-rw-r--r--tests/core/string/test_string.h124
-rw-r--r--tests/core/string/test_translation.h1
-rw-r--r--tests/core/string/test_translation_server.h2
-rw-r--r--tests/core/variant/test_array.h37
-rw-r--r--tests/display_server_mock.h22
-rw-r--r--tests/scene/test_audio_stream_wav.h6
-rw-r--r--tests/scene/test_code_edit.h159
-rw-r--r--tests/scene/test_graph_node.h6
-rw-r--r--tests/scene/test_image_texture_3d.h101
-rw-r--r--tests/scene/test_instance_placeholder.h534
-rw-r--r--tests/scene/test_node.h130
-rw-r--r--tests/scene/test_path_follow_2d.h128
-rw-r--r--tests/scene/test_path_follow_3d.h197
-rw-r--r--tests/scene/test_text_edit.h66
-rw-r--r--tests/servers/test_navigation_server_3d.h6
-rw-r--r--tests/test_main.cpp27
-rw-r--r--tests/test_utils.cpp7
-rw-r--r--tests/test_utils.h1
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