feat: updated engine version to 4.4-rc1

This commit is contained in:
Sara 2025-02-23 14:38:14 +01:00
parent ee00efde1f
commit 21ba8e33af
5459 changed files with 1128836 additions and 198305 deletions

View file

@ -39,7 +39,8 @@
namespace TestArrayMesh {
TEST_CASE("[SceneTree][ArrayMesh] Adding and modifying blendshapes.") {
Ref<ArrayMesh> mesh = memnew(ArrayMesh);
Ref<ArrayMesh> mesh;
mesh.instantiate();
StringName name_a{ "ShapeA" };
StringName name_b{ "ShapeB" };
@ -76,8 +77,9 @@ TEST_CASE("[SceneTree][ArrayMesh] Adding and modifying blendshapes.") {
}
SUBCASE("Adding blend shape after surface is added causes error") {
Ref<CylinderMesh> cylinder = memnew(CylinderMesh);
Array cylinder_array{};
Ref<CylinderMesh> cylinder;
cylinder.instantiate();
Array cylinder_array;
cylinder_array.resize(Mesh::ARRAY_MAX);
cylinder->create_mesh_array(cylinder_array, 3.f, 3.f, 5.f);
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, cylinder_array);
@ -88,6 +90,26 @@ TEST_CASE("[SceneTree][ArrayMesh] Adding and modifying blendshapes.") {
CHECK(mesh->get_blend_shape_count() == 0);
}
SUBCASE("Adding blend shapes once all surfaces have been removed is allowed") {
Ref<CylinderMesh> cylinder;
cylinder.instantiate();
Array cylinder_array;
cylinder_array.resize(Mesh::ARRAY_MAX);
cylinder->create_mesh_array(cylinder_array, 3.f, 3.f, 5.f);
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, cylinder_array);
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, cylinder_array);
mesh->surface_remove(0);
ERR_PRINT_OFF
mesh->add_blend_shape(name_a);
ERR_PRINT_ON
CHECK(mesh->get_blend_shape_count() == 0);
mesh->surface_remove(0);
mesh->add_blend_shape(name_a);
CHECK(mesh->get_blend_shape_count() == 1);
}
SUBCASE("Change blend shape name after adding.") {
mesh->add_blend_shape(name_a);
mesh->set_blend_shape_name(0, name_b);
@ -114,11 +136,40 @@ TEST_CASE("[SceneTree][ArrayMesh] Adding and modifying blendshapes.") {
CHECK(mesh->get_blend_shape_count() == 0);
}
SUBCASE("Clearing all blend shapes once all surfaces have been removed is allowed") {
mesh->add_blend_shape(name_a);
mesh->add_blend_shape(name_b);
Ref<CylinderMesh> cylinder;
cylinder.instantiate();
Array cylinder_array;
cylinder_array.resize(Mesh::ARRAY_MAX);
cylinder->create_mesh_array(cylinder_array, 3.f, 3.f, 5.f);
Array blend_shape;
blend_shape.resize(Mesh::ARRAY_MAX);
blend_shape[Mesh::ARRAY_VERTEX] = cylinder_array[Mesh::ARRAY_VERTEX];
blend_shape[Mesh::ARRAY_NORMAL] = cylinder_array[Mesh::ARRAY_NORMAL];
blend_shape[Mesh::ARRAY_TANGENT] = cylinder_array[Mesh::ARRAY_TANGENT];
Array blend_shapes;
blend_shapes.push_back(blend_shape);
blend_shapes.push_back(blend_shape);
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, cylinder_array, blend_shapes);
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, cylinder_array, blend_shapes);
mesh->surface_remove(0);
ERR_PRINT_OFF
mesh->clear_blend_shapes();
ERR_PRINT_ON
CHECK(mesh->get_blend_shape_count() == 2);
mesh->surface_remove(0);
mesh->clear_blend_shapes();
CHECK(mesh->get_blend_shape_count() == 0);
}
SUBCASE("Can't add surface with incorrect number of blend shapes.") {
mesh->add_blend_shape(name_a);
mesh->add_blend_shape(name_b);
Ref<CylinderMesh> cylinder = memnew(CylinderMesh);
Array cylinder_array{};
Array cylinder_array;
ERR_PRINT_OFF
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, cylinder_array);
ERR_PRINT_ON
@ -128,16 +179,17 @@ TEST_CASE("[SceneTree][ArrayMesh] Adding and modifying blendshapes.") {
SUBCASE("Can't clear blend shapes after surface had been added.") {
mesh->add_blend_shape(name_a);
mesh->add_blend_shape(name_b);
Ref<CylinderMesh> cylinder = memnew(CylinderMesh);
Array cylinder_array{};
Ref<CylinderMesh> cylinder;
cylinder.instantiate();
Array cylinder_array;
cylinder_array.resize(Mesh::ARRAY_MAX);
cylinder->create_mesh_array(cylinder_array, 3.f, 3.f, 5.f);
Array blend_shape{};
Array blend_shape;
blend_shape.resize(Mesh::ARRAY_MAX);
blend_shape[Mesh::ARRAY_VERTEX] = cylinder_array[Mesh::ARRAY_VERTEX];
blend_shape[Mesh::ARRAY_NORMAL] = cylinder_array[Mesh::ARRAY_NORMAL];
blend_shape[Mesh::ARRAY_TANGENT] = cylinder_array[Mesh::ARRAY_TANGENT];
Array blend_shapes{};
Array blend_shapes;
blend_shapes.push_back(blend_shape);
blend_shapes.push_back(blend_shape);
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, cylinder_array, blend_shapes);
@ -155,15 +207,18 @@ TEST_CASE("[SceneTree][ArrayMesh] Adding and modifying blendshapes.") {
}
TEST_CASE("[SceneTree][ArrayMesh] Surface metadata tests.") {
Ref<ArrayMesh> mesh = memnew(ArrayMesh);
Ref<CylinderMesh> cylinder = memnew(CylinderMesh);
Array cylinder_array{};
Ref<ArrayMesh> mesh;
mesh.instantiate();
Ref<CylinderMesh> cylinder;
cylinder.instantiate();
Array cylinder_array;
cylinder_array.resize(Mesh::ARRAY_MAX);
cylinder->create_mesh_array(cylinder_array, 3.f, 3.f, 5.f);
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, cylinder_array);
Ref<BoxMesh> box = memnew(BoxMesh);
Array box_array{};
Ref<BoxMesh> box;
box.instantiate();
Array box_array;
box_array.resize(Mesh::ARRAY_MAX);
box->create_mesh_array(box_array, Vector3(2.f, 1.2f, 1.6f));
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, box_array);
@ -207,7 +262,8 @@ TEST_CASE("[SceneTree][ArrayMesh] Surface metadata tests.") {
}
SUBCASE("Set material to two different surfaces.") {
Ref<Material> mat = memnew(Material);
Ref<Material> mat;
mat.instantiate();
mesh->surface_set_material(0, mat);
CHECK(mesh->surface_get_material(0) == mat);
mesh->surface_set_material(1, mat);
@ -215,7 +271,8 @@ TEST_CASE("[SceneTree][ArrayMesh] Surface metadata tests.") {
}
SUBCASE("Set same material multiple times doesn't change material of surface.") {
Ref<Material> mat = memnew(Material);
Ref<Material> mat;
mat.instantiate();
mesh->surface_set_material(0, mat);
mesh->surface_set_material(0, mat);
mesh->surface_set_material(0, mat);
@ -223,8 +280,10 @@ TEST_CASE("[SceneTree][ArrayMesh] Surface metadata tests.") {
}
SUBCASE("Set material of surface then change to different material.") {
Ref<Material> mat1 = memnew(Material);
Ref<Material> mat2 = memnew(Material);
Ref<Material> mat1;
mat1.instantiate();
Ref<Material> mat2;
mat2.instantiate();
mesh->surface_set_material(1, mat1);
CHECK(mesh->surface_get_material(1) == mat1);
mesh->surface_set_material(1, mat2);
@ -232,40 +291,48 @@ TEST_CASE("[SceneTree][ArrayMesh] Surface metadata tests.") {
}
SUBCASE("Get the LOD of the mesh.") {
Dictionary lod{};
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, cylinder_array, TypedArray<Array>{}, lod);
Dictionary lod;
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, cylinder_array, TypedArray<Array>(), lod);
CHECK(mesh->surface_get_lods(2) == lod);
}
SUBCASE("Get the blend shape arrays from the mesh.") {
TypedArray<Array> blend{};
TypedArray<Array> blend;
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, cylinder_array, blend);
CHECK(mesh->surface_get_blend_shape_arrays(2) == blend);
}
}
TEST_CASE("[SceneTree][ArrayMesh] Get/Set mesh metadata and actions") {
Ref<ArrayMesh> mesh = memnew(ArrayMesh);
Ref<CylinderMesh> cylinder = memnew(CylinderMesh);
Array cylinder_array{};
Ref<ArrayMesh> mesh;
mesh.instantiate();
Ref<CylinderMesh> cylinder;
cylinder.instantiate();
Array cylinder_array;
cylinder_array.resize(Mesh::ARRAY_MAX);
cylinder->create_mesh_array(cylinder_array, 3.f, 3.f, 5.f);
constexpr float cylinder_radius = 3.f;
constexpr float cylinder_height = 5.f;
cylinder->create_mesh_array(cylinder_array, cylinder_radius, cylinder_radius, cylinder_height);
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, cylinder_array);
Ref<BoxMesh> box = memnew(BoxMesh);
Array box_array{};
Ref<BoxMesh> box;
box.instantiate();
Array box_array;
box_array.resize(Mesh::ARRAY_MAX);
box->create_mesh_array(box_array, Vector3(2.f, 1.2f, 1.6f));
const Vector3 box_size = Vector3(2.f, 1.2f, 1.6f);
box->create_mesh_array(box_array, box_size);
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, box_array);
SUBCASE("Set the shadow mesh.") {
Ref<ArrayMesh> shadow = memnew(ArrayMesh);
Ref<ArrayMesh> shadow;
shadow.instantiate();
mesh->set_shadow_mesh(shadow);
CHECK(mesh->get_shadow_mesh() == shadow);
}
SUBCASE("Set the shadow mesh multiple times.") {
Ref<ArrayMesh> shadow = memnew(ArrayMesh);
Ref<ArrayMesh> shadow;
shadow.instantiate();
mesh->set_shadow_mesh(shadow);
mesh->set_shadow_mesh(shadow);
mesh->set_shadow_mesh(shadow);
@ -274,8 +341,10 @@ TEST_CASE("[SceneTree][ArrayMesh] Get/Set mesh metadata and actions") {
}
SUBCASE("Set the same shadow mesh on multiple meshes.") {
Ref<ArrayMesh> shadow = memnew(ArrayMesh);
Ref<ArrayMesh> mesh2 = memnew(ArrayMesh);
Ref<ArrayMesh> shadow;
shadow.instantiate();
Ref<ArrayMesh> mesh2;
mesh2.instantiate();
mesh->set_shadow_mesh(shadow);
mesh2->set_shadow_mesh(shadow);
@ -284,22 +353,24 @@ TEST_CASE("[SceneTree][ArrayMesh] Get/Set mesh metadata and actions") {
}
SUBCASE("Set the shadow mesh and then change it.") {
Ref<ArrayMesh> shadow = memnew(ArrayMesh);
Ref<ArrayMesh> shadow;
shadow.instantiate();
mesh->set_shadow_mesh(shadow);
CHECK(mesh->get_shadow_mesh() == shadow);
Ref<ArrayMesh> shadow2 = memnew(ArrayMesh);
Ref<ArrayMesh> shadow2;
shadow2.instantiate();
mesh->set_shadow_mesh(shadow2);
CHECK(mesh->get_shadow_mesh() == shadow2);
}
SUBCASE("Set custom AABB.") {
AABB bound{};
AABB bound;
mesh->set_custom_aabb(bound);
CHECK(mesh->get_custom_aabb() == bound);
}
SUBCASE("Set custom AABB multiple times.") {
AABB bound{};
AABB bound;
mesh->set_custom_aabb(bound);
mesh->set_custom_aabb(bound);
mesh->set_custom_aabb(bound);
@ -308,8 +379,8 @@ TEST_CASE("[SceneTree][ArrayMesh] Get/Set mesh metadata and actions") {
}
SUBCASE("Set custom AABB then change to another AABB.") {
AABB bound{};
AABB bound2{};
AABB bound;
AABB bound2;
mesh->set_custom_aabb(bound);
CHECK(mesh->get_custom_aabb() == bound);
mesh->set_custom_aabb(bound2);
@ -329,7 +400,8 @@ TEST_CASE("[SceneTree][ArrayMesh] Get/Set mesh metadata and actions") {
SUBCASE("Create surface from raw SurfaceData data.") {
RID mesh_rid = mesh->get_rid();
RS::SurfaceData surface_data = RS::get_singleton()->mesh_get_surface(mesh_rid, 0);
Ref<ArrayMesh> mesh2 = memnew(ArrayMesh);
Ref<ArrayMesh> mesh2;
mesh2.instantiate();
mesh2->add_surface(surface_data.format, Mesh::PRIMITIVE_TRIANGLES, surface_data.vertex_data, surface_data.attribute_data,
surface_data.skin_data, surface_data.vertex_count, surface_data.index_data, surface_data.index_count, surface_data.aabb);
CHECK(mesh2->get_surface_count() == 1);
@ -337,6 +409,43 @@ TEST_CASE("[SceneTree][ArrayMesh] Get/Set mesh metadata and actions") {
CHECK((mesh2->surface_get_format(0) & surface_data.format) != 0);
CHECK(mesh2->get_aabb().is_equal_approx(surface_data.aabb));
}
SUBCASE("Removing a surface decreases surface count.") {
REQUIRE(mesh->get_surface_count() == 2);
mesh->surface_remove(0);
CHECK(mesh->get_surface_count() == 1);
mesh->surface_remove(0);
CHECK(mesh->get_surface_count() == 0);
}
SUBCASE("Remove the first surface and check the mesh's AABB.") {
REQUIRE(mesh->get_surface_count() >= 1);
mesh->surface_remove(0);
const AABB box_aabb = AABB(-box_size / 2, box_size);
CHECK(mesh->get_aabb().is_equal_approx(box_aabb));
}
SUBCASE("Remove the last surface and check the mesh's AABB.") {
REQUIRE(mesh->get_surface_count() >= 1);
mesh->surface_remove(mesh->get_surface_count() - 1);
const AABB cylinder_aabb = AABB(Vector3(-cylinder_radius, -cylinder_height / 2, -cylinder_radius),
Vector3(2 * cylinder_radius, cylinder_height, 2 * cylinder_radius));
CHECK(mesh->get_aabb().is_equal_approx(cylinder_aabb));
}
SUBCASE("Remove all surfaces and check the mesh's AABB.") {
while (mesh->get_surface_count()) {
mesh->surface_remove(0);
}
CHECK(mesh->get_aabb() == AABB());
}
SUBCASE("Removing a non-existent surface causes error.") {
ERR_PRINT_OFF
mesh->surface_remove(42);
ERR_PRINT_ON
CHECK(mesh->get_surface_count() == 2);
}
}
} // namespace TestArrayMesh

View file

@ -37,11 +37,6 @@
#include "tests/test_macros.h"
#ifdef TOOLS_ENABLED
#include "core/io/resource_loader.h"
#include "editor/import/resource_importer_wav.h"
#endif
namespace TestAudioStreamWAV {
// Default wav rate for test cases.
@ -148,23 +143,8 @@ void run_test(String file_name, AudioStreamWAV::Format data_format, bool stereo,
Ref<FileAccess> wav_file = FileAccess::open(save_path, FileAccess::READ, &error);
REQUIRE(error == OK);
#ifdef TOOLS_ENABLED
// The WAV importer can be used if enabled to check that the saved file is valid.
Ref<ResourceImporterWAV> wav_importer = memnew(ResourceImporterWAV);
List<ResourceImporter::ImportOption> options_list;
wav_importer->get_import_options("", &options_list);
HashMap<StringName, Variant> options_map;
for (const ResourceImporter::ImportOption &E : options_list) {
options_map[E.option.name] = E.default_value;
}
REQUIRE(wav_importer->import(save_path, save_path, options_map, nullptr) == OK);
String load_path = save_path + "." + wav_importer->get_save_extension();
Ref<AudioStreamWAV> loaded_stream = ResourceLoader::load(load_path, "AudioStreamWAV", ResourceFormatImporter::CACHE_MODE_IGNORE, &error);
REQUIRE(error == OK);
Dictionary options;
Ref<AudioStreamWAV> loaded_stream = AudioStreamWAV::load_from_file(save_path, options);
CHECK(loaded_stream->get_format() == stream->get_format());
CHECK(loaded_stream->get_loop_mode() == stream->get_loop_mode());
@ -175,31 +155,30 @@ void run_test(String file_name, AudioStreamWAV::Format data_format, bool stereo,
CHECK(loaded_stream->get_length() == stream->get_length());
CHECK(loaded_stream->is_monophonic() == stream->is_monophonic());
CHECK(loaded_stream->get_data() == stream->get_data());
#endif
}
}
TEST_CASE("[AudioStreamWAV] Mono PCM8 format") {
TEST_CASE("[Audio][AudioStreamWAV] Mono PCM8 format") {
run_test("test_pcm8_mono.wav", AudioStreamWAV::FORMAT_8_BITS, false, WAV_RATE, WAV_COUNT);
}
TEST_CASE("[AudioStreamWAV] Mono PCM16 format") {
TEST_CASE("[Audio][AudioStreamWAV] Mono PCM16 format") {
run_test("test_pcm16_mono.wav", AudioStreamWAV::FORMAT_16_BITS, false, WAV_RATE, WAV_COUNT);
}
TEST_CASE("[AudioStreamWAV] Stereo PCM8 format") {
TEST_CASE("[Audio][AudioStreamWAV] Stereo PCM8 format") {
run_test("test_pcm8_stereo.wav", AudioStreamWAV::FORMAT_8_BITS, true, WAV_RATE, WAV_COUNT);
}
TEST_CASE("[AudioStreamWAV] Stereo PCM16 format") {
TEST_CASE("[Audio][AudioStreamWAV] Stereo PCM16 format") {
run_test("test_pcm16_stereo.wav", AudioStreamWAV::FORMAT_16_BITS, true, WAV_RATE, WAV_COUNT);
}
TEST_CASE("[AudioStreamWAV] Alternate mix rate") {
TEST_CASE("[Audio][AudioStreamWAV] Alternate mix rate") {
run_test("test_pcm16_stereo_38000Hz.wav", AudioStreamWAV::FORMAT_16_BITS, true, 38000, 38000);
}
TEST_CASE("[AudioStreamWAV] save_to_wav() adds '.wav' file extension automatically") {
TEST_CASE("[Audio][AudioStreamWAV] save_to_wav() adds '.wav' file extension automatically") {
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);
@ -211,7 +190,7 @@ TEST_CASE("[AudioStreamWAV] save_to_wav() adds '.wav' file extension automatical
CHECK(error == OK);
}
TEST_CASE("[AudioStreamWAV] Default values") {
TEST_CASE("[Audio][AudioStreamWAV] Default values") {
Ref<AudioStreamWAV> stream = memnew(AudioStreamWAV);
CHECK(stream->get_format() == AudioStreamWAV::FORMAT_8_BITS);
CHECK(stream->get_loop_mode() == AudioStreamWAV::LOOP_DISABLED);
@ -225,11 +204,11 @@ TEST_CASE("[AudioStreamWAV] Default values") {
CHECK(stream->get_stream_name() == "");
}
TEST_CASE("[AudioStreamWAV] Save empty file") {
TEST_CASE("[Audio][AudioStreamWAV] Save empty file") {
run_test("test_empty.wav", AudioStreamWAV::FORMAT_8_BITS, false, WAV_RATE, 0);
}
TEST_CASE("[AudioStreamWAV] Saving IMA ADPCM is not supported") {
TEST_CASE("[Audio][AudioStreamWAV] Saving IMA ADPCM is not supported") {
String save_path = TestUtils::get_temp_path("test_adpcm.wav");
Ref<AudioStreamWAV> stream = memnew(AudioStreamWAV);
stream->set_format(AudioStreamWAV::FORMAT_IMA_ADPCM);

View file

@ -0,0 +1,66 @@
/**************************************************************************/
/* test_button.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_BUTTON_H
#define TEST_BUTTON_H
#include "scene/gui/button.h"
#include "scene/main/window.h"
#include "tests/test_macros.h"
namespace TestButton {
TEST_CASE("[SceneTree][Button] is_hovered()") {
// Create new button instance.
Button *button = memnew(Button);
CHECK(button != nullptr);
Window *root = SceneTree::get_singleton()->get_root();
root->add_child(button);
// Set up button's size and position.
button->set_size(Size2i(50, 50));
button->set_position(Size2i(10, 10));
// Button should initially be not hovered.
CHECK(button->is_hovered() == false);
// Simulate mouse entering the button.
SEND_GUI_MOUSE_MOTION_EVENT(Point2i(25, 25), MouseButtonMask::NONE, Key::NONE);
CHECK(button->is_hovered() == true);
// Simulate mouse exiting the button.
SEND_GUI_MOUSE_MOTION_EVENT(Point2i(150, 150), MouseButtonMask::NONE, Key::NONE);
CHECK(button->is_hovered() == false);
memdelete(button);
}
} //namespace TestButton
#endif // TEST_BUTTON_H

View file

@ -231,10 +231,13 @@ TEST_CASE("[SceneTree][Camera3D] Project/Unproject position") {
test_camera->set_orthogonal(5.0f, 0.5f, 1000.0f);
// Center.
CHECK(test_camera->project_position(Vector2(200, 100), 0.5f).is_equal_approx(Vector3(0, 0, -0.5f)));
CHECK(test_camera->project_position(Vector2(200, 100), test_camera->get_far()).is_equal_approx(Vector3(0, 0, -test_camera->get_far())));
// Top left.
CHECK(test_camera->project_position(Vector2(0, 0), 1.5f).is_equal_approx(Vector3(-5.0f, 2.5f, -1.5f)));
CHECK(test_camera->project_position(Vector2(0, 0), test_camera->get_near()).is_equal_approx(Vector3(-5.0f, 2.5f, -test_camera->get_near())));
// Bottom right.
CHECK(test_camera->project_position(Vector2(400, 200), 5.0f).is_equal_approx(Vector3(5.0f, -2.5f, -5.0f)));
CHECK(test_camera->project_position(Vector2(400, 200), test_camera->get_far()).is_equal_approx(Vector3(5.0f, -2.5f, -test_camera->get_far())));
}
SUBCASE("Perspective projection") {
@ -242,12 +245,15 @@ TEST_CASE("[SceneTree][Camera3D] Project/Unproject position") {
// Center.
CHECK(test_camera->project_position(Vector2(200, 100), 0.5f).is_equal_approx(Vector3(0, 0, -0.5f)));
CHECK(test_camera->project_position(Vector2(200, 100), 100.0f).is_equal_approx(Vector3(0, 0, -100.0f)));
CHECK(test_camera->project_position(Vector2(200, 100), test_camera->get_far()).is_equal_approx(Vector3(0, 0, -1.0f) * test_camera->get_far()));
// 3/4th way to Top left.
CHECK(test_camera->project_position(Vector2(100, 50), 0.5f).is_equal_approx(Vector3(-SQRT3 * 0.5f, SQRT3 * 0.25f, -0.5f)));
CHECK(test_camera->project_position(Vector2(100, 50), 1.0f).is_equal_approx(Vector3(-SQRT3, SQRT3 * 0.5f, -1.0f)));
CHECK(test_camera->project_position(Vector2(100, 50), test_camera->get_near()).is_equal_approx(Vector3(-SQRT3, SQRT3 * 0.5f, -1.0f) * test_camera->get_near()));
// 3/4th way to Bottom right.
CHECK(test_camera->project_position(Vector2(300, 150), 0.5f).is_equal_approx(Vector3(SQRT3 * 0.5f, -SQRT3 * 0.25f, -0.5f)));
CHECK(test_camera->project_position(Vector2(300, 150), 1.0f).is_equal_approx(Vector3(SQRT3, -SQRT3 * 0.5f, -1.0f)));
CHECK(test_camera->project_position(Vector2(300, 150), test_camera->get_far()).is_equal_approx(Vector3(SQRT3, -SQRT3 * 0.5f, -1.0f) * test_camera->get_far()));
}
}

View file

@ -848,7 +848,7 @@ TEST_CASE("[SceneTree][CodeEdit] delimiters") {
SceneTree::get_singleton()->get_root()->add_child(code_edit);
code_edit->grab_focus();
const Point2 OUTSIDE_DELIMETER = Point2(-1, -1);
const Point2 OUTSIDE_DELIMITER = Point2(-1, -1);
code_edit->clear_string_delimiters();
code_edit->clear_comment_delimiters();
@ -1047,13 +1047,13 @@ TEST_CASE("[SceneTree][CodeEdit] delimiters") {
/* Check line above is not in string. */
CHECK(code_edit->is_in_string(0, 1) == -1);
CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMITER);
/* Check column before start key is not in string. */
CHECK(code_edit->is_in_string(1, 0) == -1);
CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMITER);
/* Check column after start key is in string and start / end positions are correct. */
CHECK(code_edit->is_in_string(1, 1) != -1);
@ -1062,8 +1062,8 @@ TEST_CASE("[SceneTree][CodeEdit] delimiters") {
/* Check line after is not in string. */
CHECK(code_edit->is_in_string(2, 1) == -1);
CHECK(code_edit->get_delimiter_start_position(2, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(2, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(2, 1) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(2, 1) == OUTSIDE_DELIMITER);
/* Check region metadata. */
int idx = code_edit->is_in_string(1, 1);
@ -1075,13 +1075,13 @@ TEST_CASE("[SceneTree][CodeEdit] delimiters") {
/* Check line above is not in string. */
CHECK(code_edit->is_in_string(0, 1) == -1);
CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMITER);
/* Check column before first start key is not in string. */
CHECK(code_edit->is_in_string(1, 0) == -1);
CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMITER);
/* Check column after the first start key is in string and start / end positions are correct. */
CHECK(code_edit->is_in_string(1, 1) != -1);
@ -1095,8 +1095,8 @@ TEST_CASE("[SceneTree][CodeEdit] delimiters") {
/* Check line after is not in string. */
CHECK(code_edit->is_in_string(2, 1) == -1);
CHECK(code_edit->get_delimiter_start_position(2, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(2, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(2, 1) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(2, 1) == OUTSIDE_DELIMITER);
/* Check is in string with no column returns true if entire line is comment excluding whitespace. */
code_edit->set_text(" \n # # \n ");
@ -1138,13 +1138,13 @@ TEST_CASE("[SceneTree][CodeEdit] delimiters") {
/* Check line above is not in comment. */
CHECK(code_edit->is_in_comment(0, 1) == -1);
CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMITER);
/* Check column before start key is not in comment. */
CHECK(code_edit->is_in_comment(1, 0) == -1);
CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMITER);
/* Check column after start key is in comment and start / end positions are correct. */
CHECK(code_edit->is_in_comment(1, 1) != -1);
@ -1153,8 +1153,8 @@ TEST_CASE("[SceneTree][CodeEdit] delimiters") {
/* Check line after is not in comment. */
CHECK(code_edit->is_in_comment(2, 1) == -1);
CHECK(code_edit->get_delimiter_start_position(2, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(2, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(2, 1) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(2, 1) == OUTSIDE_DELIMITER);
/* Check region metadata. */
int idx = code_edit->is_in_comment(1, 1);
@ -1166,13 +1166,13 @@ TEST_CASE("[SceneTree][CodeEdit] delimiters") {
/* Check line above is not in comment. */
CHECK(code_edit->is_in_comment(0, 1) == -1);
CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMITER);
/* Check column before first start key is not in comment. */
CHECK(code_edit->is_in_comment(1, 0) == -1);
CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMITER);
/* Check column after the first start key is in comment and start / end positions are correct. */
CHECK(code_edit->is_in_comment(1, 1) != -1);
@ -1186,8 +1186,8 @@ TEST_CASE("[SceneTree][CodeEdit] delimiters") {
/* Check line after is not in comment. */
CHECK(code_edit->is_in_comment(2, 1) == -1);
CHECK(code_edit->get_delimiter_start_position(2, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(2, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(2, 1) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(2, 1) == OUTSIDE_DELIMITER);
/* Check is in comment with no column returns true if entire line is comment excluding whitespace. */
code_edit->set_text(" \n # # \n ");
@ -1235,13 +1235,13 @@ TEST_CASE("[SceneTree][CodeEdit] delimiters") {
/* Check line above is not in comment. */
CHECK(code_edit->is_in_comment(0, 1) == -1);
CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMITER);
/* Check column before first start key is not in comment. */
CHECK(code_edit->is_in_comment(1, 0) == -1);
CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMITER);
/* Check column after the first start key is in comment and start / end positions are correct. */
CHECK(code_edit->is_in_comment(1, 1) != -1);
@ -1256,8 +1256,8 @@ TEST_CASE("[SceneTree][CodeEdit] delimiters") {
/* Check line after is not in comment. */
CHECK(code_edit->is_in_comment(2, 1) == -1);
CHECK(code_edit->get_delimiter_start_position(2, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(2, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(2, 1) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(2, 1) == OUTSIDE_DELIMITER);
/* Remove the comment delimiter. */
code_edit->remove_comment_delimiter("#");
@ -1266,8 +1266,8 @@ TEST_CASE("[SceneTree][CodeEdit] delimiters") {
/* The "first" comment region is no longer valid. */
CHECK(code_edit->is_in_comment(1, 1) == -1);
CHECK(code_edit->get_delimiter_start_position(1, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(1, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(1, 1) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(1, 1) == OUTSIDE_DELIMITER);
/* The "second" region as string is now valid. */
CHECK(code_edit->is_in_string(1, 5) != -1);
@ -1291,13 +1291,13 @@ TEST_CASE("[SceneTree][CodeEdit] delimiters") {
/* Check line above is not in string. */
CHECK(code_edit->is_in_string(0, 1) == -1);
CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMITER);
/* Check column before start key is not in string. */
CHECK(code_edit->is_in_string(1, 0) == -1);
CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMITER);
/* Check column before closing delimiter is in string. */
CHECK(code_edit->is_in_string(1, 2) != -1);
@ -1306,13 +1306,13 @@ TEST_CASE("[SceneTree][CodeEdit] delimiters") {
/* Check column after end key is not in string. */
CHECK(code_edit->is_in_string(1, 6) == -1);
CHECK(code_edit->get_delimiter_start_position(1, 6) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(1, 6) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(1, 6) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(1, 6) == OUTSIDE_DELIMITER);
/* Check line after is not in string. */
CHECK(code_edit->is_in_string(2, 1) == -1);
CHECK(code_edit->get_delimiter_start_position(2, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(2, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(2, 1) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(2, 1) == OUTSIDE_DELIMITER);
/* Check the region metadata. */
int idx = code_edit->is_in_string(1, 2);
@ -1324,13 +1324,13 @@ TEST_CASE("[SceneTree][CodeEdit] delimiters") {
/* Check line above is not in string. */
CHECK(code_edit->is_in_string(0, 1) == -1);
CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMITER);
/* Check column before start key is not in string. */
CHECK(code_edit->is_in_string(1, 0) == -1);
CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMITER);
/* Check column just after start key is in string. */
CHECK(code_edit->is_in_string(1, 2) != -1);
@ -1349,26 +1349,26 @@ TEST_CASE("[SceneTree][CodeEdit] delimiters") {
/* Check column after end key is not in string. */
CHECK(code_edit->is_in_string(3, 3) == -1);
CHECK(code_edit->get_delimiter_start_position(3, 3) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(3, 3) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(3, 3) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(3, 3) == OUTSIDE_DELIMITER);
/* Check line after is not in string. */
CHECK(code_edit->is_in_string(4, 1) == -1);
CHECK(code_edit->get_delimiter_start_position(4, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(4, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(4, 1) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(4, 1) == OUTSIDE_DELIMITER);
/* Next test over a multiple non-blank lines. */
code_edit->set_text(" \n # \n \n # \n ");
/* Check line above is not in string. */
CHECK(code_edit->is_in_string(0, 1) == -1);
CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMITER);
/* Check column before start key is not in string. */
CHECK(code_edit->is_in_string(1, 0) == -1);
CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMITER);
/* Check column just after start key is in string. */
CHECK(code_edit->is_in_string(1, 2) != -1);
@ -1387,13 +1387,13 @@ TEST_CASE("[SceneTree][CodeEdit] delimiters") {
/* Check column after end key is not in string. */
CHECK(code_edit->is_in_string(3, 3) == -1);
CHECK(code_edit->get_delimiter_start_position(3, 3) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(3, 3) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(3, 3) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(3, 3) == OUTSIDE_DELIMITER);
/* Check line after is not in string. */
CHECK(code_edit->is_in_string(4, 1) == -1);
CHECK(code_edit->get_delimiter_start_position(4, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(4, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(4, 1) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(4, 1) == OUTSIDE_DELIMITER);
/* check the region metadata. */
idx = code_edit->is_in_string(1, 2);
@ -1409,13 +1409,13 @@ TEST_CASE("[SceneTree][CodeEdit] delimiters") {
/* Check line above is not in string. */
CHECK(code_edit->is_in_string(0, 1) == -1);
CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMITER);
/* Check column before start key is not in string. */
CHECK(code_edit->is_in_string(1, 0) == -1);
CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMITER);
/* Check column just after start key is in string. */
CHECK(code_edit->is_in_string(1, 2) != -1);
@ -1434,13 +1434,13 @@ TEST_CASE("[SceneTree][CodeEdit] delimiters") {
/* Check column after end key is not in string. */
CHECK(code_edit->is_in_string(3, 3) == -1);
CHECK(code_edit->get_delimiter_start_position(3, 3) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(3, 3) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(3, 3) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(3, 3) == OUTSIDE_DELIMITER);
/* Check line after is not in string. */
CHECK(code_edit->is_in_string(4, 1) == -1);
CHECK(code_edit->get_delimiter_start_position(4, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(4, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(4, 1) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(4, 1) == OUTSIDE_DELIMITER);
/* check the region metadata. */
idx = code_edit->is_in_string(1, 2);
@ -1493,13 +1493,13 @@ TEST_CASE("[SceneTree][CodeEdit] delimiters") {
/* Check line above is not in comment. */
CHECK(code_edit->is_in_comment(0, 1) == -1);
CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMITER);
/* Check column before start key is not in comment. */
CHECK(code_edit->is_in_comment(1, 0) == -1);
CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMITER);
/* Check column before closing delimiter is in comment. */
CHECK(code_edit->is_in_comment(1, 2) != -1);
@ -1508,13 +1508,13 @@ TEST_CASE("[SceneTree][CodeEdit] delimiters") {
/* Check column after end key is not in comment. */
CHECK(code_edit->is_in_comment(1, 6) == -1);
CHECK(code_edit->get_delimiter_start_position(1, 6) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(1, 6) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(1, 6) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(1, 6) == OUTSIDE_DELIMITER);
/* Check line after is not in comment. */
CHECK(code_edit->is_in_comment(2, 1) == -1);
CHECK(code_edit->get_delimiter_start_position(2, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(2, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(2, 1) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(2, 1) == OUTSIDE_DELIMITER);
/* Check the region metadata. */
int idx = code_edit->is_in_comment(1, 2);
@ -1526,13 +1526,13 @@ TEST_CASE("[SceneTree][CodeEdit] delimiters") {
/* Check line above is not in comment. */
CHECK(code_edit->is_in_comment(0, 1) == -1);
CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMITER);
/* Check column before start key is not in comment. */
CHECK(code_edit->is_in_comment(1, 0) == -1);
CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMITER);
/* Check column just after start key is in comment. */
CHECK(code_edit->is_in_comment(1, 2) != -1);
@ -1551,26 +1551,26 @@ TEST_CASE("[SceneTree][CodeEdit] delimiters") {
/* Check column after end key is not in comment. */
CHECK(code_edit->is_in_comment(3, 3) == -1);
CHECK(code_edit->get_delimiter_start_position(3, 3) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(3, 3) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(3, 3) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(3, 3) == OUTSIDE_DELIMITER);
/* Check line after is not in comment. */
CHECK(code_edit->is_in_comment(4, 1) == -1);
CHECK(code_edit->get_delimiter_start_position(4, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(4, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(4, 1) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(4, 1) == OUTSIDE_DELIMITER);
/* Next test over a multiple non-blank lines. */
code_edit->set_text(" \n # \n \n # \n ");
/* Check line above is not in comment. */
CHECK(code_edit->is_in_comment(0, 1) == -1);
CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMITER);
/* Check column before start key is not in comment. */
CHECK(code_edit->is_in_comment(1, 0) == -1);
CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMITER);
/* Check column just after start key is in comment. */
CHECK(code_edit->is_in_comment(1, 2) != -1);
@ -1589,13 +1589,13 @@ TEST_CASE("[SceneTree][CodeEdit] delimiters") {
/* Check column after end key is not in comment. */
CHECK(code_edit->is_in_comment(3, 3) == -1);
CHECK(code_edit->get_delimiter_start_position(3, 3) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(3, 3) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(3, 3) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(3, 3) == OUTSIDE_DELIMITER);
/* Check line after is not in comment. */
CHECK(code_edit->is_in_comment(4, 1) == -1);
CHECK(code_edit->get_delimiter_start_position(4, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(4, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(4, 1) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(4, 1) == OUTSIDE_DELIMITER);
/* check the region metadata. */
idx = code_edit->is_in_comment(1, 2);
@ -1611,13 +1611,13 @@ TEST_CASE("[SceneTree][CodeEdit] delimiters") {
/* Check line above is not in comment. */
CHECK(code_edit->is_in_comment(0, 1) == -1);
CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMITER);
/* Check column before start key is not in comment. */
CHECK(code_edit->is_in_comment(1, 0) == -1);
CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMITER);
/* Check column just after start key is in comment. */
CHECK(code_edit->is_in_comment(1, 2) != -1);
@ -1636,13 +1636,13 @@ TEST_CASE("[SceneTree][CodeEdit] delimiters") {
/* Check column after end key is not in comment. */
CHECK(code_edit->is_in_comment(3, 3) == -1);
CHECK(code_edit->get_delimiter_start_position(3, 3) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(3, 3) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(3, 3) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(3, 3) == OUTSIDE_DELIMITER);
/* Check line after is not in comment. */
CHECK(code_edit->is_in_comment(4, 1) == -1);
CHECK(code_edit->get_delimiter_start_position(4, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(4, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(4, 1) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(4, 1) == OUTSIDE_DELIMITER);
/* check the region metadata. */
idx = code_edit->is_in_comment(1, 2);
@ -1700,13 +1700,13 @@ TEST_CASE("[SceneTree][CodeEdit] delimiters") {
/* Check line above is not in comment. */
CHECK(code_edit->is_in_comment(0, 1) == -1);
CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMITER);
/* Check column before start key is not in comment. */
CHECK(code_edit->is_in_comment(1, 0) == -1);
CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMITER);
/* Check column just after start key is in comment. */
CHECK(code_edit->is_in_comment(1, 2) != -1);
@ -1725,13 +1725,13 @@ TEST_CASE("[SceneTree][CodeEdit] delimiters") {
/* Check column after end key is not in comment. */
CHECK(code_edit->is_in_comment(3, 3) == -1);
CHECK(code_edit->get_delimiter_start_position(3, 3) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(3, 3) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(3, 3) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(3, 3) == OUTSIDE_DELIMITER);
/* Check line after is not in comment. */
CHECK(code_edit->is_in_comment(4, 1) == -1);
CHECK(code_edit->get_delimiter_start_position(4, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_end_position(4, 1) == OUTSIDE_DELIMETER);
CHECK(code_edit->get_delimiter_start_position(4, 1) == OUTSIDE_DELIMITER);
CHECK(code_edit->get_delimiter_end_position(4, 1) == OUTSIDE_DELIMITER);
/* check the region metadata. */
int idx = code_edit->is_in_comment(1, 2);
@ -4609,6 +4609,26 @@ TEST_CASE("[SceneTree][CodeEdit] text manipulation") {
CHECK(code_edit->get_text() == "line 1\nline 2\nline 3");
CHECK(code_edit->get_caret_line() == 0);
CHECK(code_edit->get_caret_column() == 0);
// Unfold previous folded line on backspace if the caret is at the first column.
code_edit->set_line_folding_enabled(true);
code_edit->set_text("line 1\n\tline 2\nline 3");
code_edit->set_caret_line(2);
code_edit->set_caret_column(0);
code_edit->fold_line(0);
code_edit->backspace();
CHECK_FALSE(code_edit->is_line_folded(0));
code_edit->set_line_folding_enabled(false);
// Do not unfold previous line on backspace if the caret is not at the first column.
code_edit->set_line_folding_enabled(true);
code_edit->set_text("line 1\n\tline 2\nline 3");
code_edit->set_caret_line(2);
code_edit->set_caret_column(4);
code_edit->fold_line(0);
code_edit->backspace();
CHECK(code_edit->is_line_folded(0));
code_edit->set_line_folding_enabled(false);
}
SUBCASE("[TextEdit] cut") {
@ -4759,6 +4779,31 @@ TEST_CASE("[SceneTree][CodeEdit] text manipulation") {
CHECK(code_edit->get_caret_column(3) == 0);
}
SUBCASE("[SceneTree][CodeEdit] cut when empty selection clipboard disabled") {
DisplayServerMock *DS = (DisplayServerMock *)(DisplayServer::get_singleton());
code_edit->set_empty_selection_clipboard_enabled(false);
DS->clipboard_set("");
code_edit->set_text("this is\nsome\n");
code_edit->set_caret_line(0);
code_edit->set_caret_column(6);
MessageQueue::get_singleton()->flush();
SIGNAL_DISCARD("text_set");
SIGNAL_DISCARD("text_changed");
SIGNAL_DISCARD("lines_edited_from");
SIGNAL_DISCARD("caret_changed");
code_edit->cut();
MessageQueue::get_singleton()->flush();
CHECK(DS->clipboard_get() == "");
CHECK(code_edit->get_text() == "this is\nsome\n");
CHECK(code_edit->get_caret_line() == 0);
CHECK(code_edit->get_caret_column() == 6);
SIGNAL_CHECK_FALSE("caret_changed");
SIGNAL_CHECK_FALSE("text_changed");
SIGNAL_CHECK_FALSE("lines_edited_from");
}
SUBCASE("[SceneTree][CodeEdit] new line") {
// Add a new line.
code_edit->set_text("test new line");

View file

@ -37,7 +37,7 @@
namespace TestControl {
TEST_CASE("[SceneTree][Control]") {
TEST_CASE("[SceneTree][Control] Transforms") {
SUBCASE("[Control][Global Transform] Global Transform should be accessible while not in SceneTree.") { // GH-79453
Control *test_node = memnew(Control);
Control *test_child = memnew(Control);
@ -61,6 +61,265 @@ TEST_CASE("[SceneTree][Control]") {
}
}
TEST_CASE("[SceneTree][Control] Focus") {
Control *ctrl = memnew(Control);
SceneTree::get_singleton()->get_root()->add_child(ctrl);
SUBCASE("[SceneTree][Control] Default focus") {
CHECK_FALSE(ctrl->has_focus());
}
SUBCASE("[SceneTree][Control] Can't grab focus with default focus mode") {
ERR_PRINT_OFF
ctrl->grab_focus();
ERR_PRINT_ON
CHECK_FALSE(ctrl->has_focus());
}
SUBCASE("[SceneTree][Control] Can grab focus") {
ctrl->set_focus_mode(Control::FocusMode::FOCUS_ALL);
ctrl->grab_focus();
CHECK(ctrl->has_focus());
}
SUBCASE("[SceneTree][Control] Can release focus") {
ctrl->set_focus_mode(Control::FocusMode::FOCUS_ALL);
ctrl->grab_focus();
CHECK(ctrl->has_focus());
ctrl->release_focus();
CHECK_FALSE(ctrl->has_focus());
}
SUBCASE("[SceneTree][Control] Only one can grab focus at the same time") {
ctrl->set_focus_mode(Control::FocusMode::FOCUS_ALL);
ctrl->grab_focus();
CHECK(ctrl->has_focus());
Control *other_ctrl = memnew(Control);
SceneTree::get_singleton()->get_root()->add_child(other_ctrl);
other_ctrl->set_focus_mode(Control::FocusMode::FOCUS_ALL);
other_ctrl->grab_focus();
CHECK(other_ctrl->has_focus());
CHECK_FALSE(ctrl->has_focus());
memdelete(other_ctrl);
}
memdelete(ctrl);
}
TEST_CASE("[SceneTree][Control] Anchoring") {
Control *test_control = memnew(Control);
Control *test_child = memnew(Control);
test_control->add_child(test_child);
test_control->set_size(Size2(2, 2));
Window *root = SceneTree::get_singleton()->get_root();
root->add_child(test_control);
SUBCASE("Anchoring without offsets") {
test_child->set_anchor(SIDE_RIGHT, 0.75);
test_child->set_anchor(SIDE_BOTTOM, 0.1);
CHECK_MESSAGE(
test_child->get_size().is_equal_approx(Vector2(1.5, 0.2)),
"With no LEFT or TOP anchors, positive RIGHT and BOTTOM anchors should be proportional to the size.");
CHECK_MESSAGE(
test_child->get_position().is_equal_approx(Vector2(0, 0)),
"With positive RIGHT and BOTTOM anchors set and no LEFT or TOP anchors, the position should not change.");
test_child->set_anchor(SIDE_LEFT, 0.5);
test_child->set_anchor(SIDE_TOP, 0.01);
CHECK_MESSAGE(
test_child->get_size().is_equal_approx(Vector2(0.5, 0.18)),
"With all anchors set, the size should fit between all four anchors.");
CHECK_MESSAGE(
test_child->get_position().is_equal_approx(Vector2(1, 0.02)),
"With all anchors set, the LEFT and TOP anchors should proportional to the position.");
}
SUBCASE("Anchoring with offsets") {
test_child->set_offset(SIDE_RIGHT, 0.33);
test_child->set_offset(SIDE_BOTTOM, 0.2);
CHECK_MESSAGE(
test_child->get_size().is_equal_approx(Vector2(0.33, 0.2)),
"With no anchors or LEFT or TOP offsets set, the RIGHT and BOTTOM offsets should be equal to size.");
CHECK_MESSAGE(
test_child->get_position().is_equal_approx(Vector2(0, 0)),
"With only positive RIGHT and BOTTOM offsets set, the position should not change.");
test_child->set_offset(SIDE_LEFT, 0.1);
test_child->set_offset(SIDE_TOP, 0.05);
CHECK_MESSAGE(
test_child->get_size().is_equal_approx(Vector2(0.23, 0.15)),
"With no anchors set, the size should fit between all four offsets.");
CHECK_MESSAGE(
test_child->get_position().is_equal_approx(Vector2(0.1, 0.05)),
"With no anchors set, the LEFT and TOP offsets should be equal to the position.");
test_child->set_anchor(SIDE_RIGHT, 0.5);
test_child->set_anchor(SIDE_BOTTOM, 0.3);
test_child->set_anchor(SIDE_LEFT, 0.2);
test_child->set_anchor(SIDE_TOP, 0.1);
CHECK_MESSAGE(
test_child->get_size().is_equal_approx(Vector2(0.83, 0.55)),
"Anchors adjust size first then it is affected by offsets.");
CHECK_MESSAGE(
test_child->get_position().is_equal_approx(Vector2(0.5, 0.25)),
"Anchors adjust positions first then it is affected by offsets.");
test_child->set_offset(SIDE_RIGHT, -0.1);
test_child->set_offset(SIDE_BOTTOM, -0.01);
test_child->set_offset(SIDE_LEFT, -0.33);
test_child->set_offset(SIDE_TOP, -0.16);
CHECK_MESSAGE(
test_child->get_size().is_equal_approx(Vector2(0.83, 0.55)),
"Keeping offset distance equal when changing offsets, keeps size equal.");
CHECK_MESSAGE(
test_child->get_position().is_equal_approx(Vector2(0.07, 0.04)),
"Negative offsets move position in top left direction.");
}
SUBCASE("Anchoring is preserved on parent size changed") {
test_child->set_offset(SIDE_RIGHT, -0.05);
test_child->set_offset(SIDE_BOTTOM, 0.1);
test_child->set_offset(SIDE_LEFT, 0.05);
test_child->set_offset(SIDE_TOP, 0.1);
test_child->set_anchor(SIDE_RIGHT, 0.3);
test_child->set_anchor(SIDE_BOTTOM, 0.85);
test_child->set_anchor(SIDE_LEFT, 0.2);
test_child->set_anchor(SIDE_TOP, 0.55);
CHECK(test_child->get_rect().is_equal_approx(
Rect2(Vector2(0.45, 1.2), Size2(0.1, 0.6))));
test_control->set_size(Size2(4, 1));
CHECK(test_child->get_rect().is_equal_approx(
Rect2(Vector2(0.85, 0.65), Size2(0.3, 0.3))));
}
memdelete(test_child);
memdelete(test_control);
}
TEST_CASE("[SceneTree][Control] Custom minimum size") {
Control *test_control = memnew(Control);
test_control->set_custom_minimum_size(Size2(4, 2));
Window *root = SceneTree::get_singleton()->get_root();
root->add_child(test_control);
CHECK_MESSAGE(
test_control->get_size().is_equal_approx(Vector2(4, 2)),
"Size increases to match custom minimum size.");
test_control->set_size(Size2(5, 4));
CHECK_MESSAGE(
test_control->get_size().is_equal_approx(Vector2(5, 4)),
"Size does not change if above custom minimum size.");
test_control->set_size(Size2(1, 1));
CHECK_MESSAGE(
test_control->get_size().is_equal_approx(Vector2(4, 2)),
"Size matches minimum size if set below custom minimum size.");
test_control->set_size(Size2(3, 3));
CHECK_MESSAGE(
test_control->get_size().is_equal_approx(Vector2(4, 3)),
"Adjusts only x axis size if x is below custom minimum size.");
test_control->set_size(Size2(10, 0.1));
CHECK_MESSAGE(
test_control->get_size().is_equal_approx(Vector2(10, 2)),
"Adjusts only y axis size if y is below custom minimum size.");
memdelete(test_control);
}
TEST_CASE("[SceneTree][Control] Grow direction") {
Control *test_control = memnew(Control);
test_control->set_size(Size2(1, 1));
Window *root = SceneTree::get_singleton()->get_root();
root->add_child(test_control);
SUBCASE("Defaults") {
CHECK(test_control->get_h_grow_direction() == Control::GROW_DIRECTION_END);
CHECK(test_control->get_v_grow_direction() == Control::GROW_DIRECTION_END);
}
SIGNAL_WATCH(test_control, SNAME("minimum_size_changed"))
Array signal_args;
signal_args.push_back(Array());
SUBCASE("Horizontal grow direction begin") {
test_control->set_h_grow_direction(Control::GROW_DIRECTION_BEGIN);
test_control->set_custom_minimum_size(Size2(2, 2));
SceneTree::get_singleton()->process(0);
SIGNAL_CHECK("minimum_size_changed", signal_args)
CHECK_MESSAGE(
test_control->get_rect().is_equal_approx(
Rect2(Vector2(-1, 0), Size2(2, 2))),
"Expand leftwards.");
}
SUBCASE("Vertical grow direction begin") {
test_control->set_v_grow_direction(Control::GROW_DIRECTION_BEGIN);
test_control->set_custom_minimum_size(Size2(4, 3));
SceneTree::get_singleton()->process(0);
SIGNAL_CHECK("minimum_size_changed", signal_args);
CHECK_MESSAGE(
test_control->get_rect().is_equal_approx(
Rect2(Vector2(0, -2), Size2(4, 3))),
"Expand upwards.");
}
SUBCASE("Horizontal grow direction end") {
test_control->set_h_grow_direction(Control::GROW_DIRECTION_END);
test_control->set_custom_minimum_size(Size2(5, 3));
SceneTree::get_singleton()->process(0);
SIGNAL_CHECK("minimum_size_changed", signal_args);
CHECK_MESSAGE(
test_control->get_rect().is_equal_approx(
Rect2(Vector2(0, 0), Size2(5, 3))),
"Expand rightwards.");
}
SUBCASE("Vertical grow direction end") {
test_control->set_v_grow_direction(Control::GROW_DIRECTION_END);
test_control->set_custom_minimum_size(Size2(4, 4));
SceneTree::get_singleton()->process(0);
SIGNAL_CHECK("minimum_size_changed", signal_args);
CHECK_MESSAGE(
test_control->get_rect().is_equal_approx(
Rect2(Vector2(0, 0), Size2(4, 4))),
"Expand downwards.");
;
}
SUBCASE("Horizontal grow direction both") {
test_control->set_h_grow_direction(Control::GROW_DIRECTION_BOTH);
test_control->set_custom_minimum_size(Size2(2, 4));
SceneTree::get_singleton()->process(0);
SIGNAL_CHECK("minimum_size_changed", signal_args);
CHECK_MESSAGE(
test_control->get_rect().is_equal_approx(
Rect2(Vector2(-0.5, 0), Size2(2, 4))),
"Expand equally leftwards and rightwards.");
}
SUBCASE("Vertical grow direction both") {
test_control->set_v_grow_direction(Control::GROW_DIRECTION_BOTH);
test_control->set_custom_minimum_size(Size2(6, 3));
SceneTree::get_singleton()->process(0);
SIGNAL_CHECK("minimum_size_changed", signal_args);
CHECK_MESSAGE(
test_control->get_rect().is_equal_approx(
Rect2(Vector2(0, -1), Size2(6, 3))),
"Expand equally upwards and downwards.");
}
memdelete(test_control);
}
} // namespace TestControl
#endif // TEST_CONTROL_H

View file

@ -55,7 +55,7 @@ TEST_CASE("[Curve] Default curve") {
"Default curve should return the expected value at offset 1.0.");
}
TEST_CASE("[Curve] Custom curve with free tangents") {
TEST_CASE("[Curve] Custom unit curve with free tangents") {
Ref<Curve> curve = memnew(Curve);
// "Sawtooth" curve with an open ending towards the 1.0 offset.
curve->add_point(Vector2(0, 0));
@ -136,7 +136,90 @@ TEST_CASE("[Curve] Custom curve with free tangents") {
"Custom free curve should return the expected baked value at offset 0.6 after clearing all points.");
}
TEST_CASE("[Curve] Custom curve with linear tangents") {
TEST_CASE("[Curve] Custom non-unit curve with free tangents") {
Ref<Curve> curve = memnew(Curve);
curve->set_min_domain(-100.0);
curve->set_max_domain(100.0);
// "Sawtooth" curve with an open ending towards the 100 offset.
curve->add_point(Vector2(-100, 0));
curve->add_point(Vector2(-50, 1));
curve->add_point(Vector2(0, 0));
curve->add_point(Vector2(50, 1));
curve->set_bake_resolution(11);
CHECK_MESSAGE(
Math::is_zero_approx(curve->get_point_left_tangent(0)),
"get_point_left_tangent() should return the expected value for point index 0.");
CHECK_MESSAGE(
Math::is_zero_approx(curve->get_point_right_tangent(0)),
"get_point_right_tangent() should return the expected value for point index 0.");
CHECK_MESSAGE(
curve->get_point_left_mode(0) == Curve::TangentMode::TANGENT_FREE,
"get_point_left_mode() should return the expected value for point index 0.");
CHECK_MESSAGE(
curve->get_point_right_mode(0) == Curve::TangentMode::TANGENT_FREE,
"get_point_right_mode() should return the expected value for point index 0.");
CHECK_MESSAGE(
curve->get_point_count() == 4,
"Custom free curve should contain the expected number of points.");
CHECK_MESSAGE(
Math::is_zero_approx(curve->sample(-200)),
"Custom free curve should return the expected value at offset -200.");
CHECK_MESSAGE(
curve->sample(0.1 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.352),
"Custom free curve should return the expected value at offset equivalent to a unit curve's 0.1.");
CHECK_MESSAGE(
curve->sample(0.4 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.352),
"Custom free curve should return the expected value at offset equivalent to a unit curve's 0.4.");
CHECK_MESSAGE(
curve->sample(0.7 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.896),
"Custom free curve should return the expected value at offset equivalent to a unit curve's 0.7.");
CHECK_MESSAGE(
curve->sample(1 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(1),
"Custom free curve should return the expected value at offset equivalent to a unit curve's 1.");
CHECK_MESSAGE(
curve->sample(2 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(1),
"Custom free curve should return the expected value at offset equivalent to a unit curve's 2.");
CHECK_MESSAGE(
Math::is_zero_approx(curve->sample_baked(-200)),
"Custom free curve should return the expected baked value at offset equivalent to a unit curve's -0.1.");
CHECK_MESSAGE(
curve->sample_baked(0.1 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.352),
"Custom free curve should return the expected baked value at offset equivalent to a unit curve's 0.1.");
CHECK_MESSAGE(
curve->sample_baked(0.4 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.352),
"Custom free curve should return the expected baked value at offset equivalent to a unit curve's 0.4.");
CHECK_MESSAGE(
curve->sample_baked(0.7 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.896),
"Custom free curve should return the expected baked value at offset equivalent to a unit curve's 0.7.");
CHECK_MESSAGE(
curve->sample_baked(1 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(1),
"Custom free curve should return the expected baked value at offset equivalent to a unit curve's 1.");
CHECK_MESSAGE(
curve->sample_baked(2 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(1),
"Custom free curve should return the expected baked value at offset equivalent to a unit curve's 2.");
curve->remove_point(1);
CHECK_MESSAGE(
curve->sample(0.1 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(0),
"Custom free curve should return the expected value at offset equivalent to a unit curve's 0.1 after removing point at index 1.");
CHECK_MESSAGE(
curve->sample_baked(0.1 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(0),
"Custom free curve should return the expected baked value at offset equivalent to a unit curve's 0.1 after removing point at index 1.");
curve->clear_points();
CHECK_MESSAGE(
curve->sample(0.6 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(0),
"Custom free curve should return the expected value at offset 0.6 after clearing all points.");
CHECK_MESSAGE(
curve->sample_baked(0.6 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(0),
"Custom free curve should return the expected baked value at offset 0.6 after clearing all points.");
}
TEST_CASE("[Curve] Custom unit curve with linear tangents") {
Ref<Curve> curve = memnew(Curve);
// "Sawtooth" curve with an open ending towards the 1.0 offset.
curve->add_point(Vector2(0, 0), 0, 0, Curve::TangentMode::TANGENT_LINEAR, Curve::TangentMode::TANGENT_LINEAR);
@ -219,6 +302,91 @@ TEST_CASE("[Curve] Custom curve with linear tangents") {
"Custom free curve should return the expected baked value at offset 0.7 after removing point at invalid index 10.");
}
TEST_CASE("[Curve] Custom non-unit curve with linear tangents") {
Ref<Curve> curve = memnew(Curve);
curve->set_min_domain(-100.0);
curve->set_max_domain(100.0);
// "Sawtooth" curve with an open ending towards the 100 offset.
curve->add_point(Vector2(-100, 0), 0, 0, Curve::TangentMode::TANGENT_LINEAR, Curve::TangentMode::TANGENT_LINEAR);
curve->add_point(Vector2(-50, 1), 0, 0, Curve::TangentMode::TANGENT_LINEAR, Curve::TangentMode::TANGENT_LINEAR);
curve->add_point(Vector2(0, 0), 0, 0, Curve::TangentMode::TANGENT_LINEAR, Curve::TangentMode::TANGENT_LINEAR);
curve->add_point(Vector2(50, 1), 0, 0, Curve::TangentMode::TANGENT_LINEAR, Curve::TangentMode::TANGENT_LINEAR);
CHECK_MESSAGE(
curve->get_point_left_tangent(3) == doctest::Approx(1.f / 50),
"get_point_left_tangent() should return the expected value for point index 3.");
CHECK_MESSAGE(
Math::is_zero_approx(curve->get_point_right_tangent(3)),
"get_point_right_tangent() should return the expected value for point index 3.");
CHECK_MESSAGE(
curve->get_point_left_mode(3) == Curve::TangentMode::TANGENT_LINEAR,
"get_point_left_mode() should return the expected value for point index 3.");
CHECK_MESSAGE(
curve->get_point_right_mode(3) == Curve::TangentMode::TANGENT_LINEAR,
"get_point_right_mode() should return the expected value for point index 3.");
ERR_PRINT_OFF;
CHECK_MESSAGE(
Math::is_zero_approx(curve->get_point_right_tangent(300)),
"get_point_right_tangent() should return the expected value for invalid point index 300.");
CHECK_MESSAGE(
curve->get_point_left_mode(-12345) == Curve::TangentMode::TANGENT_FREE,
"get_point_left_mode() should return the expected value for invalid point index -12345.");
ERR_PRINT_ON;
CHECK_MESSAGE(
curve->get_point_count() == 4,
"Custom linear unit curve should contain the expected number of points.");
CHECK_MESSAGE(
Math::is_zero_approx(curve->sample(-0.1 * curve->get_domain_range() + curve->get_min_domain())),
"Custom linear curve should return the expected value at offset equivalent to a unit curve's -0.1.");
CHECK_MESSAGE(
curve->sample(0.1 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.4),
"Custom linear curve should return the expected value at offset equivalent to a unit curve's 0.1.");
CHECK_MESSAGE(
curve->sample(0.4 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.4),
"Custom linear curve should return the expected value at offset equivalent to a unit curve's 0.4.");
CHECK_MESSAGE(
curve->sample(0.7 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.8),
"Custom linear curve should return the expected value at offset equivalent to a unit curve's 0.7.");
CHECK_MESSAGE(
curve->sample(1 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(1),
"Custom linear curve should return the expected value at offset equivalent to a unit curve's 1.0.");
CHECK_MESSAGE(
curve->sample(2 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(1),
"Custom linear curve should return the expected value at offset equivalent to a unit curve's 2.0.");
CHECK_MESSAGE(
Math::is_zero_approx(curve->sample_baked(-0.1 * curve->get_domain_range() + curve->get_min_domain())),
"Custom linear curve should return the expected baked value at offset equivalent to a unit curve's -0.1.");
CHECK_MESSAGE(
curve->sample_baked(0.1 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.4),
"Custom linear curve should return the expected baked value at offset equivalent to a unit curve's 0.1.");
CHECK_MESSAGE(
curve->sample_baked(0.4 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.4),
"Custom linear curve should return the expected baked value at offset equivalent to a unit curve's 0.4.");
CHECK_MESSAGE(
curve->sample_baked(0.7 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.8),
"Custom linear curve should return the expected baked value at offset equivalent to a unit curve's 0.7.");
CHECK_MESSAGE(
curve->sample_baked(1 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(1),
"Custom linear curve should return the expected baked value at offset equivalent to a unit curve's 1.0.");
CHECK_MESSAGE(
curve->sample_baked(2 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(1),
"Custom linear curve should return the expected baked value at offset equivalent to a unit curve's 2.0.");
ERR_PRINT_OFF;
curve->remove_point(10);
ERR_PRINT_ON;
CHECK_MESSAGE(
curve->sample(0.7 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.8),
"Custom free curve should return the expected value at offset equivalent to a unit curve's 0.7 after removing point at invalid index 10.");
CHECK_MESSAGE(
curve->sample_baked(0.7 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.8),
"Custom free curve should return the expected baked value at offset equivalent to a unit curve's 0.7 after removing point at invalid index 10.");
}
TEST_CASE("[Curve] Straight line offset test") {
Ref<Curve> curve = memnew(Curve);
curve->add_point(Vector2(0, 0));

View file

@ -0,0 +1,82 @@
/**************************************************************************/
/* test_fontfile.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_FONTFILE_H
#define TEST_FONTFILE_H
#include "modules/modules_enabled.gen.h"
#include "scene/resources/font.h"
#include "tests/test_macros.h"
namespace TestFontfile {
TEST_CASE("[FontFile] Create font file and check data") {
// Create test instance.
Ref<FontFile> font_file;
font_file.instantiate();
#ifdef MODULE_FREETYPE_ENABLED
// Try to load non-existent files.
ERR_PRINT_OFF
CHECK(font_file->load_dynamic_font("") == OK);
CHECK_MESSAGE(font_file->get_data().is_empty() == true, "Invalid fontfile should not be loaded.");
CHECK(font_file->load_dynamic_font("thirdparty/fonts/nofonthasthisname.woff2") == OK);
CHECK_MESSAGE(font_file->get_data().is_empty() == true, "Invalid fontfile should not be loaded.");
ERR_PRINT_ON
// Load a valid file.
CHECK(font_file->load_dynamic_font("thirdparty/fonts/NotoSans_Regular.woff2") == OK);
// Check fontfile data.
CHECK_MESSAGE(font_file->get_data().is_empty() == false, "Fontfile should have been loaded.");
CHECK_MESSAGE(font_file->get_font_name() == "Noto Sans", "Loaded correct font name.");
CHECK_MESSAGE(font_file->get_font_style_name() == "Regular", "Loaded correct font style.");
CHECK_MESSAGE(font_file->get_data().size() == 148480llu, "Whole fontfile was loaded.");
// Valid glyphs.
CHECK_MESSAGE(font_file->get_glyph_index(2, 'a', 0) != 0, "Glyph index for 'a' is valid.");
CHECK_MESSAGE(font_file->get_glyph_index(2, 'b', 0) != 0, "Glyph index for 'b' is valid.");
CHECK_MESSAGE(font_file->get_glyph_index(2, 0x0103, 0) != 0, "Glyph index for 'latin small letter a with breve' is valid.");
CHECK_MESSAGE(font_file->get_glyph_index(2, 0x03a8, 0) != 0, "Glyph index for 'Greek psi' is valid.");
CHECK_MESSAGE(font_file->get_glyph_index(2, 0x0416, 0) != 0, "Glyph index for 'Cyrillic zhe' is valid.");
CHECK_MESSAGE(font_file->get_glyph_index(2, '&', 0) != 0, "Glyph index for '&' is valid.");
// Invalid glyphs.
CHECK_MESSAGE(font_file->get_glyph_index(2, 0x4416, 0) == 0, "Glyph index is invalid.");
CHECK_MESSAGE(font_file->get_glyph_index(2, 0x5555, 0) == 0, "Glyph index is invalid.");
CHECK_MESSAGE(font_file->get_glyph_index(2, 0x2901, 0) == 0, "Glyph index is invalid.");
#endif
}
} // namespace TestFontfile
#endif // TEST_FONTFILE_H

View file

@ -0,0 +1,250 @@
/**************************************************************************/
/* test_gltf_document.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_GLTF_DOCUMENT_H
#define TEST_GLTF_DOCUMENT_H
#include "modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.h"
#include "modules/gltf/gltf_document.h"
#include "tests/test_macros.h"
#include "tests/test_utils.h"
namespace TestGLTFDocument {
struct GLTFArraySize {
String key;
int val;
};
struct GLTFKeyValue {
String key;
Variant val;
};
struct GLTFTestCase {
String filename;
String copyright;
String generator;
String version;
Vector<GLTFArraySize> array_sizes;
Vector<GLTFArraySize> json_array_sizes;
Vector<GLTFKeyValue> keyvalues;
};
const GLTFTestCase glTF_test_cases[] = {
{ "models/cube.gltf",
"",
"Khronos glTF Blender I/O v4.3.47",
"2.0",
// Here are the array sizes.
{
{ "nodes", 1 },
{ "buffers", 1 },
{ "buffer_views", 13 },
{ "accessors", 13 },
{ "meshes", 1 },
{ "materials", 2 },
{ "root_nodes", 1 },
{ "textures", 0 },
{ "texture_samplers", 0 },
{ "images", 0 },
{ "skins", 0 },
{ "cameras", 0 },
{ "lights", 0 },
{ "skeletons", 0 },
{ "animations", 1 },
},
// Here are the json array sizes.
{
{ "scenes", 1 },
{ "nodes", 1 },
{ "animations", 1 },
{ "meshes", 1 },
{ "accessors", 13 },
{ "bufferViews", 13 },
{ "buffers", 1 },
},
// Here are the key-value pairs.
{
{ "major_version", 2 },
{ "minor_version", 0 },
{ "scene_name", "cube" },
{ "filename", "cube" } } },
{ "models/suzanne.glb",
"this is example text",
"Khronos glTF Blender I/O v4.3.47",
"2.0",
// Here are the array sizes.
{
{ "glb_data", 68908 },
{ "nodes", 2 },
{ "buffers", 1 },
{ "buffer_views", 5 },
{ "accessors", 4 },
{ "meshes", 1 },
{ "materials", 1 },
{ "root_nodes", 2 },
{ "textures", 1 },
{ "texture_samplers", 1 },
{ "images", 1 },
{ "skins", 0 },
{ "cameras", 1 },
{ "lights", 0 },
{ "unique_names", 4 },
{ "skeletons", 0 },
{ "animations", 0 },
},
// Here are the json array sizes.
{
{ "scenes", 1 },
{ "nodes", 2 },
{ "cameras", 1 },
{ "materials", 1 },
{ "meshes", 1 },
{ "textures", 1 },
{ "images", 1 },
{ "accessors", 4 },
{ "bufferViews", 5 },
{ "buffers", 1 },
},
// Here are the key-value pairs.
{
{ "major_version", 2 },
{ "minor_version", 0 },
{ "scene_name", "suzanne" },
{ "filename", "suzanne" } } },
};
void register_gltf_extension() {
GLTFDocument::unregister_all_gltf_document_extensions();
// Ensures meshes become a MeshInstance3D and not an ImporterMeshInstance3D.
Ref<GLTFDocumentExtensionConvertImporterMesh> extension_GLTFDocumentExtensionConvertImporterMesh;
extension_GLTFDocumentExtensionConvertImporterMesh.instantiate();
GLTFDocument::register_gltf_document_extension(extension_GLTFDocumentExtensionConvertImporterMesh);
}
void test_gltf_document_values(Ref<GLTFDocument> &p_gltf_document, Ref<GLTFState> &p_gltf_state, const GLTFTestCase &p_test_case) {
const Error err = p_gltf_document->append_from_file(TestUtils::get_data_path(p_test_case.filename), p_gltf_state);
REQUIRE(err == OK);
for (GLTFArraySize array_size : p_test_case.array_sizes) {
CHECK_MESSAGE(((Array)(p_gltf_state->getvar(array_size.key))).size() == array_size.val, "Expected \"", array_size.key, "\" to have ", array_size.val, " elements.");
}
for (GLTFArraySize array_size : p_test_case.json_array_sizes) {
CHECK(p_gltf_state->get_json().has(array_size.key));
CHECK_MESSAGE(((Array)(p_gltf_state->get_json()[array_size.key])).size() == array_size.val, "Expected \"", array_size.key, "\" to have ", array_size.val, " elements.");
}
for (GLTFKeyValue key_value : p_test_case.keyvalues) {
CHECK_MESSAGE(p_gltf_state->getvar(key_value.key) == key_value.val, "Expected \"", key_value.key, "\" to be \"", key_value.val, "\".");
}
CHECK(p_gltf_state->get_copyright() == p_test_case.copyright);
CHECK(((Dictionary)p_gltf_state->get_json()["asset"])["generator"] == p_test_case.generator);
CHECK(((Dictionary)p_gltf_state->get_json()["asset"])["version"] == p_test_case.version);
}
void test_gltf_save(Node *p_node) {
Ref<GLTFDocument> gltf_document_save;
gltf_document_save.instantiate();
Ref<GLTFState> gltf_state_save;
gltf_state_save.instantiate();
gltf_document_save->append_from_scene(p_node, gltf_state_save);
// Check saving the scene to gltf and glb.
const Error err_save_gltf = gltf_document_save->write_to_filesystem(gltf_state_save, TestUtils::get_temp_path("cube.gltf"));
const Error err_save_glb = gltf_document_save->write_to_filesystem(gltf_state_save, TestUtils::get_temp_path("cube.glb"));
CHECK(err_save_gltf == OK);
CHECK(err_save_glb == OK);
}
TEST_CASE("[SceneTree][GLTFDocument] Load cube.gltf") {
register_gltf_extension();
Ref<GLTFDocument> gltf_document;
gltf_document.instantiate();
Ref<GLTFState> gltf_state;
gltf_state.instantiate();
test_gltf_document_values(gltf_document, gltf_state, glTF_test_cases[0]);
Node *node = gltf_document->generate_scene(gltf_state);
// Check the loaded scene.
CHECK(node->is_class("Node3D"));
CHECK(node->get_name() == "cube");
CHECK(node->get_child(0)->is_class("MeshInstance3D"));
CHECK(node->get_child(0)->get_name() == "Cube");
CHECK(node->get_child(1)->is_class("AnimationPlayer"));
CHECK(node->get_child(1)->get_name() == "AnimationPlayer");
test_gltf_save(node);
// Clean up the node.
memdelete(node);
}
TEST_CASE("[SceneTree][GLTFDocument] Load suzanne.glb") {
register_gltf_extension();
Ref<GLTFDocument> gltf_document;
gltf_document.instantiate();
Ref<GLTFState> gltf_state;
gltf_state.instantiate();
test_gltf_document_values(gltf_document, gltf_state, glTF_test_cases[1]);
Node *node = gltf_document->generate_scene(gltf_state);
// Check the loaded scene.
CHECK(node->is_class("Node3D"));
CHECK(node->get_name() == "suzanne");
CHECK(node->get_child(0)->is_class("MeshInstance3D"));
CHECK(node->get_child(0)->get_name() == "Suzanne");
CHECK(node->get_child(1)->is_class("Camera3D"));
CHECK(node->get_child(1)->get_name() == "Camera");
test_gltf_save(node);
// Clean up the node.
memdelete(node);
}
} // namespace TestGLTFDocument
#endif // TEST_GLTF_DOCUMENT_H

View file

@ -70,11 +70,11 @@ TEST_CASE("[Gradient] Default gradient") {
TEST_CASE("[Gradient] Custom gradient (points specified in order)") {
// Red-yellow-green gradient (with overbright green).
Ref<Gradient> gradient = memnew(Gradient);
Vector<Gradient::Point> points;
points.push_back({ 0.0, Color(1, 0, 0) });
points.push_back({ 0.5, Color(1, 1, 0) });
points.push_back({ 1.0, Color(0, 2, 0) });
gradient->set_points(points);
Vector<float> offsets = { 0.0, 0.5, 1.0 };
Vector<Color> colors = { Color(1, 0, 0), Color(1, 1, 0), Color(0, 2, 0) };
gradient->set_offsets(offsets);
gradient->set_colors(colors);
CHECK_MESSAGE(
gradient->get_point_count() == 3,
@ -109,14 +109,12 @@ TEST_CASE("[Gradient] Custom gradient (points specified out-of-order)") {
// HSL rainbow with points specified out of order.
// These should be sorted automatically when adding points.
Ref<Gradient> gradient = memnew(Gradient);
Vector<Gradient::Point> points;
points.push_back({ 0.2, Color(1, 0, 0) });
points.push_back({ 0.0, Color(1, 1, 0) });
points.push_back({ 0.8, Color(0, 1, 0) });
points.push_back({ 0.4, Color(0, 1, 1) });
points.push_back({ 1.0, Color(0, 0, 1) });
points.push_back({ 0.6, Color(1, 0, 1) });
gradient->set_points(points);
LocalVector<Gradient::Point> points;
Vector<float> offsets = { 0.2, 0.0, 0.8, 0.4, 1.0, 0.6 };
Vector<Color> colors = { Color(1, 0, 0), Color(1, 1, 0), Color(0, 1, 0), Color(0, 1, 1), Color(0, 0, 1), Color(1, 0, 1) };
gradient->set_offsets(offsets);
gradient->set_colors(colors);
CHECK_MESSAGE(
gradient->get_point_count() == 6,

View file

@ -0,0 +1,87 @@
/**************************************************************************/
/* test_gradient_texture.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_GRADIENT_TEXTURE_H
#define TEST_GRADIENT_TEXTURE_H
#include "scene/resources/gradient_texture.h"
#include "tests/test_macros.h"
namespace TestGradientTexture {
// [SceneTree] in a test case name enables initializing a mock render server,
// which ImageTexture is dependent on.
TEST_CASE("[SceneTree][GradientTexture1D] Create GradientTexture1D") {
Ref<GradientTexture1D> gradient_texture = memnew(GradientTexture1D);
Ref<Gradient> test_gradient = memnew(Gradient);
gradient_texture->set_gradient(test_gradient);
CHECK(gradient_texture->get_gradient() == test_gradient);
gradient_texture->set_width(83);
CHECK(gradient_texture->get_width() == 83);
gradient_texture->set_use_hdr(true);
CHECK(gradient_texture->is_using_hdr());
}
TEST_CASE("[SceneTree][GradientTexture2D] Create GradientTexture2D") {
Ref<GradientTexture2D> gradient_texture = memnew(GradientTexture2D);
Ref<Gradient> test_gradient = memnew(Gradient);
gradient_texture->set_gradient(test_gradient);
CHECK(gradient_texture->get_gradient() == test_gradient);
gradient_texture->set_width(82);
CHECK(gradient_texture->get_width() == 82);
gradient_texture->set_height(81);
CHECK(gradient_texture->get_height() == 81);
gradient_texture->set_use_hdr(true);
CHECK(gradient_texture->is_using_hdr());
gradient_texture->set_fill(GradientTexture2D::Fill::FILL_SQUARE);
CHECK(gradient_texture->get_fill() == GradientTexture2D::Fill::FILL_SQUARE);
gradient_texture->set_fill_from(Vector2(0.2, 0.25));
CHECK(gradient_texture->get_fill_from() == Vector2(0.2, 0.25));
gradient_texture->set_fill_to(Vector2(0.35, 0.5));
CHECK(gradient_texture->get_fill_to() == Vector2(0.35, 0.5));
gradient_texture->set_repeat(GradientTexture2D::Repeat::REPEAT);
CHECK(gradient_texture->get_repeat() == GradientTexture2D::Repeat::REPEAT);
}
} //namespace TestGradientTexture
#endif // TEST_GRADIENT_TEXTURE_H

View file

@ -0,0 +1,122 @@
/**************************************************************************/
/* test_height_map_shape_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_HEIGHT_MAP_SHAPE_3D_H
#define TEST_HEIGHT_MAP_SHAPE_3D_H
#include "scene/resources/3d/height_map_shape_3d.h"
#include "scene/resources/image_texture.h"
#include "tests/test_macros.h"
#include "tests/test_utils.h"
namespace TestHeightMapShape3D {
TEST_CASE("[SceneTree][HeightMapShape3D] Constructor") {
Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D);
CHECK(height_map_shape->get_map_width() == 2);
CHECK(height_map_shape->get_map_depth() == 2);
CHECK(height_map_shape->get_map_data().size() == 4);
CHECK(height_map_shape->get_min_height() == 0.0);
CHECK(height_map_shape->get_max_height() == 0.0);
}
TEST_CASE("[SceneTree][HeightMapShape3D] set_map_width and get_map_width") {
Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D);
height_map_shape->set_map_width(10);
CHECK(height_map_shape->get_map_width() == 10);
}
TEST_CASE("[SceneTree][HeightMapShape3D] set_map_depth and get_map_depth") {
Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D);
height_map_shape->set_map_depth(15);
CHECK(height_map_shape->get_map_depth() == 15);
}
TEST_CASE("[SceneTree][HeightMapShape3D] set_map_data and get_map_data") {
Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D);
Vector<real_t> map_data;
map_data.push_back(1.0);
map_data.push_back(2.0);
height_map_shape->set_map_data(map_data);
CHECK(height_map_shape->get_map_data().size() == 4.0);
CHECK(height_map_shape->get_map_data()[0] == 0.0);
CHECK(height_map_shape->get_map_data()[1] == 0.0);
}
TEST_CASE("[SceneTree][HeightMapShape3D] get_min_height") {
Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D);
height_map_shape->set_map_width(3);
height_map_shape->set_map_depth(1);
height_map_shape->set_map_data(Vector<real_t>{ 1.0, 2.0, 0.5 });
CHECK(height_map_shape->get_min_height() == 0.5);
}
TEST_CASE("[SceneTree][HeightMapShape3D] get_max_height") {
Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D);
height_map_shape->set_map_width(3);
height_map_shape->set_map_depth(1);
height_map_shape->set_map_data(Vector<real_t>{ 1.0, 2.0, 0.5 });
CHECK(height_map_shape->get_max_height() == 2.0);
}
TEST_CASE("[SceneTree][HeightMapShape3D] update_map_data_from_image") {
// Create a HeightMapShape3D instance.
Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D);
// Create a mock image with FORMAT_R8 and set its data.
Vector<uint8_t> image_data;
image_data.push_back(0);
image_data.push_back(128);
image_data.push_back(255);
image_data.push_back(64);
Ref<Image> image = memnew(Image);
image->set_data(2, 2, false, Image::FORMAT_R8, image_data);
height_map_shape->update_map_data_from_image(image, 0.0, 10.0);
// Check the map data.
Vector<real_t> expected_map_data = { 0.0, 5.0, 10.0, 2.5 };
Vector<real_t> actual_map_data = height_map_shape->get_map_data();
real_t tolerance = 0.1;
for (int i = 0; i < expected_map_data.size(); ++i) {
CHECK(Math::abs(actual_map_data[i] - expected_map_data[i]) < tolerance);
}
// Check the min and max heights.
CHECK(height_map_shape->get_min_height() == 0.0);
CHECK(height_map_shape->get_max_height() == 10.0);
}
} // namespace TestHeightMapShape3D
#endif // TEST_HEIGHT_MAP_SHAPE_3D_H

View file

@ -86,6 +86,131 @@ TEST_CASE("[SceneTree][Node2D]") {
}
}
TEST_CASE("[SceneTree][Node2D] Utility methods") {
Node2D *test_node1 = memnew(Node2D);
Node2D *test_node2 = memnew(Node2D);
Node2D *test_node3 = memnew(Node2D);
Node2D *test_sibling = memnew(Node2D);
SceneTree::get_singleton()->get_root()->add_child(test_node1);
test_node1->set_position(Point2(100, 100));
test_node1->set_rotation(Math::deg_to_rad(30.0));
test_node1->set_scale(Size2(1, 1));
test_node1->add_child(test_node2);
test_node2->set_position(Point2(10, 0));
test_node2->set_rotation(Math::deg_to_rad(60.0));
test_node2->set_scale(Size2(1, 1));
test_node2->add_child(test_node3);
test_node2->add_child(test_sibling);
test_node3->set_position(Point2(0, 10));
test_node3->set_rotation(Math::deg_to_rad(0.0));
test_node3->set_scale(Size2(2, 2));
test_sibling->set_position(Point2(5, 10));
test_sibling->set_rotation(Math::deg_to_rad(90.0));
test_sibling->set_scale(Size2(2, 1));
SUBCASE("[Node2D] look_at") {
test_node3->look_at(Vector2(1, 1));
CHECK(test_node3->get_global_position().is_equal_approx(Point2(98.66026, 105)));
CHECK(Math::is_equal_approx(test_node3->get_global_rotation(), real_t(-2.32477)));
CHECK(test_node3->get_global_scale().is_equal_approx(Vector2(2, 2)));
CHECK(test_node3->get_position().is_equal_approx(Vector2(0, 10)));
CHECK(Math::is_equal_approx(test_node3->get_rotation(), real_t(2.38762)));
CHECK(test_node3->get_scale().is_equal_approx(Vector2(2, 2)));
test_node3->look_at(Vector2(0, 10));
CHECK(test_node3->get_global_position().is_equal_approx(Vector2(98.66026, 105)));
CHECK(Math::is_equal_approx(test_node3->get_global_rotation(), real_t(-2.37509)));
CHECK(test_node3->get_global_scale().is_equal_approx(Vector2(2, 2)));
CHECK(test_node3->get_position().is_equal_approx(Vector2(0, 10)));
CHECK(Math::is_equal_approx(test_node3->get_rotation(), real_t(2.3373)));
CHECK(test_node3->get_scale().is_equal_approx(Vector2(2, 2)));
// Don't do anything if look_at own position.
test_node3->look_at(test_node3->get_global_position());
CHECK(test_node3->get_global_position().is_equal_approx(Vector2(98.66026, 105)));
CHECK(Math::is_equal_approx(test_node3->get_global_rotation(), real_t(-2.37509)));
CHECK(test_node3->get_global_scale().is_equal_approx(Vector2(2, 2)));
CHECK(test_node3->get_position().is_equal_approx(Vector2(0, 10)));
CHECK(Math::is_equal_approx(test_node3->get_rotation(), real_t(2.3373)));
CHECK(test_node3->get_scale().is_equal_approx(Vector2(2, 2)));
// Revert any rotation caused by look_at, must run after look_at tests
test_node3->set_rotation(Math::deg_to_rad(0.0));
}
SUBCASE("[Node2D] get_angle_to") {
CHECK(Math::is_equal_approx(test_node3->get_angle_to(Vector2(1, 1)), real_t(2.38762)));
CHECK(Math::is_equal_approx(test_node3->get_angle_to(Vector2(0, 10)), real_t(2.3373)));
CHECK(Math::is_equal_approx(test_node3->get_angle_to(Vector2(2, -5)), real_t(2.42065)));
CHECK(Math::is_equal_approx(test_node3->get_angle_to(Vector2(-2, 5)), real_t(2.3529)));
// Return 0 when get_angle_to own position.
CHECK(Math::is_equal_approx(test_node3->get_angle_to(test_node3->get_global_position()), real_t(0)));
}
SUBCASE("[Node2D] to_local") {
Point2 node3_local = test_node3->to_local(Point2(1, 2));
CHECK(node3_local.is_equal_approx(Point2(-51.5, 48.83013)));
node3_local = test_node3->to_local(Point2(-2, 1));
CHECK(node3_local.is_equal_approx(Point2(-52, 50.33013)));
node3_local = test_node3->to_local(Point2(0, 0));
CHECK(node3_local.is_equal_approx(Point2(-52.5, 49.33013)));
node3_local = test_node3->to_local(test_node3->get_global_position());
CHECK(node3_local.is_equal_approx(Point2(0, 0)));
}
SUBCASE("[Node2D] to_global") {
Point2 node3_global = test_node3->to_global(Point2(1, 2));
CHECK(node3_global.is_equal_approx(Point2(94.66026, 107)));
node3_global = test_node3->to_global(Point2(-2, 1));
CHECK(node3_global.is_equal_approx(Point2(96.66026, 101)));
node3_global = test_node3->to_global(Point2(0, 0));
CHECK(node3_global.is_equal_approx(test_node3->get_global_position()));
}
SUBCASE("[Node2D] get_relative_transform_to_parent") {
Transform2D relative_xform = test_node3->get_relative_transform_to_parent(test_node3);
CHECK(relative_xform.is_equal_approx(Transform2D()));
relative_xform = test_node3->get_relative_transform_to_parent(test_node2);
CHECK(relative_xform.get_origin().is_equal_approx(Vector2(0, 10)));
CHECK(Math::is_equal_approx(relative_xform.get_rotation(), real_t(0)));
CHECK(relative_xform.get_scale().is_equal_approx(Vector2(2, 2)));
relative_xform = test_node3->get_relative_transform_to_parent(test_node1);
CHECK(relative_xform.get_origin().is_equal_approx(Vector2(1.339746, 5)));
CHECK(Math::is_equal_approx(relative_xform.get_rotation(), real_t(1.0472)));
CHECK(relative_xform.get_scale().is_equal_approx(Vector2(2, 2)));
ERR_PRINT_OFF;
// In case of a sibling all transforms until the root are accumulated.
Transform2D xform = test_node3->get_relative_transform_to_parent(test_sibling);
Transform2D return_xform = test_node1->get_global_transform().inverse() * test_node3->get_global_transform();
CHECK(xform.is_equal_approx(return_xform));
ERR_PRINT_ON;
}
memdelete(test_sibling);
memdelete(test_node3);
memdelete(test_node2);
memdelete(test_node1);
}
} // namespace TestNode2D
#endif // TEST_NODE_2D_H

View file

@ -0,0 +1,136 @@
/**************************************************************************/
/* test_option_button.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_OPTION_BUTTON_H
#define TEST_OPTION_BUTTON_H
#include "scene/gui/option_button.h"
#include "tests/test_macros.h"
namespace TestOptionButton {
TEST_CASE("[SceneTree][OptionButton] Initialization") {
OptionButton *test_opt = memnew(OptionButton);
SUBCASE("There should be no options right after initialization") {
CHECK_FALSE(test_opt->has_selectable_items());
CHECK(test_opt->get_item_count() == 0);
}
memdelete(test_opt);
}
TEST_CASE("[SceneTree][OptionButton] Single item") {
OptionButton *test_opt = memnew(OptionButton);
SUBCASE("There should a single item after after adding one") {
test_opt->add_item("single", 1013);
CHECK(test_opt->has_selectable_items());
CHECK(test_opt->get_item_count() == 1);
CHECK(test_opt->get_item_index(1013) == 0);
CHECK(test_opt->get_item_id(0) == 1013);
test_opt->remove_item(0);
CHECK_FALSE(test_opt->has_selectable_items());
CHECK(test_opt->get_item_count() == 0);
}
SUBCASE("There should a single item after after adding an icon") {
Ref<Texture2D> test_icon = memnew(Texture2D);
test_opt->add_icon_item(test_icon, "icon", 345);
CHECK(test_opt->has_selectable_items());
CHECK(test_opt->get_item_count() == 1);
CHECK(test_opt->get_item_index(345) == 0);
CHECK(test_opt->get_item_id(0) == 345);
test_opt->remove_item(0);
CHECK_FALSE(test_opt->has_selectable_items());
CHECK(test_opt->get_item_count() == 0);
}
memdelete(test_opt);
}
TEST_CASE("[SceneTree][OptionButton] Complex structure") {
OptionButton *test_opt = memnew(OptionButton);
SUBCASE("Creating a complex structure and checking getters") {
// Regular item at index 0.
Ref<Texture2D> test_icon1 = memnew(Texture2D);
Ref<Texture2D> test_icon2 = memnew(Texture2D);
// Regular item at index 3.
Ref<Texture2D> test_icon4 = memnew(Texture2D);
test_opt->add_item("first", 100);
test_opt->add_icon_item(test_icon1, "second_icon", 101);
test_opt->add_icon_item(test_icon2, "third_icon", 102);
test_opt->add_item("fourth", 104);
test_opt->add_icon_item(test_icon4, "fifth_icon", 104);
// Disable test_icon4.
test_opt->set_item_disabled(4, true);
CHECK(test_opt->has_selectable_items());
CHECK(test_opt->get_item_count() == 5);
// Check for test_icon2.
CHECK(test_opt->get_item_index(102) == 2);
CHECK(test_opt->get_item_id(2) == 102);
// Remove the two regular items.
test_opt->remove_item(3);
test_opt->remove_item(0);
CHECK(test_opt->has_selectable_items());
CHECK(test_opt->get_item_count() == 3);
// Check test_icon4.
CHECK(test_opt->get_item_index(104) == 2);
CHECK(test_opt->get_item_id(2) == 104);
// Remove the two non-disabled icon items.
test_opt->remove_item(1);
test_opt->remove_item(0);
CHECK_FALSE(test_opt->has_selectable_items());
CHECK(test_opt->get_item_count() == 1);
}
memdelete(test_opt);
}
} // namespace TestOptionButton
#endif // TEST_OPTION_BUTTON_H

View file

@ -56,6 +56,72 @@ TEST_CASE("[PackedScene] Pack Scene and Retrieve State") {
memdelete(scene);
}
TEST_CASE("[PackedScene] Signals Preserved when Packing Scene") {
// Create main scene
// root
// `- sub_node (local)
// `- sub_scene (instance of another scene)
// `- sub_scene_node (owned by sub_scene)
Node *main_scene_root = memnew(Node);
Node *sub_node = memnew(Node);
Node *sub_scene_root = memnew(Node);
Node *sub_scene_node = memnew(Node);
main_scene_root->add_child(sub_node);
sub_node->set_owner(main_scene_root);
sub_scene_root->add_child(sub_scene_node);
sub_scene_node->set_owner(sub_scene_root);
main_scene_root->add_child(sub_scene_root);
sub_scene_root->set_owner(main_scene_root);
SUBCASE("Signals that should be saved") {
int main_flags = Object::CONNECT_PERSIST;
// sub node to a node in main scene
sub_node->connect("ready", callable_mp(main_scene_root, &Node::is_ready), main_flags);
// subscene root to a node in main scene
sub_scene_root->connect("ready", callable_mp(main_scene_root, &Node::is_ready), main_flags);
//subscene root to subscene root (connected within main scene)
sub_scene_root->connect("ready", callable_mp(sub_scene_root, &Node::is_ready), main_flags);
// Pack the scene.
Ref<PackedScene> packed_scene;
packed_scene.instantiate();
const Error err = packed_scene->pack(main_scene_root);
CHECK(err == OK);
// Make sure the right connections are in packed scene.
Ref<SceneState> state = packed_scene->get_state();
CHECK_EQ(state->get_connection_count(), 3);
}
/*
// FIXME: This subcase requires GH-48064 to be fixed.
SUBCASE("Signals that should not be saved") {
int subscene_flags = Object::CONNECT_PERSIST | Object::CONNECT_INHERITED;
// subscene node to itself
sub_scene_node->connect("ready", callable_mp(sub_scene_node, &Node::is_ready), subscene_flags);
// subscene node to subscene root
sub_scene_node->connect("ready", callable_mp(sub_scene_root, &Node::is_ready), subscene_flags);
//subscene root to subscene root (connected within sub scene)
sub_scene_root->connect("ready", callable_mp(sub_scene_root, &Node::is_ready), subscene_flags);
// Pack the scene.
Ref<PackedScene> packed_scene;
packed_scene.instantiate();
const Error err = packed_scene->pack(main_scene_root);
CHECK(err == OK);
// Make sure the right connections are in packed scene.
Ref<SceneState> state = packed_scene->get_state();
CHECK_EQ(state->get_connection_count(), 0);
}
*/
memdelete(main_scene_root);
}
TEST_CASE("[PackedScene] Clear Packed Scene") {
// Create a scene to pack.
Node *scene = memnew(Node);

View file

@ -0,0 +1,131 @@
/**************************************************************************/
/* test_parallax_2d.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_PARALLAX_2D_H
#define TEST_PARALLAX_2D_H
#include "scene/2d/parallax_2d.h"
#include "tests/test_macros.h"
namespace TestParallax2D {
// Test cases for the Parallax2D class to ensure its properties are set and retrieved correctly.
TEST_CASE("[SceneTree][Parallax2D] Scroll Scale") {
// Test setting and getting the scroll scale.
Parallax2D *parallax = memnew(Parallax2D);
Size2 scale(2, 2);
parallax->set_scroll_scale(scale);
CHECK(parallax->get_scroll_scale() == scale);
memdelete(parallax);
}
TEST_CASE("[SceneTree][Parallax2D] Repeat Size") {
// Test setting and getting the repeat size.
Parallax2D *parallax = memnew(Parallax2D);
Size2 size(100, 100);
parallax->set_repeat_size(size);
CHECK(parallax->get_repeat_size() == size);
memdelete(parallax);
}
TEST_CASE("[SceneTree][Parallax2D] Repeat Times") {
// Test setting and getting the repeat times.
Parallax2D *parallax = memnew(Parallax2D);
int times = 5;
parallax->set_repeat_times(times);
CHECK(parallax->get_repeat_times() == times);
memdelete(parallax);
}
TEST_CASE("[SceneTree][Parallax2D] Autoscroll") {
// Test setting and getting the autoscroll values.
Parallax2D *parallax = memnew(Parallax2D);
Point2 autoscroll(1, 1);
parallax->set_autoscroll(autoscroll);
CHECK(parallax->get_autoscroll() == autoscroll);
memdelete(parallax);
}
TEST_CASE("[SceneTree][Parallax2D] Scroll Offset") {
// Test setting and getting the scroll offset.
Parallax2D *parallax = memnew(Parallax2D);
Point2 offset(10, 10);
parallax->set_scroll_offset(offset);
CHECK(parallax->get_scroll_offset() == offset);
memdelete(parallax);
}
TEST_CASE("[SceneTree][Parallax2D] Screen Offset") {
// Test setting and getting the screen offset.
Parallax2D *parallax = memnew(Parallax2D);
Point2 offset(20, 20);
parallax->set_screen_offset(offset);
CHECK(parallax->get_screen_offset() == offset);
memdelete(parallax);
}
TEST_CASE("[SceneTree][Parallax2D] Limit Begin") {
// Test setting and getting the limit begin values.
Parallax2D *parallax = memnew(Parallax2D);
Point2 limit_begin(-100, -100);
parallax->set_limit_begin(limit_begin);
CHECK(parallax->get_limit_begin() == limit_begin);
memdelete(parallax);
}
TEST_CASE("[SceneTree][Parallax2D] Limit End") {
// Test setting and getting the limit end values.
Parallax2D *parallax = memnew(Parallax2D);
Point2 limit_end(100, 100);
parallax->set_limit_end(limit_end);
CHECK(parallax->get_limit_end() == limit_end);
memdelete(parallax);
}
TEST_CASE("[SceneTree][Parallax2D] Follow Viewport") {
// Test setting and getting the follow viewport flag.
Parallax2D *parallax = memnew(Parallax2D);
parallax->set_follow_viewport(false);
CHECK_FALSE(parallax->get_follow_viewport());
memdelete(parallax);
}
TEST_CASE("[SceneTree][Parallax2D] Ignore Camera Scroll") {
// Test setting and getting the ignore camera scroll flag.
Parallax2D *parallax = memnew(Parallax2D);
parallax->set_ignore_camera_scroll(true);
CHECK(parallax->is_ignore_camera_scroll());
memdelete(parallax);
}
} // namespace TestParallax2D
#endif // TEST_PARALLAX_2D_H

View file

@ -40,7 +40,7 @@ namespace TestPath2D {
TEST_CASE("[SceneTree][Path2D] Initialization") {
SUBCASE("Path should be empty right after initialization") {
Path2D *test_path = memnew(Path2D);
CHECK(test_path->get_curve() == nullptr);
CHECK(test_path->get_curve().is_null());
memdelete(test_path);
}
}

View file

@ -40,7 +40,7 @@ namespace TestPath3D {
TEST_CASE("[Path3D] Initialization") {
SUBCASE("Path should be empty right after initialization") {
Path3D *test_path = memnew(Path3D);
CHECK(test_path->get_curve() == nullptr);
CHECK(test_path->get_curve().is_null());
memdelete(test_path);
}
}

View file

@ -60,39 +60,30 @@ TEST_CASE("[SceneTree][PathFollow3D] Sampling with progress ratio") {
SceneTree::get_singleton()->get_root()->add_child(path);
path_follow_3d->set_progress_ratio(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);
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);
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);
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);
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);
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);
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);
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);
path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 0, 100), path_follow_3d->get_transform().get_origin()));
memdelete(path);
@ -113,39 +104,30 @@ TEST_CASE("[SceneTree][PathFollow3D] Sampling with progress") {
SceneTree::get_singleton()->get_root()->add_child(path);
path_follow_3d->set_progress(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);
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);
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);
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);
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);
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);
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);
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);
path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 0, 100), path_follow_3d->get_transform().get_origin()));
memdelete(path);
@ -163,13 +145,11 @@ TEST_CASE("[SceneTree][PathFollow3D] Removal of a point in curve") {
SceneTree::get_singleton()->get_root()->add_child(path);
path_follow_3d->set_progress_ratio(0.5);
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(
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");
@ -270,47 +250,36 @@ TEST_CASE("[SceneTree][PathFollow3D] Calculate forward vector") {
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);

View file

@ -0,0 +1,107 @@
/**************************************************************************/
/* test_physics_material.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_PHYSICS_MATERIAL_H
#define TEST_PHYSICS_MATERIAL_H
#include "scene/resources/physics_material.h"
#include "tests/test_macros.h"
namespace TestPhysics_material {
TEST_CASE("[Physics_material] Defaults") {
Ref<PhysicsMaterial> physics_material;
physics_material.instantiate();
CHECK(physics_material->get_friction() == 1.);
CHECK(physics_material->is_rough() == false);
CHECK(physics_material->get_bounce() == 0.);
CHECK(physics_material->is_absorbent() == false);
}
TEST_CASE("[Physics_material] Friction") {
Ref<PhysicsMaterial> physics_material;
physics_material.instantiate();
real_t friction = 0.314;
physics_material->set_friction(friction);
CHECK(physics_material->get_friction() == friction);
}
TEST_CASE("[Physics_material] Rough") {
Ref<PhysicsMaterial> physics_material;
physics_material.instantiate();
bool rough = true;
physics_material->set_rough(rough);
CHECK(physics_material->is_rough() == rough);
real_t friction = 0.314;
physics_material->set_friction(friction);
CHECK(physics_material->computed_friction() == -friction);
rough = false;
physics_material->set_rough(rough);
CHECK(physics_material->is_rough() == rough);
CHECK(physics_material->computed_friction() == friction);
}
TEST_CASE("[Physics_material] Bounce") {
Ref<PhysicsMaterial> physics_material;
physics_material.instantiate();
real_t bounce = 0.271;
physics_material->set_bounce(bounce);
CHECK(physics_material->get_bounce() == bounce);
}
TEST_CASE("[Physics_material] Absorbent") {
Ref<PhysicsMaterial> physics_material;
physics_material.instantiate();
bool absorbent = true;
physics_material->set_absorbent(absorbent);
CHECK(physics_material->is_absorbent() == absorbent);
real_t bounce = 0.271;
physics_material->set_bounce(bounce);
CHECK(physics_material->computed_bounce() == -bounce);
absorbent = false;
physics_material->set_absorbent(absorbent);
CHECK(physics_material->is_absorbent() == absorbent);
CHECK(physics_material->computed_bounce() == bounce);
}
} // namespace TestPhysics_material
#endif // TEST_PHYSICS_MATERIAL_H

View file

@ -104,8 +104,9 @@ TEST_CASE("[SceneTree][Primitive][Capsule] Capsule Primitive") {
float dist_to_yaxis = 0.f;
for (Vector3 point : points) {
float new_dist_to_y = point.x * point.x + point.z * point.z;
if (new_dist_to_y > dist_to_yaxis)
if (new_dist_to_y > dist_to_yaxis) {
dist_to_yaxis = new_dist_to_y;
}
}
CHECK(dist_to_yaxis <= radius * radius);
@ -114,10 +115,12 @@ TEST_CASE("[SceneTree][Primitive][Capsule] Capsule Primitive") {
float max_y{ 0.f };
float min_y{ 0.f };
for (Vector3 point : points) {
if (point.y > max_y)
if (point.y > max_y) {
max_y = point.y;
if (point.y < min_y)
}
if (point.y < min_y) {
min_y = point.y;
}
}
CHECK(max_y - min_y <= height);
@ -196,12 +199,14 @@ TEST_CASE("[SceneTree][Primitive][Box] Box Primitive") {
for (const Vector3 &normal : normals) {
bool add_normal{ true };
for (const Vector3 &vec : distinct_normals) {
if (vec.is_equal_approx(normal))
if (vec.is_equal_approx(normal)) {
add_normal = false;
}
}
if (add_normal)
if (add_normal) {
distinct_normals.push_back(normal);
}
}
CHECK_MESSAGE(distinct_normals.size() == 6,
@ -218,8 +223,9 @@ TEST_CASE("[SceneTree][Primitive][Box] Box Primitive") {
break;
}
}
if (!normal_correct_direction)
if (!normal_correct_direction) {
break;
}
}
CHECK_MESSAGE(normal_correct_direction,
@ -609,7 +615,7 @@ TEST_CASE("[SceneTree][Primitive][TubeTrail] TubeTrail Primitive") {
CHECK(tube->get_sections() >= 0);
CHECK(tube->get_section_length() > 0);
CHECK(tube->get_section_rings() >= 0);
CHECK(tube->get_curve() == nullptr);
CHECK(tube->get_curve().is_null());
CHECK(tube->get_builtin_bind_pose_count() >= 0);
}
@ -669,7 +675,7 @@ TEST_CASE("[SceneTree][Primitive][RibbonTrail] RibbonTrail Primitive") {
CHECK(ribbon->get_section_length() > 0);
CHECK(ribbon->get_section_segments() >= 0);
CHECK(ribbon->get_builtin_bind_pose_count() >= 0);
CHECK(ribbon->get_curve() == nullptr);
CHECK(ribbon->get_curve().is_null());
CHECK((ribbon->get_shape() == RibbonTrailMesh::SHAPE_CROSS ||
ribbon->get_shape() == RibbonTrailMesh::SHAPE_FLAT));
}
@ -731,7 +737,7 @@ TEST_CASE("[SceneTree][Primitive][Text] Text Primitive") {
text->get_vertical_alignment() == VERTICAL_ALIGNMENT_TOP ||
text->get_vertical_alignment() == VERTICAL_ALIGNMENT_CENTER ||
text->get_vertical_alignment() == VERTICAL_ALIGNMENT_FILL));
CHECK(text->get_font() == nullptr);
CHECK(text->get_font().is_null());
CHECK(text->get_font_size() > 0);
CHECK(text->get_line_spacing() >= 0);
CHECK((text->get_autowrap_mode() == TextServer::AUTOWRAP_OFF ||

View file

@ -0,0 +1,78 @@
/**************************************************************************/
/* test_skeleton_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_SKELETON_3D_H
#define TEST_SKELETON_3D_H
#include "tests/test_macros.h"
#include "scene/3d/skeleton_3d.h"
namespace TestSkeleton3D {
TEST_CASE("[Skeleton3D] Test per-bone meta") {
Skeleton3D *skeleton = memnew(Skeleton3D);
skeleton->add_bone("root");
skeleton->set_bone_rest(0, Transform3D());
// Adding meta to bone.
skeleton->set_bone_meta(0, "key1", "value1");
skeleton->set_bone_meta(0, "key2", 12345);
CHECK_MESSAGE(skeleton->get_bone_meta(0, "key1") == "value1", "Bone meta missing.");
CHECK_MESSAGE(skeleton->get_bone_meta(0, "key2") == Variant(12345), "Bone meta missing.");
// Rename bone and check if meta persists.
skeleton->set_bone_name(0, "renamed_root");
CHECK_MESSAGE(skeleton->get_bone_meta(0, "key1") == "value1", "Bone meta missing.");
CHECK_MESSAGE(skeleton->get_bone_meta(0, "key2") == Variant(12345), "Bone meta missing.");
// Retrieve list of keys.
List<StringName> keys;
skeleton->get_bone_meta_list(0, &keys);
CHECK_MESSAGE(keys.size() == 2, "Wrong number of bone meta keys.");
CHECK_MESSAGE(keys.find("key1"), "key1 not found in bone meta list");
CHECK_MESSAGE(keys.find("key2"), "key2 not found in bone meta list");
// Removing meta.
skeleton->set_bone_meta(0, "key1", Variant());
skeleton->set_bone_meta(0, "key2", Variant());
CHECK_MESSAGE(!skeleton->has_bone_meta(0, "key1"), "Bone meta key1 should be deleted.");
CHECK_MESSAGE(!skeleton->has_bone_meta(0, "key2"), "Bone meta key2 should be deleted.");
List<StringName> should_be_empty_keys;
skeleton->get_bone_meta_list(0, &should_be_empty_keys);
CHECK_MESSAGE(should_be_empty_keys.size() == 0, "Wrong number of bone meta keys.");
// Deleting non-existing key should succeed.
skeleton->set_bone_meta(0, "non-existing-key", Variant());
memdelete(skeleton);
}
} // namespace TestSkeleton3D
#endif // TEST_SKELETON_3D_H

View file

@ -0,0 +1,222 @@
/**************************************************************************/
/* test_sky.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_SKY_H
#define TEST_SKY_H
#include "scene/resources/sky.h"
#include "tests/test_macros.h"
namespace TestSky {
TEST_CASE("[SceneTree][Sky] Constructor") {
Ref<Sky> test_sky;
test_sky.instantiate();
CHECK(test_sky->get_process_mode() == Sky::PROCESS_MODE_AUTOMATIC);
CHECK(test_sky->get_radiance_size() == Sky::RADIANCE_SIZE_256);
CHECK(test_sky->get_material().is_null());
}
TEST_CASE("[SceneTree][Sky] Radiance size setter and getter") {
Ref<Sky> test_sky;
test_sky.instantiate();
// Check default.
CHECK(test_sky->get_radiance_size() == Sky::RADIANCE_SIZE_256);
test_sky->set_radiance_size(Sky::RADIANCE_SIZE_1024);
CHECK(test_sky->get_radiance_size() == Sky::RADIANCE_SIZE_1024);
ERR_PRINT_OFF;
// Check setting invalid radiance size.
test_sky->set_radiance_size(Sky::RADIANCE_SIZE_MAX);
ERR_PRINT_ON;
CHECK(test_sky->get_radiance_size() == Sky::RADIANCE_SIZE_1024);
}
TEST_CASE("[SceneTree][Sky] Process mode setter and getter") {
Ref<Sky> test_sky;
test_sky.instantiate();
// Check default.
CHECK(test_sky->get_process_mode() == Sky::PROCESS_MODE_AUTOMATIC);
test_sky->set_process_mode(Sky::PROCESS_MODE_INCREMENTAL);
CHECK(test_sky->get_process_mode() == Sky::PROCESS_MODE_INCREMENTAL);
}
TEST_CASE("[SceneTree][Sky] Material setter and getter") {
Ref<Sky> test_sky;
test_sky.instantiate();
Ref<Material> material;
material.instantiate();
SUBCASE("Material passed to the class should remain the same") {
test_sky->set_material(material);
CHECK(test_sky->get_material() == material);
}
SUBCASE("Material passed many times to the class should remain the same") {
test_sky->set_material(material);
test_sky->set_material(material);
test_sky->set_material(material);
CHECK(test_sky->get_material() == material);
}
SUBCASE("Material rewrite testing") {
Ref<Material> material1;
Ref<Material> material2;
material1.instantiate();
material2.instantiate();
test_sky->set_material(material1);
test_sky->set_material(material2);
CHECK_MESSAGE(test_sky->get_material() != material1,
"After rewrite, second material should be in class.");
CHECK_MESSAGE(test_sky->get_material() == material2,
"After rewrite, second material should be in class.");
}
SUBCASE("Assign same material to two skys") {
Ref<Sky> sky2;
sky2.instantiate();
test_sky->set_material(material);
sky2->set_material(material);
CHECK_MESSAGE(test_sky->get_material() == sky2->get_material(),
"Both skys should have the same material.");
}
SUBCASE("Swapping materials between two skys") {
Ref<Sky> sky2;
sky2.instantiate();
Ref<Material> material1;
Ref<Material> material2;
material1.instantiate();
material2.instantiate();
test_sky->set_material(material1);
sky2->set_material(material2);
CHECK(test_sky->get_material() == material1);
CHECK(sky2->get_material() == material2);
// Do the swap.
Ref<Material> temp = test_sky->get_material();
test_sky->set_material(sky2->get_material());
sky2->set_material(temp);
CHECK(test_sky->get_material() == material2);
CHECK(sky2->get_material() == material1);
}
}
TEST_CASE("[SceneTree][Sky] Invalid radiance size handling") {
Ref<Sky> test_sky;
test_sky.instantiate();
// Attempt to set an invalid radiance size.
ERR_PRINT_OFF;
test_sky->set_radiance_size(Sky::RADIANCE_SIZE_MAX);
ERR_PRINT_ON;
// Verify that the radiance size remains unchanged.
CHECK(test_sky->get_radiance_size() == Sky::RADIANCE_SIZE_256);
}
TEST_CASE("[SceneTree][Sky] Process mode variations") {
Ref<Sky> test_sky;
test_sky.instantiate();
// Test all process modes.
const Sky::ProcessMode process_modes[] = {
Sky::PROCESS_MODE_AUTOMATIC,
Sky::PROCESS_MODE_QUALITY,
Sky::PROCESS_MODE_INCREMENTAL,
Sky::PROCESS_MODE_REALTIME
};
for (Sky::ProcessMode mode : process_modes) {
test_sky->set_process_mode(mode);
CHECK(test_sky->get_process_mode() == mode);
}
}
TEST_CASE("[SceneTree][Sky] Radiance size variations") {
Ref<Sky> test_sky;
test_sky.instantiate();
// Test all radiance sizes except MAX.
const Sky::RadianceSize radiance_sizes[] = {
Sky::RADIANCE_SIZE_32,
Sky::RADIANCE_SIZE_64,
Sky::RADIANCE_SIZE_128,
Sky::RADIANCE_SIZE_256,
Sky::RADIANCE_SIZE_512,
Sky::RADIANCE_SIZE_1024,
Sky::RADIANCE_SIZE_2048
};
for (Sky::RadianceSize size : radiance_sizes) {
test_sky->set_radiance_size(size);
CHECK(test_sky->get_radiance_size() == size);
}
}
TEST_CASE("[SceneTree][Sky] Null material handling") {
Ref<Sky> test_sky;
test_sky.instantiate();
SUBCASE("Setting null material") {
test_sky->set_material(Ref<Material>());
CHECK(test_sky->get_material().is_null());
}
SUBCASE("Overwriting existing material with null") {
Ref<Material> material;
material.instantiate();
test_sky->set_material(material);
test_sky->set_material(Ref<Material>());
CHECK(test_sky->get_material().is_null());
}
}
TEST_CASE("[SceneTree][Sky] RID generation") {
Ref<Sky> test_sky;
test_sky.instantiate();
// Check validity.
CHECK(!test_sky->get_rid().is_valid());
}
} // namespace TestSky
#endif // TEST_SKY_H

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,194 @@
/**************************************************************************/
/* test_style_box_texture.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_STYLE_BOX_TEXTURE_H
#define TEST_STYLE_BOX_TEXTURE_H
#include "scene/resources/style_box_texture.h"
#include "tests/test_macros.h"
namespace TestStyleBoxTexture {
TEST_CASE("[StyleBoxTexture] Constructor") {
Ref<StyleBoxTexture> style_box_texture = memnew(StyleBoxTexture);
CHECK(style_box_texture->get_h_axis_stretch_mode() == style_box_texture->AXIS_STRETCH_MODE_STRETCH);
CHECK(style_box_texture->get_v_axis_stretch_mode() == style_box_texture->AXIS_STRETCH_MODE_STRETCH);
CHECK(style_box_texture->is_draw_center_enabled() == true);
CHECK(style_box_texture->get_expand_margin(SIDE_LEFT) == 0);
CHECK(style_box_texture->get_expand_margin(SIDE_TOP) == 0);
CHECK(style_box_texture->get_expand_margin(SIDE_RIGHT) == 0);
CHECK(style_box_texture->get_expand_margin(SIDE_BOTTOM) == 0);
CHECK(style_box_texture->get_modulate() == Color(1, 1, 1, 1));
CHECK(style_box_texture->get_region_rect() == Rect2(0, 0, 0, 0));
CHECK(style_box_texture->get_texture() == Ref<Texture2D>());
CHECK(style_box_texture->get_texture_margin(SIDE_LEFT) == 0);
CHECK(style_box_texture->get_texture_margin(SIDE_TOP) == 0);
CHECK(style_box_texture->get_texture_margin(SIDE_RIGHT) == 0);
CHECK(style_box_texture->get_texture_margin(SIDE_BOTTOM) == 0);
}
TEST_CASE("[StyleBoxTexture] set_texture, get_texture") {
Ref<StyleBoxTexture> style_box_texture = memnew(StyleBoxTexture);
Ref<Texture2D> texture = memnew(Texture2D);
style_box_texture->set_texture(texture);
CHECK(style_box_texture->get_texture() == texture);
}
TEST_CASE("[StyleBoxTexture] set_texture_margin, set_texture_margin_all, set_texture_margin_individual, get_texture_margin") {
Ref<StyleBoxTexture> style_box_texture = memnew(StyleBoxTexture);
SUBCASE("set_texture_margin, get_texture_margin") {
style_box_texture->set_texture_margin(SIDE_LEFT, 1);
style_box_texture->set_texture_margin(SIDE_TOP, 1);
style_box_texture->set_texture_margin(SIDE_RIGHT, 1);
style_box_texture->set_texture_margin(SIDE_BOTTOM, 1);
CHECK(style_box_texture->get_texture_margin(SIDE_LEFT) == 1);
CHECK(style_box_texture->get_texture_margin(SIDE_TOP) == 1);
CHECK(style_box_texture->get_texture_margin(SIDE_RIGHT) == 1);
CHECK(style_box_texture->get_texture_margin(SIDE_BOTTOM) == 1);
}
SUBCASE("set_texture_margin_all") {
style_box_texture->set_texture_margin_all(2);
CHECK(style_box_texture->get_texture_margin(SIDE_LEFT) == 2);
CHECK(style_box_texture->get_texture_margin(SIDE_TOP) == 2);
CHECK(style_box_texture->get_texture_margin(SIDE_RIGHT) == 2);
CHECK(style_box_texture->get_texture_margin(SIDE_BOTTOM) == 2);
}
SUBCASE("set_texture_margin_individual") {
style_box_texture->set_texture_margin_individual(3, 4, 5, 6);
CHECK(style_box_texture->get_texture_margin(SIDE_LEFT) == 3);
CHECK(style_box_texture->get_texture_margin(SIDE_TOP) == 4);
CHECK(style_box_texture->get_texture_margin(SIDE_RIGHT) == 5);
CHECK(style_box_texture->get_texture_margin(SIDE_BOTTOM) == 6);
}
}
TEST_CASE("[StyleBoxTexture] set_expand_margin, set_expand_margin_all, set_expand_margin_individual") {
Ref<StyleBoxTexture> style_box_texture = memnew(StyleBoxTexture);
SUBCASE("set_expand_margin, get_expand_margin") {
style_box_texture->set_expand_margin(SIDE_LEFT, 1);
style_box_texture->set_expand_margin(SIDE_TOP, 1);
style_box_texture->set_expand_margin(SIDE_RIGHT, 1);
style_box_texture->set_expand_margin(SIDE_BOTTOM, 1);
CHECK(style_box_texture->get_expand_margin(SIDE_LEFT) == 1);
CHECK(style_box_texture->get_expand_margin(SIDE_TOP) == 1);
CHECK(style_box_texture->get_expand_margin(SIDE_RIGHT) == 1);
CHECK(style_box_texture->get_expand_margin(SIDE_BOTTOM) == 1);
}
SUBCASE("set_expand_margin_all") {
style_box_texture->set_expand_margin_all(2);
CHECK(style_box_texture->get_expand_margin(SIDE_LEFT) == 2);
CHECK(style_box_texture->get_expand_margin(SIDE_TOP) == 2);
CHECK(style_box_texture->get_expand_margin(SIDE_RIGHT) == 2);
CHECK(style_box_texture->get_expand_margin(SIDE_BOTTOM) == 2);
}
SUBCASE("set_expand_margin_individual") {
style_box_texture->set_expand_margin_individual(3, 4, 5, 6);
CHECK(style_box_texture->get_expand_margin(SIDE_LEFT) == 3);
CHECK(style_box_texture->get_expand_margin(SIDE_TOP) == 4);
CHECK(style_box_texture->get_expand_margin(SIDE_RIGHT) == 5);
CHECK(style_box_texture->get_expand_margin(SIDE_BOTTOM) == 6);
}
}
TEST_CASE("[StyleBoxTexture] set_region_rect, get_region_rect") {
Ref<StyleBoxTexture> style_box_texture = memnew(StyleBoxTexture);
style_box_texture->set_region_rect(Rect2(1, 1, 1, 1));
CHECK(style_box_texture->get_region_rect() == Rect2(1, 1, 1, 1));
}
TEST_CASE("[StyleBoxTexture] set_draw_center, get_draw_center") {
Ref<StyleBoxTexture> style_box_texture = memnew(StyleBoxTexture);
style_box_texture->set_draw_center(false);
CHECK(style_box_texture->is_draw_center_enabled() == false);
}
TEST_CASE("[StyleBoxTexture] set_h_axis_stretch_mode, set_v_axis_stretch_mode, get_h_axis_stretch_mode, get_v_axis_stretch_mode") {
Ref<StyleBoxTexture> style_box_texture = memnew(StyleBoxTexture);
SUBCASE("set_h_axis_stretch_mode, get_h_axis_stretch_mode") {
style_box_texture->set_h_axis_stretch_mode(style_box_texture->AXIS_STRETCH_MODE_TILE);
CHECK(style_box_texture->get_h_axis_stretch_mode() == style_box_texture->AXIS_STRETCH_MODE_TILE);
style_box_texture->set_h_axis_stretch_mode(style_box_texture->AXIS_STRETCH_MODE_TILE_FIT);
CHECK(style_box_texture->get_h_axis_stretch_mode() == style_box_texture->AXIS_STRETCH_MODE_TILE_FIT);
style_box_texture->set_h_axis_stretch_mode(style_box_texture->AXIS_STRETCH_MODE_STRETCH);
CHECK(style_box_texture->get_h_axis_stretch_mode() == style_box_texture->AXIS_STRETCH_MODE_STRETCH);
}
SUBCASE("set_v_axis_stretch_mode, get_v_axis_stretch_mode") {
style_box_texture->set_v_axis_stretch_mode(style_box_texture->AXIS_STRETCH_MODE_TILE);
CHECK(style_box_texture->get_v_axis_stretch_mode() == style_box_texture->AXIS_STRETCH_MODE_TILE);
style_box_texture->set_v_axis_stretch_mode(style_box_texture->AXIS_STRETCH_MODE_TILE_FIT);
CHECK(style_box_texture->get_v_axis_stretch_mode() == style_box_texture->AXIS_STRETCH_MODE_TILE_FIT);
style_box_texture->set_v_axis_stretch_mode(style_box_texture->AXIS_STRETCH_MODE_STRETCH);
CHECK(style_box_texture->get_v_axis_stretch_mode() == style_box_texture->AXIS_STRETCH_MODE_STRETCH);
}
}
TEST_CASE("[StyleBoxTexture] set_modulate, get_modulate") {
Ref<StyleBoxTexture> style_box_texture = memnew(StyleBoxTexture);
style_box_texture->set_modulate(Color(0, 0, 0, 0));
CHECK(style_box_texture->get_modulate() == Color(0, 0, 0, 0));
}
TEST_CASE("[StyleBoxTexture] get_draw_rect") {
Ref<StyleBoxTexture> style_box_texture = memnew(StyleBoxTexture);
style_box_texture->set_expand_margin_all(5);
CHECK(style_box_texture->get_draw_rect(Rect2(0, 0, 1, 1)) == Rect2(-5, -5, 11, 11));
}
} // namespace TestStyleBoxTexture
#endif // TEST_STYLE_BOX_TEXTURE_H

View file

@ -0,0 +1,864 @@
/**************************************************************************/
/* test_tab_bar.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_TAB_BAR_H
#define TEST_TAB_BAR_H
#include "scene/gui/tab_bar.h"
#include "scene/main/window.h"
#include "tests/test_macros.h"
namespace TestTabBar {
static inline Array build_array() {
return Array();
}
template <typename... Targs>
static inline Array build_array(Variant item, Targs... Fargs) {
Array a = build_array(Fargs...);
a.push_front(item);
return a;
}
TEST_CASE("[SceneTree][TabBar] tab operations") {
TabBar *tab_bar = memnew(TabBar);
SceneTree::get_singleton()->get_root()->add_child(tab_bar);
tab_bar->set_clip_tabs(false);
MessageQueue::get_singleton()->flush();
SIGNAL_WATCH(tab_bar, "tab_selected");
SIGNAL_WATCH(tab_bar, "tab_changed");
SUBCASE("[TabBar] no tabs") {
CHECK(tab_bar->get_tab_count() == 0);
CHECK(tab_bar->get_current_tab() == -1);
CHECK(tab_bar->get_previous_tab() == -1);
}
SUBCASE("[TabBar] add tabs") {
tab_bar->add_tab("tab0");
CHECK(tab_bar->get_tab_count() == 1);
CHECK(tab_bar->get_current_tab() == 0);
CHECK(tab_bar->get_previous_tab() == -1);
SIGNAL_CHECK("tab_selected", build_array(build_array(0)));
SIGNAL_CHECK("tab_changed", build_array(build_array(0)));
tab_bar->add_tab("tab1");
CHECK(tab_bar->get_tab_count() == 2);
CHECK(tab_bar->get_current_tab() == 0);
CHECK(tab_bar->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
tab_bar->add_tab("tab2");
CHECK(tab_bar->get_tab_count() == 3);
CHECK(tab_bar->get_current_tab() == 0);
CHECK(tab_bar->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
CHECK(tab_bar->get_tab_title(0) == "tab0");
CHECK(tab_bar->get_tab_tooltip(0) == "");
CHECK(tab_bar->get_tab_text_direction(0) == Control::TEXT_DIRECTION_INHERITED);
CHECK_FALSE(tab_bar->is_tab_disabled(0));
CHECK_FALSE(tab_bar->is_tab_hidden(0));
CHECK(tab_bar->get_tab_title(1) == "tab1");
CHECK(tab_bar->get_tab_tooltip(1) == "");
CHECK(tab_bar->get_tab_text_direction(1) == Control::TEXT_DIRECTION_INHERITED);
CHECK_FALSE(tab_bar->is_tab_disabled(1));
CHECK_FALSE(tab_bar->is_tab_hidden(1));
CHECK(tab_bar->get_tab_title(2) == "tab2");
CHECK(tab_bar->get_tab_tooltip(2) == "");
CHECK(tab_bar->get_tab_text_direction(2) == Control::TEXT_DIRECTION_INHERITED);
CHECK_FALSE(tab_bar->is_tab_disabled(2));
CHECK_FALSE(tab_bar->is_tab_hidden(2));
}
SUBCASE("[TabBar] set tab count") {
// Adds multiple tabs at once.
tab_bar->set_tab_count(3);
CHECK(tab_bar->get_tab_count() == 3);
CHECK(tab_bar->get_current_tab() == 0);
CHECK(tab_bar->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
CHECK(tab_bar->get_tab_title(0) == "");
CHECK(tab_bar->get_tab_tooltip(0) == "");
CHECK(tab_bar->get_tab_text_direction(0) == Control::TEXT_DIRECTION_INHERITED);
CHECK_FALSE(tab_bar->is_tab_disabled(0));
CHECK_FALSE(tab_bar->is_tab_hidden(0));
CHECK(tab_bar->get_tab_title(1) == "");
CHECK(tab_bar->get_tab_tooltip(1) == "");
CHECK(tab_bar->get_tab_text_direction(1) == Control::TEXT_DIRECTION_INHERITED);
CHECK_FALSE(tab_bar->is_tab_disabled(1));
CHECK_FALSE(tab_bar->is_tab_hidden(1));
CHECK(tab_bar->get_tab_title(2) == "");
CHECK(tab_bar->get_tab_tooltip(2) == "");
CHECK(tab_bar->get_tab_text_direction(2) == Control::TEXT_DIRECTION_INHERITED);
CHECK_FALSE(tab_bar->is_tab_disabled(2));
CHECK_FALSE(tab_bar->is_tab_hidden(2));
// Setting to less tabs than there are removes from the end.
tab_bar->set_tab_title(0, "tab0");
tab_bar->set_tab_title(1, "tab1");
tab_bar->set_tab_title(2, "tab2");
tab_bar->set_tab_count(2);
CHECK(tab_bar->get_tab_count() == 2);
CHECK(tab_bar->get_current_tab() == 0);
CHECK(tab_bar->get_previous_tab() == -1);
CHECK(tab_bar->get_tab_title(0) == "tab0");
CHECK(tab_bar->get_tab_title(1) == "tab1");
// Remove all tabs.
tab_bar->set_tab_count(0);
CHECK(tab_bar->get_tab_count() == 0);
CHECK(tab_bar->get_current_tab() == -1);
CHECK(tab_bar->get_previous_tab() == -1);
}
SUBCASE("[TabBar] clear tabs") {
CHECK(tab_bar->get_tab_count() == 0);
CHECK(tab_bar->get_current_tab() == -1);
CHECK(tab_bar->get_previous_tab() == -1);
tab_bar->set_tab_count(2);
CHECK(tab_bar->get_tab_count() == 2);
CHECK(tab_bar->get_current_tab() == 0);
CHECK(tab_bar->get_previous_tab() == -1);
SIGNAL_DISCARD("tab_selected");
SIGNAL_DISCARD("tab_changed");
tab_bar->clear_tabs();
CHECK(tab_bar->get_tab_count() == 0);
CHECK(tab_bar->get_current_tab() == -1);
CHECK(tab_bar->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
}
SUBCASE("[TabBar] remove tabs") {
tab_bar->add_tab("tab0");
tab_bar->add_tab("tab1");
tab_bar->add_tab("tab2");
tab_bar->set_current_tab(1);
CHECK(tab_bar->get_tab_count() == 3);
CHECK(tab_bar->get_current_tab() == 1);
CHECK(tab_bar->get_previous_tab() == 0);
SIGNAL_DISCARD("tab_selected");
SIGNAL_DISCARD("tab_changed");
// Remove first tab.
tab_bar->remove_tab(0);
CHECK(tab_bar->get_tab_count() == 2);
CHECK(tab_bar->get_tab_title(0) == "tab1");
CHECK(tab_bar->get_tab_title(1) == "tab2");
CHECK(tab_bar->get_current_tab() == 0);
CHECK(tab_bar->get_previous_tab() == 0);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// Remove last tab.
tab_bar->remove_tab(1);
CHECK(tab_bar->get_tab_count() == 1);
CHECK(tab_bar->get_tab_title(0) == "tab1");
CHECK(tab_bar->get_current_tab() == 0);
CHECK(tab_bar->get_previous_tab() == 0);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// Remove only tab.
tab_bar->remove_tab(0);
CHECK(tab_bar->get_tab_count() == 0);
CHECK(tab_bar->get_current_tab() == -1);
CHECK(tab_bar->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK("tab_changed", build_array(build_array(-1)));
// Remove current tab when there are other tabs.
tab_bar->add_tab("tab0");
tab_bar->add_tab("tab1");
tab_bar->add_tab("tab2");
tab_bar->set_current_tab(1);
tab_bar->set_current_tab(2);
CHECK(tab_bar->get_tab_count() == 3);
CHECK(tab_bar->get_current_tab() == 2);
CHECK(tab_bar->get_previous_tab() == 1);
SIGNAL_DISCARD("tab_selected");
SIGNAL_DISCARD("tab_changed");
tab_bar->remove_tab(2);
CHECK(tab_bar->get_tab_count() == 2);
CHECK(tab_bar->get_current_tab() == 1);
CHECK(tab_bar->get_previous_tab() == 1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK("tab_changed", build_array(build_array(1)));
}
SUBCASE("[TabBar] move tabs") {
tab_bar->add_tab("tab0");
tab_bar->add_tab("tab1");
tab_bar->add_tab("tab2");
tab_bar->set_current_tab(1);
CHECK(tab_bar->get_current_tab() == 1);
CHECK(tab_bar->get_previous_tab() == 0);
SIGNAL_DISCARD("tab_selected");
SIGNAL_DISCARD("tab_changed");
// Don't move if index is the same.
tab_bar->move_tab(0, 0);
CHECK(tab_bar->get_tab_title(0) == "tab0");
CHECK(tab_bar->get_tab_title(1) == "tab1");
CHECK(tab_bar->get_tab_title(2) == "tab2");
CHECK(tab_bar->get_current_tab() == 1);
CHECK(tab_bar->get_previous_tab() == 0);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// Move the first tab to the end.
tab_bar->move_tab(0, 2);
CHECK(tab_bar->get_tab_title(0) == "tab1");
CHECK(tab_bar->get_tab_title(1) == "tab2");
CHECK(tab_bar->get_tab_title(2) == "tab0");
CHECK(tab_bar->get_current_tab() == 0);
CHECK(tab_bar->get_previous_tab() == 2);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// Move the second tab to the front.
tab_bar->move_tab(1, 0);
CHECK(tab_bar->get_tab_title(0) == "tab2");
CHECK(tab_bar->get_tab_title(1) == "tab1");
CHECK(tab_bar->get_tab_title(2) == "tab0");
CHECK(tab_bar->get_current_tab() == 1);
CHECK(tab_bar->get_previous_tab() == 2);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
}
SUBCASE("[TabBar] set current tab") {
tab_bar->add_tab("tab0");
tab_bar->add_tab("tab1");
tab_bar->add_tab("tab2");
CHECK(tab_bar->get_current_tab() == 0);
CHECK(tab_bar->get_previous_tab() == -1);
SIGNAL_CHECK("tab_selected", build_array(build_array(0)));
SIGNAL_CHECK("tab_changed", build_array(build_array(0)));
// Set the current tab.
tab_bar->set_current_tab(1);
CHECK(tab_bar->get_current_tab() == 1);
CHECK(tab_bar->get_previous_tab() == 0);
SIGNAL_CHECK("tab_selected", build_array(build_array(1)));
SIGNAL_CHECK("tab_changed", build_array(build_array(1)));
// Set to same tab.
tab_bar->set_current_tab(1);
CHECK(tab_bar->get_current_tab() == 1);
CHECK(tab_bar->get_previous_tab() == 1);
SIGNAL_CHECK("tab_selected", build_array(build_array(1)));
SIGNAL_CHECK_FALSE("tab_changed");
// Out of bounds.
ERR_PRINT_OFF;
tab_bar->set_current_tab(-5);
CHECK(tab_bar->get_current_tab() == 1);
CHECK(tab_bar->get_previous_tab() == 1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
tab_bar->set_current_tab(5);
CHECK(tab_bar->get_current_tab() == 1);
CHECK(tab_bar->get_previous_tab() == 1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
ERR_PRINT_ON;
}
SUBCASE("[TabBar] deselection enabled") {
tab_bar->add_tab("tab0");
tab_bar->add_tab("tab1");
tab_bar->add_tab("tab2");
CHECK(tab_bar->get_current_tab() == 0);
CHECK(tab_bar->get_previous_tab() == -1);
SIGNAL_DISCARD("tab_selected");
SIGNAL_DISCARD("tab_changed");
// Setting deselect enabled doesn't change current tab.
tab_bar->set_deselect_enabled(true);
CHECK(tab_bar->get_deselect_enabled());
CHECK(tab_bar->get_current_tab() == 0);
CHECK(tab_bar->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// Can deselect all tabs by setting current to -1.
tab_bar->set_current_tab(-1);
CHECK(tab_bar->get_current_tab() == -1);
CHECK(tab_bar->get_previous_tab() == 0);
SIGNAL_CHECK("tab_selected", build_array(build_array(-1)));
SIGNAL_CHECK("tab_changed", build_array(build_array(-1)));
// Adding a tab will still set the current tab to 0.
tab_bar->clear_tabs();
CHECK(tab_bar->get_current_tab() == -1);
CHECK(tab_bar->get_previous_tab() == -1);
tab_bar->add_tab("tab0");
tab_bar->add_tab("tab1");
tab_bar->add_tab("tab2");
CHECK(tab_bar->get_tab_count() == 3);
CHECK(tab_bar->get_current_tab() == 0);
CHECK(tab_bar->get_previous_tab() == -1);
SIGNAL_CHECK("tab_selected", build_array(build_array(0)));
SIGNAL_CHECK("tab_changed", build_array(build_array(0)));
tab_bar->set_current_tab(-1);
SIGNAL_DISCARD("tab_selected");
SIGNAL_DISCARD("tab_changed");
// Disabling while at -1 will select the first available tab.
tab_bar->set_deselect_enabled(false);
CHECK_FALSE(tab_bar->get_deselect_enabled());
CHECK(tab_bar->get_current_tab() == 0);
CHECK(tab_bar->get_previous_tab() == -1);
SIGNAL_CHECK("tab_selected", build_array(build_array(0)));
SIGNAL_CHECK("tab_changed", build_array(build_array(0)));
// Cannot set to -1 if disabled.
ERR_PRINT_OFF;
tab_bar->set_current_tab(-1);
CHECK(tab_bar->get_current_tab() == 0);
CHECK(tab_bar->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
ERR_PRINT_ON;
// Disabling while at -1 skips any disabled or hidden tabs.
tab_bar->set_deselect_enabled(true);
tab_bar->set_tab_disabled(0, true);
tab_bar->set_tab_hidden(1, true);
tab_bar->set_current_tab(-1);
SIGNAL_DISCARD("tab_selected");
SIGNAL_DISCARD("tab_changed");
tab_bar->set_deselect_enabled(false);
CHECK(tab_bar->get_current_tab() == 2);
CHECK(tab_bar->get_previous_tab() == -1);
SIGNAL_CHECK("tab_selected", build_array(build_array(2)));
SIGNAL_CHECK("tab_changed", build_array(build_array(2)));
}
SUBCASE("[TabBar] hidden tabs") {
tab_bar->add_tab("tab0");
tab_bar->add_tab("tab1");
tab_bar->add_tab("tab2");
tab_bar->set_current_tab(1);
CHECK(tab_bar->get_current_tab() == 1);
CHECK(tab_bar->get_previous_tab() == 0);
CHECK_FALSE(tab_bar->is_tab_hidden(1));
SIGNAL_DISCARD("tab_selected");
SIGNAL_DISCARD("tab_changed");
MessageQueue::get_singleton()->flush();
Vector<Rect2> tab_rects = {
tab_bar->get_tab_rect(0),
tab_bar->get_tab_rect(1),
tab_bar->get_tab_rect(2)
};
// Hiding a tab does not affect current tab.
tab_bar->set_tab_hidden(1, true);
CHECK(tab_bar->is_tab_hidden(1));
CHECK(tab_bar->get_current_tab() == 1);
CHECK(tab_bar->get_previous_tab() == 0);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// The tabs after are moved over.
MessageQueue::get_singleton()->flush();
CHECK(tab_bar->get_tab_rect(0) == tab_rects[0]);
CHECK(tab_bar->get_tab_rect(2) == tab_rects[1]);
// Unhiding a tab does not affect current tab.
tab_bar->set_tab_hidden(1, false);
CHECK_FALSE(tab_bar->is_tab_hidden(1));
CHECK(tab_bar->get_current_tab() == 1);
CHECK(tab_bar->get_previous_tab() == 0);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// The tabs are back where they were.
MessageQueue::get_singleton()->flush();
CHECK(tab_bar->get_tab_rect(0) == tab_rects[0]);
CHECK(tab_bar->get_tab_rect(1) == tab_rects[1]);
CHECK(tab_bar->get_tab_rect(2) == tab_rects[2]);
}
SUBCASE("[TabBar] disabled tabs") {
tab_bar->add_tab("tab0");
tab_bar->add_tab("tab1");
tab_bar->add_tab("tab2");
CHECK_FALSE(tab_bar->is_tab_disabled(1));
tab_bar->set_current_tab(1);
CHECK(tab_bar->get_current_tab() == 1);
CHECK(tab_bar->get_previous_tab() == 0);
CHECK_FALSE(tab_bar->is_tab_hidden(1));
SIGNAL_DISCARD("tab_selected");
SIGNAL_DISCARD("tab_changed");
// Disabling a tab does not affect current tab.
tab_bar->set_tab_disabled(1, true);
CHECK(tab_bar->is_tab_disabled(1));
CHECK(tab_bar->get_current_tab() == 1);
CHECK(tab_bar->get_previous_tab() == 0);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// Enabling a tab does not affect current tab.
tab_bar->set_tab_disabled(1, false);
CHECK_FALSE(tab_bar->is_tab_disabled(1));
CHECK(tab_bar->get_current_tab() == 1);
CHECK(tab_bar->get_previous_tab() == 0);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
}
SUBCASE("[TabBar] select next available") {
tab_bar->add_tab("tab0");
tab_bar->add_tab("tab1");
tab_bar->add_tab("tab2");
tab_bar->add_tab("tab3");
tab_bar->add_tab("tab4");
tab_bar->set_tab_disabled(2, true);
tab_bar->set_tab_hidden(3, true);
tab_bar->set_current_tab(0);
CHECK(tab_bar->get_current_tab() == 0);
CHECK(tab_bar->get_previous_tab() == 0);
SIGNAL_DISCARD("tab_selected");
SIGNAL_DISCARD("tab_changed");
// Selects the next tab.
CHECK(tab_bar->select_next_available());
CHECK(tab_bar->get_current_tab() == 1);
CHECK(tab_bar->get_previous_tab() == 0);
SIGNAL_CHECK("tab_selected", build_array(build_array(1)));
SIGNAL_CHECK("tab_changed", build_array(build_array(1)));
// Skips over disabled and hidden tabs.
CHECK(tab_bar->select_next_available());
CHECK(tab_bar->get_current_tab() == 4);
CHECK(tab_bar->get_previous_tab() == 1);
SIGNAL_CHECK("tab_selected", build_array(build_array(4)));
SIGNAL_CHECK("tab_changed", build_array(build_array(4)));
// Does not wrap around.
CHECK_FALSE(tab_bar->select_next_available());
CHECK(tab_bar->get_current_tab() == 4);
CHECK(tab_bar->get_previous_tab() == 1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// Fails if there is only one valid tab.
tab_bar->remove_tab(0);
tab_bar->remove_tab(3);
CHECK(tab_bar->get_current_tab() == 0);
CHECK(tab_bar->get_previous_tab() == 0);
SIGNAL_DISCARD("tab_selected");
SIGNAL_DISCARD("tab_changed");
CHECK_FALSE(tab_bar->select_next_available());
CHECK(tab_bar->get_current_tab() == 0);
CHECK(tab_bar->get_previous_tab() == 0);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// Fails if there are no valid tabs.
tab_bar->remove_tab(0);
CHECK(tab_bar->get_current_tab() == -1);
CHECK(tab_bar->get_previous_tab() == 0);
SIGNAL_DISCARD("tab_selected");
SIGNAL_DISCARD("tab_changed");
CHECK_FALSE(tab_bar->select_next_available());
CHECK(tab_bar->get_current_tab() == -1);
CHECK(tab_bar->get_previous_tab() == 0);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// Fails if there are no tabs.
tab_bar->clear_tabs();
CHECK(tab_bar->get_current_tab() == -1);
CHECK(tab_bar->get_previous_tab() == -1);
SIGNAL_DISCARD("tab_selected");
SIGNAL_DISCARD("tab_changed");
CHECK_FALSE(tab_bar->select_next_available());
CHECK(tab_bar->get_current_tab() == -1);
CHECK(tab_bar->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
}
SUBCASE("[TabBar] select previous available") {
tab_bar->add_tab("tab0");
tab_bar->add_tab("tab1");
tab_bar->add_tab("tab2");
tab_bar->add_tab("tab3");
tab_bar->add_tab("tab4");
tab_bar->set_tab_disabled(1, true);
tab_bar->set_tab_hidden(2, true);
tab_bar->set_current_tab(4);
CHECK(tab_bar->get_current_tab() == 4);
CHECK(tab_bar->get_previous_tab() == 0);
SIGNAL_DISCARD("tab_selected");
SIGNAL_DISCARD("tab_changed");
// Selects the previous tab.
CHECK(tab_bar->select_previous_available());
CHECK(tab_bar->get_current_tab() == 3);
CHECK(tab_bar->get_previous_tab() == 4);
SIGNAL_CHECK("tab_selected", build_array(build_array(3)));
SIGNAL_CHECK("tab_changed", build_array(build_array(3)));
// Skips over disabled and hidden tabs.
CHECK(tab_bar->select_previous_available());
CHECK(tab_bar->get_current_tab() == 0);
CHECK(tab_bar->get_previous_tab() == 3);
SIGNAL_CHECK("tab_selected", build_array(build_array(0)));
SIGNAL_CHECK("tab_changed", build_array(build_array(0)));
// Does not wrap around.
CHECK_FALSE(tab_bar->select_previous_available());
CHECK(tab_bar->get_current_tab() == 0);
CHECK(tab_bar->get_previous_tab() == 3);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// Fails if there is only one valid tab.
tab_bar->remove_tab(4);
tab_bar->remove_tab(3);
CHECK(tab_bar->get_current_tab() == 0);
CHECK(tab_bar->get_previous_tab() == 2);
SIGNAL_DISCARD("tab_selected");
SIGNAL_DISCARD("tab_changed");
CHECK_FALSE(tab_bar->select_previous_available());
CHECK(tab_bar->get_current_tab() == 0);
CHECK(tab_bar->get_previous_tab() == 2);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// Fails if there are no valid tabs.
tab_bar->remove_tab(0);
CHECK(tab_bar->get_current_tab() == -1);
CHECK(tab_bar->get_previous_tab() == 1);
SIGNAL_DISCARD("tab_selected");
SIGNAL_DISCARD("tab_changed");
CHECK_FALSE(tab_bar->select_previous_available());
CHECK(tab_bar->get_current_tab() == -1);
CHECK(tab_bar->get_previous_tab() == 1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// Fails if there are no tabs.
tab_bar->clear_tabs();
CHECK(tab_bar->get_current_tab() == -1);
CHECK(tab_bar->get_previous_tab() == -1);
SIGNAL_DISCARD("tab_selected");
SIGNAL_DISCARD("tab_changed");
CHECK_FALSE(tab_bar->select_previous_available());
CHECK(tab_bar->get_current_tab() == -1);
CHECK(tab_bar->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
}
SIGNAL_UNWATCH(tab_bar, "tab_selected");
SIGNAL_UNWATCH(tab_bar, "tab_changed");
memdelete(tab_bar);
}
TEST_CASE("[SceneTree][TabBar] initialization") {
TabBar *tab_bar = memnew(TabBar);
SIGNAL_WATCH(tab_bar, "tab_selected");
SIGNAL_WATCH(tab_bar, "tab_changed");
SUBCASE("[TabBar] current tab can be set before tabs are set") {
// This queues the current tab to update on when tabs are set.
tab_bar->set_current_tab(1);
CHECK(tab_bar->get_current_tab() == -1);
CHECK(tab_bar->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
tab_bar->set_tab_count(2);
CHECK(tab_bar->get_tab_count() == 2);
CHECK(tab_bar->get_current_tab() == 1);
CHECK(tab_bar->get_previous_tab() == 0);
SIGNAL_CHECK("tab_selected", build_array(build_array(1)));
SIGNAL_CHECK("tab_changed", build_array(build_array(1)));
// Does not work again.
ERR_PRINT_OFF;
tab_bar->set_current_tab(2);
CHECK(tab_bar->get_tab_count() == 2);
CHECK(tab_bar->get_current_tab() == 1);
CHECK(tab_bar->get_previous_tab() == 0);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
tab_bar->set_tab_count(3);
CHECK(tab_bar->get_tab_count() == 3);
CHECK(tab_bar->get_current_tab() == 1);
CHECK(tab_bar->get_previous_tab() == 0);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
ERR_PRINT_ON;
}
SUBCASE("[TabBar] setting tabs works normally if no current tab was set") {
CHECK(tab_bar->get_current_tab() == -1);
CHECK(tab_bar->get_previous_tab() == -1);
tab_bar->set_tab_count(2);
CHECK(tab_bar->get_tab_count() == 2);
CHECK(tab_bar->get_current_tab() == 0);
CHECK(tab_bar->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
}
SUBCASE("[TabBar] cannot set current tab to an invalid value before tabs are set") {
tab_bar->set_current_tab(100);
CHECK(tab_bar->get_current_tab() == -1);
CHECK(tab_bar->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// This will print an error message as if `set_current_tab` was called after.
ERR_PRINT_OFF;
tab_bar->set_tab_count(2);
CHECK(tab_bar->get_tab_count() == 2);
CHECK(tab_bar->get_current_tab() == 0);
CHECK(tab_bar->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
ERR_PRINT_ON;
}
SUBCASE("[TabBar] setting the current tab before tabs only works when out of tree") {
tab_bar->set_current_tab(1);
CHECK(tab_bar->get_current_tab() == -1);
CHECK(tab_bar->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
SceneTree::get_singleton()->get_root()->add_child(tab_bar);
MessageQueue::get_singleton()->flush();
CHECK(tab_bar->get_tab_count() == 0);
CHECK(tab_bar->get_current_tab() == -1);
CHECK(tab_bar->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// Works normally.
tab_bar->set_tab_count(2);
CHECK(tab_bar->get_tab_count() == 2);
CHECK(tab_bar->get_current_tab() == 0);
CHECK(tab_bar->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
}
SIGNAL_UNWATCH(tab_bar, "tab_selected");
SIGNAL_UNWATCH(tab_bar, "tab_changed");
memdelete(tab_bar);
}
TEST_CASE("[SceneTree][TabBar] layout and offset") {
TabBar *tab_bar = memnew(TabBar);
SceneTree::get_singleton()->get_root()->add_child(tab_bar);
tab_bar->set_clip_tabs(false);
tab_bar->add_tab("tab0");
tab_bar->add_tab("tab1 ");
tab_bar->add_tab("tab2 ");
MessageQueue::get_singleton()->flush();
Size2 all_tabs_size = tab_bar->get_size();
Vector<Rect2> tab_rects = {
tab_bar->get_tab_rect(0),
tab_bar->get_tab_rect(1),
tab_bar->get_tab_rect(2)
};
SUBCASE("[TabBar] tabs are arranged next to each other") {
// Horizontal positions are next to each other.
CHECK(tab_rects[0].position.x == 0);
CHECK(tab_rects[1].position.x == tab_rects[0].size.x);
CHECK(tab_rects[2].position.x == tab_rects[1].position.x + tab_rects[1].size.x);
// Fills the entire width.
CHECK(tab_rects[2].position.x + tab_rects[2].size.x == all_tabs_size.x);
// Horizontal sizes are positive.
CHECK(tab_rects[0].size.x > 0);
CHECK(tab_rects[1].size.x > 0);
CHECK(tab_rects[2].size.x > 0);
// Vertical positions are at 0.
CHECK(tab_rects[0].position.y == 0);
CHECK(tab_rects[1].position.y == 0);
CHECK(tab_rects[2].position.y == 0);
// Vertical sizes are the same.
CHECK(tab_rects[0].size.y == tab_rects[1].size.y);
CHECK(tab_rects[1].size.y == tab_rects[2].size.y);
}
SUBCASE("[TabBar] tab alignment") {
// Add extra space so the alignment can be seen.
tab_bar->set_size(Size2(all_tabs_size.x + 100, all_tabs_size.y));
// Left alignment.
tab_bar->set_tab_alignment(TabBar::ALIGNMENT_LEFT);
MessageQueue::get_singleton()->flush();
tab_rects = {
tab_bar->get_tab_rect(0),
tab_bar->get_tab_rect(1),
tab_bar->get_tab_rect(2)
};
CHECK(tab_bar->get_tab_alignment() == TabBar::ALIGNMENT_LEFT);
CHECK(tab_rects[0].position.x == 0);
CHECK(tab_rects[1].position.x == tab_rects[0].size.x);
CHECK(tab_rects[2].position.x == tab_rects[1].position.x + tab_rects[1].size.x);
// Right alignment.
tab_bar->set_tab_alignment(TabBar::ALIGNMENT_RIGHT);
MessageQueue::get_singleton()->flush();
tab_rects = {
tab_bar->get_tab_rect(0),
tab_bar->get_tab_rect(1),
tab_bar->get_tab_rect(2)
};
CHECK(tab_bar->get_tab_alignment() == TabBar::ALIGNMENT_RIGHT);
CHECK(tab_rects[2].position.x == tab_bar->get_size().x - tab_rects[2].size.x);
CHECK(tab_rects[1].position.x == tab_rects[2].position.x - tab_rects[1].size.x);
CHECK(tab_rects[0].position.x == tab_rects[1].position.x - tab_rects[0].size.x);
// Center alignment.
tab_bar->set_tab_alignment(TabBar::ALIGNMENT_CENTER);
MessageQueue::get_singleton()->flush();
tab_rects = {
tab_bar->get_tab_rect(0),
tab_bar->get_tab_rect(1),
tab_bar->get_tab_rect(2)
};
CHECK(tab_bar->get_tab_alignment() == TabBar::ALIGNMENT_CENTER);
float center_pos = tab_bar->get_size().x / 2;
CHECK(tab_rects[0].position.x == center_pos - all_tabs_size.x / 2);
CHECK(tab_rects[1].position.x == tab_rects[0].position.x + tab_rects[0].size.x);
CHECK(tab_rects[2].position.x == tab_rects[1].position.x + tab_rects[1].size.x);
}
SUBCASE("[TabBar] clip tabs") {
// Clip tabs disabled means all tabs are visible and the minimum size holds all of them.
tab_bar->set_clip_tabs(false);
CHECK_FALSE(tab_bar->get_clip_tabs());
MessageQueue::get_singleton()->flush();
CHECK(tab_bar->get_tab_offset() == 0);
CHECK(tab_bar->get_minimum_size() == tab_bar->get_size());
CHECK(tab_bar->get_size().x == tab_rects[0].size.x + tab_rects[1].size.x + tab_rects[2].size.x);
CHECK(tab_bar->get_size().y == MAX(tab_rects[0].size.y, MAX(tab_rects[1].size.y, tab_rects[2].size.y)));
tab_bar->set_clip_tabs(true);
CHECK(tab_bar->get_clip_tabs());
MessageQueue::get_singleton()->flush();
CHECK(tab_bar->get_tab_offset() == 0);
// Horizontal size and minimum size get set to 0.
CHECK(tab_bar->get_minimum_size().x == 0);
CHECK(tab_bar->get_minimum_size().y == all_tabs_size.y);
CHECK(tab_bar->get_size().x == 0);
CHECK(tab_bar->get_size().y == all_tabs_size.y);
}
SUBCASE("[TabBar] ensure tab visible") {
tab_bar->set_scroll_to_selected(false);
tab_bar->set_clip_tabs(true);
// Resize tab bar to only be able to fit 2 tabs.
const float offset_button_size = tab_bar->get_theme_icon("decrement_icon")->get_width() + tab_bar->get_theme_icon("increment_icon")->get_width();
tab_bar->set_size(Size2(tab_rects[2].size.x + tab_rects[1].size.x + offset_button_size, all_tabs_size.y));
MessageQueue::get_singleton()->flush();
CHECK(tab_bar->get_tab_offset() == 0);
CHECK(tab_bar->get_offset_buttons_visible());
// Scroll right to a tab that is not visible.
tab_bar->ensure_tab_visible(2);
CHECK(tab_bar->get_tab_offset() == 1);
CHECK(tab_bar->get_tab_rect(1).position.x == 0);
CHECK(tab_bar->get_tab_rect(2).position.x == tab_rects[1].size.x);
tab_bar->set_tab_offset(2);
CHECK(tab_bar->get_tab_offset() == 2);
CHECK(tab_bar->get_tab_rect(2).position.x == 0);
// Scroll left to a previous tab.
tab_bar->ensure_tab_visible(1);
CHECK(tab_bar->get_tab_offset() == 1);
CHECK(tab_bar->get_tab_rect(1).position.x == 0);
CHECK(tab_bar->get_tab_rect(2).position.x == tab_rects[1].size.x);
// Will not scroll if the tab is already visible.
tab_bar->ensure_tab_visible(2);
CHECK(tab_bar->get_tab_offset() == 1);
CHECK(tab_bar->get_tab_rect(1).position.x == 0);
CHECK(tab_bar->get_tab_rect(2).position.x == tab_rects[1].size.x);
}
memdelete(tab_bar);
}
// FIXME: Add tests for mouse click, keyboard navigation, and drag and drop.
} // namespace TestTabBar
#endif // TEST_TAB_BAR_H

View file

@ -0,0 +1,671 @@
/**************************************************************************/
/* test_tab_container.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_TAB_CONTAINER_H
#define TEST_TAB_CONTAINER_H
#include "scene/gui/tab_container.h"
#include "tests/test_macros.h"
namespace TestTabContainer {
static inline Array build_array() {
return Array();
}
template <typename... Targs>
static inline Array build_array(Variant item, Targs... Fargs) {
Array a = build_array(Fargs...);
a.push_front(item);
return a;
}
TEST_CASE("[SceneTree][TabContainer] tab operations") {
TabContainer *tab_container = memnew(TabContainer);
SceneTree::get_singleton()->get_root()->add_child(tab_container);
MessageQueue::get_singleton()->flush();
SIGNAL_WATCH(tab_container, "tab_selected");
SIGNAL_WATCH(tab_container, "tab_changed");
Control *tab0 = memnew(Control);
tab0->set_name("tab0");
Control *tab1 = memnew(Control);
tab1->set_name("tab1");
Control *tab2 = memnew(Control);
tab2->set_name("tab2");
SUBCASE("[TabContainer] add tabs by adding children") {
CHECK(tab_container->get_tab_count() == 0);
CHECK(tab_container->get_current_tab() == -1);
CHECK(tab_container->get_previous_tab() == -1);
// Add first tab child.
tab_container->add_child(tab0);
// MessageQueue::get_singleton()->flush();
CHECK(tab_container->get_tab_count() == 1);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
SIGNAL_CHECK("tab_selected", build_array(build_array(0)));
SIGNAL_CHECK("tab_changed", build_array(build_array(0)));
// Add second tab child.
tab_container->add_child(tab1);
CHECK(tab_container->get_tab_count() == 2);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// Check default values, the title is the name of the child.
CHECK(tab_container->get_tab_control(0) == tab0);
CHECK(tab_container->get_tab_idx_from_control(tab0) == 0);
CHECK(tab_container->get_tab_title(0) == "tab0");
CHECK(tab_container->get_tab_tooltip(0) == "");
CHECK_FALSE(tab_container->is_tab_disabled(0));
CHECK_FALSE(tab_container->is_tab_hidden(0));
CHECK(tab_container->get_tab_control(1) == tab1);
CHECK(tab_container->get_tab_idx_from_control(tab1) == 1);
CHECK(tab_container->get_tab_title(1) == "tab1");
CHECK(tab_container->get_tab_tooltip(1) == "");
CHECK_FALSE(tab_container->is_tab_disabled(1));
CHECK_FALSE(tab_container->is_tab_hidden(1));
}
SUBCASE("[TabContainer] remove tabs by removing children") {
tab_container->add_child(tab0);
tab_container->add_child(tab1);
tab_container->add_child(tab2);
tab_container->set_current_tab(1);
CHECK(tab_container->get_tab_count() == 3);
CHECK(tab_container->get_current_tab() == 1);
CHECK(tab_container->get_previous_tab() == 0);
SIGNAL_DISCARD("tab_selected");
SIGNAL_DISCARD("tab_changed");
// Remove first tab.
tab_container->remove_child(tab0);
CHECK(tab_container->get_tab_count() == 2);
CHECK(tab_container->get_tab_title(0) == "tab1");
CHECK(tab_container->get_tab_title(1) == "tab2");
CHECK(tab_container->get_tab_idx_from_control(tab1) == 0);
CHECK(tab_container->get_tab_idx_from_control(tab2) == 1);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == 0);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// Remove last tab.
tab_container->remove_child(tab2);
CHECK(tab_container->get_tab_count() == 1);
CHECK(tab_container->get_tab_title(0) == "tab1");
CHECK(tab_container->get_tab_idx_from_control(tab1) == 0);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == 0);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// Remove only tab.
tab_container->remove_child(tab1);
CHECK(tab_container->get_tab_count() == 0);
CHECK(tab_container->get_current_tab() == -1);
CHECK(tab_container->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK("tab_changed", build_array(build_array(-1)));
// Remove current tab when there are other tabs.
tab_container->add_child(tab0);
tab_container->add_child(tab1);
tab_container->add_child(tab2);
tab_container->set_current_tab(1);
tab_container->set_current_tab(2);
CHECK(tab_container->get_tab_count() == 3);
CHECK(tab_container->get_current_tab() == 2);
CHECK(tab_container->get_previous_tab() == 1);
SIGNAL_DISCARD("tab_selected");
SIGNAL_DISCARD("tab_changed");
tab_container->remove_child(tab2);
CHECK(tab_container->get_tab_count() == 2);
CHECK(tab_container->get_current_tab() == 1);
CHECK(tab_container->get_previous_tab() == 1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK("tab_changed", build_array(build_array(1)));
}
SUBCASE("[TabContainer] move tabs by moving children") {
tab_container->add_child(tab0);
tab_container->add_child(tab1);
tab_container->add_child(tab2);
tab_container->set_current_tab(1);
CHECK(tab_container->get_current_tab() == 1);
CHECK(tab_container->get_previous_tab() == 0);
SIGNAL_DISCARD("tab_selected");
SIGNAL_DISCARD("tab_changed");
// Move the first tab to the end.
tab_container->move_child(tab0, 2);
CHECK(tab_container->get_tab_idx_from_control(tab0) == 2);
CHECK(tab_container->get_tab_idx_from_control(tab1) == 0);
CHECK(tab_container->get_tab_idx_from_control(tab2) == 1);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == 2);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// Move the second tab to the front.
tab_container->move_child(tab2, 0);
CHECK(tab_container->get_tab_idx_from_control(tab0) == 2);
CHECK(tab_container->get_tab_idx_from_control(tab1) == 1);
CHECK(tab_container->get_tab_idx_from_control(tab2) == 0);
CHECK(tab_container->get_current_tab() == 1);
CHECK(tab_container->get_previous_tab() == 2);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
}
SUBCASE("[TabContainer] set current tab") {
tab_container->add_child(tab0);
tab_container->add_child(tab1);
tab_container->add_child(tab2);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
SIGNAL_CHECK("tab_selected", build_array(build_array(0)));
SIGNAL_CHECK("tab_changed", build_array(build_array(0)));
MessageQueue::get_singleton()->flush();
CHECK(tab0->is_visible());
CHECK_FALSE(tab1->is_visible());
CHECK_FALSE(tab2->is_visible());
// Set the current tab.
tab_container->set_current_tab(1);
CHECK(tab_container->get_current_tab() == 1);
CHECK(tab_container->get_previous_tab() == 0);
SIGNAL_CHECK("tab_selected", build_array(build_array(1)));
SIGNAL_CHECK("tab_changed", build_array(build_array(1)));
MessageQueue::get_singleton()->flush();
CHECK_FALSE(tab0->is_visible());
CHECK(tab1->is_visible());
CHECK_FALSE(tab2->is_visible());
// Set to same tab.
tab_container->set_current_tab(1);
CHECK(tab_container->get_current_tab() == 1);
CHECK(tab_container->get_previous_tab() == 1);
SIGNAL_CHECK("tab_selected", build_array(build_array(1)));
SIGNAL_CHECK_FALSE("tab_changed");
MessageQueue::get_singleton()->flush();
CHECK_FALSE(tab0->is_visible());
CHECK(tab1->is_visible());
CHECK_FALSE(tab2->is_visible());
// Out of bounds.
ERR_PRINT_OFF;
tab_container->set_current_tab(-5);
CHECK(tab_container->get_current_tab() == 1);
CHECK(tab_container->get_previous_tab() == 1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
MessageQueue::get_singleton()->flush();
CHECK_FALSE(tab0->is_visible());
CHECK(tab1->is_visible());
CHECK_FALSE(tab2->is_visible());
tab_container->set_current_tab(5);
CHECK(tab_container->get_current_tab() == 1);
CHECK(tab_container->get_previous_tab() == 1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
MessageQueue::get_singleton()->flush();
CHECK_FALSE(tab0->is_visible());
CHECK(tab1->is_visible());
CHECK_FALSE(tab2->is_visible());
ERR_PRINT_ON;
}
SUBCASE("[TabContainer] change current tab by changing visibility of children") {
tab_container->add_child(tab0);
tab_container->add_child(tab1);
tab_container->add_child(tab2);
SIGNAL_DISCARD("tab_selected");
SIGNAL_DISCARD("tab_changed");
MessageQueue::get_singleton()->flush();
CHECK(tab0->is_visible());
CHECK_FALSE(tab1->is_visible());
CHECK_FALSE(tab2->is_visible());
// Show a child to make it the current tab.
tab1->show();
CHECK(tab_container->get_current_tab() == 1);
CHECK(tab_container->get_previous_tab() == 0);
SIGNAL_CHECK("tab_selected", build_array(build_array(1)));
SIGNAL_CHECK("tab_changed", build_array(build_array(1)));
MessageQueue::get_singleton()->flush();
CHECK_FALSE(tab0->is_visible());
CHECK(tab1->is_visible());
CHECK_FALSE(tab2->is_visible());
// Hide the visible child to select the next tab.
tab1->hide();
CHECK(tab_container->get_current_tab() == 2);
CHECK(tab_container->get_previous_tab() == 1);
SIGNAL_CHECK("tab_selected", build_array(build_array(2)));
SIGNAL_CHECK("tab_changed", build_array(build_array(2)));
MessageQueue::get_singleton()->flush();
CHECK_FALSE(tab0->is_visible());
CHECK_FALSE(tab1->is_visible());
CHECK(tab2->is_visible());
// Hide the visible child to select the previous tab if there is no next.
tab2->hide();
CHECK(tab_container->get_current_tab() == 1);
CHECK(tab_container->get_previous_tab() == 2);
SIGNAL_CHECK("tab_selected", build_array(build_array(1)));
SIGNAL_CHECK("tab_changed", build_array(build_array(1)));
MessageQueue::get_singleton()->flush();
CHECK_FALSE(tab0->is_visible());
CHECK(tab1->is_visible());
CHECK_FALSE(tab2->is_visible());
// Cannot hide if there is only one valid child since deselection is not enabled.
tab_container->remove_child(tab1);
tab_container->remove_child(tab2);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == 0);
SIGNAL_DISCARD("tab_selected");
SIGNAL_DISCARD("tab_changed");
MessageQueue::get_singleton()->flush();
CHECK(tab0->is_visible());
tab0->hide();
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == 0);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
MessageQueue::get_singleton()->flush();
CHECK(tab0->is_visible());
// Can hide the last tab if deselection is enabled.
tab_container->set_deselect_enabled(true);
tab0->hide();
CHECK(tab_container->get_current_tab() == -1);
CHECK(tab_container->get_previous_tab() == 0);
SIGNAL_CHECK("tab_selected", build_array(build_array(-1)));
SIGNAL_CHECK("tab_changed", build_array(build_array(-1)));
MessageQueue::get_singleton()->flush();
CHECK_FALSE(tab0->is_visible());
}
SIGNAL_UNWATCH(tab_container, "tab_selected");
SIGNAL_UNWATCH(tab_container, "tab_changed");
memdelete(tab2);
memdelete(tab1);
memdelete(tab0);
memdelete(tab_container);
}
TEST_CASE("[SceneTree][TabContainer] initialization") {
TabContainer *tab_container = memnew(TabContainer);
Control *tab0 = memnew(Control);
tab0->set_name("tab0");
Control *tab1 = memnew(Control);
tab1->set_name("tab1 ");
Control *tab2 = memnew(Control);
tab2->set_name("tab2 ");
SIGNAL_WATCH(tab_container, "tab_selected");
SIGNAL_WATCH(tab_container, "tab_changed");
SUBCASE("[TabContainer] add children before entering tree") {
CHECK(tab_container->get_current_tab() == -1);
CHECK(tab_container->get_previous_tab() == -1);
tab_container->add_child(tab0);
CHECK(tab_container->get_tab_count() == 1);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
tab_container->add_child(tab1);
CHECK(tab_container->get_tab_count() == 2);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
SceneTree::get_singleton()->get_root()->add_child(tab_container);
MessageQueue::get_singleton()->flush();
CHECK(tab_container->get_tab_count() == 2);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
CHECK(tab0->is_visible());
CHECK_FALSE(tab1->is_visible());
}
SUBCASE("[TabContainer] current tab can be set before children are added") {
// Set the current tab before there are any tabs.
// This queues the current tab to update on entering the tree.
tab_container->set_current_tab(1);
CHECK(tab_container->get_current_tab() == -1);
CHECK(tab_container->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
tab_container->add_child(tab0);
CHECK(tab_container->get_tab_count() == 1);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
tab_container->add_child(tab1);
CHECK(tab_container->get_tab_count() == 2);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
tab_container->add_child(tab2);
CHECK(tab_container->get_tab_count() == 3);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// Current tab is set when entering the tree.
SceneTree::get_singleton()->get_root()->add_child(tab_container);
MessageQueue::get_singleton()->flush();
CHECK(tab_container->get_tab_count() == 3);
CHECK(tab_container->get_current_tab() == 1);
CHECK(tab_container->get_previous_tab() == 0);
SIGNAL_CHECK("tab_selected", build_array(build_array(1)));
SIGNAL_CHECK("tab_changed", build_array(build_array(1)));
CHECK_FALSE(tab0->is_visible());
CHECK(tab1->is_visible());
CHECK_FALSE(tab2->is_visible());
}
SUBCASE("[TabContainer] cannot set current tab to an invalid value before tabs are set") {
tab_container->set_current_tab(100);
CHECK(tab_container->get_current_tab() == -1);
CHECK(tab_container->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
tab_container->add_child(tab0);
CHECK(tab_container->get_tab_count() == 1);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
tab_container->add_child(tab1);
CHECK(tab_container->get_tab_count() == 2);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// This will print an error message as if `set_current_tab` was called after.
ERR_PRINT_OFF;
SceneTree::get_singleton()->get_root()->add_child(tab_container);
MessageQueue::get_singleton()->flush();
CHECK(tab_container->get_tab_count() == 2);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
ERR_PRINT_ON;
}
SUBCASE("[TabContainer] children visibility before entering tree") {
CHECK(tab_container->get_current_tab() == -1);
CHECK(tab_container->get_previous_tab() == -1);
// Adding a hidden child first will change visibility because it is the current tab.
tab0->hide();
tab_container->add_child(tab0);
CHECK(tab_container->get_tab_count() == 1);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
MessageQueue::get_singleton()->flush();
CHECK(tab0->is_visible());
// Adding a visible child after will hide it because it is not the current tab.
tab_container->add_child(tab1);
CHECK(tab_container->get_tab_count() == 2);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
MessageQueue::get_singleton()->flush();
CHECK(tab0->is_visible());
CHECK_FALSE(tab1->is_visible());
// Can change current by showing child now after children have been added.
// This queues the current tab to update on entering the tree.
tab1->show();
MessageQueue::get_singleton()->flush();
CHECK(tab_container->get_tab_count() == 2);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
CHECK(tab0->is_visible());
CHECK(tab1->is_visible());
SceneTree::get_singleton()->get_root()->add_child(tab_container);
MessageQueue::get_singleton()->flush();
CHECK(tab_container->get_tab_count() == 2);
CHECK(tab_container->get_current_tab() == 1);
CHECK(tab_container->get_previous_tab() == 0);
SIGNAL_CHECK("tab_selected", build_array(build_array(1)));
SIGNAL_CHECK("tab_changed", build_array(build_array(1)));
CHECK_FALSE(tab0->is_visible());
CHECK(tab1->is_visible());
}
SUBCASE("[TabContainer] setting current tab and changing child visibility after adding") {
tab_container->add_child(tab0);
tab_container->add_child(tab1);
tab_container->add_child(tab2);
MessageQueue::get_singleton()->flush();
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
tab2->show();
MessageQueue::get_singleton()->flush();
CHECK(tab_container->get_tab_count() == 3);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
CHECK(tab0->is_visible());
CHECK_FALSE(tab1->is_visible());
CHECK(tab2->is_visible());
// Whichever happens last will have priority.
tab_container->set_current_tab(1);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
// Current tab is set when entering the tree.
SceneTree::get_singleton()->get_root()->add_child(tab_container);
MessageQueue::get_singleton()->flush();
CHECK(tab_container->get_tab_count() == 3);
CHECK(tab_container->get_current_tab() == 1);
CHECK(tab_container->get_previous_tab() == 0);
SIGNAL_CHECK("tab_selected", build_array(build_array(1)));
SIGNAL_CHECK("tab_changed", build_array(build_array(1)));
CHECK_FALSE(tab0->is_visible());
CHECK(tab1->is_visible());
CHECK_FALSE(tab2->is_visible());
}
SIGNAL_UNWATCH(tab_container, "tab_selected");
SIGNAL_UNWATCH(tab_container, "tab_changed");
memdelete(tab2);
memdelete(tab1);
memdelete(tab0);
memdelete(tab_container);
}
TEST_CASE("[SceneTree][TabContainer] layout and offset") {
TabContainer *tab_container = memnew(TabContainer);
SceneTree::get_singleton()->get_root()->add_child(tab_container);
tab_container->set_clip_tabs(false);
Control *tab0 = memnew(Control);
tab0->set_name("tab0");
Control *tab1 = memnew(Control);
tab1->set_name("tab1 ");
Control *tab2 = memnew(Control);
tab2->set_name("tab2 ");
tab_container->add_child(tab0);
tab_container->add_child(tab1);
tab_container->add_child(tab2);
MessageQueue::get_singleton()->flush();
Size2 all_tabs_size = tab_container->get_size();
const float side_margin = tab_container->get_theme_constant("side_margin");
TabBar *tab_bar = tab_container->get_tab_bar();
Vector<Rect2> tab_rects = {
tab_bar->get_tab_rect(0),
tab_bar->get_tab_rect(1),
tab_bar->get_tab_rect(2)
};
SUBCASE("[TabContainer] tabs are arranged next to each other") {
// Horizontal positions are next to each other.
CHECK(tab_rects[0].position.x == 0);
CHECK(tab_rects[1].position.x == tab_rects[0].size.x);
CHECK(tab_rects[2].position.x == tab_rects[1].position.x + tab_rects[1].size.x);
// Fills the entire width.
CHECK(tab_rects[2].position.x + tab_rects[2].size.x == all_tabs_size.x - side_margin);
// Horizontal sizes are positive.
CHECK(tab_rects[0].size.x > 0);
CHECK(tab_rects[1].size.x > 0);
CHECK(tab_rects[2].size.x > 0);
// Vertical positions are at 0.
CHECK(tab_rects[0].position.y == 0);
CHECK(tab_rects[1].position.y == 0);
CHECK(tab_rects[2].position.y == 0);
// Vertical sizes are the same.
CHECK(tab_rects[0].size.y == tab_rects[1].size.y);
CHECK(tab_rects[1].size.y == tab_rects[2].size.y);
}
SUBCASE("[TabContainer] tab position") {
float tab_height = tab_rects[0].size.y;
Ref<StyleBox> panel_style = tab_container->get_theme_stylebox("panel_style");
// Initial position, same as top position.
// Tab bar is at the top.
CHECK(tab_bar->get_anchor(SIDE_TOP) == 0);
CHECK(tab_bar->get_anchor(SIDE_BOTTOM) == 0);
CHECK(tab_bar->get_anchor(SIDE_LEFT) == 0);
CHECK(tab_bar->get_anchor(SIDE_RIGHT) == 1);
CHECK(tab_bar->get_offset(SIDE_TOP) == 0);
CHECK(tab_bar->get_offset(SIDE_BOTTOM) == tab_height);
CHECK(tab_bar->get_offset(SIDE_LEFT) == side_margin);
CHECK(tab_bar->get_offset(SIDE_RIGHT) == 0);
// Child is expanded and below the tab bar.
CHECK(tab0->get_anchor(SIDE_TOP) == 0);
CHECK(tab0->get_anchor(SIDE_BOTTOM) == 1);
CHECK(tab0->get_anchor(SIDE_LEFT) == 0);
CHECK(tab0->get_anchor(SIDE_RIGHT) == 1);
CHECK(tab0->get_offset(SIDE_TOP) == tab_height);
CHECK(tab0->get_offset(SIDE_BOTTOM) == 0);
CHECK(tab0->get_offset(SIDE_LEFT) == 0);
CHECK(tab0->get_offset(SIDE_RIGHT) == 0);
// Bottom position.
tab_container->set_tabs_position(TabContainer::POSITION_BOTTOM);
CHECK(tab_container->get_tabs_position() == TabContainer::POSITION_BOTTOM);
MessageQueue::get_singleton()->flush();
// Tab bar is at the bottom.
CHECK(tab_bar->get_anchor(SIDE_TOP) == 1);
CHECK(tab_bar->get_anchor(SIDE_BOTTOM) == 1);
CHECK(tab_bar->get_anchor(SIDE_LEFT) == 0);
CHECK(tab_bar->get_anchor(SIDE_RIGHT) == 1);
CHECK(tab_bar->get_offset(SIDE_TOP) == -tab_height);
CHECK(tab_bar->get_offset(SIDE_BOTTOM) == 0);
CHECK(tab_bar->get_offset(SIDE_LEFT) == side_margin);
CHECK(tab_bar->get_offset(SIDE_RIGHT) == 0);
// Child is expanded and above the tab bar.
CHECK(tab0->get_anchor(SIDE_TOP) == 0);
CHECK(tab0->get_anchor(SIDE_BOTTOM) == 1);
CHECK(tab0->get_anchor(SIDE_LEFT) == 0);
CHECK(tab0->get_anchor(SIDE_RIGHT) == 1);
CHECK(tab0->get_offset(SIDE_TOP) == 0);
CHECK(tab0->get_offset(SIDE_BOTTOM) == -tab_height);
CHECK(tab0->get_offset(SIDE_LEFT) == 0);
CHECK(tab0->get_offset(SIDE_RIGHT) == 0);
// Top position.
tab_container->set_tabs_position(TabContainer::POSITION_TOP);
CHECK(tab_container->get_tabs_position() == TabContainer::POSITION_TOP);
MessageQueue::get_singleton()->flush();
// Tab bar is at the top.
CHECK(tab_bar->get_anchor(SIDE_TOP) == 0);
CHECK(tab_bar->get_anchor(SIDE_BOTTOM) == 0);
CHECK(tab_bar->get_anchor(SIDE_LEFT) == 0);
CHECK(tab_bar->get_anchor(SIDE_RIGHT) == 1);
CHECK(tab_bar->get_offset(SIDE_TOP) == 0);
CHECK(tab_bar->get_offset(SIDE_BOTTOM) == tab_height);
CHECK(tab_bar->get_offset(SIDE_LEFT) == side_margin);
CHECK(tab_bar->get_offset(SIDE_RIGHT) == 0);
// Child is expanded and below the tab bar.
CHECK(tab0->get_anchor(SIDE_TOP) == 0);
CHECK(tab0->get_anchor(SIDE_BOTTOM) == 1);
CHECK(tab0->get_anchor(SIDE_LEFT) == 0);
CHECK(tab0->get_anchor(SIDE_RIGHT) == 1);
CHECK(tab0->get_offset(SIDE_TOP) == tab_height);
CHECK(tab0->get_offset(SIDE_BOTTOM) == 0);
CHECK(tab0->get_offset(SIDE_LEFT) == 0);
CHECK(tab0->get_offset(SIDE_RIGHT) == 0);
}
memdelete(tab_container);
}
// FIXME: Add tests for mouse click, keyboard navigation, and drag and drop.
} // namespace TestTabContainer
#endif // TEST_TAB_CONTAINER_H

View file

@ -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") {
@ -3563,6 +3585,54 @@ TEST_CASE("[SceneTree][TextEdit] text entry") {
SIGNAL_CHECK_FALSE("lines_edited_from");
}
SUBCASE("[TextEdit] cut when empty selection clipboard disabled") {
text_edit->set_empty_selection_clipboard_enabled(false);
DS->clipboard_set("");
text_edit->set_text("this is\nsome\n");
text_edit->set_caret_line(0);
text_edit->set_caret_column(6);
MessageQueue::get_singleton()->flush();
SIGNAL_DISCARD("text_set");
SIGNAL_DISCARD("text_changed");
SIGNAL_DISCARD("lines_edited_from");
SIGNAL_DISCARD("caret_changed");
text_edit->cut();
MessageQueue::get_singleton()->flush();
CHECK(DS->clipboard_get() == "");
CHECK(text_edit->get_text() == "this is\nsome\n");
CHECK(text_edit->get_caret_line() == 0);
CHECK(text_edit->get_caret_column() == 6);
SIGNAL_CHECK_FALSE("caret_changed");
SIGNAL_CHECK_FALSE("text_changed");
SIGNAL_CHECK_FALSE("lines_edited_from");
}
SUBCASE("[TextEdit] copy when empty selection clipboard disabled") {
text_edit->set_empty_selection_clipboard_enabled(false);
DS->clipboard_set("");
text_edit->set_text("this is\nsome\n");
text_edit->set_caret_line(0);
text_edit->set_caret_column(6);
MessageQueue::get_singleton()->flush();
SIGNAL_DISCARD("text_set");
SIGNAL_DISCARD("text_changed");
SIGNAL_DISCARD("lines_edited_from");
SIGNAL_DISCARD("caret_changed");
text_edit->copy();
MessageQueue::get_singleton()->flush();
CHECK(DS->clipboard_get() == "");
CHECK(text_edit->get_text() == "this is\nsome\n");
CHECK(text_edit->get_caret_line() == 0);
CHECK(text_edit->get_caret_column() == 6);
SIGNAL_CHECK_FALSE("caret_changed");
SIGNAL_CHECK_FALSE("text_changed");
SIGNAL_CHECK_FALSE("lines_edited_from");
}
SIGNAL_UNWATCH(text_edit, "text_set");
SIGNAL_UNWATCH(text_edit, "text_changed");
SIGNAL_UNWATCH(text_edit, "lines_edited_from");
@ -4210,6 +4280,18 @@ TEST_CASE("[SceneTree][TextEdit] text entry") {
CHECK(text_edit->get_caret_line(0) == 0);
CHECK(text_edit->get_caret_column(0) == 4);
text_edit->remove_secondary_carets();
// Remove when there are no words, only symbols.
text_edit->set_text("#{}");
text_edit->set_caret_line(0);
text_edit->set_caret_column(3);
SEND_GUI_ACTION("ui_text_backspace_word");
CHECK(text_edit->get_viewport()->is_input_handled());
CHECK_FALSE(text_edit->has_selection());
CHECK(text_edit->get_text() == "");
CHECK(text_edit->get_caret_line(0) == 0);
CHECK(text_edit->get_caret_column(0) == 0);
}
SUBCASE("[TextEdit] ui_text_backspace_word same line") {
@ -4869,6 +4951,18 @@ TEST_CASE("[SceneTree][TextEdit] text entry") {
CHECK(text_edit->get_caret_line(0) == 0);
CHECK(text_edit->get_caret_column(0) == 2);
text_edit->remove_secondary_carets();
// Remove when there are no words, only symbols.
text_edit->set_text("#{}");
text_edit->set_caret_line(0);
text_edit->set_caret_column(0);
SEND_GUI_ACTION("ui_text_delete_word");
CHECK(text_edit->get_viewport()->is_input_handled());
CHECK_FALSE(text_edit->has_selection());
CHECK(text_edit->get_text() == "");
CHECK(text_edit->get_caret_line(0) == 0);
CHECK(text_edit->get_caret_column(0) == 0);
}
SUBCASE("[TextEdit] ui_text_delete_word same line") {
@ -5279,6 +5373,16 @@ TEST_CASE("[SceneTree][TextEdit] text entry") {
SIGNAL_CHECK("caret_changed", empty_signal_args);
SIGNAL_CHECK_FALSE("text_changed");
SIGNAL_CHECK_FALSE("lines_edited_from");
// Move when there are no words, only symbols.
text_edit->set_text("#{}");
text_edit->set_caret_line(0);
text_edit->set_caret_column(3);
SEND_GUI_ACTION("ui_text_caret_word_left");
CHECK(text_edit->get_viewport()->is_input_handled());
CHECK(text_edit->get_caret_line(0) == 0);
CHECK(text_edit->get_caret_column(0) == 0);
}
SUBCASE("[TextEdit] ui_text_caret_left") {
@ -5541,6 +5645,16 @@ TEST_CASE("[SceneTree][TextEdit] text entry") {
SIGNAL_CHECK("caret_changed", empty_signal_args);
SIGNAL_CHECK_FALSE("text_changed");
SIGNAL_CHECK_FALSE("lines_edited_from");
// Move when there are no words, only symbols.
text_edit->set_text("#{}");
text_edit->set_caret_line(0);
text_edit->set_caret_column(0);
SEND_GUI_ACTION("ui_text_caret_word_right");
CHECK(text_edit->get_viewport()->is_input_handled());
CHECK(text_edit->get_caret_line(0) == 0);
CHECK(text_edit->get_caret_column(0) == 3);
}
SUBCASE("[TextEdit] ui_text_caret_right") {
@ -5713,6 +5827,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 +5877,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 +5892,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 +5924,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 +5974,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 +5989,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 +7312,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 +7321,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();
@ -8001,6 +8151,93 @@ TEST_CASE("[SceneTree][TextEdit] gutters") {
// Merging tested via CodeEdit gutters.
}
SUBCASE("[TextEdit] gutter mouse") {
DisplayServerMock *DS = (DisplayServerMock *)(DisplayServer::get_singleton());
// Set size for mouse input.
text_edit->set_size(Size2(200, 200));
text_edit->set_text("test1\ntest2\ntest3\ntest4");
text_edit->grab_focus();
text_edit->add_gutter();
text_edit->set_gutter_name(0, "test_gutter");
text_edit->set_gutter_width(0, 10);
text_edit->set_gutter_clickable(0, true);
text_edit->add_gutter();
text_edit->set_gutter_name(1, "test_gutter_not_clickable");
text_edit->set_gutter_width(1, 10);
text_edit->set_gutter_clickable(1, false);
text_edit->add_gutter();
CHECK(text_edit->get_gutter_count() == 3);
text_edit->set_gutter_name(2, "test_gutter_3");
text_edit->set_gutter_width(2, 10);
text_edit->set_gutter_clickable(2, true);
MessageQueue::get_singleton()->flush();
const int line_height = text_edit->get_line_height();
// Defaults to none.
CHECK(text_edit->get_hovered_gutter() == Vector2i(-1, -1));
CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_ARROW);
// Hover over gutter.
SEND_GUI_MOUSE_MOTION_EVENT(Point2(5, line_height + line_height / 2), MouseButtonMask::NONE, Key::NONE);
CHECK(text_edit->get_hovered_gutter() == Vector2i(0, 1));
SIGNAL_CHECK_FALSE("gutter_clicked");
CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_POINTING_HAND);
// Click on gutter.
SEND_GUI_MOUSE_BUTTON_EVENT(Point2(5, line_height / 2), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
CHECK(text_edit->get_hovered_gutter() == Vector2i(0, 0));
SIGNAL_CHECK("gutter_clicked", build_array(build_array(0, 0)));
// Click on gutter on another line.
SEND_GUI_MOUSE_BUTTON_EVENT(Point2(5, line_height * 3 + line_height / 2), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
CHECK(text_edit->get_hovered_gutter() == Vector2i(0, 3));
SIGNAL_CHECK("gutter_clicked", build_array(build_array(3, 0)));
// Unclickable gutter can be hovered.
SEND_GUI_MOUSE_MOTION_EVENT(Point2(15, line_height + line_height / 2), MouseButtonMask::NONE, Key::NONE);
CHECK(text_edit->get_hovered_gutter() == Vector2i(1, 1));
SIGNAL_CHECK_FALSE("gutter_clicked");
CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_ARROW);
// Unclickable gutter can be clicked.
SEND_GUI_MOUSE_BUTTON_EVENT(Point2(15, line_height * 2 + line_height / 2), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
CHECK(text_edit->get_hovered_gutter() == Vector2i(1, 2));
SIGNAL_CHECK("gutter_clicked", build_array(build_array(2, 1)));
CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_ARROW);
// Hover past last line.
SEND_GUI_MOUSE_MOTION_EVENT(Point2(5, line_height * 5), MouseButtonMask::NONE, Key::NONE);
CHECK(text_edit->get_hovered_gutter() == Vector2i(-1, -1));
SIGNAL_CHECK_FALSE("gutter_clicked");
CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_ARROW);
// Click on gutter past last line.
SEND_GUI_MOUSE_BUTTON_EVENT(Point2(5, line_height * 5), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
CHECK(text_edit->get_hovered_gutter() == Vector2i(-1, -1));
SIGNAL_CHECK_FALSE("gutter_clicked");
// Mouse exit resets hover.
SEND_GUI_MOUSE_MOTION_EVENT(Point2(5, line_height + line_height / 2), MouseButtonMask::NONE, Key::NONE);
CHECK(text_edit->get_hovered_gutter() == Vector2i(0, 1));
SEND_GUI_MOUSE_MOTION_EVENT(Point2(-1, -1), MouseButtonMask::NONE, Key::NONE);
CHECK(text_edit->get_hovered_gutter() == Vector2i(-1, -1));
// Removing gutter updates hover.
SEND_GUI_MOUSE_MOTION_EVENT(Point2(25, line_height + line_height / 2), MouseButtonMask::NONE, Key::NONE);
CHECK(text_edit->get_hovered_gutter() == Vector2i(2, 1));
text_edit->remove_gutter(2);
CHECK(text_edit->get_hovered_gutter() == Vector2i(-1, -1));
// Updating size updates hover.
text_edit->set_gutter_width(1, 20);
CHECK(text_edit->get_hovered_gutter() == Vector2i(1, 1));
}
SIGNAL_UNWATCH(text_edit, "gutter_clicked");
SIGNAL_UNWATCH(text_edit, "gutter_added");
SIGNAL_UNWATCH(text_edit, "gutter_removed");

View file

@ -0,0 +1,92 @@
/**************************************************************************/
/* test_texture_progress_bar.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_TEXTURE_PROGRESS_BAR_H
#define TEST_TEXTURE_PROGRESS_BAR_H
#include "scene/gui/texture_progress_bar.h"
#include "tests/test_macros.h"
namespace TestTextureProgressBar {
TEST_CASE("[SceneTree][TextureProgressBar]") {
TextureProgressBar *texture_progress_bar = memnew(TextureProgressBar);
SUBCASE("[TextureProgressBar] set_radial_initial_angle() should wrap angle between 0 and 360 degrees (inclusive).") {
texture_progress_bar->set_radial_initial_angle(0.0);
CHECK(Math::is_equal_approx(texture_progress_bar->get_radial_initial_angle(), (float)0.0));
texture_progress_bar->set_radial_initial_angle(360.0);
CHECK(Math::is_equal_approx(texture_progress_bar->get_radial_initial_angle(), (float)360.0));
texture_progress_bar->set_radial_initial_angle(30.5);
CHECK(Math::is_equal_approx(texture_progress_bar->get_radial_initial_angle(), (float)30.5));
texture_progress_bar->set_radial_initial_angle(-30.5);
CHECK(Math::is_equal_approx(texture_progress_bar->get_radial_initial_angle(), (float)(360 - 30.5)));
texture_progress_bar->set_radial_initial_angle(36000 + 30.5);
CHECK(Math::is_equal_approx(texture_progress_bar->get_radial_initial_angle(), (float)30.5));
texture_progress_bar->set_radial_initial_angle(-(36000 + 30.5));
CHECK(Math::is_equal_approx(texture_progress_bar->get_radial_initial_angle(), (float)(360 - 30.5)));
}
SUBCASE("[TextureProgressBar] set_radial_initial_angle() should not set non-finite values.") {
texture_progress_bar->set_radial_initial_angle(30.5);
ERR_PRINT_OFF;
texture_progress_bar->set_radial_initial_angle(INFINITY);
ERR_PRINT_ON;
CHECK(Math::is_equal_approx(texture_progress_bar->get_radial_initial_angle(), (float)30.5));
ERR_PRINT_OFF;
texture_progress_bar->set_radial_initial_angle(-INFINITY);
ERR_PRINT_ON;
CHECK(Math::is_equal_approx(texture_progress_bar->get_radial_initial_angle(), (float)30.5));
ERR_PRINT_OFF;
texture_progress_bar->set_radial_initial_angle(NAN);
ERR_PRINT_ON;
CHECK(Math::is_equal_approx(texture_progress_bar->get_radial_initial_angle(), (float)30.5));
ERR_PRINT_OFF;
texture_progress_bar->set_radial_initial_angle(-NAN);
ERR_PRINT_ON;
CHECK(Math::is_equal_approx(texture_progress_bar->get_radial_initial_angle(), (float)30.5));
}
memdelete(texture_progress_bar);
}
} // namespace TestTextureProgressBar
#endif // TEST_TEXTURE_PROGRESS_BAR_H

View file

@ -0,0 +1,301 @@
/**************************************************************************/
/* test_tree.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_TREE_H
#define TEST_TREE_H
#include "scene/gui/tree.h"
#include "tests/test_macros.h"
namespace TestTree {
TEST_CASE("[SceneTree][Tree]") {
SUBCASE("[Tree] Create and remove items.") {
Tree *tree = memnew(Tree);
TreeItem *root = tree->create_item();
TreeItem *child1 = tree->create_item();
CHECK_EQ(root->get_child_count(), 1);
TreeItem *child2 = tree->create_item(root);
CHECK_EQ(root->get_child_count(), 2);
TreeItem *child3 = tree->create_item(root, 0);
CHECK_EQ(root->get_child_count(), 3);
CHECK_EQ(root->get_child(0), child3);
CHECK_EQ(root->get_child(1), child1);
CHECK_EQ(root->get_child(2), child2);
root->remove_child(child3);
CHECK_EQ(root->get_child_count(), 2);
root->add_child(child3);
CHECK_EQ(root->get_child_count(), 3);
TreeItem *child4 = root->create_child();
CHECK_EQ(root->get_child_count(), 4);
CHECK_EQ(root->get_child(0), child1);
CHECK_EQ(root->get_child(1), child2);
CHECK_EQ(root->get_child(2), child3);
CHECK_EQ(root->get_child(3), child4);
memdelete(tree);
}
SUBCASE("[Tree] Clear items.") {
Tree *tree = memnew(Tree);
TreeItem *root = tree->create_item();
for (int i = 0; i < 10; i++) {
tree->create_item();
}
CHECK_EQ(root->get_child_count(), 10);
root->clear_children();
CHECK_EQ(root->get_child_count(), 0);
memdelete(tree);
}
SUBCASE("[Tree] Get last item.") {
Tree *tree = memnew(Tree);
TreeItem *root = tree->create_item();
TreeItem *last;
for (int i = 0; i < 10; i++) {
last = tree->create_item();
}
CHECK_EQ(root->get_child_count(), 10);
CHECK_EQ(tree->get_last_item(), last);
// Check nested.
TreeItem *old_last = last;
for (int i = 0; i < 10; i++) {
last = tree->create_item(old_last);
}
CHECK_EQ(tree->get_last_item(), last);
memdelete(tree);
}
// https://github.com/godotengine/godot/issues/96205
SUBCASE("[Tree] Get last item after removal.") {
Tree *tree = memnew(Tree);
TreeItem *root = tree->create_item();
TreeItem *child1 = tree->create_item(root);
TreeItem *child2 = tree->create_item(root);
CHECK_EQ(root->get_child_count(), 2);
CHECK_EQ(tree->get_last_item(), child2);
root->remove_child(child2);
CHECK_EQ(root->get_child_count(), 1);
CHECK_EQ(tree->get_last_item(), child1);
root->add_child(child2);
CHECK_EQ(root->get_child_count(), 2);
CHECK_EQ(tree->get_last_item(), child2);
memdelete(tree);
}
SUBCASE("[Tree] Previous and Next items.") {
Tree *tree = memnew(Tree);
TreeItem *root = tree->create_item();
TreeItem *child1 = tree->create_item();
TreeItem *child2 = tree->create_item();
TreeItem *child3 = tree->create_item();
CHECK_EQ(root->get_next(), nullptr);
CHECK_EQ(root->get_next_visible(), child1);
CHECK_EQ(root->get_next_in_tree(), child1);
CHECK_EQ(child1->get_next(), child2);
CHECK_EQ(child1->get_next_visible(), child2);
CHECK_EQ(child1->get_next_in_tree(), child2);
CHECK_EQ(child2->get_next(), child3);
CHECK_EQ(child2->get_next_visible(), child3);
CHECK_EQ(child2->get_next_in_tree(), child3);
CHECK_EQ(child3->get_next(), nullptr);
CHECK_EQ(child3->get_next_visible(), nullptr);
CHECK_EQ(child3->get_next_in_tree(), nullptr);
CHECK_EQ(root->get_prev(), nullptr);
CHECK_EQ(root->get_prev_visible(), nullptr);
CHECK_EQ(root->get_prev_in_tree(), nullptr);
CHECK_EQ(child1->get_prev(), nullptr);
CHECK_EQ(child1->get_prev_visible(), root);
CHECK_EQ(child1->get_prev_in_tree(), root);
CHECK_EQ(child2->get_prev(), child1);
CHECK_EQ(child2->get_prev_visible(), child1);
CHECK_EQ(child2->get_prev_in_tree(), child1);
CHECK_EQ(child3->get_prev(), child2);
CHECK_EQ(child3->get_prev_visible(), child2);
CHECK_EQ(child3->get_prev_in_tree(), child2);
TreeItem *nested1 = tree->create_item(child2);
TreeItem *nested2 = tree->create_item(child2);
TreeItem *nested3 = tree->create_item(child2);
CHECK_EQ(child1->get_next(), child2);
CHECK_EQ(child1->get_next_visible(), child2);
CHECK_EQ(child1->get_next_in_tree(), child2);
CHECK_EQ(child2->get_next(), child3);
CHECK_EQ(child2->get_next_visible(), nested1);
CHECK_EQ(child2->get_next_in_tree(), nested1);
CHECK_EQ(child3->get_prev(), child2);
CHECK_EQ(child3->get_prev_visible(), nested3);
CHECK_EQ(child3->get_prev_in_tree(), nested3);
CHECK_EQ(nested1->get_prev_in_tree(), child2);
CHECK_EQ(nested1->get_next_in_tree(), nested2);
CHECK_EQ(nested3->get_next_in_tree(), child3);
memdelete(tree);
}
SUBCASE("[Tree] Previous and Next items with hide root.") {
Tree *tree = memnew(Tree);
tree->set_hide_root(true);
TreeItem *root = tree->create_item();
TreeItem *child1 = tree->create_item();
TreeItem *child2 = tree->create_item();
TreeItem *child3 = tree->create_item();
CHECK_EQ(root->get_next(), nullptr);
CHECK_EQ(root->get_next_visible(), child1);
CHECK_EQ(root->get_next_in_tree(), child1);
CHECK_EQ(child1->get_next(), child2);
CHECK_EQ(child1->get_next_visible(), child2);
CHECK_EQ(child1->get_next_in_tree(), child2);
CHECK_EQ(child2->get_next(), child3);
CHECK_EQ(child2->get_next_visible(), child3);
CHECK_EQ(child2->get_next_in_tree(), child3);
CHECK_EQ(child3->get_next(), nullptr);
CHECK_EQ(child3->get_next_visible(), nullptr);
CHECK_EQ(child3->get_next_in_tree(), nullptr);
CHECK_EQ(root->get_prev(), nullptr);
CHECK_EQ(root->get_prev_visible(), nullptr);
CHECK_EQ(root->get_prev_in_tree(), nullptr);
CHECK_EQ(child1->get_prev(), nullptr);
CHECK_EQ(child1->get_prev_visible(), nullptr);
CHECK_EQ(child1->get_prev_in_tree(), nullptr);
CHECK_EQ(child2->get_prev(), child1);
CHECK_EQ(child2->get_prev_visible(), child1);
CHECK_EQ(child2->get_prev_in_tree(), child1);
CHECK_EQ(child3->get_prev(), child2);
CHECK_EQ(child3->get_prev_visible(), child2);
CHECK_EQ(child3->get_prev_in_tree(), child2);
memdelete(tree);
}
SUBCASE("[Tree] Previous and Next items wrapping.") {
Tree *tree = memnew(Tree);
TreeItem *root = tree->create_item();
TreeItem *child1 = tree->create_item();
TreeItem *child2 = tree->create_item();
TreeItem *child3 = tree->create_item();
CHECK_EQ(root->get_next_visible(true), child1);
CHECK_EQ(root->get_next_in_tree(true), child1);
CHECK_EQ(child1->get_next_visible(true), child2);
CHECK_EQ(child1->get_next_in_tree(true), child2);
CHECK_EQ(child2->get_next_visible(true), child3);
CHECK_EQ(child2->get_next_in_tree(true), child3);
CHECK_EQ(child3->get_next_visible(true), root);
CHECK_EQ(child3->get_next_in_tree(true), root);
CHECK_EQ(root->get_prev_visible(true), child3);
CHECK_EQ(root->get_prev_in_tree(true), child3);
CHECK_EQ(child1->get_prev_visible(true), root);
CHECK_EQ(child1->get_prev_in_tree(true), root);
CHECK_EQ(child2->get_prev_visible(true), child1);
CHECK_EQ(child2->get_prev_in_tree(true), child1);
CHECK_EQ(child3->get_prev_visible(true), child2);
CHECK_EQ(child3->get_prev_in_tree(true), child2);
TreeItem *nested1 = tree->create_item(child2);
TreeItem *nested2 = tree->create_item(child2);
TreeItem *nested3 = tree->create_item(child2);
CHECK_EQ(child1->get_next_visible(true), child2);
CHECK_EQ(child1->get_next_in_tree(true), child2);
CHECK_EQ(child2->get_next_visible(true), nested1);
CHECK_EQ(child2->get_next_in_tree(true), nested1);
CHECK_EQ(nested3->get_next_visible(true), child3);
CHECK_EQ(nested3->get_next_in_tree(true), child3);
CHECK_EQ(child3->get_prev_visible(true), nested3);
CHECK_EQ(child3->get_prev_in_tree(true), nested3);
CHECK_EQ(nested1->get_prev_in_tree(true), child2);
CHECK_EQ(nested1->get_next_in_tree(true), nested2);
CHECK_EQ(nested3->get_next_in_tree(true), child3);
memdelete(tree);
}
SUBCASE("[Tree] Previous and Next items wrapping with hide root.") {
Tree *tree = memnew(Tree);
tree->set_hide_root(true);
TreeItem *root = tree->create_item();
TreeItem *child1 = tree->create_item();
TreeItem *child2 = tree->create_item();
TreeItem *child3 = tree->create_item();
CHECK_EQ(root->get_next_visible(true), child1);
CHECK_EQ(root->get_next_in_tree(true), child1);
CHECK_EQ(child1->get_next_visible(true), child2);
CHECK_EQ(child1->get_next_in_tree(true), child2);
CHECK_EQ(child2->get_next_visible(true), child3);
CHECK_EQ(child2->get_next_in_tree(true), child3);
CHECK_EQ(child3->get_next_visible(true), root);
CHECK_EQ(child3->get_next_in_tree(true), root);
CHECK_EQ(root->get_prev_visible(true), child3);
CHECK_EQ(root->get_prev_in_tree(true), child3);
CHECK_EQ(child1->get_prev_visible(true), child3);
CHECK_EQ(child1->get_prev_in_tree(true), child3);
CHECK_EQ(child2->get_prev_visible(true), child1);
CHECK_EQ(child2->get_prev_in_tree(true), child1);
CHECK_EQ(child3->get_prev_visible(true), child2);
CHECK_EQ(child3->get_prev_in_tree(true), child2);
memdelete(tree);
}
}
} // namespace TestTree
#endif // TEST_TREE_H

View file

@ -38,6 +38,7 @@
#include "scene/main/canvas_layer.h"
#include "scene/main/window.h"
#include "scene/resources/2d/rectangle_shape_2d.h"
#include "servers/physics_server_2d_dummy.h"
#include "tests/test_macros.h"
@ -119,8 +120,23 @@ public:
class DragTarget : public NotificationControlViewport {
GDCLASS(DragTarget, NotificationControlViewport);
protected:
void _notification(int p_what) {
switch (p_what) {
case NOTIFICATION_DRAG_BEGIN: {
during_drag = true;
} break;
case NOTIFICATION_DRAG_END: {
during_drag = false;
} break;
}
}
public:
Variant drag_data;
bool valid_drop = false;
bool during_drag = false;
virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const override {
StringName string_data = p_data;
// Verify drag data is compatible.
@ -136,6 +152,7 @@ public:
virtual void drop_data(const Point2 &p_point, const Variant &p_data) override {
drag_data = p_data;
valid_drop = true;
}
};
@ -1107,12 +1124,10 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
SUBCASE("[Viewport][GuiInputEvent] Drag and Drop") {
// FIXME: Drag-Preview will likely change. Tests for this part would have to be rewritten anyway.
// See https://github.com/godotengine/godot/pull/67531#issuecomment-1385353430 for details.
// FIXME: Testing Drag and Drop with non-embedded windows would require DisplayServerMock additions
// FIXME: Drag and Drop currently doesn't work with embedded Windows and SubViewports - not testing.
// See https://github.com/godotengine/godot/issues/28522 for example.
// Note: Testing Drag and Drop with non-embedded windows would require DisplayServerMock additions.
int min_grab_movement = 11;
SUBCASE("[Viewport][GuiInputEvent] Drag from one Control to another in the same viewport.") {
SUBCASE("[Viewport][GuiInputEvent] Perform successful Drag and Drop on a different Control.") {
SUBCASE("[Viewport][GuiInputEvent][DnD] Drag from one Control to another in the same viewport.") {
SUBCASE("[Viewport][GuiInputEvent][DnD] Perform successful Drag and Drop on a different Control.") {
SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
CHECK_FALSE(root->gui_is_dragging());
@ -1131,7 +1146,7 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
CHECK((StringName)node_d->drag_data == SNAME("Drag Data"));
}
SUBCASE("[Viewport][GuiInputEvent] Perform unsuccessful drop on Control.") {
SUBCASE("[Viewport][GuiInputEvent][DnD] Perform unsuccessful drop on Control.") {
SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
CHECK_FALSE(root->gui_is_dragging());
@ -1157,7 +1172,7 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
CHECK_FALSE(root->gui_is_drag_successful());
}
SUBCASE("[Viewport][GuiInputEvent] Perform unsuccessful drop on No-Control.") {
SUBCASE("[Viewport][GuiInputEvent][DnD] Perform unsuccessful drop on No-Control.") {
SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
CHECK_FALSE(root->gui_is_dragging());
@ -1171,7 +1186,7 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
// Move away from Controls.
SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::LEFT, Key::NONE);
CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_ARROW); // This could also be CURSOR_FORBIDDEN.
CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_ARROW);
CHECK(root->gui_is_dragging());
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_background, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
@ -1179,7 +1194,7 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
CHECK_FALSE(root->gui_is_drag_successful());
}
SUBCASE("[Viewport][GuiInputEvent] Perform unsuccessful drop outside of window.") {
SUBCASE("[Viewport][GuiInputEvent][DnD] Perform unsuccessful drop outside of window.") {
SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
CHECK_FALSE(root->gui_is_dragging());
@ -1192,7 +1207,6 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
// Move outside of window.
SEND_GUI_MOUSE_MOTION_EVENT(on_outside, MouseButtonMask::LEFT, Key::NONE);
CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_ARROW);
CHECK(root->gui_is_dragging());
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_outside, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
@ -1200,7 +1214,7 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
CHECK_FALSE(root->gui_is_drag_successful());
}
SUBCASE("[Viewport][GuiInputEvent] Drag and Drop doesn't work with other Mouse Buttons than LMB.") {
SUBCASE("[Viewport][GuiInputEvent][DnD] Drag and Drop doesn't work with other Mouse Buttons than LMB.") {
SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::MIDDLE, MouseButtonMask::MIDDLE, Key::NONE);
CHECK_FALSE(root->gui_is_dragging());
@ -1209,7 +1223,7 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_a, MouseButton::MIDDLE, MouseButtonMask::NONE, Key::NONE);
}
SUBCASE("[Viewport][GuiInputEvent] Drag and Drop parent propagation.") {
SUBCASE("[Viewport][GuiInputEvent][DnD] Drag and Drop parent propagation.") {
Node2D *node_aa = memnew(Node2D);
Control *node_aaa = memnew(Control);
Node2D *node_dd = memnew(Node2D);
@ -1318,7 +1332,7 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
memdelete(node_aa);
}
SUBCASE("[Viewport][GuiInputEvent] Force Drag and Drop.") {
SUBCASE("[Viewport][GuiInputEvent][DnD] Force Drag and Drop.") {
SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(root->gui_is_dragging());
node_a->force_drag(SNAME("Drag Data"), nullptr);
@ -1327,8 +1341,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
SEND_GUI_MOUSE_MOTION_EVENT(on_d, MouseButtonMask::NONE, Key::NONE);
// Force Drop doesn't get triggered by mouse Buttons other than LMB.
SEND_GUI_MOUSE_BUTTON_EVENT(on_d, MouseButton::RIGHT, MouseButtonMask::RIGHT, Key::NONE);
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_a, MouseButton::RIGHT, MouseButtonMask::NONE, Key::NONE);
SEND_GUI_MOUSE_BUTTON_EVENT(on_d, MouseButton::MIDDLE, MouseButtonMask::MIDDLE, Key::NONE);
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_a, MouseButton::MIDDLE, MouseButtonMask::NONE, Key::NONE);
CHECK(root->gui_is_dragging());
// Force Drop with LMB-Down.
@ -1337,8 +1351,122 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
CHECK(root->gui_is_drag_successful());
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_d, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
node_a->force_drag(SNAME("Drag Data"), nullptr);
CHECK(root->gui_is_dragging());
// Cancel with RMB.
SEND_GUI_MOUSE_BUTTON_EVENT(on_d, MouseButton::RIGHT, MouseButtonMask::RIGHT, Key::NONE);
CHECK_FALSE(root->gui_is_dragging());
CHECK_FALSE(root->gui_is_drag_successful());
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_a, MouseButton::RIGHT, MouseButtonMask::NONE, Key::NONE);
}
}
SUBCASE("[Viewport][GuiInputEvent][DnD] Drag to a different Viewport.") {
SubViewportContainer *svc = memnew(SubViewportContainer);
svc->set_size(Size2(100, 100));
svc->set_position(Point2(200, 50));
root->add_child(svc);
SubViewport *sv = memnew(SubViewport);
sv->set_embedding_subwindows(true);
sv->set_size(Size2i(100, 100));
svc->add_child(sv);
DragStart *sv_a = memnew(DragStart);
sv_a->set_position(Point2(10, 10));
sv_a->set_size(Size2(10, 10));
sv->add_child(sv_a);
Point2i on_sva = Point2i(215, 65);
DragTarget *sv_b = memnew(DragTarget);
sv_b->set_position(Point2(30, 30));
sv_b->set_size(Size2(20, 20));
sv->add_child(sv_b);
Point2i on_svb = Point2i(235, 85);
Window *ew = memnew(Window);
ew->set_position(Point2(50, 200));
ew->set_size(Size2(100, 100));
root->add_child(ew);
DragStart *ew_a = memnew(DragStart);
ew_a->set_position(Point2(10, 10));
ew_a->set_size(Size2(10, 10));
ew->add_child(ew_a);
Point2i on_ewa = Point2i(65, 215);
DragTarget *ew_b = memnew(DragTarget);
ew_b->set_position(Point2(30, 30));
ew_b->set_size(Size2(20, 20));
ew->add_child(ew_b);
Point2i on_ewb = Point2i(85, 235);
SUBCASE("[Viewport][GuiInputEvent][DnD] Drag to SubViewport") {
sv_b->valid_drop = false;
SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
SEND_GUI_MOUSE_MOTION_EVENT(on_a + Point2i(min_grab_movement, 0), MouseButtonMask::LEFT, Key::NONE);
CHECK(root->gui_is_dragging());
CHECK(sv_b->during_drag);
SEND_GUI_MOUSE_MOTION_EVENT(on_svb, MouseButtonMask::LEFT, Key::NONE);
CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_CAN_DROP);
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_svb, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
CHECK(sv_b->valid_drop);
CHECK(!sv_b->during_drag);
}
SUBCASE("[Viewport][GuiInputEvent][DnD] Drag from SubViewport") {
node_d->valid_drop = false;
SEND_GUI_MOUSE_BUTTON_EVENT(on_sva, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
SEND_GUI_MOUSE_MOTION_EVENT(on_sva + Point2i(min_grab_movement, 0), MouseButtonMask::LEFT, Key::NONE);
CHECK(sv->gui_is_dragging());
CHECK(node_d->during_drag);
SEND_GUI_MOUSE_MOTION_EVENT(on_d, MouseButtonMask::LEFT, Key::NONE);
CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_CAN_DROP);
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_d, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
CHECK(node_d->valid_drop);
CHECK(!node_d->during_drag);
}
SUBCASE("[Viewport][GuiInputEvent][DnD] Drag to embedded Window") {
ew_b->valid_drop = false;
SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
SEND_GUI_MOUSE_MOTION_EVENT(on_a + Point2i(min_grab_movement, 0), MouseButtonMask::LEFT, Key::NONE);
CHECK(root->gui_is_dragging());
CHECK(ew_b->during_drag);
SEND_GUI_MOUSE_MOTION_EVENT(on_ewb, MouseButtonMask::LEFT, Key::NONE);
CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_CAN_DROP);
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_ewb, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
CHECK(ew_b->valid_drop);
CHECK(!ew_b->during_drag);
}
SUBCASE("[Viewport][GuiInputEvent][DnD] Drag from embedded Window") {
node_d->valid_drop = false;
SEND_GUI_MOUSE_BUTTON_EVENT(on_ewa, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
SEND_GUI_MOUSE_MOTION_EVENT(on_ewa + Point2i(min_grab_movement, 0), MouseButtonMask::LEFT, Key::NONE);
CHECK(ew->gui_is_dragging());
CHECK(node_d->during_drag);
SEND_GUI_MOUSE_MOTION_EVENT(on_d, MouseButtonMask::LEFT, Key::NONE);
CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_CAN_DROP);
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_d, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
CHECK(node_d->valid_drop);
CHECK(!node_d->during_drag);
}
memdelete(ew_a);
memdelete(ew_b);
memdelete(ew);
memdelete(sv_a);
memdelete(sv_b);
memdelete(sv);
memdelete(svc);
}
}
memdelete(node_j);
@ -1432,6 +1560,12 @@ int TestArea2D::counter = 0;
TEST_CASE("[SceneTree][Viewport] Physics Picking 2D") {
// FIXME: MOUSE_MODE_CAPTURED if-conditions are not testable, because DisplayServerMock doesn't support it.
// NOTE: This test requires a real physics server.
PhysicsServer2DDummy *physics_server_2d_dummy = Object::cast_to<PhysicsServer2DDummy>(PhysicsServer2D::get_singleton());
if (physics_server_2d_dummy) {
return;
}
struct PickingCollider {
TestArea2D *a;
CollisionShape2D *c;
@ -1452,7 +1586,7 @@ TEST_CASE("[SceneTree][Viewport] Physics Picking 2D") {
PickingCollider pc;
pc.a = memnew(TestArea2D);
pc.c = memnew(CollisionShape2D);
pc.r = Ref<RectangleShape2D>(memnew(RectangleShape2D));
pc.r.instantiate();
pc.r->set_size(Size2(150, 150));
pc.c->set_shape(pc.r);
pc.a->add_child(pc.c);