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

@ -1,4 +1,5 @@
#!/usr/bin/python
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")

View file

@ -40,7 +40,7 @@ class TestProjectSettingsInternalsAccessor {
public:
static String &resource_path() {
return ProjectSettings::get_singleton()->resource_path;
};
}
};
namespace TestProjectSettings {
@ -126,10 +126,9 @@ TEST_CASE("[ProjectSettings] localize_path") {
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("path\\.\\filename"), "res://path/filename");
#endif
// FIXME?: These checks pass, but that doesn't seems correct
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("../filename"), "res://filename");
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("../path/filename"), "res://path/filename");
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("..\\path\\filename"), "res://path/filename");
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("../filename"), "../filename");
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("../path/filename"), "../path/filename");
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("..\\path\\filename"), "../path/filename");
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("/testroot/filename"), "/testroot/filename");
CHECK_EQ(ProjectSettings::get_singleton()->localize_path("/testroot/path/filename"), "/testroot/path/filename");

View file

@ -39,7 +39,7 @@ namespace TestFileAccess {
TEST_CASE("[FileAccess] CSV read") {
Ref<FileAccess> f = FileAccess::open(TestUtils::get_data_path("testdata.csv"), FileAccess::READ);
REQUIRE(!f.is_null());
REQUIRE(f.is_valid());
Vector<String> header = f->get_csv_line(); // Default delimiter: ",".
REQUIRE(header.size() == 4);
@ -84,7 +84,7 @@ TEST_CASE("[FileAccess] CSV read") {
TEST_CASE("[FileAccess] Get as UTF-8 String") {
Ref<FileAccess> f_lf = FileAccess::open(TestUtils::get_data_path("line_endings_lf.test.txt"), FileAccess::READ);
REQUIRE(!f_lf.is_null());
REQUIRE(f_lf.is_valid());
String s_lf = f_lf->get_as_utf8_string();
f_lf->seek(0);
String s_lf_nocr = f_lf->get_as_utf8_string(true);
@ -92,7 +92,7 @@ TEST_CASE("[FileAccess] Get as UTF-8 String") {
CHECK(s_lf_nocr == "Hello darkness\nMy old friend\nI've come to talk\nWith you again\n");
Ref<FileAccess> f_crlf = FileAccess::open(TestUtils::get_data_path("line_endings_crlf.test.txt"), FileAccess::READ);
REQUIRE(!f_crlf.is_null());
REQUIRE(f_crlf.is_valid());
String s_crlf = f_crlf->get_as_utf8_string();
f_crlf->seek(0);
String s_crlf_nocr = f_crlf->get_as_utf8_string(true);
@ -100,13 +100,105 @@ TEST_CASE("[FileAccess] Get as UTF-8 String") {
CHECK(s_crlf_nocr == "Hello darkness\nMy old friend\nI've come to talk\nWith you again\n");
Ref<FileAccess> f_cr = FileAccess::open(TestUtils::get_data_path("line_endings_cr.test.txt"), FileAccess::READ);
REQUIRE(!f_cr.is_null());
REQUIRE(f_cr.is_valid());
String s_cr = f_cr->get_as_utf8_string();
f_cr->seek(0);
String s_cr_nocr = f_cr->get_as_utf8_string(true);
CHECK(s_cr == "Hello darkness\rMy old friend\rI've come to talk\rWith you again\r");
CHECK(s_cr_nocr == "Hello darknessMy old friendI've come to talkWith you again");
}
TEST_CASE("[FileAccess] Get/Store floating point values") {
// BigEndian Hex: 0x40490E56
// LittleEndian Hex: 0x560E4940
float value = 3.1415f;
SUBCASE("Little Endian") {
const String file_path = TestUtils::get_data_path("floating_point_little_endian.bin");
const String file_path_new = TestUtils::get_data_path("floating_point_little_endian_new.bin");
Ref<FileAccess> f = FileAccess::open(file_path, FileAccess::READ);
REQUIRE(f.is_valid());
CHECK_EQ(f->get_float(), value);
Ref<FileAccess> fw = FileAccess::open(file_path_new, FileAccess::WRITE);
REQUIRE(fw.is_valid());
fw->store_float(value);
fw->close();
CHECK_EQ(FileAccess::get_sha256(file_path_new), FileAccess::get_sha256(file_path));
DirAccess::remove_file_or_error(file_path_new);
}
SUBCASE("Big Endian") {
const String file_path = TestUtils::get_data_path("floating_point_big_endian.bin");
const String file_path_new = TestUtils::get_data_path("floating_point_big_endian_new.bin");
Ref<FileAccess> f = FileAccess::open(file_path, FileAccess::READ);
REQUIRE(f.is_valid());
f->set_big_endian(true);
CHECK_EQ(f->get_float(), value);
Ref<FileAccess> fw = FileAccess::open(file_path_new, FileAccess::WRITE);
REQUIRE(fw.is_valid());
fw->set_big_endian(true);
fw->store_float(value);
fw->close();
CHECK_EQ(FileAccess::get_sha256(file_path_new), FileAccess::get_sha256(file_path));
DirAccess::remove_file_or_error(file_path_new);
}
}
TEST_CASE("[FileAccess] Get/Store floating point half precision values") {
// IEEE 754 half-precision binary floating-point format:
// sign exponent (5 bits) fraction (10 bits)
// 0 01101 0101010101
// BigEndian Hex: 0x3555
// LittleEndian Hex: 0x5535
float value = 0.33325195f;
SUBCASE("Little Endian") {
const String file_path = TestUtils::get_data_path("half_precision_floating_point_little_endian.bin");
const String file_path_new = TestUtils::get_data_path("half_precision_floating_point_little_endian_new.bin");
Ref<FileAccess> f = FileAccess::open(file_path, FileAccess::READ);
REQUIRE(f.is_valid());
CHECK_EQ(f->get_half(), value);
Ref<FileAccess> fw = FileAccess::open(file_path_new, FileAccess::WRITE);
REQUIRE(fw.is_valid());
fw->store_half(value);
fw->close();
CHECK_EQ(FileAccess::get_sha256(file_path_new), FileAccess::get_sha256(file_path));
DirAccess::remove_file_or_error(file_path_new);
}
SUBCASE("Big Endian") {
const String file_path = TestUtils::get_data_path("half_precision_floating_point_big_endian.bin");
const String file_path_new = TestUtils::get_data_path("half_precision_floating_point_big_endian_new.bin");
Ref<FileAccess> f = FileAccess::open(file_path, FileAccess::READ);
REQUIRE(f.is_valid());
f->set_big_endian(true);
CHECK_EQ(f->get_half(), value);
Ref<FileAccess> fw = FileAccess::open(file_path_new, FileAccess::WRITE);
REQUIRE(fw.is_valid());
fw->set_big_endian(true);
fw->store_half(value);
fw->close();
CHECK_EQ(FileAccess::get_sha256(file_path_new), FileAccess::get_sha256(file_path));
DirAccess::remove_file_or_error(file_path_new);
}
}
} // namespace TestFileAccess
#endif // TEST_FILE_ACCESS_H

View file

@ -41,7 +41,7 @@ namespace TestHTTPClient {
TEST_CASE("[HTTPClient] Instantiation") {
Ref<HTTPClient> client = HTTPClient::create();
CHECK_MESSAGE(client != nullptr, "A HTTP Client created should not be a null pointer");
CHECK_MESSAGE(client.is_valid(), "A HTTP Client created should not be a null pointer");
}
TEST_CASE("[HTTPClient] query_string_from_dict") {

View file

@ -35,10 +35,11 @@
#include "core/os/os.h"
#include "tests/test_utils.h"
#include "thirdparty/doctest/doctest.h"
#include "modules/modules_enabled.gen.h"
#include "thirdparty/doctest/doctest.h"
namespace TestImage {
TEST_CASE("[Image] Instantiation") {
@ -113,7 +114,7 @@ TEST_CASE("[Image] Saving and loading") {
// Load BMP
Ref<Image> image_bmp = memnew(Image());
Ref<FileAccess> f_bmp = FileAccess::open(TestUtils::get_data_path("images/icon.bmp"), FileAccess::READ, &err);
REQUIRE(!f_bmp.is_null());
REQUIRE(f_bmp.is_valid());
PackedByteArray data_bmp;
data_bmp.resize(f_bmp->get_length() + 1);
f_bmp->get_buffer(data_bmp.ptrw(), f_bmp->get_length());
@ -126,7 +127,7 @@ TEST_CASE("[Image] Saving and loading") {
// Load JPG
Ref<Image> image_jpg = memnew(Image());
Ref<FileAccess> f_jpg = FileAccess::open(TestUtils::get_data_path("images/icon.jpg"), FileAccess::READ, &err);
REQUIRE(!f_jpg.is_null());
REQUIRE(f_jpg.is_valid());
PackedByteArray data_jpg;
data_jpg.resize(f_jpg->get_length() + 1);
f_jpg->get_buffer(data_jpg.ptrw(), f_jpg->get_length());
@ -139,7 +140,7 @@ TEST_CASE("[Image] Saving and loading") {
// Load WebP
Ref<Image> image_webp = memnew(Image());
Ref<FileAccess> f_webp = FileAccess::open(TestUtils::get_data_path("images/icon.webp"), FileAccess::READ, &err);
REQUIRE(!f_webp.is_null());
REQUIRE(f_webp.is_valid());
PackedByteArray data_webp;
data_webp.resize(f_webp->get_length() + 1);
f_webp->get_buffer(data_webp.ptrw(), f_webp->get_length());
@ -151,7 +152,7 @@ TEST_CASE("[Image] Saving and loading") {
// Load PNG
Ref<Image> image_png = memnew(Image());
Ref<FileAccess> f_png = FileAccess::open(TestUtils::get_data_path("images/icon.png"), FileAccess::READ, &err);
REQUIRE(!f_png.is_null());
REQUIRE(f_png.is_valid());
PackedByteArray data_png;
data_png.resize(f_png->get_length() + 1);
f_png->get_buffer(data_png.ptrw(), f_png->get_length());
@ -163,7 +164,7 @@ TEST_CASE("[Image] Saving and loading") {
// Load TGA
Ref<Image> image_tga = memnew(Image());
Ref<FileAccess> f_tga = FileAccess::open(TestUtils::get_data_path("images/icon.tga"), FileAccess::READ, &err);
REQUIRE(!f_tga.is_null());
REQUIRE(f_tga.is_valid());
PackedByteArray data_tga;
data_tga.resize(f_tga->get_length() + 1);
f_tga->get_buffer(data_tga.ptrw(), f_tga->get_length());

View file

@ -232,6 +232,91 @@ TEST_CASE("[JSON] Parsing escape sequences") {
ERR_PRINT_ON
}
}
TEST_CASE("[JSON] Serialization") {
JSON json;
struct FpTestCase {
double number;
String json;
};
struct IntTestCase {
int64_t number;
String json;
};
struct UIntTestCase {
uint64_t number;
String json;
};
static FpTestCase fp_tests_default_precision[] = {
{ 0.0, "0.0" },
{ 1000.1234567890123456789, "1000.12345678901" },
{ -1000.1234567890123456789, "-1000.12345678901" },
{ DBL_MAX, "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0" },
{ DBL_MAX - 1, "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0" },
{ pow(2, 53), "9007199254740992.0" },
{ -pow(2, 53), "-9007199254740992.0" },
{ 0.00000000000000011, "0.00000000000000011" },
{ -0.00000000000000011, "-0.00000000000000011" },
{ 1.0 / 3.0, "0.333333333333333" },
{ 0.9999999999999999, "1.0" },
{ 1.0000000000000001, "1.0" },
};
static FpTestCase fp_tests_full_precision[] = {
{ 0.0, "0.0" },
{ 1000.1234567890123456789, "1000.12345678901238" },
{ -1000.1234567890123456789, "-1000.12345678901238" },
{ DBL_MAX, "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0" },
{ DBL_MAX - 1, "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0" },
{ pow(2, 53), "9007199254740992.0" },
{ -pow(2, 53), "-9007199254740992.0" },
{ 0.00000000000000011, "0.00000000000000011" },
{ -0.00000000000000011, "-0.00000000000000011" },
{ 1.0 / 3.0, "0.333333333333333315" },
{ 0.9999999999999999, "0.999999999999999889" },
{ 1.0000000000000001, "1.0" },
};
static IntTestCase int_tests[] = {
{ 0, "0" },
{ INT64_MAX, "9223372036854775807" },
{ INT64_MIN, "-9223372036854775808" },
};
SUBCASE("Floating point default precision") {
for (FpTestCase &test : fp_tests_default_precision) {
String json_value = json.stringify(test.number, "", true, false);
CHECK_MESSAGE(
json_value == test.json,
vformat("Serializing `%.20d` to JSON should return the expected value.", test.number));
}
}
SUBCASE("Floating point full precision") {
for (FpTestCase &test : fp_tests_full_precision) {
String json_value = json.stringify(test.number, "", true, true);
CHECK_MESSAGE(
json_value == test.json,
vformat("Serializing `%20f` to JSON should return the expected value.", test.number));
}
}
SUBCASE("Signed integer") {
for (IntTestCase &test : int_tests) {
String json_value = json.stringify(test.number, "", true, true);
CHECK_MESSAGE(
json_value == test.json,
vformat("Serializing `%d` to JSON should return the expected value.", test.number));
}
}
}
} // namespace TestJSON
#endif // TEST_JSON_H

View file

@ -0,0 +1,227 @@
/**************************************************************************/
/* test_json_native.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_JSON_NATIVE_H
#define TEST_JSON_NATIVE_H
#include "core/io/json.h"
#include "core/variant/typed_array.h"
#include "core/variant/typed_dictionary.h"
#include "tests/test_macros.h"
namespace TestJSONNative {
String encode(const Variant &p_variant, bool p_full_objects = false) {
return JSON::stringify(JSON::from_native(p_variant, p_full_objects), "", false);
}
Variant decode(const String &p_string, bool p_allow_objects = false) {
return JSON::to_native(JSON::parse_string(p_string), p_allow_objects);
}
void test(const Variant &p_variant, const String &p_string, bool p_with_objects = false) {
CHECK(encode(p_variant, p_with_objects) == p_string);
CHECK(decode(p_string, p_with_objects).get_construct_string() == p_variant.get_construct_string());
}
TEST_CASE("[JSON][Native] Conversion between native and JSON formats") {
// `Nil` and `bool` (represented as JSON keyword literals).
test(Variant(), "null");
test(false, "false");
test(true, "true");
// Numbers and strings (represented as JSON strings).
test(1, R"("i:1")");
test(1.0, R"("f:1.0")");
test(String("abc"), R"("s:abc")");
test(StringName("abc"), R"("sn:abc")");
test(NodePath("abc"), R"("np:abc")");
// Non-serializable types (always empty after deserialization).
test(RID(), R"({"type":"RID"})");
test(Callable(), R"({"type":"Callable"})");
test(Signal(), R"({"type":"Signal"})");
// Math types.
test(Vector2(1, 2), R"({"type":"Vector2","args":[1.0,2.0]})");
test(Vector2i(1, 2), R"({"type":"Vector2i","args":[1,2]})");
test(Rect2(1, 2, 3, 4), R"({"type":"Rect2","args":[1.0,2.0,3.0,4.0]})");
test(Rect2i(1, 2, 3, 4), R"({"type":"Rect2i","args":[1,2,3,4]})");
test(Vector3(1, 2, 3), R"({"type":"Vector3","args":[1.0,2.0,3.0]})");
test(Vector3i(1, 2, 3), R"({"type":"Vector3i","args":[1,2,3]})");
test(Transform2D(1, 2, 3, 4, 5, 6), R"({"type":"Transform2D","args":[1.0,2.0,3.0,4.0,5.0,6.0]})");
test(Vector4(1, 2, 3, 4), R"({"type":"Vector4","args":[1.0,2.0,3.0,4.0]})");
test(Vector4i(1, 2, 3, 4), R"({"type":"Vector4i","args":[1,2,3,4]})");
test(Plane(1, 2, 3, 4), R"({"type":"Plane","args":[1.0,2.0,3.0,4.0]})");
test(Quaternion(1, 2, 3, 4), R"({"type":"Quaternion","args":[1.0,2.0,3.0,4.0]})");
test(AABB(Vector3(1, 2, 3), Vector3(4, 5, 6)), R"({"type":"AABB","args":[1.0,2.0,3.0,4.0,5.0,6.0]})");
const Basis b(Vector3(1, 2, 3), Vector3(4, 5, 6), Vector3(7, 8, 9));
test(b, R"({"type":"Basis","args":[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0]})");
const Transform3D tr3d(Vector3(1, 2, 3), Vector3(4, 5, 6), Vector3(7, 8, 9), Vector3(10, 11, 12));
test(tr3d, R"({"type":"Transform3D","args":[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0]})");
const Projection p(Vector4(1, 2, 3, 4), Vector4(5, 6, 7, 8), Vector4(9, 10, 11, 12), Vector4(13, 14, 15, 16));
test(p, R"({"type":"Projection","args":[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0,13.0,14.0,15.0,16.0]})");
test(Color(1, 2, 3, 4), R"({"type":"Color","args":[1.0,2.0,3.0,4.0]})");
// `Object`.
Ref<Resource> res;
res.instantiate();
// The properties are stored in an array because the order in which they are assigned may be important during initialization.
const String res_repr = R"({"type":"Resource","props":["resource_local_to_scene",false,"resource_name","s:","script",null]})";
test(res, res_repr, true);
ERR_PRINT_OFF;
CHECK(encode(res) == "null");
CHECK(decode(res_repr).get_type() == Variant::NIL);
ERR_PRINT_ON;
// `Dictionary`.
Dictionary dict;
dict[false] = true;
dict[0] = 1;
dict[0.0] = 1.0;
// Godot dictionaries preserve insertion order, so an array is used for keys/values.
test(dict, R"({"type":"Dictionary","args":[false,true,"i:0","i:1","f:0.0","f:1.0"]})");
TypedDictionary<int64_t, int64_t> int_int_dict;
int_int_dict[1] = 2;
int_int_dict[3] = 4;
test(int_int_dict, R"({"type":"Dictionary","key_type":"int","value_type":"int","args":["i:1","i:2","i:3","i:4"]})");
TypedDictionary<int64_t, Variant> int_var_dict;
int_var_dict[1] = "2";
int_var_dict[3] = "4";
test(int_var_dict, R"({"type":"Dictionary","key_type":"int","args":["i:1","s:2","i:3","s:4"]})");
TypedDictionary<Variant, int64_t> var_int_dict;
var_int_dict["1"] = 2;
var_int_dict["3"] = 4;
test(var_int_dict, R"({"type":"Dictionary","value_type":"int","args":["s:1","i:2","s:3","i:4"]})");
Dictionary dict2;
dict2["x"] = res;
const String dict2_repr = vformat(R"({"type":"Dictionary","args":["s:x",%s]})", res_repr);
test(dict2, dict2_repr, true);
ERR_PRINT_OFF;
CHECK(encode(dict2) == R"({"type":"Dictionary","args":["s:x",null]})");
CHECK(decode(dict2_repr).get_construct_string() == "{\n\"x\": null\n}");
ERR_PRINT_ON;
TypedDictionary<String, Resource> res_dict;
res_dict["x"] = res;
const String res_dict_repr = vformat(R"({"type":"Dictionary","key_type":"String","value_type":"Resource","args":["s:x",%s]})", res_repr);
test(res_dict, res_dict_repr, true);
ERR_PRINT_OFF;
CHECK(encode(res_dict) == "null");
CHECK(decode(res_dict_repr).get_type() == Variant::NIL);
ERR_PRINT_ON;
// `Array`.
Array arr;
arr.push_back(true);
arr.push_back(1);
arr.push_back("abc");
test(arr, R"([true,"i:1","s:abc"])");
TypedArray<int64_t> int_arr;
int_arr.push_back(1);
int_arr.push_back(2);
int_arr.push_back(3);
test(int_arr, R"({"type":"Array","elem_type":"int","args":["i:1","i:2","i:3"]})");
Array arr2;
arr2.push_back(1);
arr2.push_back(res);
arr2.push_back(9);
const String arr2_repr = vformat(R"(["i:1",%s,"i:9"])", res_repr);
test(arr2, arr2_repr, true);
ERR_PRINT_OFF;
CHECK(encode(arr2) == R"(["i:1",null,"i:9"])");
CHECK(decode(arr2_repr).get_construct_string() == "[1, null, 9]");
ERR_PRINT_ON;
TypedArray<Resource> res_arr;
res_arr.push_back(res);
const String res_arr_repr = vformat(R"({"type":"Array","elem_type":"Resource","args":[%s]})", res_repr);
test(res_arr, res_arr_repr, true);
ERR_PRINT_OFF;
CHECK(encode(res_arr) == "null");
CHECK(decode(res_arr_repr).get_type() == Variant::NIL);
ERR_PRINT_ON;
// Packed arrays.
test(PackedByteArray({ 1, 2, 3 }), R"({"type":"PackedByteArray","args":[1,2,3]})");
test(PackedInt32Array({ 1, 2, 3 }), R"({"type":"PackedInt32Array","args":[1,2,3]})");
test(PackedInt64Array({ 1, 2, 3 }), R"({"type":"PackedInt64Array","args":[1,2,3]})");
test(PackedFloat32Array({ 1, 2, 3 }), R"({"type":"PackedFloat32Array","args":[1.0,2.0,3.0]})");
test(PackedFloat64Array({ 1, 2, 3 }), R"({"type":"PackedFloat64Array","args":[1.0,2.0,3.0]})");
test(PackedStringArray({ "a", "b", "c" }), R"({"type":"PackedStringArray","args":["a","b","c"]})");
const PackedVector2Array pv2arr({ Vector2(1, 2), Vector2(3, 4), Vector2(5, 6) });
test(pv2arr, R"({"type":"PackedVector2Array","args":[1.0,2.0,3.0,4.0,5.0,6.0]})");
const PackedVector3Array pv3arr({ Vector3(1, 2, 3), Vector3(4, 5, 6), Vector3(7, 8, 9) });
test(pv3arr, R"({"type":"PackedVector3Array","args":[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0]})");
const PackedColorArray pcolarr({ Color(1, 2, 3, 4), Color(5, 6, 7, 8), Color(9, 10, 11, 12) });
test(pcolarr, R"({"type":"PackedColorArray","args":[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0]})");
const PackedVector4Array pv4arr({ Vector4(1, 2, 3, 4), Vector4(5, 6, 7, 8), Vector4(9, 10, 11, 12) });
test(pv4arr, R"({"type":"PackedVector4Array","args":[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0]})");
}
} // namespace TestJSONNative
#endif // TEST_JSON_NATIVE_H

View file

@ -0,0 +1,170 @@
/**************************************************************************/
/* test_logger.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_LOGGER_H
#define TEST_LOGGER_H
#include "core/io/dir_access.h"
#include "core/io/logger.h"
#include "modules/regex/regex.h"
#include "tests/test_macros.h"
namespace TestLogger {
constexpr int sleep_duration = 1200000;
void initialize_logs() {
ProjectSettings::get_singleton()->set_setting("application/config/name", "godot_tests");
DirAccess::make_dir_recursive_absolute(OS::get_singleton()->get_user_data_dir().path_join("logs"));
}
void cleanup_logs() {
ProjectSettings::get_singleton()->set_setting("application/config/name", "godot_tests");
Ref<DirAccess> dir = DirAccess::open("user://logs");
dir->list_dir_begin();
String file = dir->get_next();
while (file != "") {
if (file.match("*.log")) {
dir->remove(file);
}
file = dir->get_next();
}
DirAccess::remove_absolute(OS::get_singleton()->get_user_data_dir().path_join("logs"));
DirAccess::remove_absolute(OS::get_singleton()->get_user_data_dir());
}
TEST_CASE("[Logger][RotatedFileLogger] Creates the first log file and logs on it") {
initialize_logs();
String waiting_for_godot = "Waiting for Godot";
RotatedFileLogger logger("user://logs/godot.log");
logger.logf("%s", "Waiting for Godot");
Error err = Error::OK;
Ref<FileAccess> log = FileAccess::open("user://logs/godot.log", FileAccess::READ, &err);
CHECK_EQ(err, Error::OK);
CHECK_EQ(log->get_as_text(), waiting_for_godot);
cleanup_logs();
}
void get_log_files(Vector<String> &log_files) {
Ref<DirAccess> dir = DirAccess::open("user://logs");
dir->list_dir_begin();
String file = dir->get_next();
while (file != "") {
// Filtering godot.log because ordered_insert will put it first and should be the last.
if (file.match("*.log") && file != "godot.log") {
log_files.ordered_insert(file);
}
file = dir->get_next();
}
if (FileAccess::exists("user://logs/godot.log")) {
log_files.push_back("godot.log");
}
}
// All things related to log file rotation are in the same test because testing it require some sleeps.
TEST_CASE("[Logger][RotatedFileLogger] Rotates logs files") {
initialize_logs();
Vector<String> all_waiting_for_godot;
const int number_of_files = 3;
for (int i = 0; i < number_of_files; i++) {
String waiting_for_godot = "Waiting for Godot " + itos(i);
RotatedFileLogger logger("user://logs/godot.log", number_of_files);
logger.logf("%s", waiting_for_godot.ascii().get_data());
all_waiting_for_godot.push_back(waiting_for_godot);
// Required to ensure the rotation of the log file.
OS::get_singleton()->delay_usec(sleep_duration);
}
Vector<String> log_files;
get_log_files(log_files);
CHECK_MESSAGE(log_files.size() == number_of_files, "Did not rotate all files");
for (int i = 0; i < log_files.size(); i++) {
Error err = Error::OK;
Ref<FileAccess> log_file = FileAccess::open("user://logs/" + log_files[i], FileAccess::READ, &err);
REQUIRE_EQ(err, Error::OK);
CHECK_EQ(log_file->get_as_text(), all_waiting_for_godot[i]);
}
// Required to ensure the rotation of the log file.
OS::get_singleton()->delay_usec(sleep_duration);
// This time the oldest log must be removed and godot.log updated.
String new_waiting_for_godot = "Waiting for Godot " + itos(number_of_files);
all_waiting_for_godot = all_waiting_for_godot.slice(1, all_waiting_for_godot.size());
all_waiting_for_godot.push_back(new_waiting_for_godot);
RotatedFileLogger logger("user://logs/godot.log", number_of_files);
logger.logf("%s", new_waiting_for_godot.ascii().get_data());
log_files.clear();
get_log_files(log_files);
CHECK_MESSAGE(log_files.size() == number_of_files, "Did not remove old log file");
for (int i = 0; i < log_files.size(); i++) {
Error err = Error::OK;
Ref<FileAccess> log_file = FileAccess::open("user://logs/" + log_files[i], FileAccess::READ, &err);
REQUIRE_EQ(err, Error::OK);
CHECK_EQ(log_file->get_as_text(), all_waiting_for_godot[i]);
}
cleanup_logs();
}
TEST_CASE("[Logger][CompositeLogger] Logs the same into multiple loggers") {
initialize_logs();
Vector<Logger *> all_loggers;
all_loggers.push_back(memnew(RotatedFileLogger("user://logs/godot_logger_1.log", 1)));
all_loggers.push_back(memnew(RotatedFileLogger("user://logs/godot_logger_2.log", 1)));
String waiting_for_godot = "Waiting for Godot";
CompositeLogger logger(all_loggers);
logger.logf("%s", "Waiting for Godot");
Error err = Error::OK;
Ref<FileAccess> log = FileAccess::open("user://logs/godot_logger_1.log", FileAccess::READ, &err);
CHECK_EQ(err, Error::OK);
CHECK_EQ(log->get_as_text(), waiting_for_godot);
log = FileAccess::open("user://logs/godot_logger_2.log", FileAccess::READ, &err);
CHECK_EQ(err, Error::OK);
CHECK_EQ(log->get_as_text(), waiting_for_godot);
cleanup_logs();
}
} // namespace TestLogger
#endif // TEST_LOGGER_H

View file

@ -90,6 +90,20 @@ TEST_CASE("[Marshalls] Unsigned 64 bit integer decoding") {
CHECK(decode_uint64(arr) == 0x0f123456789abcdef);
}
TEST_CASE("[Marshalls] Floating point half precision encoding") {
uint8_t arr[2];
// Decimal: 0.33325195
// IEEE 754 half-precision binary floating-point format:
// sign exponent (5 bits) fraction (10 bits)
// 0 01101 0101010101
// Hexadecimal: 0x3555
unsigned int actual_size = encode_half(0.33325195f, arr);
CHECK(actual_size == sizeof(uint16_t));
CHECK(arr[0] == 0x55);
CHECK(arr[1] == 0x35);
}
TEST_CASE("[Marshalls] Floating point single precision encoding") {
uint8_t arr[4];
@ -126,6 +140,13 @@ TEST_CASE("[Marshalls] Floating point double precision encoding") {
CHECK(arr[7] == 0x3f);
}
TEST_CASE("[Marshalls] Floating point half precision decoding") {
uint8_t arr[] = { 0x55, 0x35 };
// See floating point half precision encoding test case for details behind expected values.
CHECK(decode_half(arr) == 0.33325195f);
}
TEST_CASE("[Marshalls] Floating point single precision decoding") {
uint8_t arr[] = { 0x00, 0x00, 0x20, 0x3e };
@ -160,7 +181,7 @@ TEST_CASE("[Marshalls] NIL Variant encoding") {
uint8_t buffer[4];
CHECK(encode_variant(variant, buffer, r_len) == OK);
CHECK_MESSAGE(r_len == 4, "Length == 4 bytes for header");
CHECK_MESSAGE(r_len == 4, "Length == 4 bytes for header.");
CHECK_MESSAGE(buffer[0] == 0x00, "Variant::NIL");
CHECK(buffer[1] == 0x00);
CHECK(buffer[2] == 0x00);
@ -174,7 +195,7 @@ TEST_CASE("[Marshalls] INT 32 bit Variant encoding") {
uint8_t buffer[8];
CHECK(encode_variant(variant, buffer, r_len) == OK);
CHECK_MESSAGE(r_len == 8, "Length == 4 bytes for header + 4 bytes for int32_t");
CHECK_MESSAGE(r_len == 8, "Length == 4 bytes for header + 4 bytes for `int32_t`.");
CHECK_MESSAGE(buffer[0] == 0x02, "Variant::INT");
CHECK(buffer[1] == 0x00);
CHECK(buffer[2] == 0x00);
@ -192,7 +213,7 @@ TEST_CASE("[Marshalls] INT 64 bit Variant encoding") {
uint8_t buffer[12];
CHECK(encode_variant(variant, buffer, r_len) == OK);
CHECK_MESSAGE(r_len == 12, "Length == 4 bytes for header + 8 bytes for int64_t");
CHECK_MESSAGE(r_len == 12, "Length == 4 bytes for header + 8 bytes for `int64_t`.");
CHECK_MESSAGE(buffer[0] == 0x02, "Variant::INT");
CHECK(buffer[1] == 0x00);
CHECK_MESSAGE(buffer[2] == 0x01, "HEADER_DATA_FLAG_64");
@ -214,7 +235,7 @@ TEST_CASE("[Marshalls] FLOAT single precision Variant encoding") {
uint8_t buffer[8];
CHECK(encode_variant(variant, buffer, r_len) == OK);
CHECK_MESSAGE(r_len == 8, "Length == 4 bytes for header + 4 bytes for float");
CHECK_MESSAGE(r_len == 8, "Length == 4 bytes for header + 4 bytes for `float`.");
CHECK_MESSAGE(buffer[0] == 0x03, "Variant::FLOAT");
CHECK(buffer[1] == 0x00);
CHECK(buffer[2] == 0x00);
@ -232,7 +253,7 @@ TEST_CASE("[Marshalls] FLOAT double precision Variant encoding") {
uint8_t buffer[12];
CHECK(encode_variant(variant, buffer, r_len) == OK);
CHECK_MESSAGE(r_len == 12, "Length == 4 bytes for header + 8 bytes for double");
CHECK_MESSAGE(r_len == 12, "Length == 4 bytes for header + 8 bytes for `double`.");
CHECK_MESSAGE(buffer[0] == 0x03, "Variant::FLOAT");
CHECK(buffer[1] == 0x00);
CHECK_MESSAGE(buffer[2] == 0x01, "HEADER_DATA_FLAG_64");
@ -335,10 +356,10 @@ TEST_CASE("[Marshalls] Typed array encoding") {
uint8_t buffer[24];
CHECK(encode_variant(array, buffer, r_len) == OK);
CHECK_MESSAGE(r_len == 24, "Length == 4 bytes for header + 4 bytes for array type + 4 bytes for array size + 12 bytes for element");
CHECK_MESSAGE(r_len == 24, "Length == 4 bytes for header + 4 bytes for array type + 4 bytes for array size + 12 bytes for element.");
CHECK_MESSAGE(buffer[0] == 0x1c, "Variant::ARRAY");
CHECK(buffer[1] == 0x00);
CHECK_MESSAGE(buffer[2] == 0x01, "HEADER_DATA_FIELD_TYPED_ARRAY_BUILTIN");
CHECK_MESSAGE(buffer[2] == 0x01, "CONTAINER_TYPE_KIND_BUILTIN");
CHECK(buffer[3] == 0x00);
// Check array type.
CHECK_MESSAGE(buffer[4] == 0x02, "Variant::INT");
@ -370,7 +391,7 @@ TEST_CASE("[Marshalls] Typed array decoding") {
Variant variant;
int r_len;
uint8_t buffer[] = {
0x1c, 0x00, 0x01, 0x00, // Variant::ARRAY, HEADER_DATA_FIELD_TYPED_ARRAY_BUILTIN
0x1c, 0x00, 0x01, 0x00, // Variant::ARRAY, CONTAINER_TYPE_KIND_BUILTIN
0x02, 0x00, 0x00, 0x00, // Array type (Variant::INT).
0x01, 0x00, 0x00, 0x00, // Array size.
0x02, 0x00, 0x01, 0x00, // Element type (Variant::INT, HEADER_DATA_FLAG_64).
@ -386,6 +407,89 @@ TEST_CASE("[Marshalls] Typed array decoding") {
CHECK(array[0] == Variant(uint64_t(0x0f123456789abcdef)));
}
TEST_CASE("[Marshalls] Typed dicttionary encoding") {
int r_len;
Dictionary dictionary;
dictionary.set_typed(Variant::INT, StringName(), Ref<Script>(), Variant::INT, StringName(), Ref<Script>());
dictionary[Variant(uint64_t(0x0f123456789abcdef))] = Variant(uint64_t(0x0f123456789abcdef));
uint8_t buffer[40];
CHECK(encode_variant(dictionary, buffer, r_len) == OK);
CHECK_MESSAGE(r_len == 40, "Length == 4 bytes for header + 8 bytes for dictionary type + 4 bytes for dictionary size + 24 bytes for key-value pair.");
CHECK_MESSAGE(buffer[0] == 0x1b, "Variant::DICTIONARY");
CHECK(buffer[1] == 0x00);
CHECK_MESSAGE(buffer[2] == 0x05, "key: CONTAINER_TYPE_KIND_BUILTIN | value: CONTAINER_TYPE_KIND_BUILTIN");
CHECK(buffer[3] == 0x00);
// Check dictionary key type.
CHECK_MESSAGE(buffer[4] == 0x02, "Variant::INT");
CHECK(buffer[5] == 0x00);
CHECK(buffer[6] == 0x00);
CHECK(buffer[7] == 0x00);
// Check dictionary value type.
CHECK_MESSAGE(buffer[8] == 0x02, "Variant::INT");
CHECK(buffer[9] == 0x00);
CHECK(buffer[10] == 0x00);
CHECK(buffer[11] == 0x00);
// Check dictionary size.
CHECK(buffer[12] == 0x01);
CHECK(buffer[13] == 0x00);
CHECK(buffer[14] == 0x00);
CHECK(buffer[15] == 0x00);
// Check key type.
CHECK_MESSAGE(buffer[16] == 0x02, "Variant::INT");
CHECK(buffer[17] == 0x00);
CHECK_MESSAGE(buffer[18] == 0x01, "HEADER_DATA_FLAG_64");
CHECK(buffer[19] == 0x00);
// Check key value.
CHECK(buffer[20] == 0xef);
CHECK(buffer[21] == 0xcd);
CHECK(buffer[22] == 0xab);
CHECK(buffer[23] == 0x89);
CHECK(buffer[24] == 0x67);
CHECK(buffer[25] == 0x45);
CHECK(buffer[26] == 0x23);
CHECK(buffer[27] == 0xf1);
// Check value type.
CHECK_MESSAGE(buffer[28] == 0x02, "Variant::INT");
CHECK(buffer[29] == 0x00);
CHECK_MESSAGE(buffer[30] == 0x01, "HEADER_DATA_FLAG_64");
CHECK(buffer[31] == 0x00);
// Check value value.
CHECK(buffer[32] == 0xef);
CHECK(buffer[33] == 0xcd);
CHECK(buffer[34] == 0xab);
CHECK(buffer[35] == 0x89);
CHECK(buffer[36] == 0x67);
CHECK(buffer[37] == 0x45);
CHECK(buffer[38] == 0x23);
CHECK(buffer[39] == 0xf1);
}
TEST_CASE("[Marshalls] Typed dictionary decoding") {
Variant variant;
int r_len;
uint8_t buffer[] = {
0x1b, 0x00, 0x05, 0x00, // Variant::DICTIONARY, key: CONTAINER_TYPE_KIND_BUILTIN | value: CONTAINER_TYPE_KIND_BUILTIN
0x02, 0x00, 0x00, 0x00, // Dictionary key type (Variant::INT).
0x02, 0x00, 0x00, 0x00, // Dictionary value type (Variant::INT).
0x01, 0x00, 0x00, 0x00, // Dictionary size.
0x02, 0x00, 0x01, 0x00, // Key type (Variant::INT, HEADER_DATA_FLAG_64).
0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0xf1, // Key value.
0x02, 0x00, 0x01, 0x00, // Value type (Variant::INT, HEADER_DATA_FLAG_64).
0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0xf1, // Value value.
};
CHECK(decode_variant(variant, buffer, 40, &r_len) == OK);
CHECK(r_len == 40);
CHECK(variant.get_type() == Variant::DICTIONARY);
Dictionary dictionary = variant;
CHECK(dictionary.get_typed_key_builtin() == Variant::INT);
CHECK(dictionary.get_typed_value_builtin() == Variant::INT);
CHECK(dictionary.size() == 1);
CHECK(dictionary.has(Variant(uint64_t(0x0f123456789abcdef))));
CHECK(dictionary[Variant(uint64_t(0x0f123456789abcdef))] == Variant(uint64_t(0x0f123456789abcdef)));
}
} // namespace TestMarshalls
#endif // TEST_MARSHALLS_H

View file

@ -0,0 +1,204 @@
/**************************************************************************/
/* test_packet_peer.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_PACKET_PEER_H
#define TEST_PACKET_PEER_H
#include "core/io/packet_peer.h"
#include "tests/test_macros.h"
namespace TestPacketPeer {
TEST_CASE("[PacketPeer][PacketPeerStream] Encode buffer max size") {
Ref<PacketPeerStream> pps;
pps.instantiate();
SUBCASE("Default value") {
CHECK_EQ(pps->get_encode_buffer_max_size(), 8 * 1024 * 1024);
}
SUBCASE("Max encode buffer must be at least 1024 bytes") {
ERR_PRINT_OFF;
pps->set_encode_buffer_max_size(42);
ERR_PRINT_ON;
CHECK_EQ(pps->get_encode_buffer_max_size(), 8 * 1024 * 1024);
}
SUBCASE("Max encode buffer cannot exceed 256 MiB") {
ERR_PRINT_OFF;
pps->set_encode_buffer_max_size((256 * 1024 * 1024) + 42);
ERR_PRINT_ON;
CHECK_EQ(pps->get_encode_buffer_max_size(), 8 * 1024 * 1024);
}
SUBCASE("Should be next power of two") {
pps->set_encode_buffer_max_size(2000);
CHECK_EQ(pps->get_encode_buffer_max_size(), 2048);
}
}
TEST_CASE("[PacketPeer][PacketPeerStream] Read a variant from peer") {
String godot_rules = "Godot Rules!!!";
Ref<StreamPeerBuffer> spb;
spb.instantiate();
spb->put_var(godot_rules);
spb->seek(0);
Ref<PacketPeerStream> pps;
pps.instantiate();
pps->set_stream_peer(spb);
Variant value;
CHECK_EQ(pps->get_var(value), Error::OK);
CHECK_EQ(String(value), godot_rules);
}
TEST_CASE("[PacketPeer][PacketPeerStream] Read a variant from peer fails") {
Ref<PacketPeerStream> pps;
pps.instantiate();
Variant value;
ERR_PRINT_OFF;
CHECK_EQ(pps->get_var(value), Error::ERR_UNCONFIGURED);
ERR_PRINT_ON;
}
TEST_CASE("[PacketPeer][PacketPeerStream] Put a variant to peer") {
String godot_rules = "Godot Rules!!!";
Ref<StreamPeerBuffer> spb;
spb.instantiate();
Ref<PacketPeerStream> pps;
pps.instantiate();
pps->set_stream_peer(spb);
CHECK_EQ(pps->put_var(godot_rules), Error::OK);
spb->seek(0);
CHECK_EQ(String(spb->get_var()), godot_rules);
}
TEST_CASE("[PacketPeer][PacketPeerStream] Put a variant to peer out of memory failure") {
String more_than_1mb = String("*").repeat(1024 + 1);
Ref<StreamPeerBuffer> spb;
spb.instantiate();
Ref<PacketPeerStream> pps;
pps.instantiate();
pps->set_stream_peer(spb);
pps->set_encode_buffer_max_size(1024);
ERR_PRINT_OFF;
CHECK_EQ(pps->put_var(more_than_1mb), Error::ERR_OUT_OF_MEMORY);
ERR_PRINT_ON;
}
TEST_CASE("[PacketPeer][PacketPeerStream] Get packet buffer") {
String godot_rules = "Godot Rules!!!";
Ref<StreamPeerBuffer> spb;
spb.instantiate();
// First 4 bytes are the length of the string.
CharString cs = godot_rules.ascii();
Vector<uint8_t> buffer = { (uint8_t)(cs.length() + 1), 0, 0, 0 };
buffer.resize_zeroed(4 + cs.length() + 1);
memcpy(buffer.ptrw() + 4, cs.get_data(), cs.length());
spb->set_data_array(buffer);
Ref<PacketPeerStream> pps;
pps.instantiate();
pps->set_stream_peer(spb);
buffer.clear();
CHECK_EQ(pps->get_packet_buffer(buffer), Error::OK);
CHECK_EQ(String(reinterpret_cast<const char *>(buffer.ptr())), godot_rules);
}
TEST_CASE("[PacketPeer][PacketPeerStream] Get packet buffer from an empty peer") {
Ref<StreamPeerBuffer> spb;
spb.instantiate();
Ref<PacketPeerStream> pps;
pps.instantiate();
pps->set_stream_peer(spb);
Vector<uint8_t> buffer;
ERR_PRINT_OFF;
CHECK_EQ(pps->get_packet_buffer(buffer), Error::ERR_UNAVAILABLE);
ERR_PRINT_ON;
CHECK_EQ(buffer.size(), 0);
}
TEST_CASE("[PacketPeer][PacketPeerStream] Put packet buffer") {
String godot_rules = "Godot Rules!!!";
Ref<StreamPeerBuffer> spb;
spb.instantiate();
Ref<PacketPeerStream> pps;
pps.instantiate();
pps->set_stream_peer(spb);
CHECK_EQ(pps->put_packet_buffer(godot_rules.to_ascii_buffer()), Error::OK);
spb->seek(0);
CHECK_EQ(spb->get_string(), godot_rules);
// First 4 bytes are the length of the string.
CharString cs = godot_rules.ascii();
Vector<uint8_t> buffer = { (uint8_t)cs.length(), 0, 0, 0 };
buffer.resize(4 + cs.length());
memcpy(buffer.ptrw() + 4, cs.get_data(), cs.length());
CHECK_EQ(spb->get_data_array(), buffer);
}
TEST_CASE("[PacketPeer][PacketPeerStream] Put packet buffer when is empty") {
Ref<StreamPeerBuffer> spb;
spb.instantiate();
Ref<PacketPeerStream> pps;
pps.instantiate();
pps->set_stream_peer(spb);
Vector<uint8_t> buffer;
CHECK_EQ(pps->put_packet_buffer(buffer), Error::OK);
CHECK_EQ(spb->get_size(), 0);
}
} // namespace TestPacketPeer
#endif // TEST_PACKET_PEER_H

View file

@ -0,0 +1,71 @@
/**************************************************************************/
/* test_resource_uid.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_RESOURCE_UID_H
#define TEST_RESOURCE_UID_H
#include "core/io/resource_uid.h"
#include "thirdparty/doctest/doctest.h"
#include "tests/test_macros.h"
namespace TestResourceUID {
TEST_CASE("[ResourceUID] Must encode/decode maximum/minimum UID correctly") {
CHECK_MESSAGE(ResourceUID::get_singleton()->id_to_text(0x7fffffffffffffff) == "uid://d4n4ub6itg400", "Maximum UID must encode correctly.");
CHECK_MESSAGE(ResourceUID::get_singleton()->text_to_id("uid://d4n4ub6itg400") == 0x7fffffffffffffff, "Maximum UID must decode correctly.");
CHECK_MESSAGE(ResourceUID::get_singleton()->id_to_text(0) == "uid://a", "Minimum UID must encode correctly.");
CHECK_MESSAGE(ResourceUID::get_singleton()->text_to_id("uid://a") == 0, "Minimum UID must decode correctly.");
}
TEST_CASE("[ResourceUID] Must encode and decode invalid UIDs correctly") {
ResourceUID *rid = ResourceUID::get_singleton();
CHECK_MESSAGE(rid->id_to_text(-1) == "uid://<invalid>", "Invalid UID -1 must encode correctly.");
CHECK_MESSAGE(rid->text_to_id("uid://<invalid>") == -1, "Invalid UID -1 must decode correctly.");
CHECK_MESSAGE(rid->id_to_text(-2) == rid->id_to_text(-1), "Invalid UID -2 must encode to the same as -1.");
CHECK_MESSAGE(rid->text_to_id("dm3rdgs30kfci") == -1, "UID without scheme must decode correctly.");
}
TEST_CASE("[ResourceUID] Must encode and decode various UIDs correctly") {
ResourceUID *rid = ResourceUID::get_singleton();
CHECK_MESSAGE(rid->id_to_text(1) == "uid://b", "UID 1 must encode correctly.");
CHECK_MESSAGE(rid->text_to_id("uid://b") == 1, "UID 1 must decode correctly.");
CHECK_MESSAGE(rid->id_to_text(8060368642360689600) == "uid://dm3rdgs30kfci", "A normal UID must encode correctly.");
CHECK_MESSAGE(rid->text_to_id("uid://dm3rdgs30kfci") == 8060368642360689600, "A normal UID must decode correctly.");
}
} // namespace TestResourceUID
#endif // TEST_RESOURCE_UID_H

View file

@ -0,0 +1,311 @@
/**************************************************************************/
/* test_stream_peer.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_STREAM_PEER_H
#define TEST_STREAM_PEER_H
#include "core/io/stream_peer.h"
#include "tests/test_macros.h"
namespace TestStreamPeer {
TEST_CASE("[StreamPeer] Initialization through StreamPeerBuffer") {
Ref<StreamPeerBuffer> spb;
spb.instantiate();
CHECK_EQ(spb->is_big_endian_enabled(), false);
}
TEST_CASE("[StreamPeer] Get and sets through StreamPeerBuffer") {
Ref<StreamPeerBuffer> spb;
spb.instantiate();
SUBCASE("A int8_t value") {
int8_t value = 42;
spb->clear();
spb->put_8(value);
spb->seek(0);
CHECK_EQ(spb->get_8(), value);
}
SUBCASE("A uint8_t value") {
uint8_t value = 42;
spb->clear();
spb->put_u8(value);
spb->seek(0);
CHECK_EQ(spb->get_u8(), value);
}
SUBCASE("A int16_t value") {
int16_t value = 42;
spb->clear();
spb->put_16(value);
spb->seek(0);
CHECK_EQ(spb->get_16(), value);
}
SUBCASE("A uint16_t value") {
uint16_t value = 42;
spb->clear();
spb->put_u16(value);
spb->seek(0);
CHECK_EQ(spb->get_u16(), value);
}
SUBCASE("A int32_t value") {
int32_t value = 42;
spb->clear();
spb->put_32(value);
spb->seek(0);
CHECK_EQ(spb->get_32(), value);
}
SUBCASE("A uint32_t value") {
uint32_t value = 42;
spb->clear();
spb->put_u32(value);
spb->seek(0);
CHECK_EQ(spb->get_u32(), value);
}
SUBCASE("A int64_t value") {
int64_t value = 42;
spb->clear();
spb->put_64(value);
spb->seek(0);
CHECK_EQ(spb->get_64(), value);
}
SUBCASE("A int64_t value") {
uint64_t value = 42;
spb->clear();
spb->put_u64(value);
spb->seek(0);
CHECK_EQ(spb->get_u64(), value);
}
SUBCASE("A half-precision float value") {
float value = 3.1415927f;
float expected = 3.14062f;
spb->clear();
spb->put_half(value);
spb->seek(0);
CHECK(spb->get_half() == doctest::Approx(expected));
}
SUBCASE("A float value") {
float value = 42.0f;
spb->clear();
spb->put_float(value);
spb->seek(0);
CHECK_EQ(spb->get_float(), value);
}
SUBCASE("A double value") {
double value = 42.0;
spb->clear();
spb->put_double(value);
spb->seek(0);
CHECK_EQ(spb->get_double(), value);
}
SUBCASE("A string value") {
String value = "Hello, World!";
spb->clear();
spb->put_string(value);
spb->seek(0);
CHECK_EQ(spb->get_string(), value);
}
SUBCASE("A utf8 string value") {
String value = String::utf8("Hello✩, World✩!");
spb->clear();
spb->put_utf8_string(value);
spb->seek(0);
CHECK_EQ(spb->get_utf8_string(), value);
}
SUBCASE("A variant value") {
Array value;
value.push_front(42);
value.push_front("Hello, World!");
spb->clear();
spb->put_var(value);
spb->seek(0);
CHECK_EQ(spb->get_var(), value);
}
}
TEST_CASE("[StreamPeer] Get and sets big endian through StreamPeerBuffer") {
Ref<StreamPeerBuffer> spb;
spb.instantiate();
spb->set_big_endian(true);
SUBCASE("A int16_t value") {
int16_t value = 42;
spb->clear();
spb->put_16(value);
spb->seek(0);
CHECK_EQ(spb->get_16(), value);
}
SUBCASE("A uint16_t value") {
uint16_t value = 42;
spb->clear();
spb->put_u16(value);
spb->seek(0);
CHECK_EQ(spb->get_u16(), value);
}
SUBCASE("A int32_t value") {
int32_t value = 42;
spb->clear();
spb->put_32(value);
spb->seek(0);
CHECK_EQ(spb->get_32(), value);
}
SUBCASE("A uint32_t value") {
uint32_t value = 42;
spb->clear();
spb->put_u32(value);
spb->seek(0);
CHECK_EQ(spb->get_u32(), value);
}
SUBCASE("A int64_t value") {
int64_t value = 42;
spb->clear();
spb->put_64(value);
spb->seek(0);
CHECK_EQ(spb->get_64(), value);
}
SUBCASE("A int64_t value") {
uint64_t value = 42;
spb->clear();
spb->put_u64(value);
spb->seek(0);
CHECK_EQ(spb->get_u64(), value);
}
SUBCASE("A float value") {
float value = 42.0f;
spb->clear();
spb->put_float(value);
spb->seek(0);
CHECK_EQ(spb->get_float(), value);
}
SUBCASE("A half-precision float value") {
float value = 3.1415927f;
float expected = 3.14062f;
spb->clear();
spb->put_half(value);
spb->seek(0);
CHECK(spb->get_half() == doctest::Approx(expected));
}
SUBCASE("A double value") {
double value = 42.0;
spb->clear();
spb->put_double(value);
spb->seek(0);
CHECK_EQ(spb->get_double(), value);
}
}
TEST_CASE("[StreamPeer] Get string when there is no string") {
Ref<StreamPeerBuffer> spb;
spb.instantiate();
ERR_PRINT_OFF;
CHECK_EQ(spb->get_string(), "");
ERR_PRINT_ON;
}
TEST_CASE("[StreamPeer] Get UTF8 string when there is no string") {
Ref<StreamPeerBuffer> spb;
spb.instantiate();
ERR_PRINT_OFF;
CHECK_EQ(spb->get_utf8_string(), "");
ERR_PRINT_ON;
}
} // namespace TestStreamPeer
#endif // TEST_STREAM_PEER_H

View file

@ -0,0 +1,185 @@
/**************************************************************************/
/* test_stream_peer_buffer.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_STREAM_PEER_BUFFER_H
#define TEST_STREAM_PEER_BUFFER_H
#include "core/io/stream_peer.h"
#include "tests/test_macros.h"
namespace TestStreamPeerBuffer {
TEST_CASE("[StreamPeerBuffer] Initialization") {
Ref<StreamPeerBuffer> spb;
spb.instantiate();
CHECK_EQ(spb->get_size(), 0);
CHECK_EQ(spb->get_position(), 0);
CHECK_EQ(spb->get_available_bytes(), 0);
}
TEST_CASE("[StreamPeerBuffer] Seek") {
Ref<StreamPeerBuffer> spb;
spb.instantiate();
uint8_t first = 5;
uint8_t second = 7;
uint8_t third = 11;
spb->put_u8(first);
spb->put_u8(second);
spb->put_u8(third);
spb->seek(0);
CHECK_EQ(spb->get_u8(), first);
CHECK_EQ(spb->get_u8(), second);
CHECK_EQ(spb->get_u8(), third);
spb->seek(1);
CHECK_EQ(spb->get_position(), 1);
CHECK_EQ(spb->get_u8(), second);
spb->seek(1);
ERR_PRINT_OFF;
spb->seek(-1);
ERR_PRINT_ON;
CHECK_EQ(spb->get_position(), 1);
ERR_PRINT_OFF;
spb->seek(5);
ERR_PRINT_ON;
CHECK_EQ(spb->get_position(), 1);
}
TEST_CASE("[StreamPeerBuffer] Resize") {
Ref<StreamPeerBuffer> spb;
spb.instantiate();
CHECK_EQ(spb->get_size(), 0);
CHECK_EQ(spb->get_position(), 0);
CHECK_EQ(spb->get_available_bytes(), 0);
spb->resize(42);
CHECK_EQ(spb->get_size(), 42);
CHECK_EQ(spb->get_position(), 0);
CHECK_EQ(spb->get_available_bytes(), 42);
spb->seek(21);
CHECK_EQ(spb->get_size(), 42);
CHECK_EQ(spb->get_position(), 21);
CHECK_EQ(spb->get_available_bytes(), 21);
}
TEST_CASE("[StreamPeerBuffer] Get underlying data array") {
uint8_t first = 5;
uint8_t second = 7;
uint8_t third = 11;
Ref<StreamPeerBuffer> spb;
spb.instantiate();
spb->put_u8(first);
spb->put_u8(second);
spb->put_u8(third);
Vector<uint8_t> data_array = spb->get_data_array();
CHECK_EQ(data_array[0], first);
CHECK_EQ(data_array[1], second);
CHECK_EQ(data_array[2], third);
}
TEST_CASE("[StreamPeerBuffer] Set underlying data array") {
uint8_t first = 5;
uint8_t second = 7;
uint8_t third = 11;
Ref<StreamPeerBuffer> spb;
spb.instantiate();
spb->put_u8(1);
spb->put_u8(2);
spb->put_u8(3);
Vector<uint8_t> new_data_array;
new_data_array.push_back(first);
new_data_array.push_back(second);
new_data_array.push_back(third);
spb->set_data_array(new_data_array);
CHECK_EQ(spb->get_u8(), first);
CHECK_EQ(spb->get_u8(), second);
CHECK_EQ(spb->get_u8(), third);
}
TEST_CASE("[StreamPeerBuffer] Duplicate") {
uint8_t first = 5;
uint8_t second = 7;
uint8_t third = 11;
Ref<StreamPeerBuffer> spb;
spb.instantiate();
spb->put_u8(first);
spb->put_u8(second);
spb->put_u8(third);
Ref<StreamPeerBuffer> spb2 = spb->duplicate();
CHECK_EQ(spb2->get_u8(), first);
CHECK_EQ(spb2->get_u8(), second);
CHECK_EQ(spb2->get_u8(), third);
}
TEST_CASE("[StreamPeerBuffer] Put data with size equal to zero does nothing") {
Ref<StreamPeerBuffer> spb;
spb.instantiate();
uint8_t data = 42;
Error error = spb->put_data((const uint8_t *)&data, 0);
CHECK_EQ(error, OK);
CHECK_EQ(spb->get_size(), 0);
CHECK_EQ(spb->get_position(), 0);
CHECK_EQ(spb->get_available_bytes(), 0);
}
TEST_CASE("[StreamPeerBuffer] Get data with invalid size returns an error") {
Ref<StreamPeerBuffer> spb;
spb.instantiate();
uint8_t data = 42;
spb->put_u8(data);
spb->seek(0);
uint8_t data_out = 0;
Error error = spb->get_data(&data_out, 3);
CHECK_EQ(error, ERR_INVALID_PARAMETER);
CHECK_EQ(spb->get_size(), 1);
CHECK_EQ(spb->get_position(), 1);
}
} // namespace TestStreamPeerBuffer
#endif // TEST_STREAM_PEER_BUFFER_H

View file

@ -0,0 +1,255 @@
/**************************************************************************/
/* test_tcp_server.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_TCP_SERVER_H
#define TEST_TCP_SERVER_H
#include "core/io/stream_peer_tcp.h"
#include "core/io/tcp_server.h"
#include "tests/test_macros.h"
#include <functional>
namespace TestTCPServer {
const int PORT = 12345;
const IPAddress LOCALHOST("127.0.0.1");
const uint32_t SLEEP_DURATION = 1000;
const uint64_t MAX_WAIT_USEC = 2000000;
void wait_for_condition(std::function<bool()> f_test) {
const uint64_t time = OS::get_singleton()->get_ticks_usec();
while (!f_test() && (OS::get_singleton()->get_ticks_usec() - time) < MAX_WAIT_USEC) {
OS::get_singleton()->delay_usec(SLEEP_DURATION);
}
}
Ref<TCPServer> create_server(const IPAddress &p_address, int p_port) {
Ref<TCPServer> server;
server.instantiate();
REQUIRE_EQ(server->listen(PORT, LOCALHOST), Error::OK);
REQUIRE(server->is_listening());
CHECK_FALSE(server->is_connection_available());
return server;
}
Ref<StreamPeerTCP> create_client(const IPAddress &p_address, int p_port) {
Ref<StreamPeerTCP> client;
client.instantiate();
REQUIRE_EQ(client->connect_to_host(LOCALHOST, PORT), Error::OK);
CHECK_EQ(client->get_connected_host(), LOCALHOST);
CHECK_EQ(client->get_connected_port(), PORT);
CHECK_EQ(client->get_status(), StreamPeerTCP::STATUS_CONNECTING);
return client;
}
Ref<StreamPeerTCP> accept_connection(Ref<TCPServer> &p_server) {
wait_for_condition([&]() {
return p_server->is_connection_available();
});
REQUIRE(p_server->is_connection_available());
Ref<StreamPeerTCP> client_from_server = p_server->take_connection();
REQUIRE(client_from_server.is_valid());
CHECK_EQ(client_from_server->get_connected_host(), LOCALHOST);
CHECK_EQ(client_from_server->get_status(), StreamPeerTCP::STATUS_CONNECTED);
return client_from_server;
}
TEST_CASE("[TCPServer] Instantiation") {
Ref<TCPServer> server;
server.instantiate();
REQUIRE(server.is_valid());
CHECK_EQ(false, server->is_listening());
}
TEST_CASE("[TCPServer] Accept a connection and receive/send data") {
Ref<TCPServer> server = create_server(LOCALHOST, PORT);
Ref<StreamPeerTCP> client = create_client(LOCALHOST, PORT);
Ref<StreamPeerTCP> client_from_server = accept_connection(server);
wait_for_condition([&]() {
return client->poll() != Error::OK || client->get_status() == StreamPeerTCP::STATUS_CONNECTED;
});
CHECK_EQ(client->get_status(), StreamPeerTCP::STATUS_CONNECTED);
// Sending data from client to server.
const String hello_world = "Hello World!";
client->put_string(hello_world);
CHECK_EQ(client_from_server->get_string(), hello_world);
// Sending data from server to client.
const float pi = 3.1415;
client_from_server->put_float(pi);
CHECK_EQ(client->get_float(), pi);
client->disconnect_from_host();
server->stop();
CHECK_FALSE(server->is_listening());
}
TEST_CASE("[TCPServer] Handle multiple clients at the same time") {
Ref<TCPServer> server = create_server(LOCALHOST, PORT);
Vector<Ref<StreamPeerTCP>> clients;
for (int i = 0; i < 5; i++) {
clients.push_back(create_client(LOCALHOST, PORT));
}
Vector<Ref<StreamPeerTCP>> clients_from_server;
for (int i = 0; i < clients.size(); i++) {
clients_from_server.push_back(accept_connection(server));
}
wait_for_condition([&]() {
bool should_exit = true;
for (Ref<StreamPeerTCP> &c : clients) {
if (c->poll() != Error::OK) {
return true;
}
StreamPeerTCP::Status status = c->get_status();
if (status != StreamPeerTCP::STATUS_CONNECTED && status != StreamPeerTCP::STATUS_CONNECTING) {
return true;
}
if (status != StreamPeerTCP::STATUS_CONNECTED) {
should_exit = false;
}
}
return should_exit;
});
for (Ref<StreamPeerTCP> &c : clients) {
REQUIRE_EQ(c->get_status(), StreamPeerTCP::STATUS_CONNECTED);
}
// Sending data from each client to server.
for (int i = 0; i < clients.size(); i++) {
String hello_client = "Hello " + itos(i);
clients[i]->put_string(hello_client);
CHECK_EQ(clients_from_server[i]->get_string(), hello_client);
}
for (Ref<StreamPeerTCP> &c : clients) {
c->disconnect_from_host();
}
server->stop();
}
TEST_CASE("[TCPServer] When stopped shouldn't accept new connections") {
Ref<TCPServer> server = create_server(LOCALHOST, PORT);
Ref<StreamPeerTCP> client = create_client(LOCALHOST, PORT);
Ref<StreamPeerTCP> client_from_server = accept_connection(server);
wait_for_condition([&]() {
return client->poll() != Error::OK || client->get_status() == StreamPeerTCP::STATUS_CONNECTED;
});
CHECK_EQ(client->get_status(), StreamPeerTCP::STATUS_CONNECTED);
// Sending data from client to server.
const String hello_world = "Hello World!";
client->put_string(hello_world);
CHECK_EQ(client_from_server->get_string(), hello_world);
client->disconnect_from_host();
server->stop();
CHECK_FALSE(server->is_listening());
// Make sure the client times out in less than the wait time.
int timeout = ProjectSettings::get_singleton()->get_setting("network/limits/tcp/connect_timeout_seconds");
ProjectSettings::get_singleton()->set_setting("network/limits/tcp/connect_timeout_seconds", 1);
Ref<StreamPeerTCP> new_client = create_client(LOCALHOST, PORT);
// Reset the timeout setting.
ProjectSettings::get_singleton()->set_setting("network/limits/tcp/connect_timeout_seconds", timeout);
CHECK_FALSE(server->is_connection_available());
wait_for_condition([&]() {
return new_client->poll() != Error::OK || new_client->get_status() == StreamPeerTCP::STATUS_ERROR;
});
CHECK_FALSE(server->is_connection_available());
CHECK_EQ(new_client->get_status(), StreamPeerTCP::STATUS_ERROR);
new_client->disconnect_from_host();
CHECK_EQ(new_client->get_status(), StreamPeerTCP::STATUS_NONE);
}
TEST_CASE("[TCPServer] Should disconnect client") {
Ref<TCPServer> server = create_server(LOCALHOST, PORT);
Ref<StreamPeerTCP> client = create_client(LOCALHOST, PORT);
Ref<StreamPeerTCP> client_from_server = accept_connection(server);
wait_for_condition([&]() {
return client->poll() != Error::OK || client->get_status() == StreamPeerTCP::STATUS_CONNECTED;
});
CHECK_EQ(client->get_status(), StreamPeerTCP::STATUS_CONNECTED);
// Sending data from client to server.
const String hello_world = "Hello World!";
client->put_string(hello_world);
CHECK_EQ(client_from_server->get_string(), hello_world);
client_from_server->disconnect_from_host();
server->stop();
CHECK_FALSE(server->is_listening());
// Wait for disconnection
wait_for_condition([&]() {
return client->poll() != Error::OK || client->get_status() == StreamPeerTCP::STATUS_NONE;
});
// Wait for disconnection
wait_for_condition([&]() {
return client_from_server->poll() != Error::OK || client_from_server->get_status() == StreamPeerTCP::STATUS_NONE;
});
CHECK_EQ(client->get_status(), StreamPeerTCP::STATUS_NONE);
CHECK_EQ(client_from_server->get_status(), StreamPeerTCP::STATUS_NONE);
ERR_PRINT_OFF;
CHECK_EQ(client->get_string(), String());
CHECK_EQ(client_from_server->get_string(), String());
ERR_PRINT_ON;
}
} // namespace TestTCPServer
#endif // TEST_TCP_SERVER_H

View file

@ -0,0 +1,219 @@
/**************************************************************************/
/* test_udp_server.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_UDP_SERVER_H
#define TEST_UDP_SERVER_H
#include "core/io/packet_peer_udp.h"
#include "core/io/udp_server.h"
#include "tests/test_macros.h"
namespace TestUDPServer {
const int PORT = 12345;
const IPAddress LOCALHOST("127.0.0.1");
const uint32_t SLEEP_DURATION = 1000;
const uint64_t MAX_WAIT_USEC = 100000;
void wait_for_condition(std::function<bool()> f_test) {
const uint64_t time = OS::get_singleton()->get_ticks_usec();
while (!f_test() && (OS::get_singleton()->get_ticks_usec() - time) < MAX_WAIT_USEC) {
OS::get_singleton()->delay_usec(SLEEP_DURATION);
}
}
Ref<UDPServer> create_server(const IPAddress &p_address, int p_port) {
Ref<UDPServer> server;
server.instantiate();
Error err = server->listen(PORT, LOCALHOST);
REQUIRE_EQ(Error::OK, err);
REQUIRE(server->is_listening());
CHECK_FALSE(server->is_connection_available());
CHECK_EQ(server->get_max_pending_connections(), 16);
return server;
}
Ref<PacketPeerUDP> create_client(const IPAddress &p_address, int p_port) {
Ref<PacketPeerUDP> client;
client.instantiate();
Error err = client->connect_to_host(LOCALHOST, PORT);
REQUIRE_EQ(Error::OK, err);
CHECK(client->is_bound());
CHECK(client->is_socket_connected());
return client;
}
Ref<PacketPeerUDP> accept_connection(Ref<UDPServer> &p_server) {
wait_for_condition([&]() {
return p_server->poll() != Error::OK || p_server->is_connection_available();
});
CHECK_EQ(p_server->poll(), Error::OK);
REQUIRE(p_server->is_connection_available());
Ref<PacketPeerUDP> client_from_server = p_server->take_connection();
REQUIRE(client_from_server.is_valid());
CHECK(client_from_server->is_bound());
CHECK(client_from_server->is_socket_connected());
return client_from_server;
}
TEST_CASE("[UDPServer] Instantiation") {
Ref<UDPServer> server;
server.instantiate();
REQUIRE(server.is_valid());
CHECK_EQ(false, server->is_listening());
}
TEST_CASE("[UDPServer] Accept a connection and receive/send data") {
Ref<UDPServer> server = create_server(LOCALHOST, PORT);
Ref<PacketPeerUDP> client = create_client(LOCALHOST, PORT);
// Sending data from client to server.
const String hello_world = "Hello World!";
CHECK_EQ(client->put_var(hello_world), Error::OK);
Variant hello_world_received;
Ref<PacketPeerUDP> client_from_server = accept_connection(server);
CHECK_EQ(client_from_server->get_var(hello_world_received), Error::OK);
CHECK_EQ(String(hello_world_received), hello_world);
// Sending data from server to client.
const Variant pi = 3.1415;
CHECK_EQ(client_from_server->put_var(pi), Error::OK);
wait_for_condition([&]() {
return client->get_available_packet_count() > 0;
});
CHECK_GT(client->get_available_packet_count(), 0);
Variant pi_received;
CHECK_EQ(client->get_var(pi_received), Error::OK);
CHECK_EQ(pi_received, pi);
client->close();
server->stop();
CHECK_FALSE(server->is_listening());
}
TEST_CASE("[UDPServer] Handle multiple clients at the same time") {
Ref<UDPServer> server = create_server(LOCALHOST, PORT);
Vector<Ref<PacketPeerUDP>> clients;
for (int i = 0; i < 5; i++) {
Ref<PacketPeerUDP> c = create_client(LOCALHOST, PORT);
// Sending data from client to server.
const String hello_client = itos(i);
CHECK_EQ(c->put_var(hello_client), Error::OK);
clients.push_back(c);
}
Array packets;
for (int i = 0; i < clients.size(); i++) {
Ref<PacketPeerUDP> cfs = accept_connection(server);
Variant received_var;
CHECK_EQ(cfs->get_var(received_var), Error::OK);
CHECK_EQ(received_var.get_type(), Variant::STRING);
packets.push_back(received_var);
// Sending data from server to client.
const float sent_float = 3.1415 + received_var.operator String().to_float();
CHECK_EQ(cfs->put_var(sent_float), Error::OK);
}
CHECK_EQ(packets.size(), clients.size());
packets.sort();
for (int i = 0; i < clients.size(); i++) {
CHECK_EQ(packets[i].operator String(), itos(i));
}
wait_for_condition([&]() {
bool should_exit = true;
for (Ref<PacketPeerUDP> &c : clients) {
int count = c->get_available_packet_count();
if (count < 0) {
return true;
}
if (count == 0) {
should_exit = false;
}
}
return should_exit;
});
for (int i = 0; i < clients.size(); i++) {
CHECK_GT(clients[i]->get_available_packet_count(), 0);
Variant received_var;
const float expected = 3.1415 + i;
CHECK_EQ(clients[i]->get_var(received_var), Error::OK);
CHECK_EQ(received_var.get_type(), Variant::FLOAT);
CHECK_EQ(received_var.operator float(), expected);
}
for (Ref<PacketPeerUDP> &c : clients) {
c->close();
}
server->stop();
}
TEST_CASE("[UDPServer] Should not accept new connections after stop") {
Ref<UDPServer> server = create_server(LOCALHOST, PORT);
Ref<PacketPeerUDP> client = create_client(LOCALHOST, PORT);
// Sending data from client to server.
const String hello_world = "Hello World!";
CHECK_EQ(client->put_var(hello_world), Error::OK);
wait_for_condition([&]() {
return server->poll() != Error::OK || server->is_connection_available();
});
REQUIRE(server->is_connection_available());
server->stop();
CHECK_FALSE(server->is_listening());
CHECK_FALSE(server->is_connection_available());
}
} // namespace TestUDPServer
#endif // TEST_UDP_SERVER_H

View file

@ -48,7 +48,7 @@ TEST_CASE("[AABB] Constructor methods") {
TEST_CASE("[AABB] String conversion") {
CHECK_MESSAGE(
String(AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6))) == "[P: (-1.5, 2, -2.5), S: (4, 5, 6)]",
String(AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6))) == "[P: (-1.5, 2.0, -2.5), S: (4.0, 5.0, 6.0)]",
"The string representation should match the expected value.");
}
@ -377,23 +377,23 @@ TEST_CASE("[AABB] Get longest/shortest axis") {
TEST_CASE("[AABB] Get support") {
const AABB aabb = AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6));
CHECK_MESSAGE(
aabb.get_support(Vector3(1, 0, 0)).is_equal_approx(Vector3(2.5, 2, -2.5)),
aabb.get_support(Vector3(1, 0, 0)) == Vector3(2.5, 2, -2.5),
"get_support() should return the expected value.");
CHECK_MESSAGE(
aabb.get_support(Vector3(0.5, 1, 0)).is_equal_approx(Vector3(2.5, 7, -2.5)),
aabb.get_support(Vector3(0.5, 1, 1)) == Vector3(2.5, 7, 3.5),
"get_support() should return the expected value.");
CHECK_MESSAGE(
aabb.get_support(Vector3(0.5, 1, -400)).is_equal_approx(Vector3(2.5, 7, -2.5)),
aabb.get_support(Vector3(0.5, 1, -400)) == Vector3(2.5, 7, -2.5),
"get_support() should return the expected value.");
CHECK_MESSAGE(
aabb.get_support(Vector3(0, -1, 0)).is_equal_approx(Vector3(-1.5, 2, -2.5)),
aabb.get_support(Vector3(0, -1, 0)) == Vector3(-1.5, 2, -2.5),
"get_support() should return the expected value.");
CHECK_MESSAGE(
aabb.get_support(Vector3(0, -0.1, 0)).is_equal_approx(Vector3(-1.5, 2, -2.5)),
aabb.get_support(Vector3(0, -0.1, 0)) == Vector3(-1.5, 2, -2.5),
"get_support() should return the expected value.");
CHECK_MESSAGE(
aabb.get_support(Vector3()).is_equal_approx(Vector3(-1.5, 2, -2.5)),
"get_support() should return the expected value with a null vector.");
aabb.get_support(Vector3()) == Vector3(-1.5, 2, -2.5),
"get_support() should return the AABB position when given a zero vector.");
}
TEST_CASE("[AABB] Grow") {

View file

@ -93,9 +93,9 @@ void test_rotation(Vector3 deg_original_euler, EulerOrder rot_order) {
Basis res = to_rotation.inverse() * rotation_from_computed_euler;
CHECK_MESSAGE((res.get_column(0) - Vector3(1.0, 0.0, 0.0)).length() <= 0.1, vformat("Fail due to X %s\n", String(res.get_column(0))));
CHECK_MESSAGE((res.get_column(1) - Vector3(0.0, 1.0, 0.0)).length() <= 0.1, vformat("Fail due to Y %s\n", String(res.get_column(1))));
CHECK_MESSAGE((res.get_column(2) - Vector3(0.0, 0.0, 1.0)).length() <= 0.1, vformat("Fail due to Z %s\n", String(res.get_column(2))));
CHECK_MESSAGE((res.get_column(0) - Vector3(1.0, 0.0, 0.0)).length() <= 0.001, vformat("Fail due to X %s\n", String(res.get_column(0))));
CHECK_MESSAGE((res.get_column(1) - Vector3(0.0, 1.0, 0.0)).length() <= 0.001, vformat("Fail due to Y %s\n", String(res.get_column(1))));
CHECK_MESSAGE((res.get_column(2) - Vector3(0.0, 0.0, 1.0)).length() <= 0.001, vformat("Fail due to Z %s\n", String(res.get_column(2))));
// Double check `to_rotation` decomposing with XYZ rotation order.
const Vector3 euler_xyz_from_rotation = to_rotation.get_euler(EulerOrder::XYZ);
@ -103,9 +103,9 @@ void test_rotation(Vector3 deg_original_euler, EulerOrder rot_order) {
res = to_rotation.inverse() * rotation_from_xyz_computed_euler;
CHECK_MESSAGE((res.get_column(0) - Vector3(1.0, 0.0, 0.0)).length() <= 0.1, vformat("Double check with XYZ rot order failed, due to X %s\n", String(res.get_column(0))));
CHECK_MESSAGE((res.get_column(1) - Vector3(0.0, 1.0, 0.0)).length() <= 0.1, vformat("Double check with XYZ rot order failed, due to Y %s\n", String(res.get_column(1))));
CHECK_MESSAGE((res.get_column(2) - Vector3(0.0, 0.0, 1.0)).length() <= 0.1, vformat("Double check with XYZ rot order failed, due to Z %s\n", String(res.get_column(2))));
CHECK_MESSAGE((res.get_column(0) - Vector3(1.0, 0.0, 0.0)).length() <= 0.001, vformat("Double check with XYZ rot order failed, due to X %s\n", String(res.get_column(0))));
CHECK_MESSAGE((res.get_column(1) - Vector3(0.0, 1.0, 0.0)).length() <= 0.001, vformat("Double check with XYZ rot order failed, due to Y %s\n", String(res.get_column(1))));
CHECK_MESSAGE((res.get_column(2) - Vector3(0.0, 0.0, 1.0)).length() <= 0.001, vformat("Double check with XYZ rot order failed, due to Z %s\n", String(res.get_column(2))));
INFO(vformat("Rotation order: %s\n.", get_rot_order_name(rot_order)));
INFO(vformat("Original Rotation: %s\n", String(deg_original_euler)));
@ -176,6 +176,12 @@ TEST_CASE("[Basis] Euler conversions") {
vectors_to_test.push_back(Vector3(120.0, -150.0, -130.0));
vectors_to_test.push_back(Vector3(120.0, 150.0, -130.0));
vectors_to_test.push_back(Vector3(120.0, 150.0, 130.0));
vectors_to_test.push_back(Vector3(89.9, 0.0, 0.0));
vectors_to_test.push_back(Vector3(-89.9, 0.0, 0.0));
vectors_to_test.push_back(Vector3(0.0, 89.9, 0.0));
vectors_to_test.push_back(Vector3(0.0, -89.9, 0.0));
vectors_to_test.push_back(Vector3(0.0, 0.0, 89.9));
vectors_to_test.push_back(Vector3(0.0, 0.0, -89.9));
for (int h = 0; h < euler_order_to_test.size(); h += 1) {
for (int i = 0; i < vectors_to_test.size(); i += 1) {

View file

@ -140,7 +140,7 @@ TEST_CASE("[Color] Conversion methods") {
cyan.to_rgba64() == 0x0000'ffff'ffff'ffff,
"The returned 64-bit BGR number should match the expected value.");
CHECK_MESSAGE(
String(cyan) == "(0, 1, 1, 1)",
String(cyan) == "(0.0, 1.0, 1.0, 1.0)",
"The string representation should match the expected value.");
}
@ -160,6 +160,12 @@ TEST_CASE("[Color] Linear <-> sRGB conversion") {
CHECK_MESSAGE(
color_srgb.srgb_to_linear().is_equal_approx(Color(0.35, 0.5, 0.6, 0.7)),
"The sRGB color converted back to linear color space should match the expected value.");
CHECK_MESSAGE(
Color(1.0, 1.0, 1.0, 1.0).srgb_to_linear() == (Color(1.0, 1.0, 1.0, 1.0)),
"White converted from sRGB to linear should remain white.");
CHECK_MESSAGE(
Color(1.0, 1.0, 1.0, 1.0).linear_to_srgb() == (Color(1.0, 1.0, 1.0, 1.0)),
"White converted from linear to sRGB should remain white.");
}
TEST_CASE("[Color] Named colors") {

View file

@ -122,12 +122,68 @@ TEST_CASE("[Expression] Floating-point arithmetic") {
"Float multiplication-addition-subtraction-division should return the expected result.");
}
TEST_CASE("[Expression] Floating-point notation") {
Expression expression;
CHECK_MESSAGE(
expression.parse("2.") == OK,
"The expression should parse successfully.");
CHECK_MESSAGE(
double(expression.execute()) == doctest::Approx(2.0),
"The expression should return the expected result.");
CHECK_MESSAGE(
expression.parse("(2.)") == OK,
"The expression should parse successfully.");
CHECK_MESSAGE(
double(expression.execute()) == doctest::Approx(2.0),
"The expression should return the expected result.");
CHECK_MESSAGE(
expression.parse(".3") == OK,
"The expression should parse successfully.");
CHECK_MESSAGE(
double(expression.execute()) == doctest::Approx(0.3),
"The expression should return the expected result.");
CHECK_MESSAGE(
expression.parse("2.+5.") == OK,
"The expression should parse successfully.");
CHECK_MESSAGE(
double(expression.execute()) == doctest::Approx(7.0),
"The expression should return the expected result.");
CHECK_MESSAGE(
expression.parse(".3-.8") == OK,
"The expression should parse successfully.");
CHECK_MESSAGE(
double(expression.execute()) == doctest::Approx(-0.5),
"The expression should return the expected result.");
CHECK_MESSAGE(
expression.parse("2.+.2") == OK,
"The expression should parse successfully.");
CHECK_MESSAGE(
double(expression.execute()) == doctest::Approx(2.2),
"The expression should return the expected result.");
CHECK_MESSAGE(
expression.parse(".0*0.") == OK,
"The expression should parse successfully.");
CHECK_MESSAGE(
double(expression.execute()) == doctest::Approx(0.0),
"The expression should return the expected result.");
}
TEST_CASE("[Expression] Scientific notation") {
Expression expression;
CHECK_MESSAGE(
expression.parse("2.e5") == OK,
"The expression should parse successfully.");
CHECK_MESSAGE(
expression.parse("2.E5") == OK,
"The expression should parse successfully.");
CHECK_MESSAGE(
double(expression.execute()) == doctest::Approx(200'000),
"The expression should return the expected result.");
@ -160,6 +216,15 @@ TEST_CASE("[Expression] Underscored numeric literals") {
CHECK_MESSAGE(
expression.parse("0xff_99_00") == OK,
"The expression should parse successfully.");
CHECK_MESSAGE(
expression.parse("0Xff_99_00") == OK,
"The expression should parse successfully.");
CHECK_MESSAGE(
expression.parse("0b10_11_00") == OK,
"The expression should parse successfully.");
CHECK_MESSAGE(
expression.parse("0B10_11_00") == OK,
"The expression should parse successfully.");
}
TEST_CASE("[Expression] Built-in functions") {

View file

@ -96,7 +96,7 @@ TEST_CASE("[Plane] Plane-point operations") {
CHECK_MESSAGE(
y_facing_plane.get_any_perpendicular_normal().is_equal_approx(Vector3(1, 0, 0)),
"get_any_perpindicular_normal() should return the expected result.");
"get_any_perpendicular_normal() should return the expected result.");
// TODO distance_to()
}

View file

@ -0,0 +1,516 @@
/**************************************************************************/
/* test_projection.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_PROJECTION_H
#define TEST_PROJECTION_H
#include "core/math/aabb.h"
#include "core/math/plane.h"
#include "core/math/projection.h"
#include "core/math/rect2.h"
#include "core/math/transform_3d.h"
#include "thirdparty/doctest/doctest.h"
namespace TestProjection {
TEST_CASE("[Projection] Construction") {
Projection default_proj;
CHECK(default_proj[0].is_equal_approx(Vector4(1, 0, 0, 0)));
CHECK(default_proj[1].is_equal_approx(Vector4(0, 1, 0, 0)));
CHECK(default_proj[2].is_equal_approx(Vector4(0, 0, 1, 0)));
CHECK(default_proj[3].is_equal_approx(Vector4(0, 0, 0, 1)));
Projection from_vec4(
Vector4(1, 2, 3, 4),
Vector4(5, 6, 7, 8),
Vector4(9, 10, 11, 12),
Vector4(13, 14, 15, 16));
CHECK(from_vec4[0].is_equal_approx(Vector4(1, 2, 3, 4)));
CHECK(from_vec4[1].is_equal_approx(Vector4(5, 6, 7, 8)));
CHECK(from_vec4[2].is_equal_approx(Vector4(9, 10, 11, 12)));
CHECK(from_vec4[3].is_equal_approx(Vector4(13, 14, 15, 16)));
Transform3D transform(
Basis(
Vector3(1, 0, 0),
Vector3(0, 2, 0),
Vector3(0, 0, 3)),
Vector3(4, 5, 6));
Projection from_transform(transform);
CHECK(from_transform[0].is_equal_approx(Vector4(1, 0, 0, 0)));
CHECK(from_transform[1].is_equal_approx(Vector4(0, 2, 0, 0)));
CHECK(from_transform[2].is_equal_approx(Vector4(0, 0, 3, 0)));
CHECK(from_transform[3].is_equal_approx(Vector4(4, 5, 6, 1)));
}
TEST_CASE("[Projection] set_zero()") {
Projection proj;
proj.set_zero();
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
CHECK(proj.columns[i][j] == 0);
}
}
}
TEST_CASE("[Projection] set_identity()") {
Projection proj;
proj.set_identity();
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
CHECK(proj.columns[i][j] == (i == j ? 1 : 0));
}
}
}
TEST_CASE("[Projection] determinant()") {
Projection proj(
Vector4(1, 5, 9, 13),
Vector4(2, 6, 11, 15),
Vector4(4, 7, 11, 15),
Vector4(4, 8, 12, 16));
CHECK(proj.determinant() == -12);
}
TEST_CASE("[Projection] Inverse and invert") {
SUBCASE("[Projection] Arbitrary projection matrix inversion") {
Projection proj(
Vector4(1, 5, 9, 13),
Vector4(2, 6, 11, 15),
Vector4(4, 7, 11, 15),
Vector4(4, 8, 12, 16));
Projection inverse_truth(
Vector4(-4.0 / 12, 0, 1, -8.0 / 12),
Vector4(8.0 / 12, -1, -1, 16.0 / 12),
Vector4(-20.0 / 12, 2, -1, 5.0 / 12),
Vector4(1, -1, 1, -0.75));
Projection inverse = proj.inverse();
CHECK(inverse[0].is_equal_approx(inverse_truth[0]));
CHECK(inverse[1].is_equal_approx(inverse_truth[1]));
CHECK(inverse[2].is_equal_approx(inverse_truth[2]));
CHECK(inverse[3].is_equal_approx(inverse_truth[3]));
proj.invert();
CHECK(proj[0].is_equal_approx(inverse_truth[0]));
CHECK(proj[1].is_equal_approx(inverse_truth[1]));
CHECK(proj[2].is_equal_approx(inverse_truth[2]));
CHECK(proj[3].is_equal_approx(inverse_truth[3]));
}
SUBCASE("[Projection] Orthogonal projection matrix inversion") {
Projection p = Projection::create_orthogonal(-125.0f, 125.0f, -125.0f, 125.0f, 0.01f, 25.0f);
p = p.inverse() * p;
CHECK(p[0].is_equal_approx(Vector4(1, 0, 0, 0)));
CHECK(p[1].is_equal_approx(Vector4(0, 1, 0, 0)));
CHECK(p[2].is_equal_approx(Vector4(0, 0, 1, 0)));
CHECK(p[3].is_equal_approx(Vector4(0, 0, 0, 1)));
}
SUBCASE("[Projection] Perspective projection matrix inversion") {
Projection p = Projection::create_perspective(90.0f, 1.77777f, 0.05f, 4000.0f);
p = p.inverse() * p;
CHECK(p[0].is_equal_approx(Vector4(1, 0, 0, 0)));
CHECK(p[1].is_equal_approx(Vector4(0, 1, 0, 0)));
CHECK(p[2].is_equal_approx(Vector4(0, 0, 1, 0)));
CHECK(p[3].is_equal_approx(Vector4(0, 0, 0, 1)));
}
}
TEST_CASE("[Projection] Matrix product") {
Projection proj1(
Vector4(1, 5, 9, 13),
Vector4(2, 6, 11, 15),
Vector4(4, 7, 11, 15),
Vector4(4, 8, 12, 16));
Projection proj2(
Vector4(0, 1, 2, 3),
Vector4(10, 11, 12, 13),
Vector4(20, 21, 22, 23),
Vector4(30, 31, 32, 33));
Projection prod = proj1 * proj2;
CHECK(prod[0].is_equal_approx(Vector4(22, 44, 69, 93)));
CHECK(prod[1].is_equal_approx(Vector4(132, 304, 499, 683)));
CHECK(prod[2].is_equal_approx(Vector4(242, 564, 929, 1273)));
CHECK(prod[3].is_equal_approx(Vector4(352, 824, 1359, 1863)));
}
TEST_CASE("[Projection] Vector transformation") {
Projection proj(
Vector4(1, 5, 9, 13),
Vector4(2, 6, 11, 15),
Vector4(4, 7, 11, 15),
Vector4(4, 8, 12, 16));
Projection inverse(
Vector4(-4.0 / 12, 0, 1, -8.0 / 12),
Vector4(8.0 / 12, -1, -1, 16.0 / 12),
Vector4(-20.0 / 12, 2, -1, 5.0 / 12),
Vector4(1, -1, 1, -0.75));
Vector4 vec4(1, 2, 3, 4);
CHECK(proj.xform(vec4).is_equal_approx(Vector4(33, 70, 112, 152)));
CHECK(proj.xform_inv(vec4).is_equal_approx(Vector4(90, 107, 111, 120)));
Vector3 vec3(1, 2, 3);
CHECK(proj.xform(vec3).is_equal_approx(Vector3(21, 46, 76) / 104));
}
TEST_CASE("[Projection] Plane transformation") {
Projection proj(
Vector4(1, 5, 9, 13),
Vector4(2, 6, 11, 15),
Vector4(4, 7, 11, 15),
Vector4(4, 8, 12, 16));
Plane plane(1, 2, 3, 4);
CHECK(proj.xform4(plane).is_equal_approx(Plane(33, 70, 112, 152)));
}
TEST_CASE("[Projection] Values access") {
Projection proj(
Vector4(00, 01, 02, 03),
Vector4(10, 11, 12, 13),
Vector4(20, 21, 22, 23),
Vector4(30, 31, 32, 33));
CHECK(proj[0] == Vector4(00, 01, 02, 03));
CHECK(proj[1] == Vector4(10, 11, 12, 13));
CHECK(proj[2] == Vector4(20, 21, 22, 23));
CHECK(proj[3] == Vector4(30, 31, 32, 33));
}
TEST_CASE("[Projection] flip_y() and flipped_y()") {
Projection proj(
Vector4(00, 01, 02, 03),
Vector4(10, 11, 12, 13),
Vector4(20, 21, 22, 23),
Vector4(30, 31, 32, 33));
Projection flipped = proj.flipped_y();
CHECK(flipped[0] == proj[0]);
CHECK(flipped[1] == -proj[1]);
CHECK(flipped[2] == proj[2]);
CHECK(flipped[3] == proj[3]);
proj.flip_y();
CHECK(proj[0] == flipped[0]);
CHECK(proj[1] == flipped[1]);
CHECK(proj[2] == flipped[2]);
CHECK(proj[3] == flipped[3]);
}
TEST_CASE("[Projection] Jitter offset") {
Projection proj(
Vector4(00, 01, 02, 03),
Vector4(10, 11, 12, 13),
Vector4(20, 21, 22, 23),
Vector4(30, 31, 32, 33));
Projection offsetted = proj.jitter_offseted(Vector2(1, 2));
CHECK(offsetted[0] == proj[0]);
CHECK(offsetted[1] == proj[1]);
CHECK(offsetted[2] == proj[2]);
CHECK(offsetted[3] == proj[3] + Vector4(1, 2, 0, 0));
proj.add_jitter_offset(Vector2(1, 2));
CHECK(proj[0] == offsetted[0]);
CHECK(proj[1] == offsetted[1]);
CHECK(proj[2] == offsetted[2]);
CHECK(proj[3] == offsetted[3]);
}
TEST_CASE("[Projection] Adjust znear") {
Projection persp = Projection::create_perspective(90, 0.5, 1, 50, false);
Projection adjusted = persp.perspective_znear_adjusted(2);
CHECK(adjusted[0] == persp[0]);
CHECK(adjusted[1] == persp[1]);
CHECK(adjusted[2].is_equal_approx(Vector4(persp[2][0], persp[2][1], -1.083333, persp[2][3])));
CHECK(adjusted[3].is_equal_approx(Vector4(persp[3][0], persp[3][1], -4.166666, persp[3][3])));
persp.adjust_perspective_znear(2);
CHECK(persp[0] == adjusted[0]);
CHECK(persp[1] == adjusted[1]);
CHECK(persp[2] == adjusted[2]);
CHECK(persp[3] == adjusted[3]);
}
TEST_CASE("[Projection] Set light bias") {
Projection proj;
proj.set_light_bias();
CHECK(proj[0] == Vector4(0.5, 0, 0, 0));
CHECK(proj[1] == Vector4(0, 0.5, 0, 0));
CHECK(proj[2] == Vector4(0, 0, 0.5, 0));
CHECK(proj[3] == Vector4(0.5, 0.5, 0.5, 1));
}
TEST_CASE("[Projection] Depth correction") {
Projection corrected = Projection::create_depth_correction(true);
CHECK(corrected[0] == Vector4(1, 0, 0, 0));
CHECK(corrected[1] == Vector4(0, -1, 0, 0));
CHECK(corrected[2] == Vector4(0, 0, -0.5, 0));
CHECK(corrected[3] == Vector4(0, 0, 0.5, 1));
Projection proj;
proj.set_depth_correction(true, true, true);
CHECK(proj[0] == corrected[0]);
CHECK(proj[1] == corrected[1]);
CHECK(proj[2] == corrected[2]);
CHECK(proj[3] == corrected[3]);
proj.set_depth_correction(false, true, true);
CHECK(proj[0] == Vector4(1, 0, 0, 0));
CHECK(proj[1] == Vector4(0, 1, 0, 0));
CHECK(proj[2] == Vector4(0, 0, -0.5, 0));
CHECK(proj[3] == Vector4(0, 0, 0.5, 1));
proj.set_depth_correction(false, false, true);
CHECK(proj[0] == Vector4(1, 0, 0, 0));
CHECK(proj[1] == Vector4(0, 1, 0, 0));
CHECK(proj[2] == Vector4(0, 0, 0.5, 0));
CHECK(proj[3] == Vector4(0, 0, 0.5, 1));
proj.set_depth_correction(false, false, false);
CHECK(proj[0] == Vector4(1, 0, 0, 0));
CHECK(proj[1] == Vector4(0, 1, 0, 0));
CHECK(proj[2] == Vector4(0, 0, 1, 0));
CHECK(proj[3] == Vector4(0, 0, 0, 1));
proj.set_depth_correction(true, true, false);
CHECK(proj[0] == Vector4(1, 0, 0, 0));
CHECK(proj[1] == Vector4(0, -1, 0, 0));
CHECK(proj[2] == Vector4(0, 0, -1, 0));
CHECK(proj[3] == Vector4(0, 0, 0, 1));
}
TEST_CASE("[Projection] Light atlas rect") {
Projection rect = Projection::create_light_atlas_rect(Rect2(1, 2, 30, 40));
CHECK(rect[0] == Vector4(30, 0, 0, 0));
CHECK(rect[1] == Vector4(0, 40, 0, 0));
CHECK(rect[2] == Vector4(0, 0, 1, 0));
CHECK(rect[3] == Vector4(1, 2, 0, 1));
Projection proj;
proj.set_light_atlas_rect(Rect2(1, 2, 30, 40));
CHECK(proj[0] == rect[0]);
CHECK(proj[1] == rect[1]);
CHECK(proj[2] == rect[2]);
CHECK(proj[3] == rect[3]);
}
TEST_CASE("[Projection] Make scale") {
Projection proj;
proj.make_scale(Vector3(2, 3, 4));
CHECK(proj[0] == Vector4(2, 0, 0, 0));
CHECK(proj[1] == Vector4(0, 3, 0, 0));
CHECK(proj[2] == Vector4(0, 0, 4, 0));
CHECK(proj[3] == Vector4(0, 0, 0, 1));
}
TEST_CASE("[Projection] Scale translate to fit aabb") {
Projection fit = Projection::create_fit_aabb(AABB(Vector3(), Vector3(0.1, 0.2, 0.4)));
CHECK(fit[0] == Vector4(20, 0, 0, 0));
CHECK(fit[1] == Vector4(0, 10, 0, 0));
CHECK(fit[2] == Vector4(0, 0, 5, 0));
CHECK(fit[3] == Vector4(-1, -1, -1, 1));
Projection proj;
proj.scale_translate_to_fit(AABB(Vector3(), Vector3(0.1, 0.2, 0.4)));
CHECK(proj[0] == fit[0]);
CHECK(proj[1] == fit[1]);
CHECK(proj[2] == fit[2]);
CHECK(proj[3] == fit[3]);
}
TEST_CASE("[Projection] Perspective") {
Projection persp = Projection::create_perspective(90, 0.5, 5, 15, false);
CHECK(persp[0].is_equal_approx(Vector4(2, 0, 0, 0)));
CHECK(persp[1].is_equal_approx(Vector4(0, 1, 0, 0)));
CHECK(persp[2].is_equal_approx(Vector4(0, 0, -2, -1)));
CHECK(persp[3].is_equal_approx(Vector4(0, 0, -15, 0)));
Projection proj;
proj.set_perspective(90, 0.5, 5, 15, false);
CHECK(proj[0] == persp[0]);
CHECK(proj[1] == persp[1]);
CHECK(proj[2] == persp[2]);
CHECK(proj[3] == persp[3]);
}
TEST_CASE("[Projection] Frustum") {
Projection frustum = Projection::create_frustum(15, 20, 10, 12, 5, 15);
CHECK(frustum[0].is_equal_approx(Vector4(2, 0, 0, 0)));
CHECK(frustum[1].is_equal_approx(Vector4(0, 5, 0, 0)));
CHECK(frustum[2].is_equal_approx(Vector4(7, 11, -2, -1)));
CHECK(frustum[3].is_equal_approx(Vector4(0, 0, -15, 0)));
Projection proj;
proj.set_frustum(15, 20, 10, 12, 5, 15);
CHECK(proj[0] == frustum[0]);
CHECK(proj[1] == frustum[1]);
CHECK(proj[2] == frustum[2]);
CHECK(proj[3] == frustum[3]);
}
TEST_CASE("[Projection] Ortho") {
Projection ortho = Projection::create_orthogonal(15, 20, 10, 12, 5, 15);
CHECK(ortho[0].is_equal_approx(Vector4(0.4, 0, 0, 0)));
CHECK(ortho[1].is_equal_approx(Vector4(0, 1, 0, 0)));
CHECK(ortho[2].is_equal_approx(Vector4(0, 0, -0.2, 0)));
CHECK(ortho[3].is_equal_approx(Vector4(-7, -11, -2, 1)));
Projection proj;
proj.set_orthogonal(15, 20, 10, 12, 5, 15);
CHECK(proj[0] == ortho[0]);
CHECK(proj[1] == ortho[1]);
CHECK(proj[2] == ortho[2]);
CHECK(proj[3] == ortho[3]);
}
TEST_CASE("[Projection] get_fovy()") {
double fov = Projection::get_fovy(90, 0.5);
CHECK(fov == doctest::Approx(53.1301));
}
TEST_CASE("[Projection] Perspective values extraction") {
Projection persp = Projection::create_perspective(90, 0.5, 1, 50, true);
double znear = persp.get_z_near();
double zfar = persp.get_z_far();
double aspect = persp.get_aspect();
double fov = persp.get_fov();
CHECK(znear == doctest::Approx(1));
CHECK(zfar == doctest::Approx(50));
CHECK(aspect == doctest::Approx(0.5));
CHECK(fov == doctest::Approx(90));
}
TEST_CASE("[Projection] Orthographic check") {
Projection persp = Projection::create_perspective(90, 0.5, 1, 50, false);
Projection ortho = Projection::create_orthogonal(15, 20, 10, 12, 5, 15);
CHECK(!persp.is_orthogonal());
CHECK(ortho.is_orthogonal());
}
TEST_CASE("[Projection] Planes extraction") {
Projection persp = Projection::create_perspective(90, 1, 1, 40, false);
Vector<Plane> planes = persp.get_projection_planes(Transform3D());
CHECK(planes[Projection::PLANE_NEAR].normalized().is_equal_approx(Plane(0, 0, 1, -1)));
CHECK(planes[Projection::PLANE_FAR].normalized().is_equal_approx(Plane(0, 0, -1, 40)));
CHECK(planes[Projection::PLANE_LEFT].normalized().is_equal_approx(Plane(-0.707107, 0, 0.707107, 0)));
CHECK(planes[Projection::PLANE_TOP].normalized().is_equal_approx(Plane(0, 0.707107, 0.707107, 0)));
CHECK(planes[Projection::PLANE_RIGHT].normalized().is_equal_approx(Plane(0.707107, 0, 0.707107, 0)));
CHECK(planes[Projection::PLANE_BOTTOM].normalized().is_equal_approx(Plane(0, -0.707107, 0.707107, 0)));
Plane plane_array[6]{
persp.get_projection_plane(Projection::PLANE_NEAR),
persp.get_projection_plane(Projection::PLANE_FAR),
persp.get_projection_plane(Projection::PLANE_LEFT),
persp.get_projection_plane(Projection::PLANE_TOP),
persp.get_projection_plane(Projection::PLANE_RIGHT),
persp.get_projection_plane(Projection::PLANE_BOTTOM)
};
CHECK(plane_array[Projection::PLANE_NEAR].normalized().is_equal_approx(planes[Projection::PLANE_NEAR].normalized()));
CHECK(plane_array[Projection::PLANE_FAR].normalized().is_equal_approx(planes[Projection::PLANE_FAR].normalized()));
CHECK(plane_array[Projection::PLANE_LEFT].normalized().is_equal_approx(planes[Projection::PLANE_LEFT].normalized()));
CHECK(plane_array[Projection::PLANE_TOP].normalized().is_equal_approx(planes[Projection::PLANE_TOP].normalized()));
CHECK(plane_array[Projection::PLANE_RIGHT].normalized().is_equal_approx(planes[Projection::PLANE_RIGHT].normalized()));
CHECK(plane_array[Projection::PLANE_BOTTOM].normalized().is_equal_approx(planes[Projection::PLANE_BOTTOM].normalized()));
}
TEST_CASE("[Projection] Half extents") {
Projection persp = Projection::create_perspective(90, 1, 1, 40, false);
Vector2 ne = persp.get_viewport_half_extents();
Vector2 fe = persp.get_far_plane_half_extents();
CHECK(ne.is_equal_approx(Vector2(1, 1) * 1));
CHECK(fe.is_equal_approx(Vector2(1, 1) * 40));
}
TEST_CASE("[Projection] Endpoints") {
Projection persp = Projection::create_perspective(90, 1, 1, 40, false);
Vector3 ep[8];
persp.get_endpoints(Transform3D(), ep);
CHECK(ep[0].is_equal_approx(Vector3(-1, 1, -1) * 40));
CHECK(ep[1].is_equal_approx(Vector3(-1, -1, -1) * 40));
CHECK(ep[2].is_equal_approx(Vector3(1, 1, -1) * 40));
CHECK(ep[3].is_equal_approx(Vector3(1, -1, -1) * 40));
CHECK(ep[4].is_equal_approx(Vector3(-1, 1, -1) * 1));
CHECK(ep[5].is_equal_approx(Vector3(-1, -1, -1) * 1));
CHECK(ep[6].is_equal_approx(Vector3(1, 1, -1) * 1));
CHECK(ep[7].is_equal_approx(Vector3(1, -1, -1) * 1));
}
} //namespace TestProjection
#endif // TEST_PROJECTION_H

View file

@ -235,6 +235,39 @@ TEST_CASE("[Quaternion] Construct Basis Axes") {
CHECK(q[3] == doctest::Approx(0.8582598));
}
TEST_CASE("[Quaternion] Construct Shortest Arc For 180 Degree Arc") {
Vector3 up(0, 1, 0);
Vector3 down(0, -1, 0);
Vector3 left(-1, 0, 0);
Vector3 right(1, 0, 0);
Vector3 forward(0, 0, -1);
Vector3 back(0, 0, 1);
// When we have a 180 degree rotation quaternion which was defined as
// A to B, logically when we transform A we expect to get B.
Quaternion left_to_right(left, right);
Quaternion right_to_left(right, left);
CHECK(left_to_right.xform(left).is_equal_approx(right));
CHECK(Quaternion(right, left).xform(right).is_equal_approx(left));
CHECK(Quaternion(up, down).xform(up).is_equal_approx(down));
CHECK(Quaternion(down, up).xform(down).is_equal_approx(up));
CHECK(Quaternion(forward, back).xform(forward).is_equal_approx(back));
CHECK(Quaternion(back, forward).xform(back).is_equal_approx(forward));
// With (arbitrary) opposite vectors that are not axis-aligned as parameters.
Vector3 diagonal_up = Vector3(1.2, 2.3, 4.5).normalized();
Vector3 diagonal_down = -diagonal_up;
Quaternion q1(diagonal_up, diagonal_down);
CHECK(q1.xform(diagonal_down).is_equal_approx(diagonal_up));
CHECK(q1.xform(diagonal_up).is_equal_approx(diagonal_down));
// For the consistency of the rotation direction, they should be symmetrical to the plane.
CHECK(left_to_right.is_equal_approx(right_to_left.inverse()));
// If vectors are same, no rotation.
CHECK(Quaternion(diagonal_up, diagonal_up).is_equal_approx(Quaternion()));
}
TEST_CASE("[Quaternion] Get Euler Orders") {
double x = Math::deg_to_rad(30.0);
double y = Math::deg_to_rad(45.0);

View file

@ -57,7 +57,7 @@ TEST_CASE("[Rect2] Constructor methods") {
TEST_CASE("[Rect2] String conversion") {
// Note: This also depends on the Vector2 string representation.
CHECK_MESSAGE(
String(Rect2(0, 100, 1280, 720)) == "[P: (0, 100), S: (1280, 720)]",
String(Rect2(0, 100, 1280, 720)) == "[P: (0.0, 100.0), S: (1280.0, 720.0)]",
"The string representation should match the expected value.");
}
@ -180,6 +180,28 @@ TEST_CASE("[Rect2] Expanding") {
"expand() with non-contained Vector2 should return the expected result.");
}
TEST_CASE("[Rect2] Get support") {
const Rect2 rect = Rect2(Vector2(-1.5, 2), Vector2(4, 5));
CHECK_MESSAGE(
rect.get_support(Vector2(1, 0)) == Vector2(2.5, 2),
"get_support() should return the expected value.");
CHECK_MESSAGE(
rect.get_support(Vector2(0.5, 1)) == Vector2(2.5, 7),
"get_support() should return the expected value.");
CHECK_MESSAGE(
rect.get_support(Vector2(0.5, 1)) == Vector2(2.5, 7),
"get_support() should return the expected value.");
CHECK_MESSAGE(
rect.get_support(Vector2(0, -1)) == Vector2(-1.5, 2),
"get_support() should return the expected value.");
CHECK_MESSAGE(
rect.get_support(Vector2(0, -0.1)) == Vector2(-1.5, 2),
"get_support() should return the expected value.");
CHECK_MESSAGE(
rect.get_support(Vector2()) == Vector2(-1.5, 2),
"get_support() should return the Rect2 position when given a zero vector.");
}
TEST_CASE("[Rect2] Growing") {
CHECK_MESSAGE(
Rect2(0, 100, 1280, 720).grow(100).is_equal_approx(Rect2(-100, 0, 1480, 920)),

View file

@ -289,6 +289,38 @@ bool arg_default_value_is_assignable_to_type(const Context &p_context, const Var
return false;
}
bool arg_default_value_is_valid_data(const Variant &p_val, String *r_err_msg = nullptr) {
switch (p_val.get_type()) {
case Variant::RID:
case Variant::ARRAY:
case Variant::DICTIONARY:
case Variant::PACKED_BYTE_ARRAY:
case Variant::PACKED_INT32_ARRAY:
case Variant::PACKED_INT64_ARRAY:
case Variant::PACKED_FLOAT32_ARRAY:
case Variant::PACKED_FLOAT64_ARRAY:
case Variant::PACKED_STRING_ARRAY:
case Variant::PACKED_VECTOR2_ARRAY:
case Variant::PACKED_VECTOR3_ARRAY:
case Variant::PACKED_COLOR_ARRAY:
case Variant::PACKED_VECTOR4_ARRAY:
case Variant::CALLABLE:
case Variant::SIGNAL:
case Variant::OBJECT:
if (p_val.is_zero()) {
return true;
}
if (r_err_msg) {
*r_err_msg = "Must be zero.";
}
break;
default:
return true;
}
return false;
}
void validate_property(const Context &p_context, const ExposedClass &p_class, const PropertyData &p_prop) {
const MethodData *setter = p_class.find_method_by_name(p_prop.setter);
@ -411,6 +443,14 @@ void validate_argument(const Context &p_context, const ExposedClass &p_class, co
}
TEST_COND(!arg_defval_assignable_to_type, err_msg);
bool arg_defval_valid_data = arg_default_value_is_valid_data(p_arg.defval, &type_error_msg);
if (!type_error_msg.is_empty()) {
err_msg += " " + type_error_msg;
}
TEST_COND(!arg_defval_valid_data, err_msg);
}
}
@ -563,7 +603,7 @@ void add_exposed_classes(Context &r_context) {
MethodData method;
method.name = method_info.name;
TEST_FAIL_COND(!String(method.name).is_valid_identifier(),
TEST_FAIL_COND(!String(method.name).is_valid_ascii_identifier(),
"Method name is not a valid identifier: '", exposed_class.name, ".", method.name, "'.");
if (method_info.flags & METHOD_FLAG_VIRTUAL) {
@ -689,7 +729,7 @@ void add_exposed_classes(Context &r_context) {
const MethodInfo &method_info = signal_map.get(K.key);
signal.name = method_info.name;
TEST_FAIL_COND(!String(signal.name).is_valid_identifier(),
TEST_FAIL_COND(!String(signal.name).is_valid_ascii_identifier(),
"Signal name is not a valid identifier: '", exposed_class.name, ".", signal.name, "'.");
int i = 0;
@ -823,16 +863,19 @@ void add_global_enums(Context &r_context) {
}
}
// HARDCODED
List<StringName> hardcoded_enums;
hardcoded_enums.push_back("Vector2.Axis");
hardcoded_enums.push_back("Vector2i.Axis");
hardcoded_enums.push_back("Vector3.Axis");
hardcoded_enums.push_back("Vector3i.Axis");
for (const StringName &E : hardcoded_enums) {
// These enums are not generated and must be written manually (e.g.: Vector3.Axis)
// Here, we assume core types do not begin with underscore
r_context.enum_types.push_back(E);
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
if (i == Variant::OBJECT) {
continue;
}
const Variant::Type type = Variant::Type(i);
List<StringName> enum_names;
Variant::get_enums_for_type(type, &enum_names);
for (const StringName &enum_name : enum_names) {
r_context.enum_types.push_back(Variant::get_type_name(type) + "." + enum_name);
}
}
}

View file

@ -37,6 +37,16 @@
#include "tests/test_macros.h"
#ifdef SANITIZERS_ENABLED
#ifdef __has_feature
#if __has_feature(address_sanitizer) || __has_feature(thread_sanitizer)
#define ASAN_OR_TSAN_ENABLED
#endif
#elif defined(__SANITIZE_ADDRESS__) || defined(__SANITIZE_THREAD__)
#define ASAN_OR_TSAN_ENABLED
#endif
#endif
// Declared in global namespace because of GDCLASS macro warning (Windows):
// "Unqualified friend declaration referring to type outside of the nearest enclosing namespace
// is a Microsoft extension; add a nested name specifier".
@ -85,10 +95,10 @@ public:
}
bool property_can_revert(const StringName &p_name) const override {
return false;
};
}
bool property_get_revert(const StringName &p_name, Variant &r_ret) const override {
return false;
};
}
void get_method_list(List<MethodInfo> *p_list) const override {
}
bool has_method(const StringName &p_method) const override {
@ -174,6 +184,31 @@ TEST_CASE("[Object] Metadata") {
CHECK_MESSAGE(
meta_list2.size() == 0,
"The metadata list should contain 0 items after removing all metadata items.");
Object other;
object.set_meta("conflicting_meta", "string");
object.set_meta("not_conflicting_meta", 123);
other.set_meta("conflicting_meta", Color(0, 1, 0));
other.set_meta("other_meta", "other");
object.merge_meta_from(&other);
CHECK_MESSAGE(
Color(object.get_meta("conflicting_meta")).is_equal_approx(Color(0, 1, 0)),
"String meta should be overwritten with Color after merging.");
CHECK_MESSAGE(
int(object.get_meta("not_conflicting_meta")) == 123,
"Not conflicting meta on destination should be kept intact.");
CHECK_MESSAGE(
object.get_meta("other_meta", String()) == "other",
"Not conflicting meta name on source should merged.");
List<StringName> meta_list3;
object.get_meta_list(&meta_list3);
CHECK_MESSAGE(
meta_list3.size() == 3,
"The metadata list should contain 3 items after merging meta from two objects.");
}
TEST_CASE("[Object] Construction") {
@ -499,6 +534,74 @@ TEST_CASE("[Object] Notification order") { // GH-52325
memdelete(test_notification_object);
}
TEST_CASE("[Object] Destruction at the end of the call chain is safe") {
Object *object = memnew(Object);
ObjectID obj_id = object->get_instance_id();
class _SelfDestroyingScriptInstance : public _MockScriptInstance {
Object *self = nullptr;
// This has to be static because ~Object() also destroys the script instance.
static void free_self(Object *p_self) {
#if defined(ASAN_OR_TSAN_ENABLED)
// Regular deletion is enough becausa asan/tsan will catch a potential heap-after-use.
memdelete(p_self);
#else
// Without asan/tsan, try at least to force a crash by replacing the otherwise seemingly good data with garbage.
// Operations such as dereferencing pointers or decreasing a refcount would fail.
// Unfortunately, we may not poison the memory after the deletion, because the memory would no longer belong to us
// and on doing so we may cause a more generalized crash on some platforms (allocator implementations).
p_self->~Object();
memset((void *)p_self, 0, sizeof(Object));
Memory::free_static(p_self, false);
#endif
}
public:
Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override {
free_self(self);
return Variant();
}
Variant call_const(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override {
free_self(self);
return Variant();
}
bool has_method(const StringName &p_method) const override {
return p_method == "some_method";
}
public:
_SelfDestroyingScriptInstance(Object *p_self) :
self(p_self) {}
};
_SelfDestroyingScriptInstance *script_instance = memnew(_SelfDestroyingScriptInstance(object));
object->set_script_instance(script_instance);
SUBCASE("Within callp()") {
SUBCASE("Through call()") {
object->call("some_method");
}
SUBCASE("Through callv()") {
object->callv("some_method", Array());
}
}
SUBCASE("Within call_const()") {
Callable::CallError call_error;
object->call_const("some_method", nullptr, 0, call_error);
}
SUBCASE("Within signal handling (from emit_signalp(), through emit_signal())") {
Object emitter;
emitter.add_user_signal(MethodInfo("some_signal"));
emitter.connect("some_signal", Callable(object, "some_method"));
emitter.emit_signal("some_signal");
}
CHECK_MESSAGE(
ObjectDB::get_instance(obj_id) == nullptr,
"Object was tail-deleted without crashes.");
}
} // namespace TestObject
#endif // TEST_OBJECT_H

View file

@ -0,0 +1,83 @@
/**************************************************************************/
/* test_fuzzy_search.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_FUZZY_SEARCH_H
#define TEST_FUZZY_SEARCH_H
#include "core/string/fuzzy_search.h"
#include "tests/test_macros.h"
namespace TestFuzzySearch {
struct FuzzySearchTestCase {
String query;
String expected;
};
// Ideally each of these test queries should represent a different aspect, and potentially bottleneck, of the search process.
const FuzzySearchTestCase test_cases[] = {
// Short query, many matches, few adjacent characters
{ "///gd", "./menu/hud/hud.gd" },
// Filename match with typo
{ "sm.png", "./entity/blood_sword/sam.png" },
// Multipart filename word matches
{ "ham ", "./entity/game_trap/ha_missed_me.wav" },
// Single word token matches
{ "push background", "./entity/background_zone1/background/push.png" },
// Long token matches
{ "background_freighter background png", "./entity/background_freighter/background/background.png" },
// Many matches, many short tokens
{ "menu menu characters wav", "./menu/menu/characters/smoker/0.wav" },
// Maximize total matches
{ "entity gd", "./entity/entity_man.gd" }
};
Vector<String> load_test_data() {
Ref<FileAccess> fp = FileAccess::open(TestUtils::get_data_path("fuzzy_search/project_dir_tree.txt"), FileAccess::READ);
REQUIRE(fp.is_valid());
return fp->get_as_utf8_string().split("\n");
}
TEST_CASE("[FuzzySearch] Test fuzzy search results") {
FuzzySearch search;
Vector<FuzzySearchResult> results;
Vector<String> targets = load_test_data();
for (FuzzySearchTestCase test_case : test_cases) {
search.set_query(test_case.query);
search.search_all(targets, results);
CHECK_GT(results.size(), 0);
CHECK_EQ(results[0].target, test_case.expected);
}
}
} //namespace TestFuzzySearch
#endif // TEST_FUZZY_SEARCH_H

View file

@ -169,28 +169,31 @@ TEST_CASE("[NodePath] Empty path") {
}
TEST_CASE("[NodePath] Slice") {
const NodePath node_path_relative = NodePath("Parent/Child:prop");
const NodePath node_path_relative = NodePath("Parent/Child:prop:subprop");
const NodePath node_path_absolute = NodePath("/root/Parent/Child:prop");
CHECK_MESSAGE(
node_path_relative.slice(0, 2) == NodePath("Parent/Child"),
"The slice lower bound should be inclusive and the slice upper bound should be exclusive.");
CHECK_MESSAGE(
node_path_relative.slice(3) == NodePath(":prop"),
"Slicing on the length of the path should return the last entry.");
node_path_relative.slice(3) == NodePath(":subprop"),
"Slicing on the last index (length - 1) should return the last entry.");
CHECK_MESSAGE(
node_path_relative.slice(1) == NodePath("Child:prop:subprop"),
"Slicing without upper bound should return remaining entries after index.");
CHECK_MESSAGE(
node_path_relative.slice(1, 3) == NodePath("Child:prop"),
"Slicing should include names and subnames.");
CHECK_MESSAGE(
node_path_relative.slice(-1) == NodePath(":prop"),
node_path_relative.slice(-1) == NodePath(":subprop"),
"Slicing on -1 should return the last entry.");
CHECK_MESSAGE(
node_path_relative.slice(0, -1) == NodePath("Parent/Child"),
node_path_relative.slice(0, -1) == NodePath("Parent/Child:prop"),
"Slicing up to -1 should include the second-to-last entry.");
CHECK_MESSAGE(
node_path_relative.slice(-2, -1) == NodePath("Child"),
node_path_relative.slice(-2, -1) == NodePath(":prop"),
"Slicing from negative to negative should treat lower bound as inclusive and upper bound as exclusive.");
CHECK_MESSAGE(
node_path_relative.slice(0, 10) == NodePath("Parent/Child:prop"),
node_path_relative.slice(0, 10) == NodePath("Parent/Child:prop:subprop"),
"Slicing past the length of the path should work like slicing up to the last entry.");
CHECK_MESSAGE(
node_path_relative.slice(-10, 2) == NodePath("Parent/Child"),

View file

@ -389,6 +389,19 @@ TEST_CASE("[String] Find") {
MULTICHECK_STRING_INT_EQ(s, rfind, "", 15, -1);
}
TEST_CASE("[String] Find character") {
String s = "racecar";
CHECK_EQ(s.find_char('r'), 0);
CHECK_EQ(s.find_char('r', 1), 6);
CHECK_EQ(s.find_char('e'), 3);
CHECK_EQ(s.find_char('e', 4), -1);
CHECK_EQ(s.rfind_char('r'), 6);
CHECK_EQ(s.rfind_char('r', 5), 0);
CHECK_EQ(s.rfind_char('e'), 3);
CHECK_EQ(s.rfind_char('e', 2), -1);
}
TEST_CASE("[String] Find case insensitive") {
String s = "Pretty Whale Whale";
MULTICHECK_STRING_EQ(s, findn, "WHA", 7);
@ -433,6 +446,19 @@ TEST_CASE("[String] Insertion") {
String s = "Who is Frederic?";
s = s.insert(s.find("?"), " Chopin");
CHECK(s == "Who is Frederic Chopin?");
s = "foobar";
CHECK(s.insert(0, "X") == "Xfoobar");
CHECK(s.insert(-100, "X") == "foobar");
CHECK(s.insert(6, "X") == "foobarX");
CHECK(s.insert(100, "X") == "foobarX");
CHECK(s.insert(2, "") == "foobar");
s = "";
CHECK(s.insert(0, "abc") == "abc");
CHECK(s.insert(100, "abc") == "abc");
CHECK(s.insert(-100, "abc") == "");
CHECK(s.insert(0, "") == "");
}
TEST_CASE("[String] Erasing") {
@ -442,16 +468,32 @@ TEST_CASE("[String] Erasing") {
}
TEST_CASE("[String] Number to string") {
CHECK(String::num(0) == "0");
CHECK(String::num(0.0) == "0"); // No trailing zeros.
CHECK(String::num(-0.0) == "-0"); // Includes sign even for zero.
CHECK(String::num(0) == "0.0"); // The method takes double, so always add zeros.
CHECK(String::num(0.0) == "0.0");
CHECK(String::num(-0.0) == "-0.0"); // Includes sign even for zero.
CHECK(String::num(3.141593) == "3.141593");
CHECK(String::num(3.141593, 3) == "3.142");
CHECK(String::num(42.100023, 4) == "42.1"); // No trailing zeros.
CHECK(String::num_scientific(30000000) == "3e+07");
// String::num_int64 tests.
CHECK(String::num_int64(3141593) == "3141593");
CHECK(String::num_int64(-3141593) == "-3141593");
CHECK(String::num_int64(0xA141593, 16) == "a141593");
CHECK(String::num_int64(0xA141593, 16, true) == "A141593");
CHECK(String::num(42.100023, 4) == "42.1"); // No trailing zeros.
ERR_PRINT_OFF;
CHECK(String::num_int64(3141593, 1) == ""); // Invalid base < 2.
CHECK(String::num_int64(3141593, 37) == ""); // Invalid base > 36.
ERR_PRINT_ON;
// String::num_uint64 tests.
CHECK(String::num_uint64(4294967295) == "4294967295");
CHECK(String::num_uint64(0xF141593, 16) == "f141593");
CHECK(String::num_uint64(0xF141593, 16, true) == "F141593");
ERR_PRINT_OFF;
CHECK(String::num_uint64(4294967295, 1) == ""); // Invalid base < 2.
CHECK(String::num_uint64(4294967295, 37) == ""); // Invalid base > 36.
ERR_PRINT_ON;
// String::num_real tests.
CHECK(String::num_real(1.0) == "1.0");
@ -463,15 +505,15 @@ TEST_CASE("[String] Number to string") {
CHECK(String::num_real(3.141593) == "3.141593");
CHECK(String::num_real(3.141) == "3.141"); // No trailing zeros.
#ifdef REAL_T_IS_DOUBLE
CHECK_MESSAGE(String::num_real(123.456789) == "123.456789", "Prints the appropriate amount of digits for real_t = double.");
CHECK_MESSAGE(String::num_real(-123.456789) == "-123.456789", "Prints the appropriate amount of digits for real_t = double.");
CHECK_MESSAGE(String::num_real(Math_PI) == "3.14159265358979", "Prints the appropriate amount of digits for real_t = double.");
CHECK_MESSAGE(String::num_real(3.1415f) == "3.1414999961853", "Prints more digits of 32-bit float when real_t = double (ones that would be reliable for double) and no trailing zero.");
CHECK_MESSAGE(String::num_real(real_t(123.456789)) == "123.456789", "Prints the appropriate amount of digits for real_t = double.");
CHECK_MESSAGE(String::num_real(real_t(-123.456789)) == "-123.456789", "Prints the appropriate amount of digits for real_t = double.");
CHECK_MESSAGE(String::num_real(real_t(Math_PI)) == "3.14159265358979", "Prints the appropriate amount of digits for real_t = double.");
CHECK_MESSAGE(String::num_real(real_t(3.1415f)) == "3.1414999961853", "Prints more digits of 32-bit float when real_t = double (ones that would be reliable for double) and no trailing zero.");
#else
CHECK_MESSAGE(String::num_real(123.456789) == "123.4568", "Prints the appropriate amount of digits for real_t = float.");
CHECK_MESSAGE(String::num_real(-123.456789) == "-123.4568", "Prints the appropriate amount of digits for real_t = float.");
CHECK_MESSAGE(String::num_real(Math_PI) == "3.141593", "Prints the appropriate amount of digits for real_t = float.");
CHECK_MESSAGE(String::num_real(3.1415f) == "3.1415", "Prints only reliable digits of 32-bit float when real_t = float.");
CHECK_MESSAGE(String::num_real(real_t(123.456789)) == "123.4568", "Prints the appropriate amount of digits for real_t = float.");
CHECK_MESSAGE(String::num_real(real_t(-123.456789)) == "-123.4568", "Prints the appropriate amount of digits for real_t = float.");
CHECK_MESSAGE(String::num_real(real_t(Math_PI)) == "3.141593", "Prints the appropriate amount of digits for real_t = float.");
CHECK_MESSAGE(String::num_real(real_t(3.1415f)) == "3.1415", "Prints only reliable digits of 32-bit float when real_t = float.");
#endif // REAL_T_IS_DOUBLE
// Checks doubles with many decimal places.
@ -480,7 +522,7 @@ TEST_CASE("[String] Number to string") {
CHECK(String::num(-0.0000012345432123454321) == "-0.00000123454321");
CHECK(String::num(-10000.0000012345432123454321) == "-10000.0000012345");
CHECK(String::num(0.0000000000012345432123454321) == "0.00000000000123");
CHECK(String::num(0.0000000000012345432123454321, 3) == "0");
CHECK(String::num(0.0000000000012345432123454321, 3) == "0.0");
// Note: When relevant (remainder > 0.5), the last digit gets rounded up,
// which can also lead to not include a trailing zero, e.g. "...89" -> "...9".
@ -503,7 +545,10 @@ TEST_CASE("[String] String to integer") {
CHECK(String(nums[i]).to_int() == num[i]);
}
CHECK(String("0b1011").to_int() == 1011); // Looks like a binary number, but to_int() handles this as a base-10 number, "b" is just ignored.
CHECK(String("0B1011").to_int() == 1011);
CHECK(String("0x1012").to_int() == 1012); // Looks like a hexadecimal number, but to_int() handles this as a base-10 number, "x" is just ignored.
CHECK(String("0X1012").to_int() == 1012);
ERR_PRINT_OFF
CHECK(String("999999999999999999999999999999999999999999999999999999999").to_int() == INT64_MAX); // Too large, largest possible is returned.
@ -512,10 +557,10 @@ TEST_CASE("[String] String to integer") {
}
TEST_CASE("[String] Hex to integer") {
static const char *nums[12] = { "0xFFAE", "22", "0", "AADDAD", "0x7FFFFFFFFFFFFFFF", "-0xf", "", "000", "000f", "0xaA", "-ff", "-" };
static const int64_t num[12] = { 0xFFAE, 0x22, 0, 0xAADDAD, 0x7FFFFFFFFFFFFFFF, -0xf, 0, 0, 0xf, 0xaa, -0xff, 0x0 };
static const char *nums[13] = { "0xFFAE", "22", "0", "AADDAD", "0x7FFFFFFFFFFFFFFF", "-0xf", "", "000", "000f", "0xaA", "-ff", "-", "0XFFAE" };
static const int64_t num[13] = { 0xFFAE, 0x22, 0, 0xAADDAD, 0x7FFFFFFFFFFFFFFF, -0xf, 0, 0, 0xf, 0xaa, -0xff, 0x0, 0xFFAE };
for (int i = 0; i < 12; i++) {
for (int i = 0; i < 13; i++) {
CHECK(String(nums[i]).hex_to_int() == num[i]);
}
@ -533,10 +578,10 @@ TEST_CASE("[String] Hex to integer") {
}
TEST_CASE("[String] Bin to integer") {
static const char *nums[10] = { "", "0", "0b0", "0b1", "0b", "1", "0b1010", "-0b11", "-1010", "0b0111111111111111111111111111111111111111111111111111111111111111" };
static const int64_t num[10] = { 0, 0, 0, 1, 0, 1, 10, -3, -10, 0x7FFFFFFFFFFFFFFF };
static const char *nums[11] = { "", "0", "0b0", "0b1", "0b", "1", "0b1010", "-0b11", "-1010", "0b0111111111111111111111111111111111111111111111111111111111111111", "0B1010" };
static const int64_t num[11] = { 0, 0, 0, 1, 0, 1, 10, -3, -10, 0x7FFFFFFFFFFFFFFF, 10 };
for (int i = 0; i < 10; i++) {
for (int i = 0; i < 11; i++) {
CHECK(String(nums[i]).bin_to_int() == num[i]);
}
@ -638,64 +683,90 @@ TEST_CASE("[String] Ends with") {
}
TEST_CASE("[String] Splitting") {
String s = "Mars,Jupiter,Saturn,Uranus";
const char *slices_l[3] = { "Mars", "Jupiter", "Saturn,Uranus" };
MULTICHECK_SPLIT(s, split, ",", true, 2, slices_l, 3);
{
const String s = "Mars,Jupiter,Saturn,Uranus";
const char *slices_r[3] = { "Mars,Jupiter", "Saturn", "Uranus" };
MULTICHECK_SPLIT(s, rsplit, ",", true, 2, slices_r, 3);
const char *slices_l[3] = { "Mars", "Jupiter", "Saturn,Uranus" };
MULTICHECK_SPLIT(s, split, ",", true, 2, slices_l, 3);
s = "test";
const char *slices_3[4] = { "t", "e", "s", "t" };
MULTICHECK_SPLIT(s, split, "", true, 0, slices_3, 4);
s = "";
const char *slices_4[1] = { "" };
MULTICHECK_SPLIT(s, split, "", true, 0, slices_4, 1);
MULTICHECK_SPLIT(s, split, "", false, 0, slices_4, 0);
s = "Mars Jupiter Saturn Uranus";
const char *slices_s[4] = { "Mars", "Jupiter", "Saturn", "Uranus" };
Vector<String> l = s.split_spaces();
for (int i = 0; i < l.size(); i++) {
CHECK(l[i] == slices_s[i]);
const char *slices_r[3] = { "Mars,Jupiter", "Saturn", "Uranus" };
MULTICHECK_SPLIT(s, rsplit, ",", true, 2, slices_r, 3);
}
s = "1.2;2.3 4.5";
const double slices_d[3] = { 1.2, 2.3, 4.5 };
Vector<double> d_arr;
d_arr = s.split_floats(";");
CHECK(d_arr.size() == 2);
for (int i = 0; i < d_arr.size(); i++) {
CHECK(ABS(d_arr[i] - slices_d[i]) <= 0.00001);
{
const String s = "test";
const char *slices[4] = { "t", "e", "s", "t" };
MULTICHECK_SPLIT(s, split, "", true, 0, slices, 4);
}
Vector<String> keys;
keys.push_back(";");
keys.push_back(" ");
Vector<float> f_arr;
f_arr = s.split_floats_mk(keys);
CHECK(f_arr.size() == 3);
for (int i = 0; i < f_arr.size(); i++) {
CHECK(ABS(f_arr[i] - slices_d[i]) <= 0.00001);
{
const String s = "";
const char *slices[1] = { "" };
MULTICHECK_SPLIT(s, split, "", true, 0, slices, 1);
MULTICHECK_SPLIT(s, split, "", false, 0, slices, 0);
}
s = "1;2 4";
const int slices_i[3] = { 1, 2, 4 };
Vector<int> ii;
ii = s.split_ints(";");
CHECK(ii.size() == 2);
for (int i = 0; i < ii.size(); i++) {
CHECK(ii[i] == slices_i[i]);
{
const String s = "Mars Jupiter Saturn Uranus";
const char *slices[4] = { "Mars", "Jupiter", "Saturn", "Uranus" };
Vector<String> l = s.split_spaces();
for (int i = 0; i < l.size(); i++) {
CHECK(l[i] == slices[i]);
}
}
ii = s.split_ints_mk(keys);
CHECK(ii.size() == 3);
for (int i = 0; i < ii.size(); i++) {
CHECK(ii[i] == slices_i[i]);
{
const String s = "1.2;2.3 4.5";
const double slices[3] = { 1.2, 2.3, 4.5 };
const Vector<double> d_arr = s.split_floats(";");
CHECK(d_arr.size() == 2);
for (int i = 0; i < d_arr.size(); i++) {
CHECK(ABS(d_arr[i] - slices[i]) <= 0.00001);
}
const Vector<String> keys = { ";", " " };
const Vector<float> f_arr = s.split_floats_mk(keys);
CHECK(f_arr.size() == 3);
for (int i = 0; i < f_arr.size(); i++) {
CHECK(ABS(f_arr[i] - slices[i]) <= 0.00001);
}
}
{
const String s = " -2.0 5";
const double slices[10] = { 0, -2, 0, 0, 0, 0, 0, 0, 0, 5 };
const Vector<double> arr = s.split_floats(" ");
CHECK(arr.size() == 10);
for (int i = 0; i < arr.size(); i++) {
CHECK(ABS(arr[i] - slices[i]) <= 0.00001);
}
const Vector<String> keys = { ";", " " };
const Vector<float> mk = s.split_floats_mk(keys);
CHECK(mk.size() == 10);
for (int i = 0; i < mk.size(); i++) {
CHECK(mk[i] == slices[i]);
}
}
{
const String s = "1;2 4";
const int slices[3] = { 1, 2, 4 };
const Vector<int> arr = s.split_ints(";");
CHECK(arr.size() == 2);
for (int i = 0; i < arr.size(); i++) {
CHECK(arr[i] == slices[i]);
}
const Vector<String> keys = { ";", " " };
const Vector<int> mk = s.split_ints_mk(keys);
CHECK(mk.size() == 3);
for (int i = 0; i < mk.size(); i++) {
CHECK(mk[i] == slices[i]);
}
}
}
@ -1199,6 +1270,12 @@ TEST_CASE("[String] is_subsequence_of") {
CHECK(String("Sub").is_subsequence_ofn(a));
}
TEST_CASE("[String] is_lowercase") {
CHECK(String("abcd1234 !@#$%^&*()_-=+,.<>/\\|[]{};':\"`~").is_lowercase());
CHECK(String("").is_lowercase());
CHECK(!String("abc_ABC").is_lowercase());
}
TEST_CASE("[String] match") {
CHECK(String("img1.png").match("*.png"));
CHECK(!String("img1.jpeg").match("*.png"));
@ -1594,7 +1671,7 @@ TEST_CASE("[String] Path functions") {
static const char *base_name[8] = { "C:\\Godot\\project\\test", "/Godot/project/test", "../Godot/project/test", "Godot\\test", "C:\\test", "res://test", "user://test", "/" };
static const char *ext[8] = { "tscn", "xscn", "scn", "doc", "", "", "", "test" };
static const char *file[8] = { "test.tscn", "test.xscn", "test.scn", "test.doc", "test.", "test", "test", ".test" };
static const char *simplified[8] = { "C:/Godot/project/test.tscn", "/Godot/project/test.xscn", "Godot/project/test.scn", "Godot/test.doc", "C:/test.", "res://test", "user://test", "/.test" };
static const char *simplified[8] = { "C:/Godot/project/test.tscn", "/Godot/project/test.xscn", "../Godot/project/test.scn", "Godot/test.doc", "C:/test.", "res://test", "user://test", "/.test" };
static const bool abs[8] = { true, true, false, false, true, true, true, true };
for (int i = 0; i < 8; i++) {
@ -1613,6 +1690,10 @@ TEST_CASE("[String] Path functions") {
for (int i = 0; i < 3; i++) {
CHECK(String(file_name[i]).is_valid_filename() == valid[i]);
}
CHECK(String("res://texture.png") == String("res://folder/../folder/../texture.png").simplify_path());
CHECK(String("res://texture.png") == String("res://folder/sub/../../texture.png").simplify_path());
CHECK(String("res://../../texture.png") == String("res://../../texture.png").simplify_path());
}
TEST_CASE("[String] hash") {
@ -1785,31 +1866,45 @@ TEST_CASE("[String] SHA1/SHA256/MD5") {
}
TEST_CASE("[String] Join") {
String s = ", ";
String comma = ", ";
String empty = "";
Vector<String> parts;
CHECK(comma.join(parts) == "");
CHECK(empty.join(parts) == "");
parts.push_back("One");
CHECK(comma.join(parts) == "One");
CHECK(empty.join(parts) == "One");
parts.push_back("B");
parts.push_back("C");
String t = s.join(parts);
CHECK(t == "One, B, C");
CHECK(comma.join(parts) == "One, B, C");
CHECK(empty.join(parts) == "OneBC");
parts.push_back("");
CHECK(comma.join(parts) == "One, B, C, ");
CHECK(empty.join(parts) == "OneBC");
}
TEST_CASE("[String] Is_*") {
static const char *data[12] = { "-30", "100", "10.1", "10,1", "1e2", "1e-2", "1e2e3", "0xAB", "AB", "Test1", "1Test", "Test*1" };
static bool isnum[12] = { true, true, true, false, false, false, false, false, false, false, false, false };
static bool isint[12] = { true, true, false, false, false, false, false, false, false, false, false, false };
static bool ishex[12] = { true, true, false, false, true, false, true, false, true, false, false, false };
static bool ishex_p[12] = { false, false, false, false, false, false, false, true, false, false, false, false };
static bool isflt[12] = { true, true, true, false, true, true, false, false, false, false, false, false };
static bool isid[12] = { false, false, false, false, false, false, false, false, true, true, false, false };
for (int i = 0; i < 12; i++) {
String s = String(data[i]);
static const char *data[] = { "-30", "100", "10.1", "10,1", "1e2", "1e-2", "1e2e3", "0xAB", "AB", "Test1", "1Test", "Test*1", "文字", "1E2", "1E-2" };
static bool isnum[] = { true, true, true, false, false, false, false, false, false, false, false, false, false, false, false };
static bool isint[] = { true, true, false, false, false, false, false, false, false, false, false, false, false, false, false };
static bool ishex[] = { true, true, false, false, true, false, true, false, true, false, false, false, false, true, false };
static bool ishex_p[] = { false, false, false, false, false, false, false, true, false, false, false, false, false, false, false };
static bool isflt[] = { true, true, true, false, true, true, false, false, false, false, false, false, false, true, true };
static bool isaid[] = { false, false, false, false, false, false, false, false, true, true, false, false, false, false, false };
static bool isuid[] = { false, false, false, false, false, false, false, false, true, true, false, false, true, false, false };
for (unsigned int i = 0; i < sizeof(data) / sizeof(data[0]); i++) {
String s = String::utf8(data[i]);
CHECK(s.is_numeric() == isnum[i]);
CHECK(s.is_valid_int() == isint[i]);
CHECK(s.is_valid_hex_number(false) == ishex[i]);
CHECK(s.is_valid_hex_number(true) == ishex_p[i]);
CHECK(s.is_valid_float() == isflt[i]);
CHECK(s.is_valid_identifier() == isid[i]);
CHECK(s.is_valid_ascii_identifier() == isaid[i]);
CHECK(s.is_valid_unicode_identifier() == isuid[i]);
}
}
@ -1835,18 +1930,32 @@ TEST_CASE("[String] validate_node_name") {
CHECK(name_with_invalid_chars.validate_node_name() == "Name with invalid characters ____removed!");
}
TEST_CASE("[String] validate_identifier") {
TEST_CASE("[String] validate_ascii_identifier") {
String empty_string;
CHECK(empty_string.validate_identifier() == "_");
CHECK(empty_string.validate_ascii_identifier() == "_");
String numeric_only = "12345";
CHECK(numeric_only.validate_identifier() == "_12345");
CHECK(numeric_only.validate_ascii_identifier() == "_12345");
String name_with_spaces = "Name with spaces";
CHECK(name_with_spaces.validate_identifier() == "Name_with_spaces");
CHECK(name_with_spaces.validate_ascii_identifier() == "Name_with_spaces");
String name_with_invalid_chars = U"Invalid characters:@*#&世界";
CHECK(name_with_invalid_chars.validate_identifier() == "Invalid_characters_______");
CHECK(name_with_invalid_chars.validate_ascii_identifier() == "Invalid_characters_______");
}
TEST_CASE("[String] validate_unicode_identifier") {
String empty_string;
CHECK(empty_string.validate_unicode_identifier() == "_");
String numeric_only = "12345";
CHECK(numeric_only.validate_unicode_identifier() == "_12345");
String name_with_spaces = "Name with spaces";
CHECK(name_with_spaces.validate_unicode_identifier() == "Name_with_spaces");
String name_with_invalid_chars = U"Invalid characters:@*#&世界";
CHECK(name_with_invalid_chars.validate_unicode_identifier() == U"Invalid_characters_____世界");
}
TEST_CASE("[String] Variant indexed get") {
@ -1921,6 +2030,61 @@ TEST_CASE("[String] Variant ptr indexed set") {
CHECK_EQ(s, String("azcd"));
}
TEST_CASE("[String][URL] Parse URL") {
#define CHECK_URL(m_url_to_parse, m_expected_schema, m_expected_host, m_expected_port, m_expected_path, m_expected_fragment, m_expected_error) \
if (true) { \
int port; \
String url(m_url_to_parse), schema, host, path, fragment; \
\
CHECK_EQ(url.parse_url(schema, host, port, path, fragment), m_expected_error); \
CHECK_EQ(schema, m_expected_schema); \
CHECK_EQ(host, m_expected_host); \
CHECK_EQ(path, m_expected_path); \
CHECK_EQ(fragment, m_expected_fragment); \
CHECK_EQ(port, m_expected_port); \
} else \
((void)0)
// All elements.
CHECK_URL("https://www.example.com:8080/path/to/file.html#fragment", "https://", "www.example.com", 8080, "/path/to/file.html", "fragment", Error::OK);
// Valid URLs.
CHECK_URL("https://godotengine.org", "https://", "godotengine.org", 0, "", "", Error::OK);
CHECK_URL("https://godotengine.org/", "https://", "godotengine.org", 0, "/", "", Error::OK);
CHECK_URL("godotengine.org/", "", "godotengine.org", 0, "/", "", Error::OK);
CHECK_URL("HTTPS://godotengine.org/", "https://", "godotengine.org", 0, "/", "", Error::OK);
CHECK_URL("https://GODOTENGINE.ORG/", "https://", "godotengine.org", 0, "/", "", Error::OK);
CHECK_URL("http://godotengine.org", "http://", "godotengine.org", 0, "", "", Error::OK);
CHECK_URL("https://godotengine.org:8080", "https://", "godotengine.org", 8080, "", "", Error::OK);
CHECK_URL("https://godotengine.org/blog", "https://", "godotengine.org", 0, "/blog", "", Error::OK);
CHECK_URL("https://godotengine.org/blog/", "https://", "godotengine.org", 0, "/blog/", "", Error::OK);
CHECK_URL("https://docs.godotengine.org/en/stable", "https://", "docs.godotengine.org", 0, "/en/stable", "", Error::OK);
CHECK_URL("https://docs.godotengine.org/en/stable/", "https://", "docs.godotengine.org", 0, "/en/stable/", "", Error::OK);
CHECK_URL("https://me:secret@godotengine.org", "https://", "godotengine.org", 0, "", "", Error::OK);
CHECK_URL("https://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]/ipv6", "https://", "fedc:ba98:7654:3210:fedc:ba98:7654:3210", 0, "/ipv6", "", Error::OK);
// Scheme vs Fragment.
CHECK_URL("google.com/#goto=http://redirect_url/", "", "google.com", 0, "/", "goto=http://redirect_url/", Error::OK);
// Invalid URLs.
// Invalid Scheme.
CHECK_URL("https_://godotengine.org", "", "https_", 0, "//godotengine.org", "", Error::ERR_INVALID_PARAMETER);
// Multiple ports.
CHECK_URL("https://godotengine.org:8080:433", "https://", "", 0, "", "", Error::ERR_INVALID_PARAMETER);
// Missing ] on literal IPv6.
CHECK_URL("https://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210/ipv6", "https://", "", 0, "/ipv6", "", Error::ERR_INVALID_PARAMETER);
// Missing host.
CHECK_URL("https:///blog", "https://", "", 0, "/blog", "", Error::ERR_INVALID_PARAMETER);
// Invalid ports.
CHECK_URL("https://godotengine.org:notaport", "https://", "godotengine.org", 0, "", "", Error::ERR_INVALID_PARAMETER);
CHECK_URL("https://godotengine.org:-8080", "https://", "godotengine.org", -8080, "", "", Error::ERR_INVALID_PARAMETER);
CHECK_URL("https://godotengine.org:88888", "https://", "godotengine.org", 88888, "", "", Error::ERR_INVALID_PARAMETER);
#undef CHECK_URL
}
TEST_CASE("[Stress][String] Empty via ' == String()'") {
for (int i = 0; i < 100000; ++i) {
String str = "Hello World!";

View file

@ -34,6 +34,7 @@
#include "core/string/optimized_translation.h"
#include "core/string/translation.h"
#include "core/string/translation_po.h"
#include "core/string/translation_server.h"
#ifdef TOOLS_ENABLED
#include "editor/import/resource_importer_csv_translation.h"
@ -160,7 +161,7 @@ TEST_CASE("[TranslationCSV] CSV import") {
List<String> gen_files;
Error result = import_csv_translation->import(TestUtils::get_data_path("translations.csv"),
Error result = import_csv_translation->import(0, TestUtils::get_data_path("translations.csv"),
"", options, nullptr, &gen_files);
CHECK(result == OK);
CHECK(gen_files.size() == 4);

View file

@ -31,33 +31,43 @@
#ifndef TEST_TRANSLATION_SERVER_H
#define TEST_TRANSLATION_SERVER_H
#include "core/string/translation.h"
#include "core/string/translation_server.h"
#include "tests/test_macros.h"
namespace TestTranslationServer {
TEST_CASE("[TranslationServer] Translation operations") {
Ref<Translation> t = memnew(Translation);
t->set_locale("uk");
t->add_message("Good Morning", String::utf8("Добрий ранок"));
Ref<Translation> t1 = memnew(Translation);
t1->set_locale("uk");
t1->add_message("Good Morning", String(U"Добрий ранок"));
Ref<Translation> t2 = memnew(Translation);
t2->set_locale("uk");
t2->add_message("Hello Godot", String(U"你好戈多"));
TranslationServer *ts = TranslationServer::get_singleton();
// Adds translation for UK locale for the first time.
int l_count_before = ts->get_loaded_locales().size();
ts->add_translation(t);
ts->add_translation(t1);
int l_count_after = ts->get_loaded_locales().size();
// Newly created Translation object should be added to the list, so the counter should increase, too.
CHECK(l_count_after > l_count_before);
Ref<Translation> trans = ts->get_translation_object("uk");
CHECK(trans.is_valid());
// Adds translation for UK locale again.
ts->add_translation(t2);
CHECK_EQ(ts->get_loaded_locales().size(), l_count_after);
// Removing that translation.
ts->remove_translation(t2);
CHECK_EQ(ts->get_loaded_locales().size(), l_count_after);
CHECK(ts->get_translation_object("uk").is_valid());
ts->set_locale("uk");
CHECK(ts->translate("Good Morning") == String::utf8("Добрий ранок"));
ts->remove_translation(t);
trans = ts->get_translation_object("uk");
CHECK(trans.is_null());
ts->remove_translation(t1);
CHECK(ts->get_translation_object("uk").is_null());
// If no suitable Translation object has been found - the original message should be returned.
CHECK(ts->translate("Good Morning") == "Good Morning");
}
@ -94,6 +104,36 @@ TEST_CASE("[TranslationServer] Locale operations") {
res = ts->standardize_locale(loc);
CHECK(res == "de_DE");
// No added defaults.
loc = "es_ES";
res = ts->standardize_locale(loc, true);
CHECK(res == "es_ES");
// Add default script.
loc = "az_AZ";
res = ts->standardize_locale(loc, true);
CHECK(res == "az_Latn_AZ");
// Add default country.
loc = "pa_Arab";
res = ts->standardize_locale(loc, true);
CHECK(res == "pa_Arab_PK");
// Add default script and country.
loc = "zh";
res = ts->standardize_locale(loc, true);
CHECK(res == "zh_Hans_CN");
// Explicitly don't add defaults.
loc = "zh";
res = ts->standardize_locale(loc, false);
CHECK(res == "zh");
}
TEST_CASE("[TranslationServer] Comparing locales") {
@ -110,18 +150,50 @@ TEST_CASE("[TranslationServer] Comparing locales") {
locale_a = "sr-Latn-CS";
locale_b = "sr-Latn-RS";
// Two elements from locales match.
// Script matches (+1) but country doesn't (-1).
res = ts->compare_locales(locale_a, locale_b);
CHECK(res == 2);
CHECK(res == 5);
locale_a = "uz-Cyrl-UZ";
locale_b = "uz-Latn-UZ";
// Two elements match, but they are not sequentual.
// Country matches (+1) but script doesn't (-1).
res = ts->compare_locales(locale_a, locale_b);
CHECK(res == 2);
CHECK(res == 5);
locale_a = "aa-Latn-ER";
locale_b = "aa-Latn-ER-saaho";
// Script and country match (+2) with variant on one locale (+0).
res = ts->compare_locales(locale_a, locale_b);
CHECK(res == 7);
locale_a = "uz-Cyrl-UZ";
locale_b = "uz-Latn-KG";
// Both script and country mismatched (-2).
res = ts->compare_locales(locale_a, locale_b);
CHECK(res == 3);
locale_a = "es-ES";
locale_b = "es-AR";
// Mismatched country (-1).
res = ts->compare_locales(locale_a, locale_b);
CHECK(res == 4);
locale_a = "es";
locale_b = "es-AR";
// No country for one locale (+0).
res = ts->compare_locales(locale_a, locale_b);
CHECK(res == 5);
locale_a = "es-EC";
locale_b = "fr-LU";
@ -130,6 +202,24 @@ TEST_CASE("[TranslationServer] Comparing locales") {
res = ts->compare_locales(locale_a, locale_b);
CHECK(res == 0);
locale_a = "zh-HK";
locale_b = "zh";
// In full standardization, zh-HK becomes zh_Hant_HK and zh becomes
// zh_Hans_CN. Both script and country mismatch (-2).
res = ts->compare_locales(locale_a, locale_b);
CHECK(res == 3);
locale_a = "zh-CN";
locale_b = "zh";
// In full standardization, zh and zh-CN both become zh_Hans_CN for an
// exact match.
res = ts->compare_locales(locale_a, locale_b);
CHECK(res == 10);
}
} // namespace TestTranslationServer

View file

@ -0,0 +1,313 @@
/**************************************************************************/
/* test_a_hash_map.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_A_HASH_MAP_H
#define TEST_A_HASH_MAP_H
#include "core/templates/a_hash_map.h"
#include "tests/test_macros.h"
namespace TestAHashMap {
TEST_CASE("[AHashMap] List initialization") {
AHashMap<int, String> map{ { 0, "A" }, { 1, "B" }, { 2, "C" }, { 3, "D" }, { 4, "E" } };
CHECK(map.size() == 5);
CHECK(map[0] == "A");
CHECK(map[1] == "B");
CHECK(map[2] == "C");
CHECK(map[3] == "D");
CHECK(map[4] == "E");
}
TEST_CASE("[AHashMap] List initialization with existing elements") {
AHashMap<int, String> map{ { 0, "A" }, { 0, "B" }, { 0, "C" }, { 0, "D" }, { 0, "E" } };
CHECK(map.size() == 1);
CHECK(map[0] == "E");
}
TEST_CASE("[AHashMap] Insert element") {
AHashMap<int, int> map;
AHashMap<int, int>::Iterator e = map.insert(42, 84);
CHECK(e);
CHECK(e->key == 42);
CHECK(e->value == 84);
CHECK(map[42] == 84);
CHECK(map.has(42));
CHECK(map.find(42));
}
TEST_CASE("[AHashMap] Overwrite element") {
AHashMap<int, int> map;
map.insert(42, 84);
map.insert(42, 1234);
CHECK(map[42] == 1234);
}
TEST_CASE("[AHashMap] Erase via element") {
AHashMap<int, int> map;
AHashMap<int, int>::Iterator e = map.insert(42, 84);
map.remove(e);
CHECK(!map.has(42));
CHECK(!map.find(42));
}
TEST_CASE("[AHashMap] Erase via key") {
AHashMap<int, int> map;
map.insert(42, 84);
map.erase(42);
CHECK(!map.has(42));
CHECK(!map.find(42));
}
TEST_CASE("[AHashMap] Size") {
AHashMap<int, int> map;
map.insert(42, 84);
map.insert(123, 84);
map.insert(123, 84);
map.insert(0, 84);
map.insert(123485, 84);
CHECK(map.size() == 4);
}
TEST_CASE("[AHashMap] Iteration") {
AHashMap<int, int> map;
map.insert(42, 84);
map.insert(123, 12385);
map.insert(0, 12934);
map.insert(123485, 1238888);
map.insert(123, 111111);
Vector<Pair<int, int>> expected;
expected.push_back(Pair<int, int>(42, 84));
expected.push_back(Pair<int, int>(123, 111111));
expected.push_back(Pair<int, int>(0, 12934));
expected.push_back(Pair<int, int>(123485, 1238888));
int idx = 0;
for (const KeyValue<int, int> &E : map) {
CHECK(expected[idx] == Pair<int, int>(E.key, E.value));
idx++;
}
idx--;
for (AHashMap<int, int>::Iterator it = map.last(); it; --it) {
CHECK(expected[idx] == Pair<int, int>(it->key, it->value));
idx--;
}
}
TEST_CASE("[AHashMap] Const iteration") {
AHashMap<int, int> map;
map.insert(42, 84);
map.insert(123, 12385);
map.insert(0, 12934);
map.insert(123485, 1238888);
map.insert(123, 111111);
const AHashMap<int, int> const_map = map;
Vector<Pair<int, int>> expected;
expected.push_back(Pair<int, int>(42, 84));
expected.push_back(Pair<int, int>(123, 111111));
expected.push_back(Pair<int, int>(0, 12934));
expected.push_back(Pair<int, int>(123485, 1238888));
expected.push_back(Pair<int, int>(123, 111111));
int idx = 0;
for (const KeyValue<int, int> &E : const_map) {
CHECK(expected[idx] == Pair<int, int>(E.key, E.value));
idx++;
}
idx--;
for (AHashMap<int, int>::ConstIterator it = const_map.last(); it; --it) {
CHECK(expected[idx] == Pair<int, int>(it->key, it->value));
idx--;
}
}
TEST_CASE("[AHashMap] Replace key") {
AHashMap<int, int> map;
map.insert(42, 84);
map.insert(0, 12934);
CHECK(map.replace_key(0, 1));
CHECK(map.has(1));
CHECK(map[1] == 12934);
}
TEST_CASE("[AHashMap] Clear") {
AHashMap<int, int> map;
map.insert(42, 84);
map.insert(123, 12385);
map.insert(0, 12934);
map.clear();
CHECK(!map.has(42));
CHECK(map.size() == 0);
CHECK(map.is_empty());
}
TEST_CASE("[AHashMap] Get") {
AHashMap<int, int> map;
map.insert(42, 84);
map.insert(123, 12385);
map.insert(0, 12934);
CHECK(map.get(123) == 12385);
map.get(123) = 10;
CHECK(map.get(123) == 10);
CHECK(*map.getptr(0) == 12934);
*map.getptr(0) = 1;
CHECK(*map.getptr(0) == 1);
CHECK(map.get(42) == 84);
CHECK(map.getptr(-10) == nullptr);
}
TEST_CASE("[AHashMap] Insert, iterate and remove many elements") {
const int elem_max = 1234;
AHashMap<int, int> map;
for (int i = 0; i < elem_max; i++) {
map.insert(i, i);
}
//insert order should have been kept
int idx = 0;
for (auto &K : map) {
CHECK(idx == K.key);
CHECK(idx == K.value);
CHECK(map.has(idx));
idx++;
}
Vector<int> elems_still_valid;
for (int i = 0; i < elem_max; i++) {
if ((i % 5) == 0) {
map.erase(i);
} else {
elems_still_valid.push_back(i);
}
}
CHECK(elems_still_valid.size() == map.size());
for (int i = 0; i < elems_still_valid.size(); i++) {
CHECK(map.has(elems_still_valid[i]));
}
}
TEST_CASE("[AHashMap] Insert, iterate and remove many strings") {
const int elem_max = 432;
AHashMap<String, String> map;
for (int i = 0; i < elem_max; i++) {
map.insert(itos(i), itos(i));
}
//insert order should have been kept
int idx = 0;
for (auto &K : map) {
CHECK(itos(idx) == K.key);
CHECK(itos(idx) == K.value);
CHECK(map.has(itos(idx)));
idx++;
}
Vector<String> elems_still_valid;
for (int i = 0; i < elem_max; i++) {
if ((i % 5) == 0) {
map.erase(itos(i));
} else {
elems_still_valid.push_back(itos(i));
}
}
CHECK(elems_still_valid.size() == map.size());
for (int i = 0; i < elems_still_valid.size(); i++) {
CHECK(map.has(elems_still_valid[i]));
}
elems_still_valid.clear();
}
TEST_CASE("[AHashMap] Copy constructor") {
AHashMap<int, int> map0;
const uint32_t count = 5;
for (uint32_t i = 0; i < count; i++) {
map0.insert(i, i);
}
AHashMap<int, int> map1(map0);
CHECK(map0.size() == map1.size());
CHECK(map0.get_capacity() == map1.get_capacity());
CHECK(*map0.getptr(0) == *map1.getptr(0));
}
TEST_CASE("[AHashMap] Operator =") {
AHashMap<int, int> map0;
AHashMap<int, int> map1;
const uint32_t count = 5;
map1.insert(1234, 1234);
for (uint32_t i = 0; i < count; i++) {
map0.insert(i, i);
}
map1 = map0;
CHECK(map0.size() == map1.size());
CHECK(map0.get_capacity() == map1.get_capacity());
CHECK(*map0.getptr(0) == *map1.getptr(0));
}
TEST_CASE("[AHashMap] Array methods") {
AHashMap<int, int> map;
for (int i = 0; i < 100; i++) {
map.insert(100 - i, i);
}
for (int i = 0; i < 100; i++) {
CHECK(map.get_by_index(i).value == i);
}
int index = map.get_index(1);
CHECK(map.get_by_index(index).value == 99);
CHECK(map.erase_by_index(index));
CHECK(!map.erase_by_index(index));
CHECK(map.get_index(1) == -1);
}
} // namespace TestAHashMap
#endif // TEST_A_HASH_MAP_H

View file

@ -201,10 +201,10 @@ public:
command_queue.push_and_sync(this, &SharedThreadState::func2, tr, f);
break;
case TEST_MSGRET_FUNC1_TRANSFORM:
command_queue.push_and_ret(this, &SharedThreadState::func1r, tr, &otr);
command_queue.push_and_ret(this, &SharedThreadState::func1r, &otr, tr);
break;
case TEST_MSGRET_FUNC2_TRANSFORM_FLOAT:
command_queue.push_and_ret(this, &SharedThreadState::func2r, tr, f, &otr);
command_queue.push_and_ret(this, &SharedThreadState::func2r, &otr, tr, f);
break;
default:
break;
@ -244,6 +244,44 @@ public:
}
writer_thread.wait_to_finish();
}
struct CopyMoveTestType {
inline static int copy_count;
inline static int move_count;
int value = 0;
CopyMoveTestType(int p_value = 0) :
value(p_value) {}
CopyMoveTestType(const CopyMoveTestType &p_other) :
value(p_other.value) {
copy_count++;
}
CopyMoveTestType(CopyMoveTestType &&p_other) :
value(p_other.value) {
move_count++;
}
CopyMoveTestType &operator=(const CopyMoveTestType &p_other) {
value = p_other.value;
copy_count++;
return *this;
}
CopyMoveTestType &operator=(CopyMoveTestType &&p_other) {
value = p_other.value;
move_count++;
return *this;
}
};
void copy_move_test_copy(CopyMoveTestType p_test_type) {
}
void copy_move_test_ref(const CopyMoveTestType &p_test_type) {
}
void copy_move_test_move(CopyMoveTestType &&p_test_type) {
}
};
static void test_command_queue_basic(bool p_use_thread_pool_sync) {
@ -446,6 +484,83 @@ TEST_CASE("[Stress][CommandQueue] Stress test command queue") {
ProjectSettings::get_singleton()->set_setting(COMMAND_QUEUE_SETTING,
ProjectSettings::get_singleton()->property_get_revert(COMMAND_QUEUE_SETTING));
}
TEST_CASE("[CommandQueue] Test Parameter Passing Semantics") {
SharedThreadState sts;
sts.init_threads();
SUBCASE("Testing with lvalue") {
SharedThreadState::CopyMoveTestType::copy_count = 0;
SharedThreadState::CopyMoveTestType::move_count = 0;
SharedThreadState::CopyMoveTestType lvalue(42);
SUBCASE("Pass by copy") {
sts.command_queue.push(&sts, &SharedThreadState::copy_move_test_copy, lvalue);
sts.message_count_to_read = -1;
sts.reader_threadwork.main_start_work();
sts.reader_threadwork.main_wait_for_done();
CHECK(SharedThreadState::CopyMoveTestType::copy_count == 1);
CHECK(SharedThreadState::CopyMoveTestType::move_count == 1);
}
SUBCASE("Pass by reference") {
sts.command_queue.push(&sts, &SharedThreadState::copy_move_test_ref, lvalue);
sts.message_count_to_read = -1;
sts.reader_threadwork.main_start_work();
sts.reader_threadwork.main_wait_for_done();
CHECK(SharedThreadState::CopyMoveTestType::copy_count == 1);
CHECK(SharedThreadState::CopyMoveTestType::move_count == 0);
}
}
SUBCASE("Testing with rvalue") {
SharedThreadState::CopyMoveTestType::copy_count = 0;
SharedThreadState::CopyMoveTestType::move_count = 0;
SUBCASE("Pass by copy") {
sts.command_queue.push(&sts, &SharedThreadState::copy_move_test_copy,
SharedThreadState::CopyMoveTestType(43));
sts.message_count_to_read = -1;
sts.reader_threadwork.main_start_work();
sts.reader_threadwork.main_wait_for_done();
CHECK(SharedThreadState::CopyMoveTestType::copy_count == 0);
CHECK(SharedThreadState::CopyMoveTestType::move_count == 2);
}
SUBCASE("Pass by reference") {
sts.command_queue.push(&sts, &SharedThreadState::copy_move_test_ref,
SharedThreadState::CopyMoveTestType(43));
sts.message_count_to_read = -1;
sts.reader_threadwork.main_start_work();
sts.reader_threadwork.main_wait_for_done();
CHECK(SharedThreadState::CopyMoveTestType::copy_count == 0);
CHECK(SharedThreadState::CopyMoveTestType::move_count == 1);
}
SUBCASE("Pass by rvalue reference") {
sts.command_queue.push(&sts, &SharedThreadState::copy_move_test_move,
SharedThreadState::CopyMoveTestType(43));
sts.message_count_to_read = -1;
sts.reader_threadwork.main_start_work();
sts.reader_threadwork.main_wait_for_done();
CHECK(SharedThreadState::CopyMoveTestType::copy_count == 0);
CHECK(SharedThreadState::CopyMoveTestType::move_count == 1);
}
}
sts.destroy_threads();
}
} // namespace TestCommandQueue
#endif // TEST_COMMAND_QUEUE_H

View file

@ -37,6 +37,24 @@
namespace TestHashMap {
TEST_CASE("[HashMap] List initialization") {
HashMap<int, String> map{ { 0, "A" }, { 1, "B" }, { 2, "C" }, { 3, "D" }, { 4, "E" } };
CHECK(map.size() == 5);
CHECK(map[0] == "A");
CHECK(map[1] == "B");
CHECK(map[2] == "C");
CHECK(map[3] == "D");
CHECK(map[4] == "E");
}
TEST_CASE("[HashMap] List initialization with existing elements") {
HashMap<int, String> map{ { 0, "A" }, { 0, "B" }, { 0, "C" }, { 0, "D" }, { 0, "E" } };
CHECK(map.size() == 1);
CHECK(map[0] == "E");
}
TEST_CASE("[HashMap] Insert element") {
HashMap<int, int> map;
HashMap<int, int>::Iterator e = map.insert(42, 84);

View file

@ -37,6 +37,24 @@
namespace TestHashSet {
TEST_CASE("[HashSet] List initialization") {
HashSet<int> set{ 0, 1, 2, 3, 4 };
CHECK(set.size() == 5);
CHECK(set.has(0));
CHECK(set.has(1));
CHECK(set.has(2));
CHECK(set.has(3));
CHECK(set.has(4));
}
TEST_CASE("[HashSet] List initialization with existing elements") {
HashSet<int> set{ 0, 0, 0, 0, 0 };
CHECK(set.size() == 1);
CHECK(set.has(0));
}
TEST_CASE("[HashSet] Insert element") {
HashSet<int> set;
HashSet<int>::Iterator e = set.insert(42);

View file

@ -45,6 +45,17 @@ static void populate_integers(List<int> &p_list, List<int>::Element *r_elements[
}
}
TEST_CASE("[List] List initialization") {
List<int> list{ 0, 1, 2, 3, 4 };
CHECK(list.size() == 5);
CHECK(list.get(0) == 0);
CHECK(list.get(1) == 1);
CHECK(list.get(2) == 2);
CHECK(list.get(3) == 3);
CHECK(list.get(4) == 4);
}
TEST_CASE("[List] Push/pop back") {
List<String> list;

View file

@ -38,6 +38,32 @@
namespace TestOAHashMap {
TEST_CASE("[OAHashMap] List initialization") {
OAHashMap<int, String> map{ { 0, "A" }, { 1, "B" }, { 2, "C" }, { 3, "D" }, { 4, "E" } };
CHECK(map.get_num_elements() == 5);
String value;
CHECK(map.lookup(0, value));
CHECK(value == "A");
CHECK(map.lookup(1, value));
CHECK(value == "B");
CHECK(map.lookup(2, value));
CHECK(value == "C");
CHECK(map.lookup(3, value));
CHECK(value == "D");
CHECK(map.lookup(4, value));
CHECK(value == "E");
}
TEST_CASE("[OAHashMap] List initialization with existing elements") {
OAHashMap<int, String> map{ { 0, "A" }, { 0, "B" }, { 0, "C" }, { 0, "D" }, { 0, "E" } };
CHECK(map.get_num_elements() == 1);
String value;
CHECK(map.lookup(0, value));
CHECK(value == "E");
}
TEST_CASE("[OAHashMap] Insert element") {
OAHashMap<int, int> map;
map.insert(42, 84);

View file

@ -31,10 +31,27 @@
#ifndef TEST_RID_H
#define TEST_RID_H
#include "core/os/thread.h"
#include "core/templates/local_vector.h"
#include "core/templates/rid.h"
#include "core/templates/rid_owner.h"
#include "tests/test_macros.h"
#ifdef SANITIZERS_ENABLED
#ifdef __has_feature
#if __has_feature(thread_sanitizer)
#define TSAN_ENABLED
#endif
#elif defined(__SANITIZE_THREAD__)
#define TSAN_ENABLED
#endif
#endif
#ifdef TSAN_ENABLED
#include <sanitizer/tsan_interface.h>
#endif
namespace TestRID {
TEST_CASE("[RID] Default Constructor") {
RID rid;
@ -96,6 +113,142 @@ TEST_CASE("[RID] 'get_local_index'") {
CHECK(RID::from_uint64(4'294'967'295).get_local_index() == 4'294'967'295);
CHECK(RID::from_uint64(4'294'967'297).get_local_index() == 1);
}
// This case would let sanitizers realize data races.
// Additionally, on purely weakly ordered architectures, it would detect synchronization issues
// if RID_Alloc failed to impose proper memory ordering and the test's threads are distributed
// among multiple L1 caches.
TEST_CASE("[RID_Owner] Thread safety") {
struct DataHolder {
char data[Thread::CACHE_LINE_BYTES];
};
struct RID_OwnerTester {
uint32_t thread_count = 0;
RID_Owner<DataHolder, true> rid_owner;
TightLocalVector<Thread> threads;
SafeNumeric<uint32_t> next_thread_idx;
// Using std::atomic directly since SafeNumeric doesn't support relaxed ordering.
TightLocalVector<std::atomic<uint64_t>> rids;
std::atomic<uint32_t> sync[2] = {};
std::atomic<uint32_t> correct = 0;
// A barrier that doesn't introduce memory ordering constraints, only compiler ones.
// The idea is not to cause any sync traffic that would make the code we want to test
// seem correct as a side effect.
void lockstep(uint32_t p_step) {
uint32_t buf_idx = p_step % 2;
uint32_t target = (p_step / 2 + 1) * threads.size();
sync[buf_idx].fetch_add(1, std::memory_order_relaxed);
do {
std::this_thread::yield();
} while (sync[buf_idx].load(std::memory_order_relaxed) != target);
}
explicit RID_OwnerTester(bool p_chunk_for_all, bool p_chunks_preallocated) :
thread_count(OS::get_singleton()->get_processor_count()),
rid_owner(sizeof(DataHolder) * (p_chunk_for_all ? thread_count : 1)) {
threads.resize(thread_count);
rids.resize(threads.size());
if (p_chunks_preallocated) {
LocalVector<RID> prealloc_rids;
for (uint32_t i = 0; i < (p_chunk_for_all ? 1 : threads.size()); i++) {
prealloc_rids.push_back(rid_owner.make_rid());
}
for (uint32_t i = 0; i < prealloc_rids.size(); i++) {
rid_owner.free(prealloc_rids[i]);
}
}
}
~RID_OwnerTester() {
for (uint32_t i = 0; i < threads.size(); i++) {
rid_owner.free(RID::from_uint64(rids[i].load(std::memory_order_relaxed)));
}
}
void test() {
for (uint32_t i = 0; i < threads.size(); i++) {
threads[i].start(
[](void *p_data) {
RID_OwnerTester *rot = (RID_OwnerTester *)p_data;
auto _compute_thread_unique_byte = [](uint32_t p_idx) -> char {
return ((p_idx & 0xff) ^ (0b11111110 << (p_idx % 8)));
};
// 1. Each thread gets a zero-based index.
uint32_t self_th_idx = rot->next_thread_idx.postincrement();
rot->lockstep(0);
// 2. Each thread makes a RID holding unique data.
DataHolder initial_data;
memset(&initial_data, _compute_thread_unique_byte(self_th_idx), Thread::CACHE_LINE_BYTES);
RID my_rid = rot->rid_owner.make_rid(initial_data);
rot->rids[self_th_idx].store(my_rid.get_id(), std::memory_order_relaxed);
rot->lockstep(1);
// 3. Each thread verifies all the others.
uint32_t local_correct = 0;
for (uint32_t th_idx = 0; th_idx < rot->threads.size(); th_idx++) {
if (th_idx == self_th_idx) {
continue;
}
char expected_unique_byte = _compute_thread_unique_byte(th_idx);
RID rid = RID::from_uint64(rot->rids[th_idx].load(std::memory_order_relaxed));
DataHolder *data = rot->rid_owner.get_or_null(rid);
#ifdef TSAN_ENABLED
__tsan_acquire(data); // We know not a race in practice.
#endif
bool ok = true;
for (uint32_t j = 0; j < Thread::CACHE_LINE_BYTES; j++) {
if (data->data[j] != expected_unique_byte) {
ok = false;
break;
}
}
if (ok) {
local_correct++;
}
#ifdef TSAN_ENABLED
__tsan_release(data);
#endif
}
rot->lockstep(2);
rot->correct.fetch_add(local_correct, std::memory_order_acq_rel);
},
this);
}
for (uint32_t i = 0; i < threads.size(); i++) {
threads[i].wait_to_finish();
}
CHECK_EQ(correct.load(), threads.size() * (threads.size() - 1));
}
};
SUBCASE("All items in one chunk, pre-allocated") {
RID_OwnerTester tester(true, true);
tester.test();
}
SUBCASE("All items in one chunk, NOT pre-allocated") {
RID_OwnerTester tester(true, false);
tester.test();
}
SUBCASE("One item per chunk, pre-allocated") {
RID_OwnerTester tester(false, true);
tester.test();
}
SUBCASE("One item per chunk, NOT pre-allocated") {
RID_OwnerTester tester(false, false);
tester.test();
}
}
} // namespace TestRID
#endif // TEST_RID_H

View file

@ -215,6 +215,154 @@ TEST_CASE("[Vector] Get, set") {
CHECK(vector.get(4) == 4);
}
TEST_CASE("[Vector] To byte array (variant call)") {
// PackedInt32Array.
{
PackedInt32Array vector[] = { { 0, -1, 2008 }, {} };
PackedByteArray out[] = { { /* 0 */ 0x00, 0x00, 0x00, 0x00, /* -1 */ 0xFF, 0xFF, 0xFF, 0xFF, /* 2008 */ 0xD8, 0x07, 0x00, 0x00 }, {} };
for (size_t i = 0; i < std::size(vector); i++) {
Callable::CallError err;
Variant v_ret;
Variant v_vector = vector[i];
v_vector.callp("to_byte_array", nullptr, 0, v_ret, err);
CHECK(v_ret.get_type() == Variant::PACKED_BYTE_ARRAY);
CHECK(v_ret.operator PackedByteArray() == out[i]);
}
}
// PackedInt64Array.
{
PackedInt64Array vector[] = { { 0, -1, 2008 }, {} };
PackedByteArray out[] = { { /* 0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* -1 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 2008 */ 0xD8, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, {} };
for (size_t i = 0; i < std::size(vector); i++) {
Callable::CallError err;
Variant v_ret;
Variant v_vector = vector[i];
v_vector.callp("to_byte_array", nullptr, 0, v_ret, err);
CHECK(v_ret.get_type() == Variant::PACKED_BYTE_ARRAY);
CHECK(v_ret.operator PackedByteArray() == out[i]);
}
}
// PackedFloat32Array.
{
PackedFloat32Array vector[] = { { 0.0, -1.0, 200e24 }, {} };
PackedByteArray out[] = { { /* 0.0 */ 0x00, 0x00, 0x00, 0x00, /* -1.0 */ 0x00, 0x00, 0x80, 0xBF, /* 200e24 */ 0xA6, 0x6F, 0x25, 0x6B }, {} };
for (size_t i = 0; i < std::size(vector); i++) {
Callable::CallError err;
Variant v_ret;
Variant v_vector = vector[i];
v_vector.callp("to_byte_array", nullptr, 0, v_ret, err);
CHECK(v_ret.get_type() == Variant::PACKED_BYTE_ARRAY);
CHECK(v_ret.operator PackedByteArray() == out[i]);
}
}
// PackedFloat64Array.
{
PackedFloat64Array vector[] = { { 0.0, -1.0, 200e24 }, {} };
PackedByteArray out[] = { { /* 0.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* -1.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xBF, /* 200e24 */ 0x35, 0x03, 0x32, 0xB7, 0xF4, 0xAD, 0x64, 0x45 }, {} };
for (size_t i = 0; i < std::size(vector); i++) {
Callable::CallError err;
Variant v_ret;
Variant v_vector = vector[i];
v_vector.callp("to_byte_array", nullptr, 0, v_ret, err);
CHECK(v_ret.get_type() == Variant::PACKED_BYTE_ARRAY);
CHECK(v_ret.operator PackedByteArray() == out[i]);
}
}
// PackedStringArray.
{
PackedStringArray vector[] = { { "test", "string" }, {}, { "", "test" } };
PackedByteArray out[] = { { /* test */ 0x74, 0x65, 0x73, 0x74, /* null */ 0x00, /* string */ 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, /* null */ 0x00 }, {}, { /* null */ 0x00, /* test */ 0x74, 0x65, 0x73, 0x74, /* null */ 0x00 } };
for (size_t i = 0; i < std::size(vector); i++) {
Callable::CallError err;
Variant v_ret;
Variant v_vector = vector[i];
v_vector.callp("to_byte_array", nullptr, 0, v_ret, err);
CHECK(v_ret.get_type() == Variant::PACKED_BYTE_ARRAY);
CHECK(v_ret.operator PackedByteArray() == out[i]);
}
}
// PackedVector2Array.
{
PackedVector2Array vector[] = { { Vector2(), Vector2(1, -1) }, {} };
#ifdef REAL_T_IS_DOUBLE
PackedByteArray out[] = { { /* X=0.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Y=0.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* X=1.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, /* Y=-1.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xBF }, {} };
#else
PackedByteArray out[] = { { /* X=0.0 */ 0x00, 0x00, 0x00, 0x00, /* Y=0.0 */ 0x00, 0x00, 0x00, 0x00, /* X=1.0 */ 0x00, 0x00, 0x80, 0x3F, /* Y=-1.0 */ 0x00, 0x00, 0x80, 0xBF }, {} };
#endif
for (size_t i = 0; i < std::size(vector); i++) {
Callable::CallError err;
Variant v_ret;
Variant v_vector = vector[i];
v_vector.callp("to_byte_array", nullptr, 0, v_ret, err);
CHECK(v_ret.get_type() == Variant::PACKED_BYTE_ARRAY);
CHECK(v_ret.operator PackedByteArray() == out[i]);
}
}
// PackedVector3Array.
{
PackedVector3Array vector[] = { { Vector3(), Vector3(1, 1, -1) }, {} };
#ifdef REAL_T_IS_DOUBLE
PackedByteArray out[] = { { /* X=0.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Y=0.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Z=0.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* X=1.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, /* Y=1.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, /* Z=-1.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xBF }, {} };
#else
PackedByteArray out[] = { { /* X=0.0 */ 0x00, 0x00, 0x00, 0x00, /* Y=0.0 */ 0x00, 0x00, 0x00, 0x00, /* Z=0.0 */ 0x00, 0x00, 0x00, 0x00, /* X=1.0 */ 0x00, 0x00, 0x80, 0x3F, /* Y=1.0 */ 0x00, 0x00, 0x80, 0x3F, /* Z=-1.0 */ 0x00, 0x00, 0x80, 0xBF }, {} };
#endif
for (size_t i = 0; i < std::size(vector); i++) {
Callable::CallError err;
Variant v_ret;
Variant v_vector = vector[i];
v_vector.callp("to_byte_array", nullptr, 0, v_ret, err);
CHECK(v_ret.get_type() == Variant::PACKED_BYTE_ARRAY);
CHECK(v_ret.operator PackedByteArray() == out[i]);
}
}
// PackedColorArray.
{
PackedColorArray vector[] = { { Color(), Color(1, 1, 1) }, {} };
PackedByteArray out[] = { { /* R=0.0 */ 0x00, 0x00, 0x00, 0x00, /* G=0.0 */ 0x00, 0x00, 0x00, 0x00, /* B=0.0 */ 0x00, 0x00, 0x00, 0x00, /* A=1.0 */ 0x00, 0x00, 0x80, 0x3F, /* R=1.0 */ 0x00, 0x00, 0x80, 0x3F, /* G=1.0 */ 0x00, 0x00, 0x80, 0x3F, /* B=1.0 */ 0x00, 0x00, 0x80, 0x3F, /* A=1.0 */ 0x00, 0x00, 0x80, 0x3F }, {} };
for (size_t i = 0; i < std::size(vector); i++) {
Callable::CallError err;
Variant v_ret;
Variant v_vector = vector[i];
v_vector.callp("to_byte_array", nullptr, 0, v_ret, err);
CHECK(v_ret.get_type() == Variant::PACKED_BYTE_ARRAY);
CHECK(v_ret.operator PackedByteArray() == out[i]);
}
}
// PackedVector4Array.
{
PackedVector4Array vector[] = { { Vector4(), Vector4(1, -1, 1, -1) }, {} };
#ifdef REAL_T_IS_DOUBLE
PackedByteArray out[] = { { /* X=0.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Y=0.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Z 0.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* W=0.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* X=1.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, /* Y=-1.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xBF, /* Z=1.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, /* W=-1.0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xBF }, {} };
#else
PackedByteArray out[] = { { /* X=0.0 */ 0x00, 0x00, 0x00, 0x00, /* Y=0.0 */ 0x00, 0x00, 0x00, 0x00, /* Z=0.0 */ 0x00, 0x00, 0x00, 0x00, /* W 0.0 */ 0x00, 0x00, 0x00, 0x00, /* X 1.0 */ 0x00, 0x00, 0x80, 0x3F, /* Y=-1.0 */ 0x00, 0x00, 0x80, 0xBF, /* Z=1.0 */ 0x00, 0x00, 0x80, 0x3F, /* W=-1.0 */ 0x00, 0x00, 0x80, 0xBF }, {} };
#endif
for (size_t i = 0; i < std::size(vector); i++) {
Callable::CallError err;
Variant v_ret;
Variant v_vector = vector[i];
v_vector.callp("to_byte_array", nullptr, 0, v_ret, err);
CHECK(v_ret.get_type() == Variant::PACKED_BYTE_ARRAY);
CHECK(v_ret.operator PackedByteArray() == out[i]);
}
}
}
TEST_CASE("[Vector] To byte array") {
Vector<int> vector;
vector.push_back(0);
@ -532,6 +680,31 @@ TEST_CASE("[Vector] Operators") {
CHECK(vector != vector_other);
}
struct CyclicVectorHolder {
Vector<CyclicVectorHolder> *vector = nullptr;
bool is_destructing = false;
~CyclicVectorHolder() {
if (is_destructing) {
// The vector must exist and not expose its backing array at this point.
CHECK_NE(vector, nullptr);
CHECK_EQ(vector->ptr(), nullptr);
}
}
};
TEST_CASE("[Vector] Cyclic Reference") {
// Create a stack-space vector.
Vector<CyclicVectorHolder> vector;
// Add a new (empty) element.
vector.resize(1);
// Expose the vector to its element through CyclicVectorHolder.
// This is questionable behavior, but should still behave graciously.
vector.ptrw()[0] = CyclicVectorHolder{ &vector };
vector.ptrw()[0].is_destructing = true;
// The vector goes out of scope and destructs, calling CyclicVectorHolder's destructor.
}
} // namespace TestVector
#endif // TEST_VECTOR_H

View file

@ -634,6 +634,24 @@ TEST_CASE("[Array] Typed copying") {
a6.clear();
}
static bool _find_custom_callable(const Variant &p_val) {
return (int)p_val % 2 == 0;
}
TEST_CASE("[Array] Test find_custom") {
Array a1 = build_array(1, 3, 4, 5, 8, 9);
// Find first even number.
int index = a1.find_custom(callable_mp_static(_find_custom_callable));
CHECK_EQ(index, 2);
}
TEST_CASE("[Array] Test rfind_custom") {
Array a1 = build_array(1, 3, 4, 5, 8, 9);
// Find last even number.
int index = a1.rfind_custom(callable_mp_static(_find_custom_callable));
CHECK_EQ(index, 4);
}
} // namespace TestArray
#endif // TEST_ARRAY_H

View file

@ -135,6 +135,70 @@ TEST_CASE("[Callable] Argument count") {
memdelete(my_test);
}
class TestBoundUnboundArgumentCount : public Object {
GDCLASS(TestBoundUnboundArgumentCount, Object);
protected:
static void _bind_methods() {
ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "test_func", &TestBoundUnboundArgumentCount::test_func, MethodInfo("test_func"));
}
public:
Variant test_func(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
Array result;
result.resize(p_argcount);
for (int i = 0; i < p_argcount; i++) {
result[i] = *p_args[i];
}
return result;
}
static String get_output(const Callable &p_callable) {
Array effective_args;
effective_args.push_back(7);
effective_args.push_back(8);
effective_args.push_back(9);
effective_args.resize(3 - p_callable.get_unbound_arguments_count());
effective_args.append_array(p_callable.get_bound_arguments());
return vformat(
"%d %d %s %s %s",
p_callable.get_unbound_arguments_count(),
p_callable.get_bound_arguments_count(),
p_callable.get_bound_arguments(),
p_callable.call(7, 8, 9),
effective_args);
}
};
TEST_CASE("[Callable] Bound and unbound argument count") {
String (*get_output)(const Callable &) = TestBoundUnboundArgumentCount::get_output;
TestBoundUnboundArgumentCount *test_instance = memnew(TestBoundUnboundArgumentCount);
Callable test_func = Callable(test_instance, "test_func");
CHECK(get_output(test_func) == "0 0 [] [7, 8, 9] [7, 8, 9]");
CHECK(get_output(test_func.bind(1, 2)) == "0 2 [1, 2] [7, 8, 9, 1, 2] [7, 8, 9, 1, 2]");
CHECK(get_output(test_func.bind(1, 2).unbind(1)) == "1 2 [1, 2] [7, 8, 1, 2] [7, 8, 1, 2]");
CHECK(get_output(test_func.bind(1, 2).unbind(1).bind(3, 4)) == "0 3 [3, 1, 2] [7, 8, 9, 3, 1, 2] [7, 8, 9, 3, 1, 2]");
CHECK(get_output(test_func.bind(1, 2).unbind(1).bind(3, 4).unbind(1)) == "1 3 [3, 1, 2] [7, 8, 3, 1, 2] [7, 8, 3, 1, 2]");
CHECK(get_output(test_func.bind(1).bind(2).bind(3).unbind(1)) == "1 3 [3, 2, 1] [7, 8, 3, 2, 1] [7, 8, 3, 2, 1]");
CHECK(get_output(test_func.bind(1).bind(2).unbind(1).bind(3)) == "0 2 [2, 1] [7, 8, 9, 2, 1] [7, 8, 9, 2, 1]");
CHECK(get_output(test_func.bind(1).unbind(1).bind(2).bind(3)) == "0 2 [3, 1] [7, 8, 9, 3, 1] [7, 8, 9, 3, 1]");
CHECK(get_output(test_func.unbind(1).bind(1).bind(2).bind(3)) == "0 2 [3, 2] [7, 8, 9, 3, 2] [7, 8, 9, 3, 2]");
CHECK(get_output(test_func.unbind(1).unbind(1).unbind(1).bind(1, 2, 3)) == "0 0 [] [7, 8, 9] [7, 8, 9]");
CHECK(get_output(test_func.unbind(1).unbind(1).bind(1, 2, 3).unbind(1)) == "1 1 [1] [7, 8, 1] [7, 8, 1]");
CHECK(get_output(test_func.unbind(1).bind(1, 2, 3).unbind(1).unbind(1)) == "2 2 [1, 2] [7, 1, 2] [7, 1, 2]");
CHECK(get_output(test_func.bind(1, 2, 3).unbind(1).unbind(1).unbind(1)) == "3 3 [1, 2, 3] [1, 2, 3] [1, 2, 3]");
memdelete(test_instance);
}
} // namespace TestCallable
#endif // TEST_CALLABLE_H

View file

@ -31,7 +31,7 @@
#ifndef TEST_DICTIONARY_H
#define TEST_DICTIONARY_H
#include "core/variant/dictionary.h"
#include "core/variant/typed_dictionary.h"
#include "tests/test_macros.h"
namespace TestDictionary {
@ -66,8 +66,7 @@ TEST_CASE("[Dictionary] Assignment using bracket notation ([])") {
map[StringName("HelloName")] = 6;
CHECK(int(map[StringName("HelloName")]) == 6);
// Check that StringName key is converted to String.
CHECK(int(map.find_key(6).get_type()) == Variant::STRING);
CHECK(int(map.find_key(6).get_type()) == Variant::STRING_NAME);
map[StringName("HelloName")] = 7;
CHECK(int(map[StringName("HelloName")]) == 7);
@ -96,6 +95,27 @@ TEST_CASE("[Dictionary] Assignment using bracket notation ([])") {
CHECK(map.size() == length);
}
TEST_CASE("[Dictionary] List init") {
Dictionary dict{
{ 0, "int" },
{ "packed_string_array", PackedStringArray({ "array", "of", "values" }) },
{ "key", Dictionary({ { "nested", 200 } }) },
{ Vector2(), "v2" },
};
CHECK(dict.size() == 4);
CHECK(dict[0] == "int");
CHECK(PackedStringArray(dict["packed_string_array"])[2] == "values");
CHECK(Dictionary(dict["key"])["nested"] == Variant(200));
CHECK(dict[Vector2()] == "v2");
TypedDictionary<double, double> tdict{
{ 0.0, 1.0 },
{ 5.0, 2.0 },
};
CHECK_EQ(tdict[0.0], Variant(1.0));
CHECK_EQ(tdict[5.0], Variant(2.0));
}
TEST_CASE("[Dictionary] get_key_lists()") {
Dictionary map;
List<Variant> keys;
@ -537,6 +557,43 @@ TEST_CASE("[Dictionary] Order and find") {
CHECK_EQ(d.find_key("does not exist"), Variant());
}
TEST_CASE("[Dictionary] Typed copying") {
TypedDictionary<int, int> d1;
d1[0] = 1;
TypedDictionary<double, double> d2;
d2[0] = 1.0;
Dictionary d3 = d1;
TypedDictionary<int, int> d4 = d3;
Dictionary d5 = d2;
TypedDictionary<int, int> d6 = d5;
d3[0] = 2;
d4[0] = 3;
// Same typed TypedDictionary should be shared.
CHECK_EQ(d1[0], Variant(3));
CHECK_EQ(d3[0], Variant(3));
CHECK_EQ(d4[0], Variant(3));
d5[0] = 2.0;
d6[0] = 3.0;
// Different typed TypedDictionary should not be shared.
CHECK_EQ(d2[0], Variant(2.0));
CHECK_EQ(d5[0], Variant(2.0));
CHECK_EQ(d6[0], Variant(3.0));
d1.clear();
d2.clear();
d3.clear();
d4.clear();
d5.clear();
d6.clear();
}
} // namespace TestDictionary
#endif // TEST_DICTIONARY_H

View file

@ -1806,6 +1806,14 @@ TEST_CASE("[Variant] Writer and parser dictionary") {
CHECK_MESSAGE(d_parsed == Variant(d), "Should parse back.");
}
TEST_CASE("[Variant] Writer key sorting") {
Dictionary d = build_dictionary(StringName("C"), 3, "A", 1, StringName("B"), 2, "D", 4);
String d_str;
VariantWriter::write_to_string(d, d_str);
CHECK_EQ(d_str, "{\n\"A\": 1,\n&\"B\": 2,\n&\"C\": 3,\n\"D\": 4\n}");
}
TEST_CASE("[Variant] Writer recursive dictionary") {
// There is no way to accurately represent a recursive dictionary,
// the only thing we can do is make sure the writer doesn't blow up

View file

@ -89,7 +89,7 @@ TEST_CASE("[VariantUtility] Type conversion") {
converted = VariantUtilityFunctions::type_convert(basis, Variant::Type::STRING);
CHECK(converted.get_type() == Variant::Type::STRING);
CHECK(converted == Variant("[X: (1.2, 0, 0), Y: (0, 3.4, 0), Z: (0, 0, 5.6)]"));
CHECK(converted == Variant("[X: (1.2, 0.0, 0.0), Y: (0.0, 3.4, 0.0), Z: (0.0, 0.0, 5.6)]"));
}
{

View file

@ -13,12 +13,16 @@ def main():
os.chdir(os.path.dirname(os.path.realpath(__file__)))
parser = argparse.ArgumentParser(description="Creates a new unit test file.")
parser.add_argument("name", type=str, help="The unit test name in PascalCase notation")
parser.add_argument(
"name",
type=str,
help="Specifies the class or component name to be tested, in PascalCase (e.g., MeshInstance3D). The name will be prefixed with 'test_' for the header file and 'Test' for the namespace.",
)
parser.add_argument(
"path",
type=str,
nargs="?",
help="The path to the unit test file relative to the tests folder (default: .)",
help="The path to the unit test file relative to the tests folder (e.g. core). This should correspond to the relative path of the class or component being tested. (default: .)",
default=".",
)
parser.add_argument(
@ -29,11 +33,15 @@ def main():
)
args = parser.parse_args()
snake_case_regex = re.compile(r"(?<!^)(?=[A-Z])")
name_snake_case = snake_case_regex.sub("_", args.name).lower()
snake_case_regex = re.compile(r"(?<!^)(?=[A-Z, 0-9])")
# Replace 2D, 3D, and 4D with 2d, 3d, and 4d, respectively. This avoids undesired splits like node_3_d.
prefiltered_name = re.sub(r"([234])D", lambda match: match.group(1).lower() + "d", args.name)
name_snake_case = snake_case_regex.sub("_", prefiltered_name).lower()
file_path = os.path.normpath(os.path.join(args.path, f"test_{name_snake_case}.h"))
# Ensure the directory exists.
os.makedirs(os.path.dirname(file_path), exist_ok=True)
print(file_path)
if os.path.isfile(file_path):
print(f'ERROR: The file "{file_path}" already exists.')

View file

@ -0,0 +1 @@
@IV

View file

@ -0,0 +1 @@
VI@

View file

@ -0,0 +1,999 @@
./menu/home/home_menu.tscn
./menu/tooltips/tooltip_server.tscn
./menu/tooltips/tooltip_server.gd
./menu/tooltips/tooltip.gd
./menu/menu/characters/smoker/4.wav
./menu/menu/characters/smoker/6.wav
./menu/menu/characters/smoker/10.wav
./menu/menu/characters/smoker/smoker.tscn
./menu/menu/characters/smoker/8.wav
./menu/menu/characters/smoker/type.gd
./menu/menu/characters/smoker/9.wav
./menu/menu/characters/smoker/5.wav
./menu/menu/characters/smoker/0.wav
./menu/menu/characters/smoker/back_light.png
./menu/menu/characters/smoker/glasses.png
./menu/menu/characters/smoker/smoker.gd
./menu/menu/characters/smoker/cig.gd
./menu/menu/characters/smoker/eyes.png
./menu/menu/characters/smoker/3.wav
./menu/menu/characters/smoker/to_pixelate.gd
./menu/menu/characters/smoker/7.wav
./menu/menu/characters/smoker/cig.png
./menu/menu/characters/smoker/2.wav
./menu/menu/characters/smoker/1.wav
./menu/menu/characters/smoke.png
./menu/menu/characters/space_bandit.tres
./menu/menu/characters/dead_guy/blood_texture.png
./menu/menu/characters/dead_guy/head_gibbed.png
./menu/menu/characters/dead_guy/back_light.png
./menu/menu/characters/dead_guy/smoker.gd
./menu/menu/characters/dead_guy/eyes.png
./menu/menu/characters/dead_guy/to_pixelate.gd
./menu/menu/characters/dead_guy/dead_guy.gd
./menu/menu/characters/dead_guy/eyes.gd
./menu/menu/characters/dead_guy/x.png
./menu/menu/characters/dead_guy/dead_guy.tscn
./menu/menu/characters/dead_guy/mouth.png
./menu/menu/characters/dead_guy/dead_guy.tres
./menu/menu/characters/Label.gd
./menu/menu/characters/guns2.png
./menu/menu/characters/c.gd
./menu/menu/characters/smoke.gd
./menu/menu/characters/character.gd
./menu/menu/characters/space_bandit/eyes.tres
./menu/menu/characters/space_bandit/space_bandit_face_happy.png
./menu/menu/characters/space_bandit/space_bandit.gd
./menu/menu/characters/space_bandit/space_bandit.tscn
./menu/menu/characters/boss/smoker.tscn
./menu/menu/characters/boss/back_light.png
./menu/menu/characters/boss/glasses.png
./menu/menu/characters/boss/smoker.gd
./menu/menu/characters/boss/cig.gd
./menu/menu/characters/boss/eyes.png
./menu/menu/characters/boss/to_pixelate.gd
./menu/menu/characters/boss/x.png
./menu/menu/characters/boss/cig.png
./menu/menu/characters/eye.gd
./menu/menu/characters/space_bandit_face_happy.png
./menu/menu/characters/face.gd
./menu/menu/characters/color.tres
./menu/menu/characters/space_bandit.tscn
./menu/menu/characters/space_bandit_face_bloody.png
./menu/menu/characters/guns.png
./menu/menu/characters/eyes2.tres
./menu/options/controls/use.tres
./menu/options/controls/input_map_button.gd
./menu/options/controls/swap.tres
./menu/options/controls/teleport.tres
./menu/options/controls/joy_controls.tscn
./menu/options/controls/mouse_and_keyboard_controls.tscn
./menu/options/controls/input_map_button.tscn
./menu/options/controls/special.tres
./menu/options/controls/throw.tres
./menu/options/controls/center.tres
./menu/options/controls/input_action.gd
./menu/options/controls/move.tres
./menu/options/controls/melee.tres
./menu/options/controls/controls.gd
./menu/options/options.gd
./menu/options/options.tscn
./menu/options/graphics/graphics.tscn
./menu/options/graphics/graphics.gd
./menu/options/audio/audio.gd
./menu/options/audio/audio.tscn
./menu/options/game/game.gd
./menu/options/game/game.tscn
./menu/circle.tres
./menu/fonts/keys.png
./menu/fonts/rainbow_font.tres
./menu/fonts/fallback_font.tres
./menu/fonts/taxi_Driver.png
./menu/fonts/NotoSansJP-Regular.ttf
./menu/fonts/taxi_Driver_noise.png
./menu/fonts/rainbow_font_shader.tres
./menu/fonts/m5x7.ttf
./menu/colors.gd
./menu/toast_enter.wav
./menu/ui_colors.tres
./menu/pause/pause.gd
./menu/pause/rainbow.tres
./menu/pause/Label.gd
./menu/pause/label.tscn
./menu/pause/pause.tscn
./menu/hoola.wav
./menu/in_game_fallback.tres
./menu/widgets/next_unlock.gd
./menu/widgets/slider.gd
./menu/widgets/fade.tscn
./menu/widgets/background_hint.gd
./menu/widgets/panel_container_smoke.gd
./menu/widgets/wishlist_sticker.gd
./menu/widgets/smoke.tres
./menu/widgets/color_grade.gd
./menu/widgets/rich_text_button.gd
./menu/widgets/panel_container_smok2.tscn
./menu/widgets/slider.tscn
./menu/widgets/rich_text_heading.gd
./menu/widgets/background_hint.tscn
./menu/widgets/tip.tscn
./menu/widgets/rich_text_button.tscn
./menu/widgets/toggle.tscn
./menu/widgets/heading.tscn
./menu/widgets/hover.tscn
./menu/widgets/toggle.gd
./menu/widgets/smoke_panel_material.tres
./menu/widgets/confirm.gd
./menu/widgets/tip.gd
./menu/widgets/panel.gd
./menu/widgets/modal.gd
./menu/widgets/NinePatchRect.gd
./menu/widgets/smoke.shader
./menu/widgets/9patch.png
./menu/widgets/big_hint.gd
./menu/widgets/TDVB1i.png
./menu/widgets/color_grade.tscn
./menu/widgets/text.gd
./menu/widgets/panel_container_smoke.tscn
./menu/widgets/1x1.png
./menu/widgets/confirm.tscn
./menu/widgets/RichTextPanel.tscn
./menu/hud/cursor.png
./menu/hud/inventory/draggable.gd
./menu/hud/inventory/menu/characters/color.tres
./menu/hud/inventory/drop_zone.tscn
./menu/hud/inventory/RichTextLabel.gd
./menu/hud/inventory/hud_icon_mutation.tscn
./menu/hud/inventory/use_count.gd
./menu/hud/inventory/draggable.tscn
./menu/hud/inventory/black_shadow_font.tres
./menu/hud/inventory/x.png
./menu/hud/inventory/hud_icon_mutation.gd
./menu/hud/inventory/flash_parent.gd
./menu/hud/inventory/TextureRect4.gd
./menu/hud/cursor.tscn
./menu/hud/hud.tscn
./menu/hud/cursor.gd
./menu/hud/hud.gd
./menu/metal_text.tres
./menu/rich_text_effects/RichTextType.gd
./menu/rich_text_effects/RichTextPanel.gd
./menu/rich_text_effects/RichTextFlash.gd
./menu/rich_text_effects/RichTextTranslate.gd
./menu/in_game.tres
./menu/lcd_screen_font.tres
./menu/toast_exit.wav
./menu/stack/ahses_material.tres
./menu/stack/home.kra
./menu/stack/fade.gd
./menu/stack/stack.tscn
./menu/stack/stack.gd
./menu/stack/version.gd
./menu/stack/art.kra
./entity/unlock_skin_classic/icon.png
./entity/use.gd
./entity/chair/entity.tscn
./entity/chair/icon.png
./entity/chair/data.gd
./entity/man_desert/entity.tscn
./entity/man_desert/icon.png
./entity/man_desert/teleprompts/need_medbay.wav
./entity/man_desert/teleprompts/me_too.wav
./entity/man_desert/teleprompts/get_up_alt.wav
./entity/man_desert/teleprompts/getting_a_medpack.wav
./entity/man_desert/teleprompts/firstaid-incoming.wav
./entity/man_desert/teleprompts/batch_name.py
./entity/man_desert/teleprompts/what.wav
./entity/man_desert/teleprompts/oo.wav
./entity/man_desert/teleprompts/yell.wav
./entity/man_desert/teleprompts/rushing.wav
./entity/man_desert/teleprompts/ooo.wav
./entity/man_desert/teleprompts/coming_to_heal_ya.wav
./entity/man_desert/teleprompts/where_is_the_medpack.wav
./entity/man_desert/teleprompts/ah.wav
./entity/man_desert/teleprompts/no.wav
./entity/man_desert/teleprompts/going_to_camp_medbay.wav
./entity/man_desert/teleprompts/aa.wav
./entity/man_desert/teleprompts/pirate_alt.wav
./entity/man_desert/teleprompts/take_morphine.wav
./entity/man_desert/teleprompts/ee.wav
./entity/man_desert/teleprompts/get_up.wav
./entity/man_desert/teleprompts/aw.wav
./entity/man_desert/teleprompts/easy.wav
./entity/man_desert/teleprompts/intruder.wav
./entity/man_desert/teleprompts/amateur.wav
./entity/man_desert/teleprompts/hes_not_moving.wav
./entity/man_desert/teleprompts/pirate.wav
./entity/man_desert/teleprompts/i_dont_know.wav
./entity/man_desert/teleprompts/index.txt
./entity/man_desert/teleprompts/move.wav
./entity/man_desert/teleprompts/hes_stuck.wav
./entity/man_desert/teleprompts/how.wav
./entity/man_desert/teleprompts/uu.wav
./entity/man_desert/teleprompts/where_is_the_gun.wav
./entity/man_desert/teleprompts/getting_a_gun.wav
./entity/man_desert/data.gd
./entity/man_desert/hand.png
./entity/barrel_side_smoke/entity.tscn
./entity/barrel_side_smoke/icon.png
./entity/barrel_side_smoke/data.gd
./entity/barrel_smoke/entity.tscn
./entity/barrel_smoke/icon.png
./entity/barrel_smoke/data.gd
./entity/project_box/entity.tscn
./entity/project_box/icon.png
./entity/project_box/data.gd
./entity/mutation_saw/entity.tscn
./entity/mutation_saw/icon.png
./entity/mutation_saw/special.gd
./entity/mutation_saw/data.gd
./entity/lift_entrance/entity.tscn
./entity/lift_entrance/icon.png
./entity/lift_entrance/special.gd
./entity/lift_entrance/data.gd
./entity/mutation_accuracy_boost_DELETE/entity.tscn
./entity/mutation_accuracy_boost_DELETE/icon.png
./entity/mutation_accuracy_boost_DELETE/special.gd
./entity/mutation_accuracy_boost_DELETE/data.gd
./entity/skin_ruffle/entity.tscn
./entity/skin_ruffle/icon.png
./entity/skin_ruffle/carried.png
./entity/skin_ruffle/data.gd
./entity/editor_only_icon.gd
./entity/console_dark/entity.tscn
./entity/console_dark/icon.png
./entity/console_dark/data.gd
./entity/console_dark/animation.png
./entity/smg2/entity.tscn
./entity/smg2/used.wav
./entity/smg2/icon.png
./entity/smg2/data.gd
./entity/smg2/debug.gd
./entity/grenade_launcher/entity.tscn
./entity/grenade_launcher/used.wav
./entity/grenade_launcher/icon.png
./entity/grenade_launcher/special.gd
./entity/grenade_launcher/data.gd
./entity/floor_tile_full_square/entity.tscn
./entity/floor_tile_full_square/icon.png
./entity/floor_tile_full_square/data.gd
./entity/grate_1/entity.tscn
./entity/grate_1/icon.png
./entity/grate_1/data.gd
./entity/bed_bunk_corner/entity.tscn
./entity/bed_bunk_corner/icon.png
./entity/bed_bunk_corner/data.gd
./entity/kill_streak_rail_gun_level_3/entity.tscn
./entity/kill_streak_rail_gun_level_3/data.gd
./entity/teleporter_random_weak/entity.tscn
./entity/teleporter_random_weak/teleporter_model.gd
./entity/teleporter_random_weak/used.wav
./entity/teleporter_random_weak/icon.png
./entity/teleporter_random_weak/special.gd
./entity/teleporter_random_weak/ray.gd
./entity/teleporter_random_weak/data.gd
./entity/teleporter_random_weak/flap.png
./entity/entities.kra
./entity/jerry_can/entity.tscn
./entity/jerry_can/icon.png
./entity/jerry_can/data.gd
./entity/kill_streak_helmet_full/entity.tscn
./entity/kill_streak_helmet_full/data.gd
./entity/background_derelict/background2.gd
./entity/background_derelict/entity.tscn
./entity/background_derelict/icon.png
./entity/background_derelict/background/space.png
./entity/background_derelict/background/line.png
./entity/background_derelict/background/overlay.png
./entity/background_derelict/background/background2.png
./entity/background_derelict/background/background.png
./entity/background_derelict/background/engine_glow.tscn
./entity/background_derelict/background/lines3.png
./entity/background_derelict/background/background.tscn
./entity/background_derelict/background/lines.tres
./entity/background_derelict/background/xx.gd
./entity/background_derelict/background/background.gd
./entity/background_derelict/background/bayer16tile2.png
./entity/background_derelict/background/push.png
./entity/background_derelict/background/palette_mono.png
./entity/background_derelict/background/stars.gd
./entity/background_derelict/background/lines2.png
./entity/background_derelict/background/lines.shader
./entity/background_derelict/background/ambience.gd
./entity/background_derelict/background/space_ship_ambience.ogg
./entity/background_derelict/background/stars.png
./entity/background_derelict/data.gd
./entity/smoker/entity.tscn
./entity/smoker/right_hand.png
./entity/smoker/eyes.png
./entity/smoker/data.gd
./entity/smoker/animate.gd
./entity/smoker/left_hand.png
./entity/EntityStatic.gd
./entity/level_model.gd
./entity/class_teleporter_drop_chance/entity.tscn
./entity/class_teleporter_drop_chance/icon.png
./entity/class_teleporter_drop_chance/special.gd
./entity/class_teleporter_drop_chance/data.gd
./entity/smg4/entity.tscn
./entity/smg4/used.wav
./entity/smg4/icon.png
./entity/smg4/data.gd
./entity/medpack/entity.tscn
./entity/medpack/icon.png
./entity/medpack/dead.png
./entity/medpack/data.gd
./entity/model.gd
./entity/doom_transition/entity.tscn
./entity/doom_transition/icon.png
./entity/doom_transition/special.gd
./entity/doom_transition/Screenshot from 2021-12-08 18-25-03.png
./entity/doom_transition/data.gd
./entity/glass_block_exploding/entity.tscn
./entity/glass_block_exploding/icon.png
./entity/glass_block_exploding/special.gd
./entity/glass_block_exploding/dead.png
./entity/glass_block_exploding/data.gd
./entity/floor_ting/entity.tscn
./entity/floor_ting/icon.png
./entity/floor_ting/data.gd
./entity/background_crashed_ship/entity.tscn
./entity/background_crashed_ship/icon.png
./entity/background_crashed_ship/background/background2.kra
./entity/background_crashed_ship/background/dust_storm_negative.png
./entity/background_crashed_ship/background/background2.png
./entity/background_crashed_ship/background/background2 (copy 1).png
./entity/background_crashed_ship/background/dust_bowl.ogg
./entity/background_crashed_ship/background/background.tscn
./entity/background_crashed_ship/background/background.kra
./entity/background_crashed_ship/data.gd
./entity/game_aim_hack_boss/entity.tscn
./entity/game_aim_hack_boss/icon.png
./entity/game_aim_hack_boss/special.gd
./entity/game_aim_hack_boss/give_my_arm_back.wav
./entity/game_aim_hack_boss/my_arm_came_off.wav
./entity/game_aim_hack_boss/data.gd
./entity/sink/entity.tscn
./entity/sink/icon.png
./entity/sink/data.gd
./entity/grate_2/entity.tscn
./entity/grate_2/icon.png
./entity/grate_2/data.gd
./entity/barrel_side/entity.tscn
./entity/barrel_side/icon.png
./entity/barrel_side/data.gd
./entity/oxygen/entity.tscn
./entity/oxygen/icon.png
./entity/oxygen/shadow.png
./entity/oxygen/data.gd
./entity/oxygen/normal.png
./entity/unlock_skin_robo/entity.tscn
./entity/unlock_skin_robo/icon.png
./entity/unlock_skin_robo/special.gd
./entity/unlock_skin_robo/data.gd
./entity/entity_agency_model.gd
./entity/floor_tile_wood/entity.tscn
./entity/floor_tile_wood/icon.png
./entity/floor_tile_wood/data.gd
./entity/qr_code/entity.tscn
./entity/qr_code/icon.png
./entity/qr_code/data.gd
./entity/background_sun/overlay.png
./entity/background_sun/entity.tscn
./entity/background_sun/c.gd
./entity/background_sun/kill.tscn
./entity/background_sun/icon.png
./entity/background_sun/special.gd
./entity/background_sun/wtf.tres
./entity/background_sun/background/background2.png
./entity/background_sun/background/background.tscn
./entity/background_sun/background/color2s.tres
./entity/background_sun/background/background_glow.png
./entity/background_sun/data.gd
./entity/background_sun/kill.gd
./entity/background_sun/stars.png
./entity/background_zone_intro/overlay.png
./entity/background_zone_intro/entity.tscn
./entity/background_zone_intro/icon.png
./entity/background_zone_intro/special.gd
./entity/background_zone_intro/background/space.png
./entity/background_zone_intro/background/line.png
./entity/background_zone_intro/background/background2.png
./entity/background_zone_intro/background/background.png
./entity/background_zone_intro/background/engine_glow.tscn
./entity/background_zone_intro/background/lines3.png
./entity/background_zone_intro/background/background.tscn
./entity/background_zone_intro/background/lines.tres
./entity/background_zone_intro/background/background.gd
./entity/background_zone_intro/background/bayer16tile2.png
./entity/background_zone_intro/background/push.png
./entity/background_zone_intro/background/palette_mono.png
./entity/background_zone_intro/background/stars.gd
./entity/background_zone_intro/background/lines2.png
./entity/background_zone_intro/background/lines.shader
./entity/background_zone_intro/background/ambience.gd
./entity/background_zone_intro/background/space_ship_ambience.ogg
./entity/background_zone_intro/background/stars.png
./entity/background_zone_intro/background_end.png
./entity/background_zone_intro/data.gd
./entity/background_zone_intro/tinge.png
./entity/closet_alt/entity.tscn
./entity/closet_alt/icon.png
./entity/closet_alt/data.gd
./entity/meta_random_sound/entity.tscn
./entity/meta_random_sound/giberish.wav
./entity/meta_random_sound/icon.png
./entity/meta_random_sound/special.gd
./entity/meta_random_sound/who.wav
./entity/meta_random_sound/data.gd
./entity/meta_random_sound/hoola_boola.wav
./entity/meta_random_sound/space_bandit.wav
./entity/lines/entity.tscn
./entity/lines/icon.png
./entity/lines/data.gd
./entity/teleporter_random_avoid_ray/entity.tscn
./entity/teleporter_random_avoid_ray/used.wav
./entity/teleporter_random_avoid_ray/icon.png
./entity/teleporter_random_avoid_ray/ray.gd
./entity/teleporter_random_avoid_ray/data.gd
./entity/teleporter_random_avoid_ray/flap.png
./entity/teleporter_random_avoid_ray/RayCast2D.gd
./entity/teleporter_random_avoid_ray/area.gd
./entity/teleporter_random_avoid_ray/flap.gd
./entity/saw/blades.gd
./entity/saw/entity.tscn
./entity/saw/used.wav
./entity/saw/icon.png
./entity/saw/special.gd
./entity/saw/carried.png
./entity/saw/data.gd
./entity/saw/used (copy 1).wav
./entity/saw/saw.wav
./entity/saw/carried_blades.png
./entity/floor_tile_checkerdboard/damage.png
./entity/floor_tile_checkerdboard/entity.tscn
./entity/floor_tile_checkerdboard/icon.png
./entity/floor_tile_checkerdboard/entity.tres
./entity/floor_tile_checkerdboard/data.gd
./entity/mutation_smoke_grenade_upgrade/entity.tscn
./entity/mutation_smoke_grenade_upgrade/icon.png
./entity/mutation_smoke_grenade_upgrade/special.gd
./entity/mutation_smoke_grenade_upgrade/data.gd
./entity/mutation_smoke_grenade_upgrade/mutation_model.gd
./entity/helmet_full/entity.tscn
./entity/helmet_full/pick_up.wav
./entity/helmet_full/icon.png
./entity/helmet_full/data.gd
./entity/helmet_full/helmet-ping.wav
./entity/barrel_explosive/entity.tscn
./entity/barrel_explosive/icon.png
./entity/barrel_explosive/data.gd
./entity/bank/entity.tscn
./entity/bank/icon.png
./entity/bank/special.gd
./entity/bank/data.gd
./entity/kick/entity.tscn
./entity/kick/swipe.png
./entity/kick/used.wav
./entity/kick/icon.png
./entity/kick/AnimatedSprite.gd
./entity/kick/data.gd
./entity/battery/entity.tscn
./entity/battery/icon.png
./entity/battery/data.gd
./entity/lift/entity.tscn
./entity/lift/opening.wav
./entity/lift/doors_open.png
./entity/lift/RichTextLabel.gd
./entity/lift/icon.png
./entity/lift/open.wav
./entity/lift/elevator_end.wav
./entity/lift/lift_model.gd
./entity/lift/label.tscn
./entity/lift/rumble.gd
./entity/lift/level_portal_model.gd
./entity/lift/data.gd
./entity/lift/doors.png
./entity/lift/area.gd
./entity/snes/entity.tscn
./entity/snes/icon.png
./entity/snes/data.gd
./entity/passive_disarm/entity.tscn
./entity/passive_disarm/icon.png
./entity/passive_disarm/special.gd
./entity/passive_disarm/data.gd
./entity/mutation_lots_of_shot/entity.tscn
./entity/mutation_lots_of_shot/icon.png
./entity/mutation_lots_of_shot/special.gd
./entity/mutation_lots_of_shot/data.gd
./entity/pallet2/entity.tscn
./entity/pallet2/icon.png
./entity/pallet2/data.gd
./entity/kill_streak_sword/entity.tscn
./entity/kill_streak_sword/data.gd
./entity/rain/entity.tscn
./entity/rain/icon.png
./entity/rain/special.gd
./entity/rain/rain.png
./entity/rain/rain.tscn
./entity/rain/data.gd
./entity/rain/rain.gd
./entity/white_line/entity.tscn
./entity/white_line/icon.png
./entity/white_line/data.gd
./entity/game_break_sword/entity.tscn
./entity/game_break_sword/icon.png
./entity/game_break_sword/special.gd
./entity/game_break_sword/data.gd
./entity/background_zone1/overlay.png
./entity/background_zone1/entity.tscn
./entity/background_zone1/icon.png
./entity/background_zone1/special.gd
./entity/background_zone1/background/space.png
./entity/background_zone1/background/line.png
./entity/background_zone1/background/background2.png
./entity/background_zone1/background/background.png
./entity/background_zone1/background/engine_glow.tscn
./entity/background_zone1/background/lines3.png
./entity/background_zone1/background/background.tscn
./entity/background_zone1/background/lines.tres
./entity/background_zone1/background/background.gd
./entity/background_zone1/background/bayer16tile2.png
./entity/background_zone1/background/push.png
./entity/background_zone1/background/palette_mono.png
./entity/background_zone1/background/stars.gd
./entity/background_zone1/background/lines2.png
./entity/background_zone1/background/lines.shader
./entity/background_zone1/background/ambience.gd
./entity/background_zone1/background/space_ship_ambience.ogg
./entity/background_zone1/background/stars.png
./entity/background_zone1/data.gd
./entity/background_zone1/tinge.png
./entity/mutation_throw_trap_DELETE/entity.tscn
./entity/mutation_throw_trap_DELETE/icon.png
./entity/mutation_throw_trap_DELETE/special.gd
./entity/mutation_throw_trap_DELETE/data.gd
./entity/agency.gd
./entity/skin_cheese/entity.tscn
./entity/skin_cheese/icon.png
./entity/skin_cheese/carried.png
./entity/skin_cheese/data.gd
./entity/toilet/entity.tscn
./entity/toilet/icon.png
./entity/toilet/special.gd
./entity/toilet/water.png
./entity/toilet/drink.wav
./entity/toilet/data.gd
./entity/smg3/entity.tscn
./entity/smg3/used.wav
./entity/smg3/icon.png
./entity/smg3/dead.png
./entity/smg3/data.gd
./entity/smg3/debug.gd
./entity/teleporter_super/entity.tscn
./entity/teleporter_super/icon.png
./entity/teleporter_super/data.gd
./entity/background_zone_end/overlay.png
./entity/background_zone_end/entity.tscn
./entity/background_zone_end/icon.png
./entity/background_zone_end/special.gd
./entity/background_zone_end/stars2.png
./entity/background_zone_end/background_end.png
./entity/background_zone_end/data.gd
./entity/background_zone_end/tinge.png
./entity/kill_streak_barricade/entity.tscn
./entity/kill_streak_barricade/data.gd
./entity/game_zone_4_boss_1/entity.tscn
./entity/game_zone_4_boss_1/icon.png
./entity/game_zone_4_boss_1/special.gd
./entity/game_zone_4_boss_1/data.gd
./entity/game_zone_4_boss_1/kill_me_and_explode_ship.wav
./entity/mutation_remove_melee/entity.tscn
./entity/mutation_remove_melee/icon.png
./entity/mutation_remove_melee/special.gd
./entity/mutation_remove_melee/data.gd
./entity/he_grenade_level_2/entity.tscn
./entity/he_grenade_level_2/icon.png
./entity/he_grenade_level_2/data.gd
./entity/background_zone_2/entity.tscn
./entity/background_zone_2/icon.png
./entity/background_zone_2/background/background2.kra
./entity/background_zone_2/background/grad.png
./entity/background_zone_2/background/background2.png
./entity/background_zone_2/background/background.png
./entity/background_zone_2/background/background2 (copy 1).png
./entity/background_zone_2/background/backgrounds.gd
./entity/background_zone_2/background/wall_overlay.png
./entity/background_zone_2/background/background.tscn
./entity/background_zone_2/background/Screenshot from 2022-07-07 10-58-48.png
./entity/background_zone_2/background/background.gd
./entity/background_zone_2/background/shadow.png
./entity/background_zone_2/background/engine smoke.png
./entity/background_zone_2/background/background.kra
./entity/background_zone_2/background/sea.ogg
./entity/background_zone_2/background/background2blur.png
./entity/background_zone_2/background/test.gd
./entity/background_zone_2/background/grad3.png
./entity/background_zone_2/background/lines2.png
./entity/background_zone_2/background/smoke.tscn
./entity/background_zone_2/background/left_water.tscn
./entity/background_zone_2/background/grad2.png
./entity/background_zone_2/background/para.png
./entity/background_zone_2/data.gd
./entity/pipe_corner/entity.tscn
./entity/pipe_corner/icon.png
./entity/pipe_corner/data.gd
./entity/floor_tile_metal_cow_trap/entity.tscn
./entity/floor_tile_metal_cow_trap/icon.png
./entity/floor_tile_metal_cow_trap/data.gd
./entity/skin_naked/entity.tscn
./entity/skin_naked/icon.png
./entity/skin_naked/carried.png
./entity/skin_naked/data.gd
./entity/valve/entity.tscn
./entity/valve/icon.png
./entity/valve/.icon.png-autosave.kra
./entity/valve/data.gd
./entity/bed/entity.tscn
./entity/bed/icon.png
./entity/bed/data.gd
./entity/game_invisible_guy/entity.tscn
./entity/game_invisible_guy/icon.png
./entity/game_invisible_guy/special.gd
./entity/game_invisible_guy/data.gd
./entity/smg/entity.tscn
./entity/smg/used.wav
./entity/smg/icon.png
./entity/smg/data.gd
./entity/skin_robo/entity.tscn
./entity/skin_robo/icon.png
./entity/skin_robo/carried.png
./entity/skin_robo/data.gd
./entity/bandana/entity.tscn
./entity/bandana/bob.gd
./entity/bandana/icon.png
./entity/bandana/special.gd
./entity/bandana/carried.png
./entity/bandana/data.gd
./entity/bandana/pixel.png
./entity/floor_plug/entity.tscn
./entity/floor_plug/icon.png
./entity/floor_plug/data.gd
./entity/bench/entity.tscn
./entity/bench/icon.png
./entity/bench/data.gd
./entity/meta_strip_items/entity.tscn
./entity/meta_strip_items/special.gd
./entity/meta_strip_items/meta_strip_items_model.gd
./entity/meta_strip_items/data.gd
./entity/crate_teleporter/entity.tscn
./entity/crate_teleporter/icon.png
./entity/crate_teleporter/data.gd
./entity/crate_teleporter/satellite.kra
./entity/crate_garbage/entity.tscn
./entity/crate_garbage/icon.png
./entity/crate_garbage/data.gd
./entity/crate_garbage/gibbed.png
./entity/meta_stats/entity.tscn
./entity/meta_stats/letters.tres
./entity/meta_stats/icon.png
./entity/meta_stats/special.gd
./entity/meta_stats/data.gd
./entity/meta_stats/meta_stats_model.gd
./entity/rail_gun/entity.tscn
./entity/rail_gun/used.wav
./entity/rail_gun/icon.png
./entity/rail_gun/special.gd
./entity/rail_gun/carried.png
./entity/rail_gun/data.gd
./entity/drop_ship_door/entity.tscn
./entity/drop_ship_door/icon.png
./entity/drop_ship_door/data.gd
./entity/floor_lines/entity.tscn
./entity/floor_lines/icon.png
./entity/floor_lines/data.gd
./entity/game_trap/entity.tscn
./entity/game_trap/you_blew_up_my_force_field.wav
./entity/game_trap/droped_my_grenade_2.wav
./entity/game_trap/icon.png
./entity/game_trap/special.gd
./entity/game_trap/droped_my_grenade_0.wav
./entity/game_trap/shock.wav
./entity/game_trap/uh_my_helmet.wav
./entity/game_trap/ha_missed_me.wav
./entity/game_trap/data.gd
./entity/game_trap/try_beat_this_force_field.wav
./entity/game_trap/droped_my_grenade_1.wav
./entity/blood_sword/entity.tscn
./entity/blood_sword/pick_up.wav
./entity/blood_sword/used.wav
./entity/blood_sword/sam2.png
./entity/blood_sword/icon.png
./entity/blood_sword/special.gd
./entity/blood_sword/hit_bar.gd
./entity/blood_sword/data.gd
./entity/blood_sword/sam.png
./entity/blood_sword/dead.wav
./entity/blood_sword/animation.png
./entity/auto_cables_thick/entity.tscn
./entity/auto_cables_thick/data.gd
./entity/auto_cables_thick/wires2.png
./entity/shield/entity.tscn
./entity/shield/pick_up.wav
./entity/shield/icon.png
./entity/shield/carried.png
./entity/shield/data.gd
./entity/shield/helmet-ping.wav
./entity/game_teleport_in/entity.tscn
./entity/game_teleport_in/icon.png
./entity/game_teleport_in/special.gd
./entity/game_teleport_in/data.gd
./entity/shotgun_super/entity.tscn
./entity/shotgun_super/icon.png
./entity/shotgun_super/data.gd
./entity/bottle/entity.tscn
./entity/bottle/icon.png
./entity/bottle/data.gd
./entity/bottle/normal.png
./entity/bottle/icon_shadow.png
./entity/kill_streak_p90/entity.tscn
./entity/kill_streak_p90/data.gd
./entity/drain/entity.tscn
./entity/drain/icon.png
./entity/drain/data.gd
./entity/auto_wires_three/entity.tscn
./entity/auto_wires_three/data.gd
./entity/light/entity.tscn
./entity/light/icon.png
./entity/light/special.gd
./entity/light/light.wav
./entity/light/data.gd
./entity/debris/entity.tscn
./entity/debris/icon.png
./entity/debris/data.gd
./entity/debris/gibbed.png
./entity/mutation_rail_gun_upgrade/entity.tscn
./entity/mutation_rail_gun_upgrade/icon.png
./entity/mutation_rail_gun_upgrade/special.gd
./entity/mutation_rail_gun_upgrade/data.gd
./entity/mutation_rail_gun_upgrade/mutation_model.gd
./entity/auto_cables/entity.tscn
./entity/auto_cables/data.gd
./entity/auto_cables/wires2.png
./entity/stealth_camo/entity.tscn
./entity/stealth_camo/special.gd
./entity/stealth_camo/data.gd
./entity/colt_45/entity.tscn
./entity/colt_45/used.wav
./entity/colt_45/icon.png
./entity/colt_45/dead.png
./entity/colt_45/data.gd
./entity/quantum_suicide_drive/entity.tscn
./entity/quantum_suicide_drive/heart.ogg
./entity/quantum_suicide_drive/icon.png
./entity/quantum_suicide_drive/special.gd
./entity/quantum_suicide_drive/qsd_model.gd
./entity/quantum_suicide_drive/multi.gd
./entity/quantum_suicide_drive/multi.tscn
./entity/quantum_suicide_drive/CenterContainer.gd
./entity/quantum_suicide_drive/carried.png
./entity/quantum_suicide_drive/data.gd
./entity/helmet/entity.tscn
./entity/helmet/pick_up.wav
./entity/helmet/icon.png
./entity/helmet/special.gd
./entity/helmet/die.wav
./entity/helmet/carried.png
./entity/helmet/data.gd
./entity/helmet/helmet-ping.wav
./entity/ammo_box/entity.tscn
./entity/ammo_box/icon.png
./entity/ammo_box/data.gd
./entity/rail_gun_level_2/entity.tscn
./entity/rail_gun_level_2/icon.png
./entity/rail_gun_level_2/data.gd
./entity/glass_block_backup/entity.tscn
./entity/glass_block_backup/icon.png
./entity/glass_block_backup/data.gd
./entity/closet/entity.tscn
./entity/closet/icon.png
./entity/closet/data.gd
./entity/little_boxes/entity.tscn
./entity/little_boxes/icon.png
./entity/little_boxes/data.gd
./entity/meta_health_bar/entity.tscn
./entity/meta_health_bar/health_bar_model.gd
./entity/meta_health_bar/icon.png
./entity/meta_health_bar/special.gd
./entity/meta_health_bar/invunerable.png
./entity/meta_health_bar/data.gd
./entity/night_stand/entity.tscn
./entity/night_stand/icon_normal.png
./entity/night_stand/icon.png
./entity/night_stand/shadow.png
./entity/night_stand/data.gd
./entity/fan/entity.tscn
./entity/fan/flap2.png
./entity/fan/flaps.gd
./entity/fan/icon.png
./entity/fan/data.gd
./entity/fan/flap.png
./entity/fan/icon_shadow.png
./entity/fan/animation.png
./entity/fan/gibbed.png
./entity/game_tutorial_end/entity.tscn
./entity/game_tutorial_end/icon.png
./entity/game_tutorial_end/special.gd
./entity/game_tutorial_end/data.gd
./entity/mutation_disarmament/entity.tscn
./entity/mutation_disarmament/icon.png
./entity/mutation_disarmament/special.gd
./entity/mutation_disarmament/data.gd
./entity/air_lock/icon_open.png
./entity/air_lock/entity.tscn
./entity/air_lock/door_close.wav
./entity/air_lock/icon.png
./entity/air_lock/special.gd
./entity/air_lock/air_lock_model.gd
./entity/air_lock/data.gd
./entity/scorpion/entity.tscn
./entity/scorpion/used.wav
./entity/scorpion/laser.gd
./entity/scorpion/icon.png
./entity/scorpion/data.gd
./entity/kill_streak_aim_hack/entity.tscn
./entity/kill_streak_aim_hack/data.gd
./entity/dungeon_proc_debug/entity.tscn
./entity/dungeon_proc_debug/icon.png
./entity/dungeon_proc_debug/data.gd
./entity/dungeon_proc_debug/debug.gd
./entity/dungeon_proc_debug/debug.tscn
./entity/tarp/entity.tscn
./entity/tarp/icon.png
./entity/tarp/data.gd
./entity/hit_indicator/entity.tscn
./entity/hit_indicator/data.gd
./entity/console_corner/entity.tscn
./entity/console_corner/animation2.tscn
./entity/console_corner/icon.png
./entity/console_corner/data.gd
./entity/console_corner/animation.tscn
./entity/icon.png
./entity/couch_corner/entity.tscn
./entity/couch_corner/icon.png
./entity/couch_corner/data.gd
./entity/m4/entity.tscn
./entity/m4/used.wav
./entity/m4/icon.png
./entity/m4/data.gd
./entity/game_hud/entity.tscn
./entity/game_hud/icon.png
./entity/game_hud/data.gd
./entity/game_hud/inventory_game.tscn
./entity/prototypes.gd
./entity/agent_chicken/emotes.png
./entity/agent_chicken/entity.tscn
./entity/agent_chicken/sound_board.gd
./entity/agent_chicken/bones.tscn
./entity/agent_chicken/bones.gd
./entity/agent_chicken/barks.gd
./entity/agent_chicken/emote.gd
./entity/agent_chicken/icon.png
./entity/agent_chicken/special.gd
./entity/agent_chicken/bark.gd
./entity/agent_chicken/deaad.png
./entity/agent_chicken/icon.gd
./entity/agent_chicken/data.gd
./entity/agent_chicken/animation.tscn
./entity/agent_chicken/emote.tscn
./entity/agent_chicken/hand.png
./entity/velocity/entity.tscn
./entity/velocity/icon.png
./entity/velocity/special.gd
./entity/velocity/data.gd
./entity/aircon/entity.tscn
./entity/aircon/grate.png
./entity/aircon/icon.png
./entity/aircon/data.gd
./entity/aircon/animation.png
./entity/floor_tile_bricks/entity.tscn
./entity/floor_tile_bricks/icon.png
./entity/floor_tile_bricks/data.gd
./entity/pallet/entity.tscn
./entity/pallet/icon.png
./entity/pallet/data.gd
./entity/barricade_deployed/debug.png
./entity/barricade_deployed/field.tscn
./entity/barricade_deployed/entity.tscn
./entity/barricade_deployed/ambience.ogg
./entity/barricade_deployed/icon.png
./entity/barricade_deployed/field.gd
./entity/barricade_deployed/field_material.tres
./entity/barricade_deployed/debug2.png
./entity/barricade_deployed/data.gd
./entity/barricade_deployed/field_material_invert.tres
./entity/barricade_deployed/field_material.gd
./entity/barricade_deployed/gibbed.png
./entity/helmet_nv/entity.tscn
./entity/helmet_nv/pick_up.wav
./entity/helmet_nv/icon.png
./entity/helmet_nv/special.gd
./entity/helmet_nv/carried.png
./entity/helmet_nv/eyes.png
./entity/helmet_nv/data.gd
./entity/helmet_nv/helmet-ping.wav
./entity/helmet_nv/eyes.gd
./entity/mutation_sword/entity.tscn
./entity/mutation_sword/icon.png
./entity/mutation_sword/special.gd
./entity/mutation_sword/data.gd
./entity/field_full_super/entity.tscn
./entity/field_full_super/icon.png
./entity/field_full_super/special.gd
./entity/field_full_super/carried.png
./entity/field_full_super/data.gd
./entity/entity_man.gd
./entity/couch/entity.tscn
./entity/couch/icon.png
./entity/couch/data.gd
./entity/teleporter_lil_hunter/entity.tscn
./entity/teleporter_lil_hunter/icon.png
./entity/teleporter_lil_hunter/tubes.png
./entity/teleporter_lil_hunter/osc_shader.tres
./entity/teleporter_lil_hunter/eyes.png
./entity/teleporter_lil_hunter/data.gd
./entity/teleporter_lil_hunter/osc.tres
./entity/game_tutorial_melee_zone/entity.tscn
./entity/game_tutorial_melee_zone/icon.png
./entity/game_tutorial_melee_zone/special.gd
./entity/game_tutorial_melee_zone/data.gd
./entity/kill_streak_glock/entity.tscn
./entity/kill_streak_glock/data.gd
./entity/skin_mime/entity.tscn
./entity/skin_mime/icon.png
./entity/skin_mime/special.gd
./entity/skin_mime/carried.png
./entity/skin_mime/data.gd
./entity/medpack_hard/entity.tscn
./entity/medpack_hard/icon.png
./entity/medpack_hard/data.gd
./entity/teleporter_overload/entity.tscn
./entity/teleporter_overload/icon.png
./entity/teleporter_overload/special.gd
./entity/teleporter_overload/carried.png
./entity/teleporter_overload/data.gd
./entity/background_freighter/overlay.png
./entity/background_freighter/entity.tscn
./entity/background_freighter/icon.png
./entity/background_freighter/Master.ogg
./entity/background_freighter/background/space.png
./entity/background_freighter/background/line.png
./entity/background_freighter/background/background2.gd
./entity/background_freighter/background/good create.png
./entity/background_freighter/background/backgip.png
./entity/background_freighter/background/background2.png
./entity/background_freighter/background/background.png
./entity/background_freighter/background/engine_glow.tscn
./entity/background_freighter/background/gra2d.png
./entity/background_freighter/background/lines3.png
./entity/background_freighter/background/background.tscn
./entity/background_freighter/background/lines.tres
./entity/background_freighter/background/background.gd
./entity/background_freighter/background/bayer16tile2.png
./entity/background_freighter/background/goodcrate.png
./entity/background_freighter/background/push.png
./entity/background_freighter/background/background_floor.png
./entity/background_freighter/background/palette_mono.png
./entity/background_freighter/background/stars.gd
./entity/background_freighter/background/lines2.png
./entity/background_freighter/background/lines.shader
./entity/background_freighter/background/ambience.gd
./entity/background_freighter/background/bacsdas.png
./entity/background_freighter/background/space_ship_ambience.ogg
./entity/background_freighter/background/stars.png
./entity/background_freighter/data.gd
./entity/auto_wires/entity.tscn
./entity/auto_wires/data.gd
./entity/kill_streak/entity.tscn
./entity/kill_streak/kill_streak_toast.tscn
./entity/kill_streak/icon.png

View file

@ -0,0 +1 @@
5U

Binary file not shown.

View file

@ -0,0 +1,312 @@
{
"asset":{
"generator":"Khronos glTF Blender I/O v4.3.47",
"version":"2.0"
},
"scene":0,
"scenes":[
{
"name":"Scene",
"nodes":[
0
]
}
],
"nodes":[
{
"mesh":0,
"name":"Cube"
}
],
"animations":[
{
"channels":[
{
"sampler":0,
"target":{
"node":0,
"path":"translation"
}
},
{
"sampler":1,
"target":{
"node":0,
"path":"rotation"
}
},
{
"sampler":2,
"target":{
"node":0,
"path":"scale"
}
}
],
"name":"CubeAction",
"samplers":[
{
"input":8,
"interpolation":"LINEAR",
"output":9
},
{
"input":10,
"interpolation":"STEP",
"output":11
},
{
"input":10,
"interpolation":"STEP",
"output":12
}
]
}
],
"materials":[
{
"doubleSided":true,
"name":"Material1",
"pbrMetallicRoughness":{
"baseColorFactor":[
1.9073486328125e-06,
0,
1,
1
],
"metallicFactor":0
}
},
{
"doubleSided":true,
"name":"Material2",
"pbrMetallicRoughness":{
"baseColorFactor":[
0,
1,
0,
1
],
"roughnessFactor":0
}
}
],
"meshes":[
{
"name":"Cube.001",
"primitives":[
{
"attributes":{
"POSITION":0,
"NORMAL":1,
"TEXCOORD_0":2
},
"indices":3,
"material":0
},
{
"attributes":{
"POSITION":4,
"NORMAL":5,
"TEXCOORD_0":6
},
"indices":7,
"material":1
}
]
}
],
"accessors":[
{
"bufferView":0,
"componentType":5126,
"count":4,
"max":[
1,
1,
1
],
"min":[
-1,
1,
-1
],
"type":"VEC3"
},
{
"bufferView":1,
"componentType":5126,
"count":4,
"type":"VEC3"
},
{
"bufferView":2,
"componentType":5126,
"count":4,
"type":"VEC2"
},
{
"bufferView":3,
"componentType":5123,
"count":6,
"type":"SCALAR"
},
{
"bufferView":4,
"componentType":5126,
"count":20,
"max":[
1,
1,
1
],
"min":[
-1,
-1,
-1
],
"type":"VEC3"
},
{
"bufferView":5,
"componentType":5126,
"count":20,
"type":"VEC3"
},
{
"bufferView":6,
"componentType":5126,
"count":20,
"type":"VEC2"
},
{
"bufferView":7,
"componentType":5123,
"count":30,
"type":"SCALAR"
},
{
"bufferView":8,
"componentType":5126,
"count":20,
"max":[
0.8333333333333334
],
"min":[
0.041666666666666664
],
"type":"SCALAR"
},
{
"bufferView":9,
"componentType":5126,
"count":20,
"type":"VEC3"
},
{
"bufferView":10,
"componentType":5126,
"count":2,
"max":[
0.8333333333333334
],
"min":[
0.041666666666666664
],
"type":"SCALAR"
},
{
"bufferView":11,
"componentType":5126,
"count":2,
"type":"VEC4"
},
{
"bufferView":12,
"componentType":5126,
"count":2,
"type":"VEC3"
}
],
"bufferViews":[
{
"buffer":0,
"byteLength":48,
"byteOffset":0,
"target":34962
},
{
"buffer":0,
"byteLength":48,
"byteOffset":48,
"target":34962
},
{
"buffer":0,
"byteLength":32,
"byteOffset":96,
"target":34962
},
{
"buffer":0,
"byteLength":12,
"byteOffset":128,
"target":34963
},
{
"buffer":0,
"byteLength":240,
"byteOffset":140,
"target":34962
},
{
"buffer":0,
"byteLength":240,
"byteOffset":380,
"target":34962
},
{
"buffer":0,
"byteLength":160,
"byteOffset":620,
"target":34962
},
{
"buffer":0,
"byteLength":60,
"byteOffset":780,
"target":34963
},
{
"buffer":0,
"byteLength":80,
"byteOffset":840
},
{
"buffer":0,
"byteLength":240,
"byteOffset":920
},
{
"buffer":0,
"byteLength":8,
"byteOffset":1160
},
{
"buffer":0,
"byteLength":32,
"byteOffset":1168
},
{
"buffer":0,
"byteLength":24,
"byteOffset":1200
}
],
"buffers":[
{
"byteLength":1224,
"uri":"cube.bin"
}
]
}

Binary file not shown.

View file

@ -55,7 +55,7 @@ private:
return drivers;
}
static DisplayServer *create_func(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error) {
static DisplayServer *create_func(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) {
r_error = OK;
RasterizerDummy::make_current();
return memnew(DisplayServerMock());
@ -101,7 +101,7 @@ public:
String get_name() const override { return "mock"; }
// You can simulate DisplayServer-events by calling this function.
// The events will be deliverd to Godot's Input-system.
// The events will be delivered to Godot's Input-system.
// Mouse-events (Button & Motion) will additionally update the DisplayServer's mouse position.
// For Mouse motion events, the `relative`-property is set based on the distance to the previous mouse position.
void simulate_event(Ref<InputEvent> p_event) {

View file

@ -37,10 +37,34 @@ protected:
static const Feedback* _feedbacks=nullptr;
static const char _vertex_code[]={
10,112,114,101,99,105,115,105,111,110,32,104,105,103,104,112,32,102,108,111,97,116,59,10,112,114,101,99,105,115,105,111,110,32,104,105,103,104,112,32,105,110,116,59,10,10,108,97,121,111,117,116,40,108,111,99,97,116,105,111,110,32,61,32,48,41,32,105,110,32,104,105,103,104,112,32,118,101,99,51,32,118,101,114,116,101,120,59,10,10,111,117,116,32,104,105,103,104,112,32,118,101,99,52,32,112,111,115,105,116,105,111,110,95,105,110,116,101,114,112,59,10,10,118,111,105,100,32,109,97,105,110,40,41,32,123,10,9,112,111,115,105,116,105,111,110,95,105,110,116,101,114,112,32,61,32,118,101,99,52,40,118,101,114,116,101,120,46,120,44,49,44,48,44,49,41,59,10,125,10,10, 0};
R"<!>(
precision highp float;
precision highp int;
layout(location = 0) in highp vec3 vertex;
out highp vec4 position_interp;
void main() {
position_interp = vec4(vertex.x,1,0,1);
}
)<!>"
};
static const char _fragment_code[]={
10,112,114,101,99,105,115,105,111,110,32,104,105,103,104,112,32,102,108,111,97,116,59,10,112,114,101,99,105,115,105,111,110,32,104,105,103,104,112,32,105,110,116,59,10,10,105,110,32,104,105,103,104,112,32,118,101,99,52,32,112,111,115,105,116,105,111,110,95,105,110,116,101,114,112,59,10,10,118,111,105,100,32,109,97,105,110,40,41,32,123,10,9,104,105,103,104,112,32,102,108,111,97,116,32,100,101,112,116,104,32,61,32,40,40,112,111,115,105,116,105,111,110,95,105,110,116,101,114,112,46,122,32,47,32,112,111,115,105,116,105,111,110,95,105,110,116,101,114,112,46,119,41,32,43,32,49,46,48,41,59,10,9,102,114,97,103,95,99,111,108,111,114,32,61,32,118,101,99,52,40,100,101,112,116,104,41,59,10,125,10, 0};
R"<!>(
precision highp float;
precision highp int;
in highp vec4 position_interp;
void main() {
highp float depth = ((position_interp.z / position_interp.w) + 1.0);
frag_color = vec4(depth);
}
)<!>"
};
_setup(_vertex_code,_fragment_code,"VertexFragmentShaderGLES3",0,_uniform_strings,0,_ubo_pairs,0,_feedbacks,0,_texunit_pairs,1,_spec_pairs,1,_variant_defines);
}

View file

@ -3,6 +3,18 @@
#define COMPUTE_SHADER_GLSL_RAW_H
static const char compute_shader_glsl[] = {
35,91,99,111,109,112,117,116,101,93,10,10,35,118,101,114,115,105,111,110,32,52,53,48,10,10,35,86,69,82,83,73,79,78,95,68,69,70,73,78,69,83,10,10,10,35,100,101,102,105,110,101,32,77,95,80,73,32,51,46,49,52,49,53,57,50,54,53,51,53,57,10,10,118,111,105,100,32,109,97,105,110,40,41,32,123,10,9,118,101,99,51,32,115,116,97,116,105,99,95,108,105,103,104,116,32,61,32,118,101,99,51,40,48,44,32,49,44,32,48,41,59,10,125,10,0
R"<!>(#[compute]
#version 450
#VERSION_DEFINES
#define M_PI 3.14159265359
void main() {
vec3 static_light = vec3(0, 1, 0);
}
)<!>"
};
#endif

View file

@ -3,6 +3,38 @@
#define VERTEX_FRAGMENT_SHADER_GLSL_RAW_H
static const char vertex_fragment_shader_glsl[] = {
35,91,118,101,114,115,105,111,110,115,93,10,10,108,105,110,101,115,32,61,32,34,35,100,101,102,105,110,101,32,77,79,68,69,95,76,73,78,69,83,34,59,10,10,35,91,118,101,114,116,101,120,93,10,10,35,118,101,114,115,105,111,110,32,52,53,48,10,10,35,86,69,82,83,73,79,78,95,68,69,70,73,78,69,83,10,10,108,97,121,111,117,116,40,108,111,99,97,116,105,111,110,32,61,32,48,41,32,111,117,116,32,118,101,99,51,32,117,118,95,105,110,116,101,114,112,59,10,10,118,111,105,100,32,109,97,105,110,40,41,32,123,10,10,35,105,102,100,101,102,32,77,79,68,69,95,76,73,78,69,83,10,9,117,118,95,105,110,116,101,114,112,32,61,32,118,101,99,51,40,48,44,48,44,49,41,59,10,35,101,110,100,105,102,10,125,10,10,35,91,102,114,97,103,109,101,110,116,93,10,10,35,118,101,114,115,105,111,110,32,52,53,48,10,10,35,86,69,82,83,73,79,78,95,68,69,70,73,78,69,83,10,10,35,100,101,102,105,110,101,32,77,95,80,73,32,51,46,49,52,49,53,57,50,54,53,51,53,57,10,10,108,97,121,111,117,116,40,108,111,99,97,116,105,111,110,32,61,32,48,41,32,111,117,116,32,118,101,99,52,32,100,115,116,95,99,111,108,111,114,59,10,10,118,111,105,100,32,109,97,105,110,40,41,32,123,10,9,100,115,116,95,99,111,108,111,114,32,61,32,118,101,99,52,40,49,44,49,44,48,44,48,41,59,10,125,10,0
R"<!>(#[versions]
lines = "#define MODE_LINES";
#[vertex]
#version 450
#VERSION_DEFINES
layout(location = 0) out vec3 uv_interp;
void main() {
#ifdef MODE_LINES
uv_interp = vec3(0,0,1);
#endif
}
#[fragment]
#version 450
#VERSION_DEFINES
#define M_PI 3.14159265359
layout(location = 0) out vec4 dst_color;
void main() {
dst_color = vec4(1,1,0,0);
}
)<!>"
};
#endif

View file

@ -11,7 +11,19 @@ public:
ComputeShaderRD() {
static const char _compute_code[] = {
10,35,118,101,114,115,105,111,110,32,52,53,48,10,10,35,86,69,82,83,73,79,78,95,68,69,70,73,78,69,83,10,10,35,100,101,102,105,110,101,32,66,76,79,67,75,95,83,73,90,69,32,56,10,10,35,100,101,102,105,110,101,32,77,95,80,73,32,51,46,49,52,49,53,57,50,54,53,51,53,57,10,10,118,111,105,100,32,109,97,105,110,40,41,32,123,10,9,117,105,110,116,32,116,32,61,32,66,76,79,67,75,95,83,73,90,69,32,43,32,49,59,10,125,10,0
R"<!>(
#version 450
#VERSION_DEFINES
#define BLOCK_SIZE 8
#define M_PI 3.14159265359
void main() {
uint t = BLOCK_SIZE + 1;
}
)<!>"
};
setup(nullptr, nullptr, _compute_code, "ComputeShaderRD");
}

View file

@ -11,10 +11,33 @@ public:
VertexFragmentShaderRD() {
static const char _vertex_code[] = {
10,35,118,101,114,115,105,111,110,32,52,53,48,10,10,35,86,69,82,83,73,79,78,95,68,69,70,73,78,69,83,10,10,35,100,101,102,105,110,101,32,77,95,80,73,32,51,46,49,52,49,53,57,50,54,53,51,53,57,10,10,108,97,121,111,117,116,40,108,111,99,97,116,105,111,110,32,61,32,48,41,32,111,117,116,32,118,101,99,50,32,117,118,95,105,110,116,101,114,112,59,10,10,118,111,105,100,32,109,97,105,110,40,41,32,123,10,9,117,118,95,105,110,116,101,114,112,32,61,32,118,101,99,50,40,48,44,32,49,41,59,10,125,10,10,0
R"<!>(
#version 450
#VERSION_DEFINES
#define M_PI 3.14159265359
layout(location = 0) out vec2 uv_interp;
void main() {
uv_interp = vec2(0, 1);
}
)<!>"
};
static const char _fragment_code[] = {
10,35,118,101,114,115,105,111,110,32,52,53,48,10,10,35,86,69,82,83,73,79,78,95,68,69,70,73,78,69,83,10,10,108,97,121,111,117,116,40,108,111,99,97,116,105,111,110,32,61,32,48,41,32,105,110,32,118,101,99,50,32,117,118,95,105,110,116,101,114,112,59,10,10,118,111,105,100,32,109,97,105,110,40,41,32,123,10,9,117,118,95,105,110,116,101,114,112,32,61,32,118,101,99,50,40,49,44,32,48,41,59,10,125,10,0
R"<!>(
#version 450
#VERSION_DEFINES
layout(location = 0) in vec2 uv_interp;
void main() {
uv_interp = vec2(1, 0);
}
)<!>"
};
setup(_vertex_code, _fragment_code, nullptr, "VertexFragmentShaderRD");
}

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);

View file

@ -35,6 +35,8 @@
#include "scene/resources/3d/primitive_meshes.h"
#include "servers/navigation_server_3d.h"
#include "modules/navigation/nav_utils.h"
namespace TestNavigationServer3D {
// TODO: Find a more generic way to create `Callable` mocks.
@ -48,7 +50,7 @@ public:
}
unsigned function1_calls{ 0 };
Variant function1_latest_arg0{};
Variant function1_latest_arg0;
};
static inline Array build_array() {
@ -61,6 +63,32 @@ static inline Array build_array(Variant item, Targs... Fargs) {
return a;
}
struct GreaterThan {
bool operator()(int p_a, int p_b) const { return p_a > p_b; }
};
struct CompareArrayValues {
const int *array;
CompareArrayValues(const int *p_array) :
array(p_array) {}
bool operator()(uint32_t p_index_a, uint32_t p_index_b) const {
return array[p_index_a] < array[p_index_b];
}
};
struct RegisterHeapIndexes {
uint32_t *indexes;
RegisterHeapIndexes(uint32_t *p_indexes) :
indexes(p_indexes) {}
void operator()(uint32_t p_vector_index, uint32_t p_heap_index) {
indexes[p_vector_index] = p_heap_index;
}
};
TEST_SUITE("[Navigation]") {
TEST_CASE("[NavigationServer3D] Server should be empty when initialized") {
NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();
@ -545,6 +573,7 @@ TEST_SUITE("[Navigation]") {
RID map = navigation_server->map_create();
RID region = navigation_server->region_create();
Ref<NavigationMesh> navigation_mesh = memnew(NavigationMesh);
navigation_server->map_set_use_async_iterations(map, false);
navigation_server->map_set_active(map, true);
navigation_server->region_set_map(region, map);
navigation_server->region_set_navigation_mesh(region, navigation_mesh);
@ -605,7 +634,7 @@ TEST_SUITE("[Navigation]") {
CHECK_EQ(source_geometry->get_indices().size(), 6);
}
SUBCASE("Parsed geometry should be extendible with other geometry") {
SUBCASE("Parsed geometry should be extendable with other geometry") {
source_geometry->merge(source_geometry); // Merging with itself.
const Vector<float> vertices = source_geometry->get_vertices();
const Vector<int> indices = source_geometry->get_indices();
@ -640,6 +669,7 @@ TEST_SUITE("[Navigation]") {
RID map = navigation_server->map_create();
RID region = navigation_server->region_create();
Ref<NavigationMesh> navigation_mesh = memnew(NavigationMesh);
navigation_server->map_set_use_async_iterations(map, false);
navigation_server->map_set_active(map, true);
navigation_server->region_set_map(region, map);
navigation_server->region_set_navigation_mesh(region, navigation_mesh);
@ -689,6 +719,7 @@ TEST_SUITE("[Navigation]") {
RID map = navigation_server->map_create();
RID region = navigation_server->region_create();
navigation_server->map_set_active(map, true);
navigation_server->map_set_use_async_iterations(map, false);
navigation_server->region_set_map(region, map);
navigation_server->region_set_navigation_mesh(region, navigation_mesh);
navigation_server->process(0.0); // Give server some cycles to commit.
@ -788,6 +819,154 @@ TEST_SUITE("[Navigation]") {
CHECK_EQ(navigation_mesh->get_vertices().size(), 0);
}
*/
TEST_CASE("[NavigationServer3D] Server should simplify path properly") {
real_t simplify_epsilon = 0.2;
Vector<Vector3> source_path;
source_path.resize(7);
source_path.write[0] = Vector3(0.0, 0.0, 0.0);
source_path.write[1] = Vector3(0.0, 0.0, 1.0); // This point needs to go.
source_path.write[2] = Vector3(0.0, 0.0, 2.0); // This point needs to go.
source_path.write[3] = Vector3(0.0, 0.0, 2.0);
source_path.write[4] = Vector3(2.0, 1.0, 3.0);
source_path.write[5] = Vector3(2.0, 1.5, 4.0); // This point needs to go.
source_path.write[6] = Vector3(2.0, 2.0, 5.0);
Vector<Vector3> simplified_path = NavigationServer3D::get_singleton()->simplify_path(source_path, simplify_epsilon);
CHECK_EQ(simplified_path.size(), 4);
}
TEST_CASE("[Heap] size") {
gd::Heap<int> heap;
CHECK(heap.size() == 0);
heap.push(0);
CHECK(heap.size() == 1);
heap.push(1);
CHECK(heap.size() == 2);
heap.pop();
CHECK(heap.size() == 1);
heap.pop();
CHECK(heap.size() == 0);
}
TEST_CASE("[Heap] is_empty") {
gd::Heap<int> heap;
CHECK(heap.is_empty() == true);
heap.push(0);
CHECK(heap.is_empty() == false);
heap.pop();
CHECK(heap.is_empty() == true);
}
TEST_CASE("[Heap] push/pop") {
SUBCASE("Default comparator") {
gd::Heap<int> heap;
heap.push(2);
heap.push(7);
heap.push(5);
heap.push(3);
heap.push(4);
CHECK(heap.pop() == 7);
CHECK(heap.pop() == 5);
CHECK(heap.pop() == 4);
CHECK(heap.pop() == 3);
CHECK(heap.pop() == 2);
}
SUBCASE("Custom comparator") {
GreaterThan greaterThan;
gd::Heap<int, GreaterThan> heap(greaterThan);
heap.push(2);
heap.push(7);
heap.push(5);
heap.push(3);
heap.push(4);
CHECK(heap.pop() == 2);
CHECK(heap.pop() == 3);
CHECK(heap.pop() == 4);
CHECK(heap.pop() == 5);
CHECK(heap.pop() == 7);
}
SUBCASE("Intermediate pops") {
gd::Heap<int> heap;
heap.push(0);
heap.push(3);
heap.pop();
heap.push(1);
heap.push(2);
CHECK(heap.pop() == 2);
CHECK(heap.pop() == 1);
CHECK(heap.pop() == 0);
}
}
TEST_CASE("[Heap] shift") {
int values[] = { 5, 3, 6, 7, 1 };
uint32_t heap_indexes[] = { 0, 0, 0, 0, 0 };
CompareArrayValues comparator(values);
RegisterHeapIndexes indexer(heap_indexes);
gd::Heap<uint32_t, CompareArrayValues, RegisterHeapIndexes> heap(comparator, indexer);
heap.push(0);
heap.push(1);
heap.push(2);
heap.push(3);
heap.push(4);
// Shift down: 6 -> 2
values[2] = 2;
heap.shift(heap_indexes[2]);
// Shift up: 5 -> 8
values[0] = 8;
heap.shift(heap_indexes[0]);
CHECK(heap.pop() == 0);
CHECK(heap.pop() == 3);
CHECK(heap.pop() == 1);
CHECK(heap.pop() == 2);
CHECK(heap.pop() == 4);
CHECK(heap_indexes[0] == UINT32_MAX);
CHECK(heap_indexes[1] == UINT32_MAX);
CHECK(heap_indexes[2] == UINT32_MAX);
CHECK(heap_indexes[3] == UINT32_MAX);
CHECK(heap_indexes[4] == UINT32_MAX);
}
TEST_CASE("[Heap] clear") {
uint32_t heap_indexes[] = { 0, 0, 0, 0 };
RegisterHeapIndexes indexer(heap_indexes);
gd::Heap<uint32_t, Comparator<uint32_t>, RegisterHeapIndexes> heap(indexer);
heap.push(0);
heap.push(2);
heap.push(1);
heap.push(3);
heap.clear();
CHECK(heap.size() == 0);
CHECK(heap_indexes[0] == UINT32_MAX);
CHECK(heap_indexes[1] == UINT32_MAX);
CHECK(heap_indexes[2] == UINT32_MAX);
CHECK(heap_indexes[3] == UINT32_MAX);
}
}
} //namespace TestNavigationServer3D

View file

@ -124,7 +124,7 @@ TEST_SUITE("[TextServer]") {
RID font1 = ts->create_font();
ts->font_set_data_ptr(font1, _font_NotoSans_Regular, _font_NotoSans_Regular_size);
RID font2 = ts->create_font();
ts->font_set_data_ptr(font2, _font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size);
ts->font_set_data_ptr(font2, _font_Vazirmatn_Regular, _font_Vazirmatn_Regular_size);
Array font;
font.push_back(font1);
@ -180,7 +180,7 @@ TEST_SUITE("[TextServer]") {
ts->font_set_data_ptr(font2, _font_NotoSansThai_Regular, _font_NotoSansThai_Regular_size);
ts->font_set_allow_system_fallback(font2, false);
RID font3 = ts->create_font();
ts->font_set_data_ptr(font3, _font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size);
ts->font_set_data_ptr(font3, _font_Vazirmatn_Regular, _font_Vazirmatn_Regular_size);
ts->font_set_allow_system_fallback(font3, false);
Array font;
@ -461,7 +461,7 @@ TEST_SUITE("[TextServer]") {
ts->free_rid(ctx);
}
if (ts->has_feature(TextServer::FEATURE_BREAK_ITERATORS)) {
if (ts->has_feature(TextServer::FEATURE_BREAK_ITERATORS)) { // Line breaking opportunities.
String test = U"เป็นภาษาราชการและภาษา";
RID ctx = ts->create_shaped_text();
CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");
@ -489,6 +489,67 @@ TEST_SUITE("[TextServer]") {
ts->free_rid(ctx);
}
if (ts->has_feature(TextServer::FEATURE_BREAK_ITERATORS)) { // Break line.
struct TestCase {
String text;
PackedInt32Array breaks;
};
TestCase cases[] = {
{ U" เมาส์ตัวนี้", { 0, 17, 17, 23 } },
{ U" กู้ไฟล์", { 0, 17, 17, 21 } },
{ U" ไม่มีคำ", { 0, 18, 18, 20 } },
{ U" ไม่มีคำพูด", { 0, 18, 18, 23 } },
{ U" ไม่มีคำ", { 0, 17, 17, 19 } },
{ U" มีอุปกรณ์\nนี้", { 0, 11, 11, 19, 19, 22 } },
{ U"الحمدا لحمدا لحمـــد", { 0, 13, 13, 20 } },
{ U" الحمد test", { 0, 15, 15, 19 } },
{ U"الحمـد الرياضي العربي", { 0, 7, 7, 15, 15, 21 } },
{ U"test \rtest", { 0, 6, 6, 10 } },
{ U"test\r test", { 0, 5, 5, 10 } },
{ U"test\r test \r test", { 0, 5, 5, 12, 12, 17 } },
};
for (size_t j = 0; j < sizeof(cases) / sizeof(TestCase); j++) {
RID ctx = ts->create_shaped_text();
CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");
bool ok = ts->shaped_text_add_string(ctx, cases[j].text, font, 16);
CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");
PackedInt32Array breaks = ts->shaped_text_get_line_breaks(ctx, 90.0);
CHECK_FALSE_MESSAGE(breaks != cases[j].breaks, "Invalid break points.");
breaks = ts->shaped_text_get_line_breaks_adv(ctx, { 90.0 }, 0, false);
CHECK_FALSE_MESSAGE(breaks != cases[j].breaks, "Invalid break points.");
ts->free_rid(ctx);
}
}
if (ts->has_feature(TextServer::FEATURE_BREAK_ITERATORS)) { // Break line and trim spaces.
struct TestCase {
String text;
PackedInt32Array breaks;
};
TestCase cases[] = {
{ U"test \rtest", { 0, 4, 6, 10 } },
{ U"test\r test", { 0, 4, 6, 10 } },
{ U"test\r test \r test", { 0, 4, 6, 10, 13, 17 } },
};
for (size_t j = 0; j < sizeof(cases) / sizeof(TestCase); j++) {
RID ctx = ts->create_shaped_text();
CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");
bool ok = ts->shaped_text_add_string(ctx, cases[j].text, font, 16);
CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");
PackedInt32Array breaks = ts->shaped_text_get_line_breaks(ctx, 90.0, 0, TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_TRIM_EDGE_SPACES);
CHECK_FALSE_MESSAGE(breaks != cases[j].breaks, "Invalid break points.");
breaks = ts->shaped_text_get_line_breaks_adv(ctx, { 90.0 }, 0, false, TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_TRIM_EDGE_SPACES);
CHECK_FALSE_MESSAGE(breaks != cases[j].breaks, "Invalid break points.");
ts->free_rid(ctx);
}
}
for (int j = 0; j < font.size(); j++) {
ts->free_rid(font[j]);
}
@ -535,6 +596,19 @@ TEST_SUITE("[TextServer]") {
CHECK_FALSE_MESSAGE(brks[5] != 14, "Invalid line break position.");
}
brks = ts->shaped_text_get_line_breaks(ctx, 35.0, 0, TextServer::BREAK_WORD_BOUND | TextServer::BREAK_MANDATORY | TextServer::BREAK_TRIM_EDGE_SPACES);
CHECK_FALSE_MESSAGE(brks.size() != 6, "Invalid line breaks number.");
if (brks.size() == 6) {
CHECK_FALSE_MESSAGE(brks[0] != 0, "Invalid line break position.");
CHECK_FALSE_MESSAGE(brks[1] != 4, "Invalid line break position.");
CHECK_FALSE_MESSAGE(brks[2] != 5, "Invalid line break position.");
CHECK_FALSE_MESSAGE(brks[3] != 9, "Invalid line break position.");
CHECK_FALSE_MESSAGE(brks[4] != 10, "Invalid line break position.");
CHECK_FALSE_MESSAGE(brks[5] != 14, "Invalid line break position.");
}
ts->free_rid(ctx);
for (int j = 0; j < font.size(); j++) {
@ -556,7 +630,7 @@ TEST_SUITE("[TextServer]") {
RID font1 = ts->create_font();
ts->font_set_data_ptr(font1, _font_NotoSans_Regular, _font_NotoSans_Regular_size);
RID font2 = ts->create_font();
ts->font_set_data_ptr(font2, _font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size);
ts->font_set_data_ptr(font2, _font_Vazirmatn_Regular, _font_Vazirmatn_Regular_size);
Array font;
font.push_back(font1);
@ -754,12 +828,12 @@ TEST_SUITE("[TextServer]") {
SUBCASE("[TextServer] Word break") {
for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {
Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);
CHECK_FALSE_MESSAGE(ts.is_null(), "Invalid TS interface.");
if (!ts->has_feature(TextServer::FEATURE_SIMPLE_LAYOUT)) {
continue;
}
CHECK_FALSE_MESSAGE(ts.is_null(), "Invalid TS interface.");
{
String text1 = U"linguistically similar and effectively form";
// 14^ 22^ 26^ 38^
@ -857,6 +931,47 @@ TEST_SUITE("[TextServer]") {
}
}
}
SUBCASE("[TextServer] Buffer invalidation") {
for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {
Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);
CHECK_FALSE_MESSAGE(ts.is_null(), "Invalid TS interface.");
if (!ts->has_feature(TextServer::FEATURE_SIMPLE_LAYOUT)) {
continue;
}
RID font1 = ts->create_font();
ts->font_set_data_ptr(font1, _font_NotoSans_Regular, _font_NotoSans_Regular_size);
Array font;
font.push_back(font1);
RID ctx = ts->create_shaped_text();
CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");
bool ok = ts->shaped_text_add_string(ctx, "T", font, 16);
CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");
int gl_size = ts->shaped_text_get_glyph_count(ctx);
CHECK_MESSAGE(gl_size == 1, "Shaping failed, invalid glyph count");
ok = ts->shaped_text_add_object(ctx, "key", Size2(20, 20), INLINE_ALIGNMENT_CENTER, 1, 0.0);
CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");
gl_size = ts->shaped_text_get_glyph_count(ctx);
CHECK_MESSAGE(gl_size == 2, "Shaping failed, invalid glyph count");
ok = ts->shaped_text_add_string(ctx, "B", font, 16);
CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");
gl_size = ts->shaped_text_get_glyph_count(ctx);
CHECK_MESSAGE(gl_size == 3, "Shaping failed, invalid glyph count");
ts->free_rid(ctx);
for (int j = 0; j < font.size(); j++) {
ts->free_rid(font[j]);
}
font.clear();
}
}
}
}
}; // namespace TestTextServer

View file

@ -93,8 +93,11 @@ DOCTEST_STRINGIFY_VARIANT(Rect2);
DOCTEST_STRINGIFY_VARIANT(Rect2i);
DOCTEST_STRINGIFY_VARIANT(Vector3);
DOCTEST_STRINGIFY_VARIANT(Vector3i);
DOCTEST_STRINGIFY_VARIANT(Vector4);
DOCTEST_STRINGIFY_VARIANT(Vector4i);
DOCTEST_STRINGIFY_VARIANT(Transform2D);
DOCTEST_STRINGIFY_VARIANT(Plane);
DOCTEST_STRINGIFY_VARIANT(Projection);
DOCTEST_STRINGIFY_VARIANT(Quaternion);
DOCTEST_STRINGIFY_VARIANT(AABB);
DOCTEST_STRINGIFY_VARIANT(Basis);
@ -171,20 +174,20 @@ int register_test_command(String p_command, TestFunc p_function);
MessageQueue::get_singleton()->flush(); \
}
#define _UPDATE_EVENT_MODIFERS(m_event, m_modifers) \
m_event->set_shift_pressed(((m_modifers) & KeyModifierMask::SHIFT) != Key::NONE); \
m_event->set_alt_pressed(((m_modifers) & KeyModifierMask::ALT) != Key::NONE); \
m_event->set_ctrl_pressed(((m_modifers) & KeyModifierMask::CTRL) != Key::NONE); \
m_event->set_meta_pressed(((m_modifers) & KeyModifierMask::META) != Key::NONE);
#define _UPDATE_EVENT_MODIFIERS(m_event, m_modifiers) \
m_event->set_shift_pressed(((m_modifiers) & KeyModifierMask::SHIFT) != Key::NONE); \
m_event->set_alt_pressed(((m_modifiers) & KeyModifierMask::ALT) != Key::NONE); \
m_event->set_ctrl_pressed(((m_modifiers) & KeyModifierMask::CTRL) != Key::NONE); \
m_event->set_meta_pressed(((m_modifiers) & KeyModifierMask::META) != Key::NONE);
#define _CREATE_GUI_MOUSE_EVENT(m_screen_pos, m_input, m_mask, m_modifers) \
Ref<InputEventMouseButton> event; \
event.instantiate(); \
event->set_position(m_screen_pos); \
event->set_button_index(m_input); \
event->set_button_mask(m_mask); \
event->set_factor(1); \
_UPDATE_EVENT_MODIFERS(event, m_modifers); \
#define _CREATE_GUI_MOUSE_EVENT(m_screen_pos, m_input, m_mask, m_modifiers) \
Ref<InputEventMouseButton> event; \
event.instantiate(); \
event->set_position(m_screen_pos); \
event->set_button_index(m_input); \
event->set_button_mask(m_mask); \
event->set_factor(1); \
_UPDATE_EVENT_MODIFIERS(event, m_modifiers); \
event->set_pressed(true);
#define _CREATE_GUI_TOUCH_EVENT(m_screen_pos, m_pressed, m_double) \
@ -194,42 +197,42 @@ int register_test_command(String p_command, TestFunc p_function);
event->set_pressed(m_pressed); \
event->set_double_tap(m_double);
#define SEND_GUI_MOUSE_BUTTON_EVENT(m_screen_pos, m_input, m_mask, m_modifers) \
{ \
_CREATE_GUI_MOUSE_EVENT(m_screen_pos, m_input, m_mask, m_modifers); \
_SEND_DISPLAYSERVER_EVENT(event); \
MessageQueue::get_singleton()->flush(); \
#define SEND_GUI_MOUSE_BUTTON_EVENT(m_screen_pos, m_input, m_mask, m_modifiers) \
{ \
_CREATE_GUI_MOUSE_EVENT(m_screen_pos, m_input, m_mask, m_modifiers); \
_SEND_DISPLAYSERVER_EVENT(event); \
MessageQueue::get_singleton()->flush(); \
}
#define SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(m_screen_pos, m_input, m_mask, m_modifers) \
{ \
_CREATE_GUI_MOUSE_EVENT(m_screen_pos, m_input, m_mask, m_modifers); \
event->set_pressed(false); \
_SEND_DISPLAYSERVER_EVENT(event); \
MessageQueue::get_singleton()->flush(); \
#define SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(m_screen_pos, m_input, m_mask, m_modifiers) \
{ \
_CREATE_GUI_MOUSE_EVENT(m_screen_pos, m_input, m_mask, m_modifiers); \
event->set_pressed(false); \
_SEND_DISPLAYSERVER_EVENT(event); \
MessageQueue::get_singleton()->flush(); \
}
#define SEND_GUI_DOUBLE_CLICK(m_screen_pos, m_modifers) \
{ \
_CREATE_GUI_MOUSE_EVENT(m_screen_pos, MouseButton::LEFT, 0, m_modifers); \
event->set_double_click(true); \
_SEND_DISPLAYSERVER_EVENT(event); \
MessageQueue::get_singleton()->flush(); \
#define SEND_GUI_DOUBLE_CLICK(m_screen_pos, m_modifiers) \
{ \
_CREATE_GUI_MOUSE_EVENT(m_screen_pos, MouseButton::LEFT, 0, m_modifiers); \
event->set_double_click(true); \
_SEND_DISPLAYSERVER_EVENT(event); \
MessageQueue::get_singleton()->flush(); \
}
// We toggle _print_error_enabled to prevent display server not supported warnings.
#define SEND_GUI_MOUSE_MOTION_EVENT(m_screen_pos, m_mask, m_modifers) \
{ \
bool errors_enabled = CoreGlobals::print_error_enabled; \
CoreGlobals::print_error_enabled = false; \
Ref<InputEventMouseMotion> event; \
event.instantiate(); \
event->set_position(m_screen_pos); \
event->set_button_mask(m_mask); \
_UPDATE_EVENT_MODIFERS(event, m_modifers); \
_SEND_DISPLAYSERVER_EVENT(event); \
MessageQueue::get_singleton()->flush(); \
CoreGlobals::print_error_enabled = errors_enabled; \
#define SEND_GUI_MOUSE_MOTION_EVENT(m_screen_pos, m_mask, m_modifiers) \
{ \
bool errors_enabled = CoreGlobals::print_error_enabled; \
CoreGlobals::print_error_enabled = false; \
Ref<InputEventMouseMotion> event; \
event.instantiate(); \
event->set_position(m_screen_pos); \
event->set_button_mask(m_mask); \
_UPDATE_EVENT_MODIFIERS(event, m_modifiers); \
_SEND_DISPLAYSERVER_EVENT(event); \
MessageQueue::get_singleton()->flush(); \
CoreGlobals::print_error_enabled = errors_enabled; \
}
#define SEND_GUI_TOUCH_EVENT(m_screen_pos, m_pressed, m_double) \
@ -376,6 +379,9 @@ public:
bool check_false(const String &p_name) {
bool has = _signals.has(p_name);
if (has) {
MESSAGE("Signal has " << _signals[p_name] << " expected none.");
}
discard_signal(p_name);
return !has;
}
@ -471,6 +477,6 @@ public:
for (int i = 0; i < string_list.size(); ++i) { \
CHECK(string_list[i] == m_slices[i]); \
} \
} while (0)
} while (false)
#endif // TEST_MACROS_H

View file

@ -48,9 +48,17 @@
#include "tests/core/io/test_image.h"
#include "tests/core/io/test_ip.h"
#include "tests/core/io/test_json.h"
#include "tests/core/io/test_json_native.h"
#include "tests/core/io/test_logger.h"
#include "tests/core/io/test_marshalls.h"
#include "tests/core/io/test_packet_peer.h"
#include "tests/core/io/test_pck_packer.h"
#include "tests/core/io/test_resource.h"
#include "tests/core/io/test_resource_uid.h"
#include "tests/core/io/test_stream_peer.h"
#include "tests/core/io/test_stream_peer_buffer.h"
#include "tests/core/io/test_tcp_server.h"
#include "tests/core/io/test_udp_server.h"
#include "tests/core/io/test_xml_parser.h"
#include "tests/core/math/test_aabb.h"
#include "tests/core/math/test_astar.h"
@ -61,6 +69,7 @@
#include "tests/core/math/test_geometry_3d.h"
#include "tests/core/math/test_math_funcs.h"
#include "tests/core/math/test_plane.h"
#include "tests/core/math/test_projection.h"
#include "tests/core/math/test_quaternion.h"
#include "tests/core/math/test_random_number_generator.h"
#include "tests/core/math/test_rect2.h"
@ -78,10 +87,12 @@
#include "tests/core/object/test_object.h"
#include "tests/core/object/test_undo_redo.h"
#include "tests/core/os/test_os.h"
#include "tests/core/string/test_fuzzy_search.h"
#include "tests/core/string/test_node_path.h"
#include "tests/core/string/test_string.h"
#include "tests/core/string/test_translation.h"
#include "tests/core/string/test_translation_server.h"
#include "tests/core/templates/test_a_hash_map.h"
#include "tests/core/templates/test_command_queue.h"
#include "tests/core/templates/test_hash_map.h"
#include "tests/core/templates/test_hash_set.h"
@ -104,21 +115,28 @@
#include "tests/scene/test_animation.h"
#include "tests/scene/test_audio_stream_wav.h"
#include "tests/scene/test_bit_map.h"
#include "tests/scene/test_button.h"
#include "tests/scene/test_camera_2d.h"
#include "tests/scene/test_control.h"
#include "tests/scene/test_curve.h"
#include "tests/scene/test_curve_2d.h"
#include "tests/scene/test_curve_3d.h"
#include "tests/scene/test_fontfile.h"
#include "tests/scene/test_gradient.h"
#include "tests/scene/test_gradient_texture.h"
#include "tests/scene/test_image_texture.h"
#include "tests/scene/test_image_texture_3d.h"
#include "tests/scene/test_instance_placeholder.h"
#include "tests/scene/test_node.h"
#include "tests/scene/test_node_2d.h"
#include "tests/scene/test_packed_scene.h"
#include "tests/scene/test_parallax_2d.h"
#include "tests/scene/test_path_2d.h"
#include "tests/scene/test_path_follow_2d.h"
#include "tests/scene/test_physics_material.h"
#include "tests/scene/test_sprite_frames.h"
#include "tests/scene/test_style_box_texture.h"
#include "tests/scene/test_texture_progress_bar.h"
#include "tests/scene/test_theme.h"
#include "tests/scene/test_timer.h"
#include "tests/scene/test_viewport.h"
@ -132,7 +150,12 @@
#include "tests/scene/test_code_edit.h"
#include "tests/scene/test_color_picker.h"
#include "tests/scene/test_graph_node.h"
#include "tests/scene/test_option_button.h"
#include "tests/scene/test_split_container.h"
#include "tests/scene/test_tab_bar.h"
#include "tests/scene/test_tab_container.h"
#include "tests/scene/test_text_edit.h"
#include "tests/scene/test_tree.h"
#endif // ADVANCED_GUI_DISABLED
#ifndef _3D_DISABLED
@ -149,9 +172,13 @@
#include "tests/scene/test_arraymesh.h"
#include "tests/scene/test_camera_3d.h"
#include "tests/scene/test_gltf_document.h"
#include "tests/scene/test_height_map_shape_3d.h"
#include "tests/scene/test_path_3d.h"
#include "tests/scene/test_path_follow_3d.h"
#include "tests/scene/test_primitives.h"
#include "tests/scene/test_skeleton_3d.h"
#include "tests/scene/test_sky.h"
#endif // _3D_DISABLED
#include "modules/modules_tests.gen.h"
@ -165,8 +192,10 @@
#include "servers/navigation_server_3d.h"
#endif // _3D_DISABLED
#include "servers/physics_server_2d.h"
#include "servers/physics_server_2d_dummy.h"
#ifndef _3D_DISABLED
#include "servers/physics_server_3d.h"
#include "servers/physics_server_3d_dummy.h"
#endif // _3D_DISABLED
#include "servers/rendering/rendering_server_default.h"
@ -265,7 +294,7 @@ struct GodotTestCaseListener : public doctest::IReporter {
OS::get_singleton()->set_has_server_feature_callback(nullptr);
for (int i = 0; i < DisplayServer::get_create_function_count(); i++) {
if (String("mock") == DisplayServer::get_create_function_name(i)) {
DisplayServer::create(i, "", DisplayServer::WindowMode::WINDOW_MODE_MINIMIZED, DisplayServer::VSyncMode::VSYNC_ENABLED, 0, nullptr, Vector2i(0, 0), DisplayServer::SCREEN_PRIMARY, DisplayServer::CONTEXT_EDITOR, err);
DisplayServer::create(i, "", DisplayServer::WindowMode::WINDOW_MODE_MINIMIZED, DisplayServer::VSyncMode::VSYNC_ENABLED, 0, nullptr, Vector2i(0, 0), DisplayServer::SCREEN_PRIMARY, DisplayServer::CONTEXT_EDITOR, 0, err);
break;
}
}
@ -281,10 +310,16 @@ struct GodotTestCaseListener : public doctest::IReporter {
#ifndef _3D_DISABLED
physics_server_3d = PhysicsServer3DManager::get_singleton()->new_default_server();
if (!physics_server_3d) {
physics_server_3d = memnew(PhysicsServer3DDummy);
}
physics_server_3d->init();
#endif // _3D_DISABLED
physics_server_2d = PhysicsServer2DManager::get_singleton()->new_default_server();
if (!physics_server_2d) {
physics_server_2d = memnew(PhysicsServer2DDummy);
}
physics_server_2d->init();
#ifndef _3D_DISABLED
@ -314,7 +349,7 @@ struct GodotTestCaseListener : public doctest::IReporter {
return;
}
if (name.contains("Audio")) {
if (name.contains("[Audio]")) {
// The last driver index should always be the dummy driver.
int dummy_idx = AudioDriverManager::get_driver_count() - 1;
AudioDriverManager::initialize(dummy_idx);
@ -338,6 +373,12 @@ struct GodotTestCaseListener : public doctest::IReporter {
#ifdef TOOLS_ENABLED
if (EditorSettings::get_singleton()) {
EditorSettings::destroy();
// Instantiating the EditorSettings singleton sets the locale to the editor's language.
TranslationServer::get_singleton()->set_locale("en");
}
if (EditorPaths::get_singleton()) {
EditorPaths::free();
}
#endif // TOOLS_ENABLED