diff options
Diffstat (limited to 'tests')
38 files changed, 7598 insertions, 1423 deletions
diff --git a/tests/core/input/test_input_event.h b/tests/core/input/test_input_event.h index 6b4b80486c..c6a287e47b 100644 --- a/tests/core/input/test_input_event.h +++ b/tests/core/input/test_input_event.h @@ -43,7 +43,7 @@ TEST_CASE("[InputEvent] Signal is emitted when device is changed") { Ref<InputEventKey> input_event; input_event.instantiate(); - SIGNAL_WATCH(*input_event, SNAME("changed")); + SIGNAL_WATCH(*input_event, CoreStringName(changed)); Array args1; Array empty_args; empty_args.push_back(args1); @@ -53,7 +53,7 @@ TEST_CASE("[InputEvent] Signal is emitted when device is changed") { SIGNAL_CHECK("changed", empty_args); CHECK(input_event->get_device() == 1); - SIGNAL_UNWATCH(*input_event, SNAME("changed")); + SIGNAL_UNWATCH(*input_event, CoreStringName(changed)); } TEST_CASE("[InputEvent] Test accumulate") { diff --git a/tests/core/io/test_resource.h b/tests/core/io/test_resource.h index 9ddb51220b..a83e7f88ba 100644 --- a/tests/core/io/test_resource.h +++ b/tests/core/io/test_resource.h @@ -38,6 +38,8 @@ #include "thirdparty/doctest/doctest.h" +#include "tests/test_macros.h" + namespace TestResource { TEST_CASE("[Resource] Duplication") { @@ -124,9 +126,12 @@ TEST_CASE("[Resource] Breaking circular references on save") { const String save_path_binary = OS::get_singleton()->get_cache_path().path_join("resource.res"); const String save_path_text = OS::get_singleton()->get_cache_path().path_join("resource.tres"); ResourceSaver::save(resource_a, save_path_binary); + // Suppress expected errors caused by the resources above being uncached. + ERR_PRINT_OFF; ResourceSaver::save(resource_a, save_path_text); const Ref<Resource> &loaded_resource_a_binary = ResourceLoader::load(save_path_binary); + ERR_PRINT_ON; CHECK_MESSAGE( loaded_resource_a_binary->get_name() == "A", "The loaded resource name should be equal to the expected value."); diff --git a/tests/core/math/test_aabb.h b/tests/core/math/test_aabb.h index b9f84cca24..dbc62bc248 100644 --- a/tests/core/math/test_aabb.h +++ b/tests/core/math/test_aabb.h @@ -204,6 +204,67 @@ TEST_CASE("[AABB] Intersection") { CHECK_MESSAGE( !aabb_big.intersects_segment(Vector3(0, 300, 0), Vector3(0, 300, 0)), "intersects_segment() should return the expected result with segment of length 0."); + CHECK_MESSAGE( // Simple ray intersection test. + aabb_big.intersects_ray(Vector3(-100, 3, 0), Vector3(1, 0, 0)), + "intersects_ray() should return true when ray points directly to AABB from outside."); + CHECK_MESSAGE( // Ray parallel to an edge. + !aabb_big.intersects_ray(Vector3(10, 10, 0), Vector3(0, 1, 0)), + "intersects_ray() should return false for ray parallel and outside of AABB."); + CHECK_MESSAGE( // Ray origin inside aabb. + aabb_big.intersects_ray(Vector3(1, 1, 1), Vector3(0, 1, 0)), + "intersects_ray() should return true for rays originating inside the AABB."); + CHECK_MESSAGE( // Ray pointing away from aabb. + !aabb_big.intersects_ray(Vector3(-10, 0, 0), Vector3(-1, 0, 0)), + "intersects_ray() should return false when ray points away from AABB."); + CHECK_MESSAGE( // Ray along a diagonal of aabb. + aabb_big.intersects_ray(Vector3(0, 0, 0), Vector3(1, 1, 1)), + "intersects_ray() should return true for rays along the AABB diagonal."); + CHECK_MESSAGE( // Ray originating at aabb edge. + aabb_big.intersects_ray(aabb_big.position, Vector3(-1, 0, 0)), + "intersects_ray() should return true for rays starting on AABB's edge."); + CHECK_MESSAGE( // Ray with zero direction inside. + aabb_big.intersects_ray(Vector3(-1, 3, -2), Vector3(0, 0, 0)), + "intersects_ray() should return true because its inside."); + CHECK_MESSAGE( // Ray with zero direction outside. + !aabb_big.intersects_ray(Vector3(-1000, 3, -2), Vector3(0, 0, 0)), + "intersects_ray() should return false for being outside."); + + // Finding ray intersections. + const AABB aabb_simple = AABB(Vector3(), Vector3(1, 1, 1)); + bool inside = false; + Vector3 intersection_point; + Vector3 intersection_normal; + + // Borders. + aabb_simple.find_intersects_ray(Vector3(0.5, 0, 0.5), Vector3(0, 1, 0), inside, &intersection_point, &intersection_normal); + CHECK_MESSAGE(inside == false, "find_intersects_ray() should return outside on borders."); + CHECK_MESSAGE(intersection_point.is_equal_approx(Vector3(0.5, 0, 0.5)), "find_intersects_ray() border intersection point incorrect."); + CHECK_MESSAGE(intersection_normal.is_equal_approx(Vector3(0, -1, 0)), "find_intersects_ray() border intersection normal incorrect."); + aabb_simple.find_intersects_ray(Vector3(0.5, 1, 0.5), Vector3(0, -1, 0), inside, &intersection_point, &intersection_normal); + CHECK_MESSAGE(inside == false, "find_intersects_ray() should return outside on borders."); + CHECK_MESSAGE(intersection_point.is_equal_approx(Vector3(0.5, 1, 0.5)), "find_intersects_ray() border intersection point incorrect."); + CHECK_MESSAGE(intersection_normal.is_equal_approx(Vector3(0, 1, 0)), "find_intersects_ray() border intersection normal incorrect."); + + // Inside. + aabb_simple.find_intersects_ray(Vector3(0.5, 0.1, 0.5), Vector3(0, 1, 0), inside, &intersection_point, &intersection_normal); + CHECK_MESSAGE(inside == true, "find_intersects_ray() should return inside when inside."); + CHECK_MESSAGE(intersection_point.is_equal_approx(Vector3(0.5, 0, 0.5)), "find_intersects_ray() inside backtracking intersection point incorrect."); + CHECK_MESSAGE(intersection_normal.is_equal_approx(Vector3(0, -1, 0)), "find_intersects_ray() inside intersection normal incorrect."); + + // Zero sized AABB. + const AABB aabb_zero = AABB(Vector3(), Vector3(1, 0, 1)); + aabb_zero.find_intersects_ray(Vector3(0.5, 0, 0.5), Vector3(0, 1, 0), inside, &intersection_point, &intersection_normal); + CHECK_MESSAGE(inside == false, "find_intersects_ray() should return outside on borders of zero sized AABB."); + CHECK_MESSAGE(intersection_point.is_equal_approx(Vector3(0.5, 0, 0.5)), "find_intersects_ray() border intersection point incorrect for zero sized AABB."); + CHECK_MESSAGE(intersection_normal.is_equal_approx(Vector3(0, -1, 0)), "find_intersects_ray() border intersection normal incorrect for zero sized AABB."); + aabb_zero.find_intersects_ray(Vector3(0.5, 0, 0.5), Vector3(0, -1, 0), inside, &intersection_point, &intersection_normal); + CHECK_MESSAGE(inside == false, "find_intersects_ray() should return outside on borders of zero sized AABB."); + CHECK_MESSAGE(intersection_point.is_equal_approx(Vector3(0.5, 0, 0.5)), "find_intersects_ray() border intersection point incorrect for zero sized AABB."); + CHECK_MESSAGE(intersection_normal.is_equal_approx(Vector3(0, 1, 0)), "find_intersects_ray() border intersection normal incorrect for zero sized AABB."); + aabb_zero.find_intersects_ray(Vector3(0.5, -1, 0.5), Vector3(0, 1, 0), inside, &intersection_point, &intersection_normal); + CHECK_MESSAGE(inside == false, "find_intersects_ray() should return outside on borders of zero sized AABB."); + CHECK_MESSAGE(intersection_point.is_equal_approx(Vector3(0.5, 0, 0.5)), "find_intersects_ray() border intersection point incorrect for zero sized AABB."); + CHECK_MESSAGE(intersection_normal.is_equal_approx(Vector3(0, -1, 0)), "find_intersects_ray() border intersection normal incorrect for zero sized AABB."); } TEST_CASE("[AABB] Merging") { diff --git a/tests/core/math/test_geometry_2d.h b/tests/core/math/test_geometry_2d.h index c3ff4f3ec9..a4bb6dfca0 100644 --- a/tests/core/math/test_geometry_2d.h +++ b/tests/core/math/test_geometry_2d.h @@ -282,41 +282,67 @@ TEST_CASE("[Geometry2D] Closest point to uncapped segment") { TEST_CASE("[Geometry2D] Closest points between segments") { Vector2 c1, c2; - Geometry2D::get_closest_points_between_segments(Vector2(2, 2), Vector2(3, 3), Vector2(4, 4), Vector2(4, 5), c1, c2); - CHECK(c1.is_equal_approx(Vector2(3, 3))); - CHECK(c2.is_equal_approx(Vector2(4, 4))); + // Basis Path Testing suite + SUBCASE("[Geometry2D] Both segments degenerate to a point") { + Geometry2D::get_closest_points_between_segments(Vector2(0, 0), Vector2(0, 0), Vector2(0, 0), Vector2(0, 0), c1, c2); + CHECK(c1.is_equal_approx(Vector2(0, 0))); + CHECK(c2.is_equal_approx(Vector2(0, 0))); + } - Geometry2D::get_closest_points_between_segments(Vector2(0, 1), Vector2(-2, -1), Vector2(0, 0), Vector2(2, -2), c1, c2); - CHECK(c1.is_equal_approx(Vector2(-0.5, 0.5))); - CHECK(c2.is_equal_approx(Vector2(0, 0))); + SUBCASE("[Geometry2D] Closest point on second segment trajectory is above [0,1]") { + Geometry2D::get_closest_points_between_segments(Vector2(50, -25), Vector2(50, -10), Vector2(-50, 10), Vector2(-40, 10), c1, c2); + CHECK(c1.is_equal_approx(Vector2(50, -10))); + CHECK(c2.is_equal_approx(Vector2(-40, 10))); + } - Geometry2D::get_closest_points_between_segments(Vector2(-1, 1), Vector2(1, -1), Vector2(1, 1), Vector2(-1, -1), c1, c2); - CHECK(c1.is_equal_approx(Vector2(0, 0))); - CHECK(c2.is_equal_approx(Vector2(0, 0))); + SUBCASE("[Geometry2D] Parallel segments") { + Geometry2D::get_closest_points_between_segments(Vector2(2, 1), Vector2(4, 3), Vector2(2, 3), Vector2(4, 5), c1, c2); + CHECK(c1.is_equal_approx(Vector2(3, 2))); + CHECK(c2.is_equal_approx(Vector2(2, 3))); + } - Geometry2D::get_closest_points_between_segments(Vector2(-3, 4), Vector2(-3, 4), Vector2(-4, 3), Vector2(-2, 3), c1, c2); - CHECK_MESSAGE( - c1.is_equal_approx(Vector2(-3, 4)), - "1st line segment is only a point, this point should be the closest point to the 2nd line segment."); - CHECK_MESSAGE( - c2.is_equal_approx(Vector2(-3, 3)), - "1st line segment is only a point, this should not matter when determining the closest point on the 2nd line segment."); + SUBCASE("[Geometry2D] Closest point on second segment trajectory is within [0,1]") { + Geometry2D::get_closest_points_between_segments(Vector2(2, 4), Vector2(2, 3), Vector2(1, 1), Vector2(4, 4), c1, c2); + CHECK(c1.is_equal_approx(Vector2(2, 3))); + CHECK(c2.is_equal_approx(Vector2(2.5, 2.5))); + } - Geometry2D::get_closest_points_between_segments(Vector2(-4, 3), Vector2(-2, 3), Vector2(-3, 4), Vector2(-3, 4), c1, c2); - CHECK_MESSAGE( - c1.is_equal_approx(Vector2(-3, 3)), - "2nd line segment is only a point, this should not matter when determining the closest point on the 1st line segment."); - CHECK_MESSAGE( - c2.is_equal_approx(Vector2(-3, 4)), - "2nd line segment is only a point, this point should be the closest point to the 1st line segment."); + SUBCASE("[Geometry2D] Closest point on second segment trajectory is below [0,1]") { + Geometry2D::get_closest_points_between_segments(Vector2(-20, -20), Vector2(-10, -40), Vector2(10, 25), Vector2(25, 40), c1, c2); + CHECK(c1.is_equal_approx(Vector2(-20, -20))); + CHECK(c2.is_equal_approx(Vector2(10, 25))); + } - Geometry2D::get_closest_points_between_segments(Vector2(5, -4), Vector2(5, -4), Vector2(-2, 1), Vector2(-2, 1), c1, c2); - CHECK_MESSAGE( - c1.is_equal_approx(Vector2(5, -4)), - "Both line segments are only a point. On the 1st line segment, that point should be the closest point to the 2nd line segment."); - CHECK_MESSAGE( - c2.is_equal_approx(Vector2(-2, 1)), - "Both line segments are only a point. On the 2nd line segment, that point should be the closest point to the 1st line segment."); + SUBCASE("[Geometry2D] Second segment degenerates to a point") { + Geometry2D::get_closest_points_between_segments(Vector2(1, 2), Vector2(2, 1), Vector2(3, 3), Vector2(3, 3), c1, c2); + CHECK(c1.is_equal_approx(Vector2(1.5, 1.5))); + CHECK(c2.is_equal_approx(Vector2(3, 3))); + } + + SUBCASE("[Geometry2D] First segment degenerates to a point") { + Geometry2D::get_closest_points_between_segments(Vector2(1, 1), Vector2(1, 1), Vector2(2, 2), Vector2(4, 4), c1, c2); + CHECK(c1.is_equal_approx(Vector2(1, 1))); + CHECK(c2.is_equal_approx(Vector2(2, 2))); + } + // End Basis Path Testing suite + + SUBCASE("[Geometry2D] Segments are equal vectors") { + Geometry2D::get_closest_points_between_segments(Vector2(2, 2), Vector2(3, 3), Vector2(4, 4), Vector2(4, 5), c1, c2); + CHECK(c1.is_equal_approx(Vector2(3, 3))); + CHECK(c2.is_equal_approx(Vector2(4, 4))); + } + + SUBCASE("[Geometry2D] Standard case") { + Geometry2D::get_closest_points_between_segments(Vector2(0, 1), Vector2(-2, -1), Vector2(0, 0), Vector2(2, -2), c1, c2); + CHECK(c1.is_equal_approx(Vector2(-0.5, 0.5))); + CHECK(c2.is_equal_approx(Vector2(0, 0))); + } + + SUBCASE("[Geometry2D] Segments intersect") { + Geometry2D::get_closest_points_between_segments(Vector2(-1, 1), Vector2(1, -1), Vector2(1, 1), Vector2(-1, -1), c1, c2); + CHECK(c1.is_equal_approx(Vector2(0, 0))); + CHECK(c2.is_equal_approx(Vector2(0, 0))); + } } TEST_CASE("[Geometry2D] Make atlas") { @@ -685,12 +711,12 @@ TEST_CASE("[Geometry2D] Clip polyline with polygon") { r = Geometry2D::clip_polyline_with_polygon(l, p); REQUIRE_MESSAGE(r.size() == 2, "There should be 2 resulting clipped lines."); REQUIRE_MESSAGE(r[0].size() == 3, "The resulting clipped line should have 3 vertices."); - CHECK(r[0][0].is_equal_approx(Vector2(160, 320))); + CHECK(r[0][0].is_equal_approx(Vector2(121.412682, 225.038757))); CHECK(r[0][1].is_equal_approx(Vector2(122, 250))); - CHECK(r[0][2].is_equal_approx(Vector2(121.412682, 225.038757))); + CHECK(r[0][2].is_equal_approx(Vector2(160, 320))); REQUIRE_MESSAGE(r[1].size() == 2, "The resulting clipped line should have 2 vertices."); - CHECK(r[1][0].is_equal_approx(Vector2(53.07737, 116.143021))); - CHECK(r[1][1].is_equal_approx(Vector2(55, 70))); + CHECK(r[1][0].is_equal_approx(Vector2(55, 70))); + CHECK(r[1][1].is_equal_approx(Vector2(53.07737, 116.143021))); } } diff --git a/tests/core/math/test_math_funcs.h b/tests/core/math/test_math_funcs.h index 0a9d9c97d9..68540e4d61 100644 --- a/tests/core/math/test_math_funcs.h +++ b/tests/core/math/test_math_funcs.h @@ -381,6 +381,9 @@ TEST_CASE_TEMPLATE("[Math] remap", T, float, double) { CHECK(Math::remap((T)-100.0, (T)-100.0, (T)-200.0, (T)0.0, (T)-1000.0) == doctest::Approx((T)0.0)); CHECK(Math::remap((T)-200.0, (T)-100.0, (T)-200.0, (T)0.0, (T)-1000.0) == doctest::Approx((T)-1000.0)); CHECK(Math::remap((T)-250.0, (T)-100.0, (T)-200.0, (T)0.0, (T)-1000.0) == doctest::Approx((T)-1500.0)); + + // Note: undefined behavior can happen when `p_istart == p_istop`. We don't bother testing this as it will + // vary between hardware and compilers properly implementing IEEE 754. } TEST_CASE_TEMPLATE("[Math] angle_difference", T, float, double) { diff --git a/tests/core/math/test_transform_3d.h b/tests/core/math/test_transform_3d.h index 551b20fe74..fba0fcb280 100644 --- a/tests/core/math/test_transform_3d.h +++ b/tests/core/math/test_transform_3d.h @@ -107,6 +107,35 @@ TEST_CASE("[Transform3D] Finite number checks") { "Transform3D with two components infinite should not be finite."); } +TEST_CASE("[Transform3D] Rotate around global origin") { + // Start with the default orientation, but not centered on the origin. + // Rotating should rotate both our basis and the origin. + Transform3D transform = Transform3D(); + transform.origin = Vector3(0, 0, 1); + + Transform3D expected = Transform3D(); + expected.origin = Vector3(0, 0, -1); + expected.basis[0] = Vector3(-1, 0, 0); + expected.basis[2] = Vector3(0, 0, -1); + + const Transform3D rotated_transform = transform.rotated(Vector3(0, 1, 0), Math_PI); + CHECK_MESSAGE(rotated_transform.is_equal_approx(expected), "The rotated transform should have a new orientation and basis."); +} + +TEST_CASE("[Transform3D] Rotate in-place (local rotation)") { + // Start with the default orientation. + // Local rotation should not change the origin, only the basis. + Transform3D transform = Transform3D(); + transform.origin = Vector3(1, 2, 3); + + Transform3D expected = Transform3D(); + expected.origin = Vector3(1, 2, 3); + expected.basis[0] = Vector3(-1, 0, 0); + expected.basis[2] = Vector3(0, 0, -1); + + const Transform3D rotated_transform = Transform3D(transform.rotated_local(Vector3(0, 1, 0), Math_PI)); + CHECK_MESSAGE(rotated_transform.is_equal_approx(expected), "The rotated transform should have a new orientation but still be based on the same origin."); +} } // namespace TestTransform3D #endif // TEST_TRANSFORM_3D_H diff --git a/tests/core/object/test_class_db.h b/tests/core/object/test_class_db.h index 5f7de11c71..381d759e5b 100644 --- a/tests/core/object/test_class_db.h +++ b/tests/core/object/test_class_db.h @@ -141,7 +141,7 @@ struct NamesCache { StringName vector3_type = StaticCString::create("Vector3"); // Object not included as it must be checked for all derived classes - static constexpr int nullable_types_count = 17; + static constexpr int nullable_types_count = 18; StringName nullable_types[nullable_types_count] = { string_type, string_name_type, @@ -161,6 +161,7 @@ struct NamesCache { StaticCString::create(_STR(PackedVector2Array)), StaticCString::create(_STR(PackedVector3Array)), StaticCString::create(_STR(PackedColorArray)), + StaticCString::create(_STR(PackedVector4Array)), }; bool is_nullable_type(const StringName &p_type) const { @@ -194,12 +195,12 @@ struct Context { } bool has_type(const TypeReference &p_type_ref) const { - if (builtin_types.find(p_type_ref.name) >= 0) { + if (builtin_types.has(p_type_ref.name)) { return true; } if (p_type_ref.is_enum) { - if (enum_types.find(p_type_ref.name) >= 0) { + if (enum_types.has(p_type_ref.name)) { return true; } @@ -258,6 +259,7 @@ bool arg_default_value_is_assignable_to_type(const Context &p_context, const Var 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: return p_arg_type.name == Variant::get_type_name(p_val.get_type()); @@ -353,7 +355,7 @@ void validate_property(const Context &p_context, const ExposedClass &p_class, co const ArgumentData &idx_arg = getter->arguments.front()->get(); if (idx_arg.type.name != p_context.names_cache.int_type) { // If not an int, it can be an enum - TEST_COND(p_context.enum_types.find(idx_arg.type.name) < 0, + TEST_COND(!p_context.enum_types.has(idx_arg.type.name), "Invalid type '", idx_arg.type.name, "' for index argument of property getter: '", p_class.name, ".", String(p_prop.name), "'."); } } @@ -365,7 +367,7 @@ void validate_property(const Context &p_context, const ExposedClass &p_class, co if (idx_arg.type.name != p_context.names_cache.int_type) { // Assume the index parameter is an enum // If not an int, it can be an enum - TEST_COND(p_context.enum_types.find(idx_arg.type.name) < 0, + TEST_COND(!p_context.enum_types.has(idx_arg.type.name), "Invalid type '", idx_arg.type.name, "' for index argument of property setter: '", p_class.name, ".", String(p_prop.name), "'."); } } @@ -548,8 +550,6 @@ void add_exposed_classes(Context &r_context) { for (const MethodInfo &E : method_list) { const MethodInfo &method_info = E; - int argc = method_info.arguments.size(); - if (method_info.name.is_empty()) { continue; } @@ -611,8 +611,9 @@ void add_exposed_classes(Context &r_context) { method.return_type.name = Variant::get_type_name(return_info.type); } - for (int i = 0; i < argc; i++) { - PropertyInfo arg_info = method_info.arguments[i]; + int i = 0; + for (List<PropertyInfo>::ConstIterator itr = method_info.arguments.begin(); itr != method_info.arguments.end(); ++itr, ++i) { + const PropertyInfo &arg_info = *itr; String orig_arg_name = arg_info.name; @@ -684,10 +685,9 @@ void add_exposed_classes(Context &r_context) { TEST_FAIL_COND(!String(signal.name).is_valid_identifier(), "Signal name is not a valid identifier: '", exposed_class.name, ".", signal.name, "'."); - int argc = method_info.arguments.size(); - - for (int i = 0; i < argc; i++) { - PropertyInfo arg_info = method_info.arguments[i]; + int i = 0; + for (List<PropertyInfo>::ConstIterator itr = method_info.arguments.begin(); itr != method_info.arguments.end(); ++itr, ++i) { + const PropertyInfo &arg_info = *itr; String orig_arg_name = arg_info.name; @@ -736,7 +736,7 @@ void add_exposed_classes(Context &r_context) { for (const StringName &E : K.value.constants) { const StringName &constant_name = E; - TEST_FAIL_COND(String(constant_name).find("::") != -1, + TEST_FAIL_COND(String(constant_name).contains("::"), "Enum constant contains '::', check bindings to remove the scope: '", String(class_name), ".", String(enum_.name), ".", String(constant_name), "'."); int64_t *value = class_info->constant_map.getptr(constant_name); @@ -758,7 +758,7 @@ void add_exposed_classes(Context &r_context) { for (const String &E : constants) { const String &constant_name = E; - TEST_FAIL_COND(constant_name.find("::") != -1, + TEST_FAIL_COND(constant_name.contains("::"), "Constant contains '::', check bindings to remove the scope: '", String(class_name), ".", constant_name, "'."); int64_t *value = class_info->constant_map.getptr(StringName(E)); diff --git a/tests/core/object/test_object.h b/tests/core/object/test_object.h index 3a3013a102..57bc65328a 100644 --- a/tests/core/object/test_object.h +++ b/tests/core/object/test_object.h @@ -31,7 +31,6 @@ #ifndef TEST_OBJECT_H #define TEST_OBJECT_H -#include "core/core_string_names.h" #include "core/object/class_db.h" #include "core/object/object.h" #include "core/object/script_language.h" @@ -142,7 +141,7 @@ TEST_CASE("[Object] Core getters") { inheritance_list.size() == 1, "The inheritance list should consist of Object only"); CHECK_MESSAGE( - inheritance_list[0] == "Object", + inheritance_list.front()->get() == "Object", "The inheritance list should consist of Object only"); } @@ -251,7 +250,7 @@ TEST_CASE("[Object] Script property setter") { Variant script; bool valid = false; - object.set(CoreStringNames::get_singleton()->_script, script, &valid); + object.set(CoreStringName(script), script, &valid); CHECK(valid); CHECK_MESSAGE( object.get_script() == script, @@ -264,7 +263,7 @@ TEST_CASE("[Object] Script property getter") { object.set_script(script); bool valid = false; - const Variant &actual_value = object.get(CoreStringNames::get_singleton()->_script, &valid); + const Variant &actual_value = object.get(CoreStringName(script), &valid); CHECK(valid); CHECK_MESSAGE( actual_value == script, diff --git a/tests/core/os/test_os.h b/tests/core/os/test_os.h index 63f8b18238..6ee0ff82e7 100644 --- a/tests/core/os/test_os.h +++ b/tests/core/os/test_os.h @@ -79,8 +79,8 @@ TEST_CASE("[OS] Non-UTF-8 environment variables") { TEST_CASE("[OS] Command line arguments") { List<String> arguments = OS::get_singleton()->get_cmdline_args(); bool found = false; - for (int i = 0; i < arguments.size(); i++) { - if (arguments[i] == "--test") { + for (const String &arg : arguments) { + if (arg == "--test") { found = true; break; } diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h index 64f03e5879..cf57183a02 100644 --- a/tests/core/string/test_string.h +++ b/tests/core/string/test_string.h @@ -297,6 +297,19 @@ TEST_CASE("[String] Contains") { CHECK(!s.contains(String("\\char_test.tscn"))); } +TEST_CASE("[String] Contains case insensitive") { + String s = "C:\\Godot\\project\\string_test.tscn"; + CHECK(s.containsn("Godot")); + CHECK(s.containsn("godot")); + CHECK(s.containsn(String("Project\\string_test"))); + CHECK(s.containsn(String("\\string_Test.tscn"))); + + CHECK(!s.containsn("Godoh")); + CHECK(!s.containsn("godoh")); + CHECK(!s.containsn(String("project\\string test"))); + CHECK(!s.containsn(String("\\char_test.tscn"))); +} + TEST_CASE("[String] Test chr") { CHECK(String::chr('H') == "H"); CHECK(String::chr(0x3012)[0] == 0x3012); @@ -360,18 +373,37 @@ TEST_CASE("[String] Substr") { TEST_CASE("[String] Find") { String s = "Pretty Woman Woman"; - CHECK(s.find("tty") == 3); - CHECK(s.find("Wo", 9) == 13); - CHECK(s.find("Revenge of the Monster Truck") == -1); - CHECK(s.rfind("man") == 15); -} - -TEST_CASE("[String] Find no case") { + MULTICHECK_STRING_EQ(s, find, "tty", 3); + MULTICHECK_STRING_EQ(s, find, "Revenge of the Monster Truck", -1); + MULTICHECK_STRING_INT_EQ(s, find, "Wo", 9, 13); + MULTICHECK_STRING_EQ(s, find, "", -1); + MULTICHECK_STRING_EQ(s, find, "Pretty Woman Woman", 0); + MULTICHECK_STRING_EQ(s, find, "WOMAN", -1); + MULTICHECK_STRING_INT_EQ(s, find, "", 9, -1); + + MULTICHECK_STRING_EQ(s, rfind, "", -1); + MULTICHECK_STRING_EQ(s, rfind, "foo", -1); + MULTICHECK_STRING_EQ(s, rfind, "Pretty Woman Woman", 0); + MULTICHECK_STRING_EQ(s, rfind, "man", 15); + MULTICHECK_STRING_EQ(s, rfind, "WOMAN", -1); + MULTICHECK_STRING_INT_EQ(s, rfind, "", 15, -1); +} + +TEST_CASE("[String] Find case insensitive") { String s = "Pretty Whale Whale"; - CHECK(s.findn("WHA") == 7); - CHECK(s.findn("WHA", 9) == 13); - CHECK(s.findn("Revenge of the Monster SawFish") == -1); - CHECK(s.rfindn("WHA") == 13); + MULTICHECK_STRING_EQ(s, findn, "WHA", 7); + MULTICHECK_STRING_INT_EQ(s, findn, "WHA", 9, 13); + MULTICHECK_STRING_EQ(s, findn, "Revenge of the Monster SawFish", -1); + MULTICHECK_STRING_EQ(s, findn, "", -1); + MULTICHECK_STRING_EQ(s, findn, "wha", 7); + MULTICHECK_STRING_EQ(s, findn, "Wha", 7); + MULTICHECK_STRING_INT_EQ(s, findn, "", 3, -1); + + MULTICHECK_STRING_EQ(s, rfindn, "WHA", 13); + MULTICHECK_STRING_EQ(s, rfindn, "", -1); + MULTICHECK_STRING_EQ(s, rfindn, "wha", 13); + MULTICHECK_STRING_EQ(s, rfindn, "Wha", 13); + MULTICHECK_STRING_INT_EQ(s, rfindn, "", 13, -1); } TEST_CASE("[String] Find MK") { @@ -392,11 +424,9 @@ TEST_CASE("[String] Find MK") { TEST_CASE("[String] Find and replace") { String s = "Happy Birthday, Anna!"; - s = s.replace("Birthday", "Halloween"); - CHECK(s == "Happy Halloween, Anna!"); - - s = s.replace_first("H", "W"); - CHECK(s == "Wappy Halloween, Anna!"); + MULTICHECK_STRING_STRING_EQ(s, replace, "Birthday", "Halloween", "Happy Halloween, Anna!"); + MULTICHECK_STRING_STRING_EQ(s, replace_first, "y", "Y", "HappY Birthday, Anna!"); + MULTICHECK_STRING_STRING_EQ(s, replacen, "Y", "Y", "HappY BirthdaY, Anna!"); } TEST_CASE("[String] Insertion") { @@ -557,51 +587,76 @@ TEST_CASE("[String] String to float") { TEST_CASE("[String] Slicing") { String s = "Mars,Jupiter,Saturn,Uranus"; - const char *slices[4] = { "Mars", "Jupiter", "Saturn", "Uranus" }; - for (int i = 0; i < s.get_slice_count(","); i++) { - CHECK(s.get_slice(",", i) == slices[i]); - } + MULTICHECK_GET_SLICE(s, ",", slices); +} + +TEST_CASE("[String] Begins with") { + // Test cases for true: + MULTICHECK_STRING_EQ(String("res://foobar"), begins_with, "res://", true); + MULTICHECK_STRING_EQ(String("abc"), begins_with, "abc", true); + MULTICHECK_STRING_EQ(String("abc"), begins_with, "", true); + MULTICHECK_STRING_EQ(String(""), begins_with, "", true); + + // Test cases for false: + MULTICHECK_STRING_EQ(String("res"), begins_with, "res://", false); + MULTICHECK_STRING_EQ(String("abcdef"), begins_with, "foo", false); + MULTICHECK_STRING_EQ(String("abc"), begins_with, "ax", false); + MULTICHECK_STRING_EQ(String(""), begins_with, "abc", false); + + // Test "const char *" version also with nullptr. + String s("foo"); + bool state = s.begins_with(nullptr) == false; + CHECK_MESSAGE(state, "nullptr check failed"); + + String empty(""); + state = empty.begins_with(nullptr) == false; + CHECK_MESSAGE(state, "nullptr check with empty string failed"); +} + +TEST_CASE("[String] Ends with") { + // Test cases for true: + MULTICHECK_STRING_EQ(String("res://foobar"), ends_with, "foobar", true); + MULTICHECK_STRING_EQ(String("abc"), ends_with, "abc", true); + MULTICHECK_STRING_EQ(String("abc"), ends_with, "", true); + MULTICHECK_STRING_EQ(String(""), ends_with, "", true); + + // Test cases for false: + MULTICHECK_STRING_EQ(String("res"), ends_with, "res://", false); + MULTICHECK_STRING_EQ(String("abcdef"), ends_with, "foo", false); + MULTICHECK_STRING_EQ(String("abc"), ends_with, "ax", false); + MULTICHECK_STRING_EQ(String(""), ends_with, "abc", false); + + // Test "const char *" version also with nullptr. + String s("foo"); + bool state = s.ends_with(nullptr) == false; + CHECK_MESSAGE(state, "nullptr check failed"); + + String empty(""); + state = empty.ends_with(nullptr) == false; + CHECK_MESSAGE(state, "nullptr check with empty string failed"); } TEST_CASE("[String] Splitting") { String s = "Mars,Jupiter,Saturn,Uranus"; - Vector<String> l; - const char *slices_l[3] = { "Mars", "Jupiter", "Saturn,Uranus" }; - const char *slices_r[3] = { "Mars,Jupiter", "Saturn", "Uranus" }; - const char *slices_3[4] = { "t", "e", "s", "t" }; + MULTICHECK_SPLIT(s, split, ",", true, 2, slices_l, 3); - l = s.split(",", true, 2); - CHECK(l.size() == 3); - for (int i = 0; i < l.size(); i++) { - CHECK(l[i] == slices_l[i]); - } - - l = s.rsplit(",", true, 2); - CHECK(l.size() == 3); - for (int i = 0; i < l.size(); i++) { - CHECK(l[i] == slices_r[i]); - } + const char *slices_r[3] = { "Mars,Jupiter", "Saturn", "Uranus" }; + MULTICHECK_SPLIT(s, rsplit, ",", true, 2, slices_r, 3); s = "test"; - l = s.split(); - CHECK(l.size() == 4); - for (int i = 0; i < l.size(); i++) { - CHECK(l[i] == slices_3[i]); - } + const char *slices_3[4] = { "t", "e", "s", "t" }; + MULTICHECK_SPLIT(s, split, "", true, 0, slices_3, 4); s = ""; - l = s.split(); - CHECK(l.size() == 1); - CHECK(l[0] == ""); - - l = s.split("", false); - CHECK(l.size() == 0); + 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" }; - l = s.split_spaces(); + Vector<String> l = s.split_spaces(); for (int i = 0; i < l.size(); i++) { CHECK(l[i] == slices_s[i]); } @@ -644,69 +699,6 @@ TEST_CASE("[String] Splitting") { } } -struct test_27_data { - char const *data; - char const *part; - bool expected; -}; - -TEST_CASE("[String] Begins with") { - test_27_data tc[] = { - // Test cases for true: - { "res://foobar", "res://", true }, - { "abc", "abc", true }, - { "abc", "", true }, - { "", "", true }, - // Test cases for false: - { "res", "res://", false }, - { "abcdef", "foo", false }, - { "abc", "ax", false }, - { "", "abc", false } - }; - size_t count = sizeof(tc) / sizeof(tc[0]); - bool state = true; - for (size_t i = 0; i < count; ++i) { - String s = tc[i].data; - state = s.begins_with(tc[i].part) == tc[i].expected; - CHECK_MESSAGE(state, "first check failed at: ", i); - - String sb = tc[i].part; - state = s.begins_with(sb) == tc[i].expected; - CHECK_MESSAGE(state, "second check failed at: ", i); - } - - // Test "const char *" version also with nullptr. - String s("foo"); - state = s.begins_with(nullptr) == false; - CHECK_MESSAGE(state, "nullptr check failed"); - - String empty(""); - state = empty.begins_with(nullptr) == false; - CHECK_MESSAGE(state, "nullptr check with empty string failed"); -} - -TEST_CASE("[String] Ends with") { - test_27_data tc[] = { - // test cases for true: - { "res://foobar", "foobar", true }, - { "abc", "abc", true }, - { "abc", "", true }, - { "", "", true }, - // test cases for false: - { "res", "res://", false }, - { "", "abc", false }, - { "abcdef", "foo", false }, - { "abc", "xc", false } - }; - size_t count = sizeof(tc) / sizeof(tc[0]); - for (size_t i = 0; i < count; ++i) { - String s = tc[i].data; - String sb = tc[i].part; - bool state = s.ends_with(sb) == tc[i].expected; - CHECK_MESSAGE(state, "check failed at: ", i); - } -} - TEST_CASE("[String] format") { const String value_format = "red=\"$red\" green=\"$green\" blue=\"$blue\" alpha=\"$alpha\""; @@ -1498,39 +1490,62 @@ TEST_CASE("[String] Cyrillic to_lower()") { } TEST_CASE("[String] Count and countn functionality") { -#define COUNT_TEST(x) \ - { \ - bool success = x; \ - state = state && success; \ - } + String s = String(""); + MULTICHECK_STRING_EQ(s, count, "Test", 0); - bool state = true; + s = "Test"; + MULTICHECK_STRING_EQ(s, count, "", 0); - COUNT_TEST(String("").count("Test") == 0); - COUNT_TEST(String("Test").count("") == 0); - COUNT_TEST(String("Test").count("test") == 0); - COUNT_TEST(String("Test").count("TEST") == 0); - COUNT_TEST(String("TEST").count("TEST") == 1); - COUNT_TEST(String("Test").count("Test") == 1); - COUNT_TEST(String("aTest").count("Test") == 1); - COUNT_TEST(String("Testa").count("Test") == 1); - COUNT_TEST(String("TestTestTest").count("Test") == 3); - COUNT_TEST(String("TestTestTest").count("TestTest") == 1); - COUNT_TEST(String("TestGodotTestGodotTestGodot").count("Test") == 3); - - COUNT_TEST(String("TestTestTestTest").count("Test", 4, 8) == 1); - COUNT_TEST(String("TestTestTestTest").count("Test", 4, 12) == 2); - COUNT_TEST(String("TestTestTestTest").count("Test", 4, 16) == 3); - COUNT_TEST(String("TestTestTestTest").count("Test", 4) == 3); - - COUNT_TEST(String("Test").countn("test") == 1); - COUNT_TEST(String("Test").countn("TEST") == 1); - COUNT_TEST(String("testTest-Testatest").countn("tEst") == 4); - COUNT_TEST(String("testTest-TeStatest").countn("tEsT", 4, 16) == 2); + s = "Test"; + MULTICHECK_STRING_EQ(s, count, "test", 0); - CHECK(state); + s = "Test"; + MULTICHECK_STRING_EQ(s, count, "TEST", 0); + + s = "TEST"; + MULTICHECK_STRING_EQ(s, count, "TEST", 1); + + s = "Test"; + MULTICHECK_STRING_EQ(s, count, "Test", 1); + + s = "aTest"; + MULTICHECK_STRING_EQ(s, count, "Test", 1); + + s = "Testa"; + MULTICHECK_STRING_EQ(s, count, "Test", 1); + + s = "TestTestTest"; + MULTICHECK_STRING_EQ(s, count, "Test", 3); + + s = "TestTestTest"; + MULTICHECK_STRING_EQ(s, count, "TestTest", 1); + + s = "TestGodotTestGodotTestGodot"; + MULTICHECK_STRING_EQ(s, count, "Test", 3); + + s = "TestTestTestTest"; + MULTICHECK_STRING_INT_INT_EQ(s, count, "Test", 4, 8, 1); + + s = "TestTestTestTest"; + MULTICHECK_STRING_INT_INT_EQ(s, count, "Test", 4, 12, 2); + + s = "TestTestTestTest"; + MULTICHECK_STRING_INT_INT_EQ(s, count, "Test", 4, 16, 3); + + s = "TestTestTestTest"; + MULTICHECK_STRING_INT_EQ(s, count, "Test", 4, 3); + + s = "Test"; + MULTICHECK_STRING_EQ(s, countn, "test", 1); + + s = "Test"; + MULTICHECK_STRING_EQ(s, countn, "TEST", 1); + + s = "testTest-Testatest"; + MULTICHECK_STRING_EQ(s, countn, "tEst", 4); -#undef COUNT_TEST + s = "testTest-TeStatest"; + MULTICHECK_STRING_INT_INT_EQ(s, countn, "tEsT", 4, 16, 2); } TEST_CASE("[String] Bigrams") { @@ -1703,9 +1718,19 @@ TEST_CASE("[String] Strip edges") { TEST_CASE("[String] Trim") { String s = "aaaTestbbb"; - CHECK(s.trim_prefix("aaa") == "Testbbb"); - CHECK(s.trim_suffix("bbb") == "aaaTest"); - CHECK(s.trim_suffix("Test") == s); + MULTICHECK_STRING_EQ(s, trim_prefix, "aaa", "Testbbb"); + MULTICHECK_STRING_EQ(s, trim_prefix, "Test", s); + MULTICHECK_STRING_EQ(s, trim_prefix, "", s); + MULTICHECK_STRING_EQ(s, trim_prefix, "aaaTestbbb", ""); + MULTICHECK_STRING_EQ(s, trim_prefix, "bbb", s); + MULTICHECK_STRING_EQ(s, trim_prefix, "AAA", s); + + MULTICHECK_STRING_EQ(s, trim_suffix, "bbb", "aaaTest"); + MULTICHECK_STRING_EQ(s, trim_suffix, "Test", s); + MULTICHECK_STRING_EQ(s, trim_suffix, "", s); + MULTICHECK_STRING_EQ(s, trim_suffix, "aaaTestbbb", ""); + MULTICHECK_STRING_EQ(s, trim_suffix, "aaa", s); + MULTICHECK_STRING_EQ(s, trim_suffix, "BBB", s); } TEST_CASE("[String] Right/Left") { diff --git a/tests/core/templates/test_command_queue.h b/tests/core/templates/test_command_queue.h index e94c108694..d2957b5c40 100644 --- a/tests/core/templates/test_command_queue.h +++ b/tests/core/templates/test_command_queue.h @@ -33,6 +33,7 @@ #include "core/config/project_settings.h" #include "core/math/random_number_generator.h" +#include "core/object/worker_thread_pool.h" #include "core/os/os.h" #include "core/os/thread.h" #include "core/templates/command_queue_mt.h" @@ -100,7 +101,7 @@ public: ThreadWork reader_threadwork; ThreadWork writer_threadwork; - CommandQueueMT command_queue = CommandQueueMT(true); + CommandQueueMT command_queue; enum TestMsgType { TEST_MSG_FUNC1_TRANSFORM, @@ -119,6 +120,7 @@ public: bool exit_threads = false; Thread reader_thread; + WorkerThreadPool::TaskID reader_task_id = WorkerThreadPool::INVALID_TASK_ID; Thread writer_thread; int func1_count = 0; @@ -148,11 +150,16 @@ public: void reader_thread_loop() { reader_threadwork.thread_wait_for_work(); while (!exit_threads) { - if (message_count_to_read < 0) { + if (reader_task_id == WorkerThreadPool::INVALID_TASK_ID) { command_queue.flush_all(); - } - for (int i = 0; i < message_count_to_read; i++) { - command_queue.wait_and_flush(); + } else { + if (message_count_to_read < 0) { + command_queue.flush_all(); + } + for (int i = 0; i < message_count_to_read; i++) { + WorkerThreadPool::get_singleton()->yield(); + command_queue.wait_and_flush(); + } } message_count_to_read = 0; @@ -216,8 +223,13 @@ public: sts->writer_thread_loop(); } - void init_threads() { - reader_thread.start(&SharedThreadState::static_reader_thread_loop, this); + void init_threads(bool p_use_thread_pool_sync = false) { + if (p_use_thread_pool_sync) { + reader_task_id = WorkerThreadPool::get_singleton()->add_native_task(&SharedThreadState::static_reader_thread_loop, this, true); + command_queue.set_pump_task_id(reader_task_id); + } else { + reader_thread.start(&SharedThreadState::static_reader_thread_loop, this); + } writer_thread.start(&SharedThreadState::static_writer_thread_loop, this); } void destroy_threads() { @@ -225,16 +237,20 @@ public: reader_threadwork.main_start_work(); writer_threadwork.main_start_work(); - reader_thread.wait_to_finish(); + if (reader_task_id != WorkerThreadPool::INVALID_TASK_ID) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(reader_task_id); + } else { + reader_thread.wait_to_finish(); + } writer_thread.wait_to_finish(); } }; -TEST_CASE("[CommandQueue] Test Queue Basics") { +static void test_command_queue_basic(bool p_use_thread_pool_sync) { const char *COMMAND_QUEUE_SETTING = "memory/limits/command_queue/multithreading_queue_size_kb"; ProjectSettings::get_singleton()->set_setting(COMMAND_QUEUE_SETTING, 1); SharedThreadState sts; - sts.init_threads(); + sts.init_threads(p_use_thread_pool_sync); sts.add_msg_to_write(SharedThreadState::TEST_MSG_FUNC1_TRANSFORM); sts.writer_threadwork.main_start_work(); @@ -272,6 +288,14 @@ TEST_CASE("[CommandQueue] Test Queue Basics") { ProjectSettings::get_singleton()->property_get_revert(COMMAND_QUEUE_SETTING)); } +TEST_CASE("[CommandQueue] Test Queue Basics") { + test_command_queue_basic(false); +} + +TEST_CASE("[CommandQueue] Test Queue Basics with WorkerThreadPool sync.") { + test_command_queue_basic(true); +} + TEST_CASE("[CommandQueue] Test Queue Wrapping to same spot.") { const char *COMMAND_QUEUE_SETTING = "memory/limits/command_queue/multithreading_queue_size_kb"; ProjectSettings::get_singleton()->set_setting(COMMAND_QUEUE_SETTING, 1); diff --git a/tests/core/templates/test_local_vector.h b/tests/core/templates/test_local_vector.h index 2873a9a028..c9544c625b 100644 --- a/tests/core/templates/test_local_vector.h +++ b/tests/core/templates/test_local_vector.h @@ -63,7 +63,7 @@ TEST_CASE("[LocalVector] Push Back.") { CHECK(vector[4] == 4); } -TEST_CASE("[LocalVector] Find.") { +TEST_CASE("[LocalVector] Find, has.") { LocalVector<int> vector; vector.push_back(3); vector.push_back(1); @@ -85,6 +85,15 @@ TEST_CASE("[LocalVector] Find.") { CHECK(vector.find(-1) == -1); CHECK(vector.find(5) == -1); + + CHECK(vector.has(0)); + CHECK(vector.has(1)); + CHECK(vector.has(2)); + CHECK(vector.has(3)); + CHECK(vector.has(4)); + + CHECK(!vector.has(-1)); + CHECK(!vector.has(5)); } TEST_CASE("[LocalVector] Remove.") { diff --git a/tests/core/threads/test_worker_thread_pool.h b/tests/core/threads/test_worker_thread_pool.h index e9a762b57b..0a0291d11b 100644 --- a/tests/core/threads/test_worker_thread_pool.h +++ b/tests/core/threads/test_worker_thread_pool.h @@ -38,6 +38,7 @@ namespace TestWorkerThreadPool { static LocalVector<SafeNumeric<int>> counter; +static SafeFlag exit; static void static_test(void *p_arg) { counter[(uint64_t)p_arg].increment(); @@ -106,6 +107,72 @@ TEST_CASE("[WorkerThreadPool] Process elements using group tasks") { } } +static void static_test_daemon(void *p_arg) { + while (!exit.is_set()) { + counter[0].add(1); + WorkerThreadPool::get_singleton()->yield(); + } +} + +static void static_busy_task(void *p_arg) { + while (!exit.is_set()) { + OS::get_singleton()->delay_usec(1); + } +} + +static void static_legit_task(void *p_arg) { + *((bool *)p_arg) = counter[0].get() > 0; + counter[1].add(1); +} + +TEST_CASE("[WorkerThreadPool] Run a yielding daemon as the only hope for other tasks to run") { + exit.clear(); + counter.clear(); + counter.resize(2); + + WorkerThreadPool::TaskID daemon_task_id = WorkerThreadPool::get_singleton()->add_native_task(static_test_daemon, nullptr, true); + + int num_threads = WorkerThreadPool::get_singleton()->get_thread_count(); + + // Keep all the other threads busy. + LocalVector<WorkerThreadPool::TaskID> task_ids; + for (int i = 0; i < num_threads - 1; i++) { + task_ids.push_back(WorkerThreadPool::get_singleton()->add_native_task(static_busy_task, nullptr, true)); + } + + LocalVector<WorkerThreadPool::TaskID> legit_task_ids; + LocalVector<bool> legit_task_needed_yield; + int legit_tasks_count = num_threads * 4; + legit_task_needed_yield.resize(legit_tasks_count); + for (int i = 0; i < legit_tasks_count; i++) { + legit_task_needed_yield[i] = false; + task_ids.push_back(WorkerThreadPool::get_singleton()->add_native_task(static_legit_task, &legit_task_needed_yield[i], i >= legit_tasks_count / 2)); + } + + while (counter[1].get() != legit_tasks_count) { + OS::get_singleton()->delay_usec(1); + } + + exit.set(); + for (uint32_t i = 0; i < task_ids.size(); i++) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(task_ids[i]); + } + WorkerThreadPool::get_singleton()->notify_yield_over(daemon_task_id); + WorkerThreadPool::get_singleton()->wait_for_task_completion(daemon_task_id); + + CHECK_MESSAGE(counter[0].get() > 0, "Daemon task should have looped at least once."); + CHECK_MESSAGE(counter[1].get() == legit_tasks_count, "All legit tasks should have been able to run."); + + bool all_needed_yield = true; + for (int i = 0; i < legit_tasks_count; i++) { + if (!legit_task_needed_yield[i]) { + all_needed_yield = false; + break; + } + } + CHECK_MESSAGE(all_needed_yield, "All legit tasks should have needed the daemon yielding to run."); +} + } // namespace TestWorkerThreadPool #endif // TEST_WORKER_THREAD_POOL_H diff --git a/tests/core/variant/test_array.h b/tests/core/variant/test_array.h index 287345e831..c54854e4d7 100644 --- a/tests/core/variant/test_array.h +++ b/tests/core/variant/test_array.h @@ -555,6 +555,8 @@ TEST_CASE("[Array] Iteration") { idx++; } + CHECK_EQ(idx, a1.size()); + idx = 0; for (const Variant &E : (const Array &)a1) { @@ -562,6 +564,8 @@ TEST_CASE("[Array] Iteration") { idx++; } + CHECK_EQ(idx, a1.size()); + a1.clear(); } diff --git a/tests/core/variant/test_dictionary.h b/tests/core/variant/test_dictionary.h index 5bc56075da..aba20972d9 100644 --- a/tests/core/variant/test_dictionary.h +++ b/tests/core/variant/test_dictionary.h @@ -105,7 +105,7 @@ TEST_CASE("[Dictionary] get_key_lists()") { map[1] = 3; map.get_key_list(ptr); CHECK(keys.size() == 1); - CHECK(int(keys[0]) == 1); + CHECK(int(keys.front()->get()) == 1); map[2] = 4; map.get_key_list(ptr); CHECK(keys.size() == 3); diff --git a/tests/core/variant/test_variant.h b/tests/core/variant/test_variant.h index 54ca06c6c4..be615975f8 100644 --- a/tests/core/variant/test_variant.h +++ b/tests/core/variant/test_variant.h @@ -2034,6 +2034,10 @@ TEST_CASE("[Variant] Identity comparison") { CHECK(packed_color_array.identity_compare(packed_color_array)); CHECK_FALSE(packed_color_array.identity_compare(PackedColorArray())); + Variant packed_vector4_array = PackedVector4Array(); + CHECK(packed_vector4_array.identity_compare(packed_vector4_array)); + CHECK_FALSE(packed_vector4_array.identity_compare(PackedVector4Array())); + Variant packed_float32_array = PackedFloat32Array(); CHECK(packed_float32_array.identity_compare(packed_float32_array)); CHECK_FALSE(packed_float32_array.identity_compare(PackedFloat32Array())); diff --git a/tests/create_test.py b/tests/create_test.py index deb53aca20..deb53aca20 100644..100755 --- a/tests/create_test.py +++ b/tests/create_test.py diff --git a/tests/display_server_mock.h b/tests/display_server_mock.h index 8d8a678e20..e4946995a7 100644 --- a/tests/display_server_mock.h +++ b/tests/display_server_mock.h @@ -47,13 +47,16 @@ private: Callable event_callback; Callable input_event_callback; + String clipboard_text; + String primary_clipboard_text; + static Vector<String> get_rendering_drivers_func() { Vector<String> drivers; drivers.push_back("dummy"); 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, 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, Error &r_error) { r_error = OK; RasterizerDummy::make_current(); return memnew(DisplayServerMock()); @@ -86,7 +89,7 @@ private: } void _send_window_event(WindowEvent p_event) { - if (!event_callback.is_null()) { + if (event_callback.is_valid()) { Variant event = int(p_event); event_callback.call(event); } @@ -97,6 +100,8 @@ public: switch (p_feature) { case FEATURE_MOUSE: case FEATURE_CURSOR_SHAPE: + case FEATURE_CLIPBOARD: + case FEATURE_CLIPBOARD_PRIMARY: return true; default: { } @@ -131,6 +136,11 @@ public: virtual Point2i mouse_get_position() const override { return mouse_position; } + virtual void clipboard_set(const String &p_text) override { clipboard_text = p_text; } + virtual String clipboard_get() const override { return clipboard_text; } + virtual void clipboard_set_primary(const String &p_text) override { primary_clipboard_text = p_text; } + virtual String clipboard_get_primary() const override { return primary_clipboard_text; } + virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override { return Size2i(1920, 1080); } diff --git a/tests/python_build/fixtures/gles3/vertex_fragment_expected_full.glsl b/tests/python_build/fixtures/gles3/vertex_fragment_expected_full.glsl index 58ddd08cdd..8ad5a23eb5 100644 --- a/tests/python_build/fixtures/gles3/vertex_fragment_expected_full.glsl +++ b/tests/python_build/fixtures/gles3/vertex_fragment_expected_full.glsl @@ -48,4 +48,3 @@ protected: }; #endif - diff --git a/tests/python_build/test_gles3_builder.py b/tests/python_build/test_gles3_builder.py index 6f16139eb9..b34d33bde7 100644 --- a/tests/python_build/test_gles3_builder.py +++ b/tests/python_build/test_gles3_builder.py @@ -2,7 +2,7 @@ import json import pytest -from gles3_builders import build_gles3_header, GLES3HeaderStruct +from gles3_builders import GLES3HeaderStruct, build_gles3_header @pytest.mark.parametrize( diff --git a/tests/python_build/test_glsl_builder.py b/tests/python_build/test_glsl_builder.py index 348ef8441c..9f548855ff 100644 --- a/tests/python_build/test_glsl_builder.py +++ b/tests/python_build/test_glsl_builder.py @@ -2,7 +2,7 @@ import json import pytest -from glsl_builders import build_raw_header, RAWHeaderStruct, build_rd_header, RDHeaderStruct +from glsl_builders import RAWHeaderStruct, RDHeaderStruct, build_raw_header, build_rd_header @pytest.mark.parametrize( diff --git a/tests/scene/test_animation.h b/tests/scene/test_animation.h index 89bf296815..6c89592e0d 100644 --- a/tests/scene/test_animation.h +++ b/tests/scene/test_animation.h @@ -41,7 +41,7 @@ TEST_CASE("[Animation] Empty animation getters") { const Ref<Animation> animation = memnew(Animation); CHECK(animation->get_length() == doctest::Approx(real_t(1.0))); - CHECK(animation->get_step() == doctest::Approx(real_t(0.1))); + CHECK(animation->get_step() == doctest::Approx(real_t(1.0 / 30))); } TEST_CASE("[Animation] Create value track") { diff --git a/tests/scene/test_code_edit.h b/tests/scene/test_code_edit.h index b0a46b8107..c02830b6df 100644 --- a/tests/scene/test_code_edit.h +++ b/tests/scene/test_code_edit.h @@ -36,6 +36,15 @@ #include "tests/test_macros.h" namespace TestCodeEdit { +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][CodeEdit] line gutters") { CodeEdit *code_edit = memnew(CodeEdit); @@ -67,10 +76,7 @@ TEST_CASE("[SceneTree][CodeEdit] line gutters") { ERR_PRINT_ON; - Array arg1; - arg1.push_back(0); - Array args; - args.push_back(arg1); + Array args = build_array(build_array(0)); code_edit->set_line_as_breakpoint(0, true); CHECK(code_edit->is_line_breakpointed(0)); @@ -86,10 +92,7 @@ TEST_CASE("[SceneTree][CodeEdit] line gutters") { code_edit->clear_breakpointed_lines(); SIGNAL_CHECK_FALSE("breakpoint_toggled"); - Array arg1; - arg1.push_back(0); - Array args; - args.push_back(arg1); + Array args = build_array(build_array(0)); code_edit->set_line_as_breakpoint(0, true); CHECK(code_edit->is_line_breakpointed(0)); @@ -101,10 +104,7 @@ TEST_CASE("[SceneTree][CodeEdit] line gutters") { } SUBCASE("[CodeEdit] breakpoints and set text") { - Array arg1; - arg1.push_back(0); - Array args; - args.push_back(arg1); + Array args = build_array(build_array(0)); code_edit->set_text("test\nline"); code_edit->set_line_as_breakpoint(0, true); @@ -121,7 +121,7 @@ TEST_CASE("[SceneTree][CodeEdit] line gutters") { code_edit->clear_breakpointed_lines(); SIGNAL_DISCARD("breakpoint_toggled") - ((Array)args[0])[0] = 1; + args = build_array(build_array(1)); code_edit->set_text("test\nline"); code_edit->set_line_as_breakpoint(1, true); CHECK(code_edit->is_line_breakpointed(1)); @@ -137,10 +137,7 @@ TEST_CASE("[SceneTree][CodeEdit] line gutters") { } SUBCASE("[CodeEdit] breakpoints and clear") { - Array arg1; - arg1.push_back(0); - Array args; - args.push_back(arg1); + Array args = build_array(build_array(0)); code_edit->set_text("test\nline"); code_edit->set_line_as_breakpoint(0, true); @@ -157,7 +154,7 @@ TEST_CASE("[SceneTree][CodeEdit] line gutters") { code_edit->clear_breakpointed_lines(); SIGNAL_DISCARD("breakpoint_toggled") - ((Array)args[0])[0] = 1; + args = build_array(build_array(1)); code_edit->set_text("test\nline"); code_edit->set_line_as_breakpoint(1, true); CHECK(code_edit->is_line_breakpointed(1)); @@ -173,21 +170,15 @@ TEST_CASE("[SceneTree][CodeEdit] line gutters") { } SUBCASE("[CodeEdit] breakpoints and new lines no text") { - Array arg1; - arg1.push_back(0); - Array args; - args.push_back(arg1); + Array args = build_array(build_array(0)); /* No text moves breakpoint. */ code_edit->set_line_as_breakpoint(0, true); CHECK(code_edit->is_line_breakpointed(0)); SIGNAL_CHECK("breakpoint_toggled", args); - /* Normal. */ - ((Array)args[0])[0] = 0; - Array arg2; - arg2.push_back(1); - args.push_back(arg2); + // Normal. + args = build_array(build_array(0), build_array(1)); SEND_GUI_ACTION("ui_text_newline"); CHECK(code_edit->get_line_count() == 2); @@ -195,18 +186,16 @@ TEST_CASE("[SceneTree][CodeEdit] line gutters") { CHECK(code_edit->is_line_breakpointed(1)); SIGNAL_CHECK("breakpoint_toggled", args); - /* Non-Breaking. */ - ((Array)args[0])[0] = 1; - ((Array)args[1])[0] = 2; + // Non-Breaking. + args = build_array(build_array(1), build_array(2)); SEND_GUI_ACTION("ui_text_newline_blank"); CHECK(code_edit->get_line_count() == 3); CHECK_FALSE(code_edit->is_line_breakpointed(1)); CHECK(code_edit->is_line_breakpointed(2)); SIGNAL_CHECK("breakpoint_toggled", args); - /* Above. */ - ((Array)args[0])[0] = 2; - ((Array)args[1])[0] = 3; + // Above. + args = build_array(build_array(2), build_array(3)); SEND_GUI_ACTION("ui_text_newline_above"); CHECK(code_edit->get_line_count() == 4); CHECK_FALSE(code_edit->is_line_breakpointed(2)); @@ -215,10 +204,7 @@ TEST_CASE("[SceneTree][CodeEdit] line gutters") { } SUBCASE("[CodeEdit] breakpoints and new lines with text") { - Array arg1; - arg1.push_back(0); - Array args; - args.push_back(arg1); + Array args = build_array(build_array(0)); /* Having text does not move breakpoint. */ code_edit->insert_text_at_caret("text"); @@ -241,11 +227,8 @@ TEST_CASE("[SceneTree][CodeEdit] line gutters") { CHECK_FALSE(code_edit->is_line_breakpointed(1)); SIGNAL_CHECK_FALSE("breakpoint_toggled"); - /* Above does move. */ - ((Array)args[0])[0] = 0; - Array arg2; - arg2.push_back(1); - args.push_back(arg2); + // Above does move. + args = build_array(build_array(0), build_array(1)); code_edit->set_caret_line(0); SEND_GUI_ACTION("ui_text_newline_above"); @@ -256,10 +239,7 @@ TEST_CASE("[SceneTree][CodeEdit] line gutters") { } SUBCASE("[CodeEdit] breakpoints and backspace") { - Array arg1; - arg1.push_back(1); - Array args; - args.push_back(arg1); + Array args = build_array(build_array(1)); code_edit->set_text("\n\n"); code_edit->set_line_as_breakpoint(1, true); @@ -281,8 +261,8 @@ TEST_CASE("[SceneTree][CodeEdit] line gutters") { ERR_PRINT_ON; SIGNAL_CHECK("breakpoint_toggled", args); - /* Backspace above breakpointed line moves it. */ - ((Array)args[0])[0] = 2; + // Backspace above breakpointed line moves it. + args = build_array(build_array(2)); code_edit->set_text("\n\n"); code_edit->set_line_as_breakpoint(2, true); @@ -291,9 +271,7 @@ TEST_CASE("[SceneTree][CodeEdit] line gutters") { code_edit->set_caret_line(1); - Array arg2; - arg2.push_back(1); - args.push_back(arg2); + args = build_array(build_array(2), build_array(1)); SEND_GUI_ACTION("ui_text_backspace"); ERR_PRINT_OFF; CHECK_FALSE(code_edit->is_line_breakpointed(2)); @@ -303,10 +281,7 @@ TEST_CASE("[SceneTree][CodeEdit] line gutters") { } SUBCASE("[CodeEdit] breakpoints and delete") { - Array arg1; - arg1.push_back(1); - Array args; - args.push_back(arg1); + Array args = build_array(build_array(1)); code_edit->set_text("\n\n"); code_edit->set_line_as_breakpoint(1, true); @@ -329,8 +304,8 @@ TEST_CASE("[SceneTree][CodeEdit] line gutters") { ERR_PRINT_ON; SIGNAL_CHECK("breakpoint_toggled", args); - /* Delete above breakpointed line moves it. */ - ((Array)args[0])[0] = 2; + // Delete above breakpointed line moves it. + args = build_array(build_array(2)); code_edit->set_text("\n\n"); code_edit->set_line_as_breakpoint(2, true); @@ -339,9 +314,7 @@ TEST_CASE("[SceneTree][CodeEdit] line gutters") { code_edit->set_caret_line(0); - Array arg2; - arg2.push_back(1); - args.push_back(arg2); + args = build_array(build_array(2), build_array(1)); SEND_GUI_ACTION("ui_text_delete"); ERR_PRINT_OFF; CHECK_FALSE(code_edit->is_line_breakpointed(2)); @@ -351,10 +324,7 @@ TEST_CASE("[SceneTree][CodeEdit] line gutters") { } SUBCASE("[CodeEdit] breakpoints and delete selection") { - Array arg1; - arg1.push_back(1); - Array args; - args.push_back(arg1); + Array args = build_array(build_array(1)); code_edit->set_text("\n\n"); code_edit->set_line_as_breakpoint(1, true); @@ -367,8 +337,8 @@ TEST_CASE("[SceneTree][CodeEdit] line gutters") { CHECK_FALSE(code_edit->is_line_breakpointed(0)); SIGNAL_CHECK("breakpoint_toggled", args); - /* Should handle breakpoint move when deleting selection by adding less text then removed. */ - ((Array)args[0])[0] = 9; + // Should handle breakpoint move when deleting selection by adding less text then removed. + args = build_array(build_array(9)); code_edit->set_text("\n\n\n\n\n\n\n\n\n"); code_edit->set_line_as_breakpoint(9, true); @@ -377,9 +347,7 @@ TEST_CASE("[SceneTree][CodeEdit] line gutters") { code_edit->select(0, 0, 6, 0); - Array arg2; - arg2.push_back(4); - args.push_back(arg2); + args = build_array(build_array(9), build_array(4)); SEND_GUI_ACTION("ui_text_newline"); ERR_PRINT_OFF; CHECK_FALSE(code_edit->is_line_breakpointed(9)); @@ -387,9 +355,8 @@ TEST_CASE("[SceneTree][CodeEdit] line gutters") { CHECK(code_edit->is_line_breakpointed(4)); SIGNAL_CHECK("breakpoint_toggled", args); - /* Should handle breakpoint move when deleting selection by adding more text then removed. */ - ((Array)args[0])[0] = 9; - ((Array)args[1])[0] = 14; + // Should handle breakpoint move when deleting selection by adding more text then removed. + args = build_array(build_array(9), build_array(14)); code_edit->insert_text_at_caret("\n\n\n\n\n"); MessageQueue::get_singleton()->flush(); @@ -404,10 +371,7 @@ TEST_CASE("[SceneTree][CodeEdit] line gutters") { } SUBCASE("[CodeEdit] breakpoints and undo") { - Array arg1; - arg1.push_back(1); - Array args; - args.push_back(arg1); + Array args = build_array(build_array(1)); code_edit->set_text("\n\n"); code_edit->set_line_as_breakpoint(1, true); @@ -1849,17 +1813,47 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { code_edit->do_indent(); CHECK(code_edit->get_line(0) == "test\t"); - /* Indent lines does entire line and works without selection. */ + // Insert in place with multiple carets. + code_edit->set_text("test text"); + code_edit->set_caret_column(5); + code_edit->add_caret(0, 7); + code_edit->add_caret(0, 2); + code_edit->do_indent(); + CHECK(code_edit->get_line(0) == "te\tst \tte\txt"); + CHECK(code_edit->get_caret_count() == 3); + CHECK(code_edit->get_caret_column(0) == 7); + CHECK(code_edit->get_caret_column(1) == 10); + CHECK(code_edit->get_caret_column(2) == 3); + code_edit->remove_secondary_carets(); + + // Indent lines does entire line and works without selection. code_edit->set_text(""); code_edit->insert_text_at_caret("test"); code_edit->indent_lines(); CHECK(code_edit->get_line(0) == "\ttest"); + CHECK(code_edit->get_caret_column() == 5); /* Selection does entire line. */ code_edit->set_text("test"); code_edit->select_all(); code_edit->do_indent(); CHECK(code_edit->get_line(0) == "\ttest"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_selection_origin_column() == 0); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 5); + + // Selection does entire line, right to left selection. + code_edit->set_text("test"); + code_edit->select(0, 4, 0, 0); + code_edit->do_indent(); + CHECK(code_edit->get_line(0) == "\ttest"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_selection_origin_column() == 5); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 0); /* Handles multiple lines. */ code_edit->set_text("test\ntext"); @@ -1867,6 +1861,11 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { code_edit->do_indent(); CHECK(code_edit->get_line(0) == "\ttest"); CHECK(code_edit->get_line(1) == "\ttext"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_selection_origin_column() == 0); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 5); /* Do not indent line if last col is zero. */ code_edit->set_text("test\ntext"); @@ -1874,6 +1873,11 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { code_edit->do_indent(); CHECK(code_edit->get_line(0) == "\ttest"); CHECK(code_edit->get_line(1) == "text"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_selection_origin_column() == 0); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 0); /* Indent even if last column of first line. */ code_edit->set_text("test\ntext"); @@ -1881,15 +1885,53 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { code_edit->do_indent(); CHECK(code_edit->get_line(0) == "\ttest"); CHECK(code_edit->get_line(1) == "text"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_selection_origin_column() == 5); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 0); + + // Indent even if last column of first line, reversed. + code_edit->set_text("test\ntext"); + code_edit->select(1, 0, 0, 4); + code_edit->do_indent(); + CHECK(code_edit->get_line(0) == "\ttest"); + CHECK(code_edit->get_line(1) == "text"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 1); + CHECK(code_edit->get_selection_origin_column() == 0); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 5); /* Check selection is adjusted. */ code_edit->set_text("test"); code_edit->select(0, 1, 0, 2); code_edit->do_indent(); - CHECK(code_edit->get_selection_from_column() == 2); - CHECK(code_edit->get_selection_to_column() == 3); CHECK(code_edit->get_line(0) == "\ttest"); - code_edit->undo(); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_selection_origin_column() == 2); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 3); + + // Indent once with multiple selections. + code_edit->set_text("test"); + code_edit->select(0, 1, 0, 2); + code_edit->add_caret(0, 4); + code_edit->select(0, 4, 0, 3, 1); + code_edit->do_indent(); + CHECK(code_edit->get_line(0) == "\ttest"); + CHECK(code_edit->get_caret_count() == 2); + CHECK(code_edit->has_selection(0)); + CHECK(code_edit->get_selection_origin_line(0) == 0); + CHECK(code_edit->get_selection_origin_column(0) == 2); + CHECK(code_edit->get_caret_line(0) == 0); + CHECK(code_edit->get_caret_column(0) == 3); + CHECK(code_edit->has_selection(1)); + CHECK(code_edit->get_selection_origin_line(1) == 0); + CHECK(code_edit->get_selection_origin_column(1) == 5); + CHECK(code_edit->get_caret_line(1) == 0); + CHECK(code_edit->get_caret_column(1) == 4); } SUBCASE("[CodeEdit] indent spaces") { @@ -1922,23 +1964,58 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { code_edit->do_indent(); CHECK(code_edit->get_line(0) == "test "); - /* Indent lines does entire line and works without selection. */ + // Insert in place with multiple carets. + code_edit->set_text("test text"); + code_edit->set_caret_column(5); + code_edit->add_caret(0, 7); + code_edit->add_caret(0, 2); + code_edit->do_indent(); + CHECK(code_edit->get_line(0) == "te st te xt"); + CHECK(code_edit->get_caret_count() == 3); + CHECK(code_edit->get_caret_column(0) == 10); + CHECK(code_edit->get_caret_column(1) == 14); + CHECK(code_edit->get_caret_column(2) == 4); + code_edit->remove_secondary_carets(); + + // Indent lines does entire line and works without selection. code_edit->set_text(""); code_edit->insert_text_at_caret("test"); code_edit->indent_lines(); CHECK(code_edit->get_line(0) == " test"); + CHECK(code_edit->get_caret_column() == 8); /* Selection does entire line. */ code_edit->set_text("test"); code_edit->select_all(); code_edit->do_indent(); CHECK(code_edit->get_line(0) == " test"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_selection_origin_column() == 0); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 8); + + // Selection does entire line, right to left selection. + code_edit->set_text("test"); + code_edit->select(0, 4, 0, 0); + code_edit->do_indent(); + CHECK(code_edit->get_line(0) == " test"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_selection_origin_column() == 8); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 0); /* single indent only add required spaces. */ code_edit->set_text(" test"); code_edit->select_all(); code_edit->do_indent(); CHECK(code_edit->get_line(0) == " test"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_selection_origin_column() == 0); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 8); /* Handles multiple lines. */ code_edit->set_text("test\ntext"); @@ -1946,6 +2023,11 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { code_edit->do_indent(); CHECK(code_edit->get_line(0) == " test"); CHECK(code_edit->get_line(1) == " text"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_selection_origin_column() == 0); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 8); /* Do not indent line if last col is zero. */ code_edit->set_text("test\ntext"); @@ -1953,6 +2035,11 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { code_edit->do_indent(); CHECK(code_edit->get_line(0) == " test"); CHECK(code_edit->get_line(1) == "text"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_selection_origin_column() == 0); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 0); /* Indent even if last column of first line. */ code_edit->set_text("test\ntext"); @@ -1960,14 +2047,53 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { code_edit->do_indent(); CHECK(code_edit->get_line(0) == " test"); CHECK(code_edit->get_line(1) == "text"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_selection_origin_column() == 8); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 0); + + // Indent even if last column of first line, right to left selection. + code_edit->set_text("test\ntext"); + code_edit->select(1, 0, 0, 4); + code_edit->do_indent(); + CHECK(code_edit->get_line(0) == " test"); + CHECK(code_edit->get_line(1) == "text"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 1); + CHECK(code_edit->get_selection_origin_column() == 0); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 8); /* Check selection is adjusted. */ code_edit->set_text("test"); code_edit->select(0, 1, 0, 2); code_edit->do_indent(); - CHECK(code_edit->get_selection_from_column() == 5); - CHECK(code_edit->get_selection_to_column() == 6); CHECK(code_edit->get_line(0) == " test"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_selection_origin_column() == 5); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 6); + + // Indent once with multiple selections. + code_edit->set_text("test"); + code_edit->select(0, 1, 0, 2); + code_edit->add_caret(0, 4); + code_edit->select(0, 4, 0, 3, 1); + code_edit->do_indent(); + CHECK(code_edit->get_line(0) == " test"); + CHECK(code_edit->get_caret_count() == 2); + CHECK(code_edit->has_selection(0)); + CHECK(code_edit->get_selection_origin_line(0) == 0); + CHECK(code_edit->get_selection_origin_column(0) == 5); + CHECK(code_edit->get_caret_line(0) == 0); + CHECK(code_edit->get_caret_column(0) == 6); + CHECK(code_edit->has_selection(1)); + CHECK(code_edit->get_selection_origin_line(1) == 0); + CHECK(code_edit->get_selection_origin_column(1) == 8); + CHECK(code_edit->get_caret_line(1) == 0); + CHECK(code_edit->get_caret_column(1) == 7); } SUBCASE("[CodeEdit] unindent tabs") { @@ -2003,11 +2129,28 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { code_edit->insert_text_at_caret("\ttest"); code_edit->unindent_lines(); CHECK(code_edit->get_line(0) == "test"); + CHECK(code_edit->get_caret_column() == 4); + + // Unindent lines once with multiple carets. + code_edit->set_text("\t\ttest"); + code_edit->set_caret_column(1); + code_edit->add_caret(0, 3); + code_edit->unindent_lines(); + CHECK(code_edit->get_line(0) == "\ttest"); + CHECK(code_edit->get_caret_count() == 2); + CHECK_FALSE(code_edit->has_selection()); + CHECK(code_edit->get_caret_line(0) == 0); + CHECK(code_edit->get_caret_column(0) == 0); + CHECK(code_edit->get_caret_line(1) == 0); + CHECK(code_edit->get_caret_column(1) == 2); + code_edit->remove_secondary_carets(); /* Caret on col zero unindent line. */ code_edit->set_text("\t\ttest"); + code_edit->set_caret_column(0); code_edit->unindent_lines(); CHECK(code_edit->get_line(0) == "\ttest"); + CHECK(code_edit->get_caret_column() == 0); /* Check input action. */ code_edit->set_text("\t\ttest"); @@ -2019,13 +2162,34 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { code_edit->select_all(); code_edit->unindent_lines(); CHECK(code_edit->get_line(0) == "\ttest"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_selection_origin_column() == 0); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 5); - /* Handles multiple lines. */ - code_edit->set_text("\ttest\n\ttext"); + // Selection does entire line, right to left selection. + code_edit->set_text("\t\ttest"); + code_edit->select(0, 6, 0, 0); + code_edit->unindent_lines(); + CHECK(code_edit->get_line(0) == "\ttest"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_selection_origin_column() == 5); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 0); + + // Handles multiple lines. + code_edit->set_text("\t\ttest\n\t\ttext"); code_edit->select_all(); code_edit->unindent_lines(); - CHECK(code_edit->get_line(0) == "test"); - CHECK(code_edit->get_line(1) == "text"); + CHECK(code_edit->get_line(0) == "\ttest"); + CHECK(code_edit->get_line(1) == "\ttext"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_selection_origin_column() == 0); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 5); /* Do not unindent line if last col is zero. */ code_edit->set_text("\ttest\n\ttext"); @@ -2033,6 +2197,23 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { code_edit->unindent_lines(); CHECK(code_edit->get_line(0) == "test"); CHECK(code_edit->get_line(1) == "\ttext"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_selection_origin_column() == 0); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 0); + + // Do not unindent line if last col is zero, right to left selection. + code_edit->set_text("\ttest\n\ttext"); + code_edit->select(1, 0, 0, 0); + code_edit->unindent_lines(); + CHECK(code_edit->get_line(0) == "test"); + CHECK(code_edit->get_line(1) == "\ttext"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 1); + CHECK(code_edit->get_selection_origin_column() == 0); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 0); /* Unindent even if last column of first line. */ code_edit->set_text("\ttest\n\ttext"); @@ -2040,14 +2221,50 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { code_edit->unindent_lines(); CHECK(code_edit->get_line(0) == "test"); CHECK(code_edit->get_line(1) == "text"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_selection_origin_column() == 4); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 0); /* Check selection is adjusted. */ code_edit->set_text("\ttest"); code_edit->select(0, 1, 0, 2); code_edit->unindent_lines(); - CHECK(code_edit->get_selection_from_column() == 0); - CHECK(code_edit->get_selection_to_column() == 1); CHECK(code_edit->get_line(0) == "test"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_selection_origin_column() == 0); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 1); + + // Deselect if only the tab was selected. + code_edit->set_text("\ttest"); + code_edit->select(0, 0, 0, 1); + code_edit->unindent_lines(); + CHECK(code_edit->get_line(0) == "test"); + CHECK_FALSE(code_edit->has_selection()); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 0); + + // Unindent once with multiple selections. + code_edit->set_text("\t\ttest"); + code_edit->select(0, 1, 0, 2); + code_edit->add_caret(0, 4); + code_edit->select(0, 4, 0, 3, 1); + code_edit->unindent_lines(); + CHECK(code_edit->get_line(0) == "\ttest"); + CHECK(code_edit->get_caret_count() == 2); + CHECK(code_edit->has_selection(0)); + CHECK(code_edit->get_selection_origin_line(0) == 0); + CHECK(code_edit->get_selection_origin_column(0) == 0); + CHECK(code_edit->get_caret_line(0) == 0); + CHECK(code_edit->get_caret_column(0) == 1); + CHECK(code_edit->has_selection(1)); + CHECK(code_edit->get_selection_origin_line(1) == 0); + CHECK(code_edit->get_selection_origin_column(1) == 3); + CHECK(code_edit->get_caret_line(1) == 0); + CHECK(code_edit->get_caret_column(1) == 2); } SUBCASE("[CodeEdit] unindent spaces") { @@ -2089,11 +2306,28 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { code_edit->insert_text_at_caret(" test"); code_edit->unindent_lines(); CHECK(code_edit->get_line(0) == "test"); + CHECK(code_edit->get_caret_column() == 4); + + // Unindent lines once with multiple carets. + code_edit->set_text(" test"); + code_edit->set_caret_column(1); + code_edit->add_caret(0, 9); + code_edit->unindent_lines(); + CHECK(code_edit->get_line(0) == " test"); + CHECK(code_edit->get_caret_count() == 2); + CHECK_FALSE(code_edit->has_selection()); + CHECK(code_edit->get_caret_line(0) == 0); + CHECK(code_edit->get_caret_column(0) == 0); + CHECK(code_edit->get_caret_line(1) == 0); + CHECK(code_edit->get_caret_column(1) == 5); + code_edit->remove_secondary_carets(); /* Caret on col zero unindent line. */ code_edit->set_text(" test"); + code_edit->set_caret_column(0); code_edit->unindent_lines(); CHECK(code_edit->get_line(0) == " test"); + CHECK(code_edit->get_caret_column() == 0); /* Only as far as needed */ code_edit->set_text(" test"); @@ -2110,13 +2344,34 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { code_edit->select_all(); code_edit->unindent_lines(); CHECK(code_edit->get_line(0) == " test"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_selection_origin_column() == 0); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 8); - /* Handles multiple lines. */ - code_edit->set_text(" test\n text"); + // Selection does entire line, right to left selection. + code_edit->set_text(" test"); + code_edit->select(0, 12, 0, 0); + code_edit->unindent_lines(); + CHECK(code_edit->get_line(0) == " test"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_selection_origin_column() == 8); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 0); + + // Handles multiple lines. + code_edit->set_text(" test\n text"); code_edit->select_all(); code_edit->unindent_lines(); - CHECK(code_edit->get_line(0) == "test"); - CHECK(code_edit->get_line(1) == "text"); + CHECK(code_edit->get_line(0) == " test"); + CHECK(code_edit->get_line(1) == " text"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_selection_origin_column() == 0); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 8); /* Do not unindent line if last col is zero. */ code_edit->set_text(" test\n text"); @@ -2124,6 +2379,23 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { code_edit->unindent_lines(); CHECK(code_edit->get_line(0) == "test"); CHECK(code_edit->get_line(1) == " text"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_selection_origin_column() == 0); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 0); + + // Do not unindent line if last col is zero, right to left selection. + code_edit->set_text(" test\n text"); + code_edit->select(1, 0, 0, 0); + code_edit->unindent_lines(); + CHECK(code_edit->get_line(0) == "test"); + CHECK(code_edit->get_line(1) == " text"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 1); + CHECK(code_edit->get_selection_origin_column() == 0); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 0); /* Unindent even if last column of first line. */ code_edit->set_text(" test\n text"); @@ -2131,14 +2403,48 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { code_edit->unindent_lines(); CHECK(code_edit->get_line(0) == "test"); CHECK(code_edit->get_line(1) == "text"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_selection_origin_column() == 1); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 0); /* Check selection is adjusted. */ code_edit->set_text(" test"); code_edit->select(0, 4, 0, 5); code_edit->unindent_lines(); - CHECK(code_edit->get_selection_from_column() == 0); - CHECK(code_edit->get_selection_to_column() == 1); CHECK(code_edit->get_line(0) == "test"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_selection_origin_column() == 0); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 1); + + // Deselect if only the tab was selected. + code_edit->set_text(" test"); + code_edit->select(0, 0, 0, 4); + code_edit->unindent_lines(); + CHECK(code_edit->get_line(0) == "test"); + CHECK_FALSE(code_edit->has_selection()); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 0); + + // Unindent once with multiple selections. + code_edit->set_text(" test"); + code_edit->select(0, 1, 0, 2); + code_edit->add_caret(0, 4); + code_edit->select(0, 12, 0, 10, 1); + code_edit->unindent_lines(); + CHECK(code_edit->get_line(0) == " test"); + CHECK(code_edit->get_caret_count() == 2); + CHECK_FALSE(code_edit->has_selection(0)); + CHECK(code_edit->get_caret_line(0) == 0); + CHECK(code_edit->get_caret_column(0) == 0); + CHECK(code_edit->has_selection(1)); + CHECK(code_edit->get_selection_origin_line(1) == 0); + CHECK(code_edit->get_selection_origin_column(1) == 8); + CHECK(code_edit->get_caret_line(1) == 0); + CHECK(code_edit->get_caret_column(1) == 6); } SUBCASE("[CodeEdit] auto indent") { @@ -2153,6 +2459,8 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { SEND_GUI_ACTION("ui_text_newline"); CHECK(code_edit->get_line(0) == "test:"); CHECK(code_edit->get_line(1) == "\t"); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 1); /* new blank line should still indent. */ code_edit->set_text(""); @@ -2160,6 +2468,8 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { SEND_GUI_ACTION("ui_text_newline_blank"); CHECK(code_edit->get_line(0) == "test:"); CHECK(code_edit->get_line(1) == "\t"); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 1); /* new line above should not indent. */ code_edit->set_text(""); @@ -2167,6 +2477,8 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { SEND_GUI_ACTION("ui_text_newline_above"); CHECK(code_edit->get_line(0) == ""); CHECK(code_edit->get_line(1) == "test:"); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 0); /* Whitespace between symbol and caret is okay. */ code_edit->set_text(""); @@ -2174,6 +2486,8 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { SEND_GUI_ACTION("ui_text_newline"); CHECK(code_edit->get_line(0) == "test: "); CHECK(code_edit->get_line(1) == "\t"); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 1); /* Comment between symbol and caret is okay. */ code_edit->add_comment_delimiter("#", ""); @@ -2183,6 +2497,8 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { CHECK(code_edit->get_line(0) == "test: # comment"); CHECK(code_edit->get_line(1) == "\t"); code_edit->remove_comment_delimiter("#"); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 1); /* Strings between symbol and caret are not okay. */ code_edit->add_string_delimiter("#", ""); @@ -2192,6 +2508,8 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { CHECK(code_edit->get_line(0) == "test: # string"); CHECK(code_edit->get_line(1) == ""); code_edit->remove_string_delimiter("#"); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 0); /* Non-whitespace prevents auto-indentation. */ code_edit->add_comment_delimiter("#", ""); @@ -2201,6 +2519,8 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { CHECK(code_edit->get_line(0) == "test := 0 # comment"); CHECK(code_edit->get_line(1) == ""); code_edit->remove_comment_delimiter("#"); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 0); /* Even when there's no comments. */ code_edit->set_text(""); @@ -2208,6 +2528,53 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { SEND_GUI_ACTION("ui_text_newline"); CHECK(code_edit->get_line(0) == "test := 0"); CHECK(code_edit->get_line(1) == ""); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 0); + + // Preserve current indentation. + code_edit->set_text("\ttest"); + code_edit->set_caret_column(3); + SEND_GUI_ACTION("ui_text_newline"); + CHECK(code_edit->get_line(0) == "\tte"); + CHECK(code_edit->get_line(1) == "\tst"); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 1); + + // Preserve current indentation blank. + code_edit->set_text("\ttest"); + code_edit->set_caret_column(3); + SEND_GUI_ACTION("ui_text_newline_blank"); + CHECK(code_edit->get_line(0) == "\ttest"); + CHECK(code_edit->get_line(1) == "\t"); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 1); + + // Preserve current indentation above. + code_edit->set_text("\ttest"); + code_edit->set_caret_column(3); + SEND_GUI_ACTION("ui_text_newline_above"); + CHECK(code_edit->get_line(0) == "\t"); + CHECK(code_edit->get_line(1) == "\ttest"); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 1); + + // Increase existing indentation. + code_edit->set_text("\ttest:"); + code_edit->set_caret_column(6); + SEND_GUI_ACTION("ui_text_newline"); + CHECK(code_edit->get_line(0) == "\ttest:"); + CHECK(code_edit->get_line(1) == "\t\t"); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 2); + + // Increase existing indentation blank. + code_edit->set_text("\ttest:"); + code_edit->set_caret_column(3); + SEND_GUI_ACTION("ui_text_newline_blank"); + CHECK(code_edit->get_line(0) == "\ttest:"); + CHECK(code_edit->get_line(1) == "\t\t"); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 2); /* If between brace pairs an extra line is added. */ code_edit->set_text(""); @@ -2217,6 +2584,8 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { CHECK(code_edit->get_line(0) == "test{"); CHECK(code_edit->get_line(1) == "\t"); CHECK(code_edit->get_line(2) == "}"); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 1); /* Except when we are going above. */ code_edit->set_text(""); @@ -2225,6 +2594,8 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { SEND_GUI_ACTION("ui_text_newline_above"); CHECK(code_edit->get_line(0) == ""); CHECK(code_edit->get_line(1) == "test{}"); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 0); /* or below. */ code_edit->set_text(""); @@ -2233,6 +2604,8 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { SEND_GUI_ACTION("ui_text_newline_blank"); CHECK(code_edit->get_line(0) == "test{}"); CHECK(code_edit->get_line(1) == ""); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 0); } SUBCASE("[CodeEdit] auto indent spaces") { @@ -2246,6 +2619,8 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { SEND_GUI_ACTION("ui_text_newline"); CHECK(code_edit->get_line(0) == "test:"); CHECK(code_edit->get_line(1) == " "); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 4); /* new blank line should still indent. */ code_edit->set_text(""); @@ -2253,6 +2628,8 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { SEND_GUI_ACTION("ui_text_newline_blank"); CHECK(code_edit->get_line(0) == "test:"); CHECK(code_edit->get_line(1) == " "); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 4); /* new line above should not indent. */ code_edit->set_text(""); @@ -2260,6 +2637,8 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { SEND_GUI_ACTION("ui_text_newline_above"); CHECK(code_edit->get_line(0) == ""); CHECK(code_edit->get_line(1) == "test:"); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 0); /* Whitespace between symbol and caret is okay. */ code_edit->set_text(""); @@ -2267,6 +2646,8 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { SEND_GUI_ACTION("ui_text_newline"); CHECK(code_edit->get_line(0) == "test: "); CHECK(code_edit->get_line(1) == " "); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 4); /* Comment between symbol and caret is okay. */ code_edit->add_comment_delimiter("#", ""); @@ -2276,6 +2657,8 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { CHECK(code_edit->get_line(0) == "test: # comment"); CHECK(code_edit->get_line(1) == " "); code_edit->remove_comment_delimiter("#"); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 4); /* Strings between symbol and caret are not okay. */ code_edit->add_string_delimiter("#", ""); @@ -2285,6 +2668,8 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { CHECK(code_edit->get_line(0) == "test: # string"); CHECK(code_edit->get_line(1) == ""); code_edit->remove_string_delimiter("#"); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 0); /* Non-whitespace prevents auto-indentation. */ code_edit->add_comment_delimiter("#", ""); @@ -2294,6 +2679,8 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { CHECK(code_edit->get_line(0) == "test := 0 # comment"); CHECK(code_edit->get_line(1) == ""); code_edit->remove_comment_delimiter("#"); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 0); /* Even when there's no comments. */ code_edit->set_text(""); @@ -2301,6 +2688,53 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { SEND_GUI_ACTION("ui_text_newline"); CHECK(code_edit->get_line(0) == "test := 0"); CHECK(code_edit->get_line(1) == ""); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 0); + + // Preserve current indentation. + code_edit->set_text(" test"); + code_edit->set_caret_column(6); + SEND_GUI_ACTION("ui_text_newline"); + CHECK(code_edit->get_line(0) == " te"); + CHECK(code_edit->get_line(1) == " st"); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 4); + + // Preserve current indentation blank. + code_edit->set_text(" test"); + code_edit->set_caret_column(6); + SEND_GUI_ACTION("ui_text_newline_blank"); + CHECK(code_edit->get_line(0) == " test"); + CHECK(code_edit->get_line(1) == " "); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 4); + + // Preserve current indentation above. + code_edit->set_text(" test"); + code_edit->set_caret_column(6); + SEND_GUI_ACTION("ui_text_newline_above"); + CHECK(code_edit->get_line(0) == " "); + CHECK(code_edit->get_line(1) == " test"); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 4); + + // Increase existing indentation. + code_edit->set_text(" test:"); + code_edit->set_caret_column(9); + SEND_GUI_ACTION("ui_text_newline"); + CHECK(code_edit->get_line(0) == " test:"); + CHECK(code_edit->get_line(1) == " "); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 8); + + // Increase existing indentation blank. + code_edit->set_text(" test:"); + code_edit->set_caret_column(9); + SEND_GUI_ACTION("ui_text_newline"); + CHECK(code_edit->get_line(0) == " test:"); + CHECK(code_edit->get_line(1) == " "); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 8); /* If between brace pairs an extra line is added. */ code_edit->set_text(""); @@ -2310,6 +2744,8 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { CHECK(code_edit->get_line(0) == "test{"); CHECK(code_edit->get_line(1) == " "); CHECK(code_edit->get_line(2) == "}"); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 4); /* Except when we are going above. */ code_edit->set_text(""); @@ -2318,6 +2754,8 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { SEND_GUI_ACTION("ui_text_newline_above"); CHECK(code_edit->get_line(0) == ""); CHECK(code_edit->get_line(1) == "test{}"); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 0); /* or below. */ code_edit->set_text(""); @@ -2326,6 +2764,8 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { SEND_GUI_ACTION("ui_text_newline_blank"); CHECK(code_edit->get_line(0) == "test{}"); CHECK(code_edit->get_line(1) == ""); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 0); /* If there is something after a colon and there is a colon in the comment it @@ -2337,6 +2777,8 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { CHECK(code_edit->get_line(0) == "test:test#:"); CHECK(code_edit->get_line(1) == ""); code_edit->remove_comment_delimiter("#"); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 0); } } @@ -2345,64 +2787,50 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { code_edit->set_indent_using_spaces(false); // Only line. - code_edit->insert_text_at_caret(" test"); - code_edit->set_caret_line(0); - code_edit->set_caret_column(8); - code_edit->select(0, 8, 0, 9); + code_edit->set_text(" test"); + code_edit->select(0, 9, 0, 8); code_edit->convert_indent(); CHECK(code_edit->get_line(0) == "\t\ttest"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_column() == 3); CHECK(code_edit->get_caret_column() == 2); - CHECK(code_edit->get_selection_from_column() == 2); - CHECK(code_edit->get_selection_to_column() == 3); // First line. - code_edit->set_text(""); - code_edit->insert_text_at_caret(" test\n"); - code_edit->set_caret_line(0); - code_edit->set_caret_column(8); + code_edit->set_text(" test\n"); code_edit->select(0, 8, 0, 9); code_edit->convert_indent(); CHECK(code_edit->get_line(0) == "\t\ttest"); - CHECK(code_edit->get_caret_column() == 2); - CHECK(code_edit->get_selection_from_column() == 2); - CHECK(code_edit->get_selection_to_column() == 3); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_column() == 2); + CHECK(code_edit->get_caret_column() == 3); // Middle line. - code_edit->set_text(""); - code_edit->insert_text_at_caret("\n test\n"); - code_edit->set_caret_line(1); - code_edit->set_caret_column(8); + code_edit->set_text("\n test\n"); code_edit->select(1, 8, 1, 9); code_edit->convert_indent(); CHECK(code_edit->get_line(1) == "\t\ttest"); - CHECK(code_edit->get_caret_column() == 2); - CHECK(code_edit->get_selection_from_column() == 2); - CHECK(code_edit->get_selection_to_column() == 3); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_column() == 2); + CHECK(code_edit->get_caret_column() == 3); // End line. - code_edit->set_text(""); - code_edit->insert_text_at_caret("\n test"); - code_edit->set_caret_line(1); - code_edit->set_caret_column(8); + code_edit->set_text("\n test"); code_edit->select(1, 8, 1, 9); code_edit->convert_indent(); CHECK(code_edit->get_line(1) == "\t\ttest"); - CHECK(code_edit->get_caret_column() == 2); - CHECK(code_edit->get_selection_from_column() == 2); - CHECK(code_edit->get_selection_to_column() == 3); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_column() == 2); + CHECK(code_edit->get_caret_column() == 3); // Within provided range. - code_edit->set_text(""); - code_edit->insert_text_at_caret(" test\n test\n"); - code_edit->set_caret_line(1); - code_edit->set_caret_column(8); + code_edit->set_text(" test\n test\n"); code_edit->select(1, 8, 1, 9); code_edit->convert_indent(1, 1); CHECK(code_edit->get_line(0) == " test"); CHECK(code_edit->get_line(1) == "\t\ttest"); - CHECK(code_edit->get_caret_column() == 2); - CHECK(code_edit->get_selection_from_column() == 2); - CHECK(code_edit->get_selection_to_column() == 3); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_column() == 2); + CHECK(code_edit->get_caret_column() == 3); } SUBCASE("[CodeEdit] convert indent to spaces") { @@ -2410,64 +2838,50 @@ TEST_CASE("[SceneTree][CodeEdit] indent") { code_edit->set_indent_using_spaces(true); // Only line. - code_edit->insert_text_at_caret("\t\ttest"); - code_edit->set_caret_line(0); - code_edit->set_caret_column(2); - code_edit->select(0, 2, 0, 3); + code_edit->set_text("\t\ttest"); + code_edit->select(0, 3, 0, 2); code_edit->convert_indent(); CHECK(code_edit->get_line(0) == " test"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_column() == 9); CHECK(code_edit->get_caret_column() == 8); - CHECK(code_edit->get_selection_from_column() == 8); - CHECK(code_edit->get_selection_to_column() == 9); // First line. - code_edit->set_text(""); - code_edit->insert_text_at_caret("\t\ttest\n"); - code_edit->set_caret_line(0); - code_edit->set_caret_column(2); + code_edit->set_text("\t\ttest\n"); code_edit->select(0, 2, 0, 3); code_edit->convert_indent(); CHECK(code_edit->get_line(0) == " test"); - CHECK(code_edit->get_caret_column() == 8); - CHECK(code_edit->get_selection_from_column() == 8); - CHECK(code_edit->get_selection_to_column() == 9); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_column() == 8); + CHECK(code_edit->get_caret_column() == 9); // Middle line. - code_edit->set_text(""); - code_edit->insert_text_at_caret("\n\t\ttest\n"); - code_edit->set_caret_line(1); - code_edit->set_caret_column(2); + code_edit->set_text("\n\t\ttest\n"); code_edit->select(1, 2, 1, 3); code_edit->convert_indent(); CHECK(code_edit->get_line(1) == " test"); - CHECK(code_edit->get_caret_column() == 8); - CHECK(code_edit->get_selection_from_column() == 8); - CHECK(code_edit->get_selection_to_column() == 9); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_column() == 8); + CHECK(code_edit->get_caret_column() == 9); // End line. - code_edit->set_text(""); - code_edit->insert_text_at_caret("\n\t\ttest"); - code_edit->set_caret_line(1); - code_edit->set_caret_column(2); + code_edit->set_text("\n\t\ttest"); code_edit->select(1, 2, 1, 3); code_edit->convert_indent(); CHECK(code_edit->get_line(1) == " test"); - CHECK(code_edit->get_caret_column() == 8); - CHECK(code_edit->get_selection_from_column() == 8); - CHECK(code_edit->get_selection_to_column() == 9); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_column() == 8); + CHECK(code_edit->get_caret_column() == 9); // Within provided range. - code_edit->set_text(""); - code_edit->insert_text_at_caret("\ttest\n\t\ttest\n"); - code_edit->set_caret_line(1); - code_edit->set_caret_column(2); + code_edit->set_text("\ttest\n\t\ttest\n"); code_edit->select(1, 2, 1, 3); code_edit->convert_indent(1, 1); CHECK(code_edit->get_line(0) == "\ttest"); CHECK(code_edit->get_line(1) == " test"); - CHECK(code_edit->get_caret_column() == 8); - CHECK(code_edit->get_selection_from_column() == 8); - CHECK(code_edit->get_selection_to_column() == 9); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_column() == 8); + CHECK(code_edit->get_caret_column() == 9); // Outside of range. ERR_PRINT_OFF; @@ -2484,6 +2898,7 @@ TEST_CASE("[SceneTree][CodeEdit] folding") { CodeEdit *code_edit = memnew(CodeEdit); SceneTree::get_singleton()->get_root()->add_child(code_edit); code_edit->grab_focus(); + code_edit->set_line_folding_enabled(true); SUBCASE("[CodeEdit] folding settings") { code_edit->set_line_folding_enabled(true); @@ -2494,8 +2909,6 @@ TEST_CASE("[SceneTree][CodeEdit] folding") { } SUBCASE("[CodeEdit] folding") { - code_edit->set_line_folding_enabled(true); - // No indent. code_edit->set_text("line1\nline2\nline3"); for (int i = 0; i < 2; i++) { @@ -2862,6 +3275,100 @@ TEST_CASE("[SceneTree][CodeEdit] folding") { CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 4); } + SUBCASE("[CodeEdit] folding carets") { + // Folding a line moves all carets that would be hidden. + code_edit->set_text("test\n\tline1\n\t\tline 2\n"); + code_edit->set_caret_line(1); + code_edit->set_caret_column(0); + code_edit->add_caret(1, 3); + code_edit->add_caret(2, 8); + code_edit->add_caret(2, 1); + code_edit->select(2, 0, 2, 1, 3); + + code_edit->fold_line(0); + CHECK(code_edit->is_line_folded(0)); + CHECK_FALSE(code_edit->is_line_folded(1)); + CHECK(code_edit->get_caret_count() == 1); + CHECK_FALSE(code_edit->has_selection()); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 4); + + // Undoing an action that puts the caret on a folded line unfolds it. + code_edit->set_text("test\n\tline1"); + code_edit->select(1, 1, 1, 2); + code_edit->duplicate_selection(); + CHECK(code_edit->get_text() == "test\n\tlline1"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 3); + CHECK(code_edit->get_selection_origin_line() == 1); + CHECK(code_edit->get_selection_origin_column() == 2); + code_edit->fold_line(0); + CHECK(code_edit->is_line_folded(0)); + CHECK_FALSE(code_edit->is_line_folded(1)); + CHECK_FALSE(code_edit->has_selection()); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 4); + + code_edit->undo(); + CHECK(code_edit->get_text() == "test\n\tline1"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 2); + CHECK(code_edit->get_selection_origin_line() == 1); + CHECK(code_edit->get_selection_origin_column() == 1); + CHECK_FALSE(code_edit->is_line_folded(0)); + CHECK_FALSE(code_edit->is_line_folded(1)); + + // Redoing doesn't refold. + code_edit->redo(); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 3); + CHECK(code_edit->get_selection_origin_line() == 1); + CHECK(code_edit->get_selection_origin_column() == 2); + CHECK_FALSE(code_edit->is_line_folded(0)); + CHECK_FALSE(code_edit->is_line_folded(1)); + } + + SUBCASE("[CodeEdit] toggle folding carets") { + code_edit->set_text("test\n\tline1\ntest2\n\tline2"); + + // Fold lines with carets on them. + code_edit->set_caret_line(0); + code_edit->set_caret_column(1); + code_edit->toggle_foldable_lines_at_carets(); + CHECK(code_edit->is_line_folded(0)); + CHECK_FALSE(code_edit->is_line_folded(2)); + + // Toggle fold on lines with carets. + code_edit->add_caret(2, 0); + code_edit->toggle_foldable_lines_at_carets(); + CHECK_FALSE(code_edit->is_line_folded(0)); + CHECK(code_edit->is_line_folded(2)); + CHECK(code_edit->get_caret_count() == 2); + CHECK(code_edit->get_caret_line(0) == 0); + CHECK(code_edit->get_caret_column(0) == 1); + CHECK(code_edit->get_caret_line(1) == 2); + CHECK(code_edit->get_caret_column(1) == 0); + + // Multiple carets as part of one fold. + code_edit->unfold_all_lines(); + code_edit->remove_secondary_carets(); + code_edit->set_caret_line(0); + code_edit->set_caret_column(1); + code_edit->add_caret(0, 4); + code_edit->add_caret(1, 2); + code_edit->toggle_foldable_lines_at_carets(); + CHECK(code_edit->is_line_folded(0)); + CHECK_FALSE(code_edit->is_line_folded(2)); + CHECK(code_edit->get_caret_count() == 2); + CHECK(code_edit->get_caret_line(0) == 0); + CHECK(code_edit->get_caret_column(0) == 1); + CHECK(code_edit->get_caret_line(1) == 0); + CHECK(code_edit->get_caret_column(1) == 4); + } + memdelete(code_edit); } @@ -2870,7 +3377,7 @@ TEST_CASE("[SceneTree][CodeEdit] region folding") { SceneTree::get_singleton()->get_root()->add_child(code_edit); code_edit->grab_focus(); - SUBCASE("[CodeEdit] region folding") { + SUBCASE("[CodeEdit] region tags") { code_edit->set_line_folding_enabled(true); // Region tag detection. @@ -2907,16 +3414,51 @@ TEST_CASE("[SceneTree][CodeEdit] region folding") { ERR_PRINT_ON; CHECK(code_edit->get_code_region_start_tag() == "region"); CHECK(code_edit->get_code_region_end_tag() == "endregion"); + } - // Region creation with selection adds start / close region lines. + SUBCASE("[CodeEdit] create code region") { + code_edit->set_line_folding_enabled(true); + + // Region creation with selection adds start and close region lines. Region name is selected and the region is folded. code_edit->set_text("line1\nline2\nline3"); code_edit->clear_comment_delimiters(); code_edit->add_comment_delimiter("#", ""); code_edit->select(1, 0, 1, 4); code_edit->create_code_region(); CHECK(code_edit->is_line_code_region_start(1)); - CHECK(code_edit->get_line(2).contains("line2")); CHECK(code_edit->is_line_code_region_end(3)); + CHECK(code_edit->get_text() == "line1\n#region New Code Region\nline2\n#endregion\nline3"); + CHECK(code_edit->get_caret_count() == 1); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selected_text() == "New Code Region"); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 23); + CHECK(code_edit->get_selection_origin_line() == 1); + CHECK(code_edit->get_selection_origin_column() == 8); + CHECK(code_edit->is_line_folded(1)); + + // Undo region creation. Line get unfolded. + code_edit->undo(); + CHECK(code_edit->get_text() == "line1\nline2\nline3"); + CHECK(code_edit->get_caret_count() == 1); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 4); + CHECK(code_edit->get_selection_origin_line() == 1); + CHECK(code_edit->get_selection_origin_column() == 0); + CHECK_FALSE(code_edit->is_line_folded(1)); + + // Redo region creation. + code_edit->redo(); + CHECK(code_edit->get_text() == "line1\n#region New Code Region\nline2\n#endregion\nline3"); + CHECK(code_edit->get_caret_count() == 1); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selected_text() == "New Code Region"); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 23); + CHECK(code_edit->get_selection_origin_line() == 1); + CHECK(code_edit->get_selection_origin_column() == 8); + CHECK_FALSE(code_edit->is_line_folded(1)); // Region creation without any selection has no effect. code_edit->set_text("line1\nline2\nline3"); @@ -2925,7 +3467,7 @@ TEST_CASE("[SceneTree][CodeEdit] region folding") { code_edit->create_code_region(); CHECK(code_edit->get_text() == "line1\nline2\nline3"); - // Region creation with multiple selections. + // Region creation with multiple selections. Secondary carets are removed and the first region name is selected. code_edit->set_text("line1\nline2\nline3"); code_edit->clear_comment_delimiters(); code_edit->add_comment_delimiter("#", ""); @@ -2934,6 +3476,25 @@ TEST_CASE("[SceneTree][CodeEdit] region folding") { code_edit->select(2, 0, 2, 5, 1); code_edit->create_code_region(); CHECK(code_edit->get_text() == "#region New Code Region\nline1\n#endregion\nline2\n#region New Code Region\nline3\n#endregion"); + CHECK(code_edit->get_caret_count() == 1); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selected_text() == "New Code Region"); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 23); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_selection_origin_column() == 8); + + // Region creation with mixed selection and non-selection carets. Regular carets are ignored. + code_edit->set_text("line1\nline2\nline3"); + code_edit->clear_comment_delimiters(); + code_edit->add_comment_delimiter("#", ""); + code_edit->select(0, 0, 0, 4, 0); + code_edit->add_caret(2, 5); + code_edit->create_code_region(); + CHECK(code_edit->get_text() == "#region New Code Region\nline1\n#endregion\nline2\nline3"); + CHECK(code_edit->get_caret_count() == 1); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selected_text() == "New Code Region"); // Two selections on the same line create only one region. code_edit->set_text("test line1\ntest line2\ntest line3"); @@ -2960,6 +3521,10 @@ TEST_CASE("[SceneTree][CodeEdit] region folding") { code_edit->add_comment_delimiter("/*", "*/"); code_edit->create_code_region(); CHECK(code_edit->get_text() == "line1\nline2\nline3"); + } + + SUBCASE("[CodeEdit] region comment delimiters") { + code_edit->set_line_folding_enabled(true); // Choose one line comment delimiter. code_edit->set_text("//region region_name\nline2\n//endregion"); @@ -2993,6 +3558,10 @@ TEST_CASE("[SceneTree][CodeEdit] region folding") { code_edit->clear_comment_delimiters(); CHECK_FALSE(code_edit->is_line_code_region_start(0)); CHECK_FALSE(code_edit->is_line_code_region_end(2)); + } + + SUBCASE("[CodeEdit] fold region") { + code_edit->set_line_folding_enabled(true); // Fold region. code_edit->clear_comment_delimiters(); @@ -3895,10 +4464,7 @@ TEST_CASE("[SceneTree][CodeEdit] symbol lookup") { SEND_GUI_KEY_EVENT(Key::CTRL); #endif - Array signal_args; - Array arg; - arg.push_back("some"); - signal_args.push_back(arg); + Array signal_args = build_array(build_array("some")); SIGNAL_CHECK("symbol_validate", signal_args); SIGNAL_UNWATCH(code_edit, "symbol_validate"); @@ -3928,178 +4494,980 @@ TEST_CASE("[SceneTree][CodeEdit] line length guidelines") { memdelete(code_edit); } -TEST_CASE("[SceneTree][CodeEdit] Backspace delete") { +TEST_CASE("[SceneTree][CodeEdit] text manipulation") { CodeEdit *code_edit = memnew(CodeEdit); SceneTree::get_singleton()->get_root()->add_child(code_edit); code_edit->grab_focus(); - /* Backspace with selection on first line. */ - code_edit->set_text(""); - code_edit->insert_text_at_caret("test backspace"); - code_edit->select(0, 0, 0, 5); - code_edit->backspace(); - CHECK(code_edit->get_line(0) == "backspace"); - - /* Backspace with selection on first line and caret at the beginning of file. */ - code_edit->set_text(""); - code_edit->insert_text_at_caret("test backspace"); - code_edit->select(0, 0, 0, 5); - code_edit->set_caret_column(0); - code_edit->backspace(); - CHECK(code_edit->get_line(0) == "backspace"); - - /* Move caret up to the previous line on backspace if caret is at the first column. */ - code_edit->set_text(""); - code_edit->insert_text_at_caret("line 1\nline 2"); - code_edit->set_caret_line(1); - code_edit->set_caret_column(0); - code_edit->backspace(); - CHECK(code_edit->get_line(0) == "line 1line 2"); - CHECK(code_edit->get_caret_line() == 0); - CHECK(code_edit->get_caret_column() == 6); - - /* Backspace delete all text if all text is selected. */ - code_edit->set_text(""); - code_edit->insert_text_at_caret("line 1\nline 2\nline 3"); - code_edit->select_all(); - code_edit->backspace(); - CHECK(code_edit->get_text().is_empty()); - - /* Backspace at the beginning without selection has no effect. */ - code_edit->set_text(""); - code_edit->insert_text_at_caret("line 1\nline 2\nline 3"); - code_edit->set_caret_line(0); - code_edit->set_caret_column(0); - code_edit->backspace(); - CHECK(code_edit->get_text() == "line 1\nline 2\nline 3"); + SUBCASE("[SceneTree][CodeEdit] backspace") { + // Backspace with selection on first line. + code_edit->set_text("test backspace"); + code_edit->select(0, 0, 0, 5); + code_edit->backspace(); + CHECK(code_edit->get_line(0) == "backspace"); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 0); - memdelete(code_edit); -} + // Backspace with selection on first line and caret at the beginning of file. + code_edit->set_text("test backspace"); + code_edit->select(0, 5, 0, 0); + code_edit->backspace(); + CHECK(code_edit->get_line(0) == "backspace"); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 0); -TEST_CASE("[SceneTree][CodeEdit] New Line") { - CodeEdit *code_edit = memnew(CodeEdit); - SceneTree::get_singleton()->get_root()->add_child(code_edit); - code_edit->grab_focus(); + // Move caret up to the previous line on backspace if caret is at the first column. + code_edit->set_text("line 1\nline 2"); + code_edit->set_caret_line(1); + code_edit->set_caret_column(0); + code_edit->backspace(); + CHECK(code_edit->get_line(0) == "line 1line 2"); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 6); - /* Add a new line. */ - code_edit->set_text(""); - code_edit->insert_text_at_caret("test new line"); - code_edit->set_caret_line(0); - code_edit->set_caret_column(13); - SEND_GUI_ACTION("ui_text_newline"); - CHECK(code_edit->get_line(0) == "test new line"); - CHECK(code_edit->get_line(1) == ""); - - /* Split line with new line. */ - code_edit->set_text(""); - code_edit->insert_text_at_caret("test new line"); - code_edit->set_caret_line(0); - code_edit->set_caret_column(5); - SEND_GUI_ACTION("ui_text_newline"); - CHECK(code_edit->get_line(0) == "test "); - CHECK(code_edit->get_line(1) == "new line"); - - /* Delete selection and split with new line. */ - code_edit->set_text(""); - code_edit->insert_text_at_caret("test new line"); - code_edit->select(0, 0, 0, 5); - SEND_GUI_ACTION("ui_text_newline"); - CHECK(code_edit->get_line(0) == ""); - CHECK(code_edit->get_line(1) == "new line"); - - /* Blank new line below with selection should not split. */ - code_edit->set_text(""); - code_edit->insert_text_at_caret("test new line"); - code_edit->select(0, 0, 0, 5); - SEND_GUI_ACTION("ui_text_newline_blank"); - CHECK(code_edit->get_line(0) == "test new line"); - CHECK(code_edit->get_line(1) == ""); - - /* Blank new line above with selection should not split. */ - code_edit->set_text(""); - code_edit->insert_text_at_caret("test new line"); - code_edit->select(0, 0, 0, 5); - SEND_GUI_ACTION("ui_text_newline_above"); - CHECK(code_edit->get_line(0) == ""); - CHECK(code_edit->get_line(1) == "test new line"); + // Multiple carets with a caret at the first column. + code_edit->set_text("line 1\nline 2"); + code_edit->set_caret_line(1); + code_edit->set_caret_column(2); + code_edit->add_caret(1, 0); + code_edit->add_caret(1, 5); + code_edit->backspace(); + CHECK(code_edit->get_text() == "line 1lne2"); + CHECK(code_edit->get_caret_count() == 3); + CHECK(code_edit->get_caret_line(0) == 0); + CHECK(code_edit->get_caret_column(0) == 7); + CHECK(code_edit->get_caret_line(1) == 0); + CHECK(code_edit->get_caret_column(1) == 6); + CHECK(code_edit->get_caret_line(2) == 0); + CHECK(code_edit->get_caret_column(2) == 9); + code_edit->remove_secondary_carets(); + + // Multiple carets close together. + code_edit->set_text("line 1\nline 2"); + code_edit->set_caret_line(1); + code_edit->set_caret_column(2); + code_edit->add_caret(1, 1); + code_edit->backspace(); + CHECK(code_edit->get_text() == "line 1\nne 2"); + CHECK(code_edit->get_caret_count() == 1); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 0); - memdelete(code_edit); -} + // Backspace delete all text if all text is selected. + code_edit->set_text("line 1\nline 2\nline 3"); + code_edit->select_all(); + code_edit->backspace(); + CHECK(code_edit->get_text().is_empty()); + CHECK_FALSE(code_edit->has_selection()); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 0); -TEST_CASE("[SceneTree][CodeEdit] Duplicate Lines") { - CodeEdit *code_edit = memnew(CodeEdit); - SceneTree::get_singleton()->get_root()->add_child(code_edit); - code_edit->grab_focus(); + // Backspace at the beginning without selection has no effect. + code_edit->set_text("line 1\nline 2\nline 3"); + code_edit->set_caret_line(0); + code_edit->set_caret_column(0); + code_edit->backspace(); + CHECK(code_edit->get_text() == "line 1\nline 2\nline 3"); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 0); + } + + SUBCASE("[TextEdit] cut") { + DisplayServerMock *DS = (DisplayServerMock *)(DisplayServer::get_singleton()); + code_edit->set_line_folding_enabled(true); + + // Cut without a selection removes the entire line. + code_edit->set_text("this is\nsome\n"); + code_edit->set_caret_line(0); + code_edit->set_caret_column(6); + + code_edit->cut(); + CHECK(DS->clipboard_get() == "this is\n"); + CHECK(code_edit->get_text() == "some\n"); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 3); // In the default font, this is the same position. + + // Undo restores the cut text. + code_edit->undo(); + CHECK(DS->clipboard_get() == "this is\n"); + CHECK(code_edit->get_text() == "this is\nsome\n"); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 6); + + // Redo. + code_edit->redo(); + CHECK(DS->clipboard_get() == "this is\n"); + CHECK(code_edit->get_text() == "some\n"); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 3); + + // Cut unfolds the line. + code_edit->set_text("this is\n\tsome\n"); + code_edit->fold_line(0); + CHECK(code_edit->is_line_folded(0)); + + code_edit->cut(); + CHECK_FALSE(code_edit->is_line_folded(0)); + CHECK(DS->clipboard_get() == "this is\n"); + CHECK(code_edit->get_text() == "\tsome\n"); + CHECK(code_edit->get_caret_line() == 0); + + // Cut with a selection removes just the selection. + code_edit->set_text("this is\nsome\n"); + code_edit->select(0, 5, 0, 7); + + SEND_GUI_ACTION("ui_cut"); + CHECK(code_edit->get_viewport()->is_input_handled()); + CHECK(DS->clipboard_get() == "is"); + CHECK(code_edit->get_text() == "this \nsome\n"); + CHECK_FALSE(code_edit->get_caret_line()); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 5); + + // Cut does not change the text if not editable. Text is still added to clipboard. + code_edit->set_text("this is\nsome\n"); + code_edit->set_caret_line(0); + code_edit->set_caret_column(5); + + code_edit->set_editable(false); + code_edit->cut(); + code_edit->set_editable(true); + CHECK(DS->clipboard_get() == "this is\n"); + CHECK(code_edit->get_text() == "this is\nsome\n"); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 5); + + // Cut line with multiple carets. + code_edit->set_text("this is\nsome\n"); + code_edit->set_caret_line(0); + code_edit->set_caret_column(3); + code_edit->add_caret(0, 2); + code_edit->add_caret(0, 4); + code_edit->add_caret(2, 0); + + code_edit->cut(); + CHECK(DS->clipboard_get() == "this is\n\n"); + CHECK(code_edit->get_text() == "some"); + CHECK(code_edit->get_caret_count() == 3); + CHECK_FALSE(code_edit->has_selection(0)); + CHECK(code_edit->get_caret_line(0) == 0); + CHECK(code_edit->get_caret_column(0) == 2); // In the default font, this is the same position. + // The previous caret at index 1 was merged. + CHECK_FALSE(code_edit->has_selection(1)); + CHECK(code_edit->get_caret_line(1) == 0); + CHECK(code_edit->get_caret_column(1) == 3); // In the default font, this is the same position. + CHECK_FALSE(code_edit->has_selection(2)); + CHECK(code_edit->get_caret_line(2) == 0); + CHECK(code_edit->get_caret_column(2) == 4); + code_edit->remove_secondary_carets(); + + // Cut on the only line removes the contents. + code_edit->set_caret_line(0); + code_edit->set_caret_column(2); + + code_edit->cut(); + CHECK(DS->clipboard_get() == "some\n"); + CHECK(code_edit->get_text() == ""); + CHECK(code_edit->get_line_count() == 1); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 0); + + // Cut empty line. + code_edit->cut(); + CHECK(DS->clipboard_get() == "\n"); + CHECK(code_edit->get_text() == ""); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 0); + + // Cut multiple lines, in order. + code_edit->set_text("this is\nsome\ntext to\nbe\n\ncut"); + code_edit->set_caret_line(2); + code_edit->set_caret_column(7); + code_edit->add_caret(3, 0); + code_edit->add_caret(0, 2); + + code_edit->cut(); + CHECK(DS->clipboard_get() == "this is\ntext to\nbe\n"); + CHECK(code_edit->get_text() == "some\n\ncut"); + CHECK(code_edit->get_caret_count() == 2); + CHECK(code_edit->get_caret_line(0) == 1); + CHECK(code_edit->get_caret_column(0) == 0); + CHECK(code_edit->get_caret_line(1) == 0); + CHECK(code_edit->get_caret_column(1) == 2); + code_edit->remove_secondary_carets(); + + // Cut multiple selections, in order. Ignores regular carets. + code_edit->set_text("this is\nsome\ntext to\nbe\n\ncut"); + code_edit->add_caret(3, 0); + code_edit->add_caret(0, 2); + code_edit->add_caret(2, 0); + code_edit->select(1, 0, 1, 2, 0); + code_edit->select(3, 0, 4, 0, 1); + code_edit->select(0, 5, 0, 3, 2); + + code_edit->cut(); + CHECK(DS->clipboard_get() == "s \nso\nbe\n"); + CHECK(code_edit->get_text() == "thiis\nme\ntext to\n\ncut"); + CHECK(code_edit->get_caret_count() == 4); + CHECK_FALSE(code_edit->has_selection()); + CHECK(code_edit->get_caret_line(0) == 1); + CHECK(code_edit->get_caret_column(0) == 0); + CHECK(code_edit->get_caret_line(1) == 3); + CHECK(code_edit->get_caret_column(1) == 0); + CHECK(code_edit->get_caret_line(2) == 0); + CHECK(code_edit->get_caret_column(2) == 3); + CHECK(code_edit->get_caret_line(3) == 2); + CHECK(code_edit->get_caret_column(3) == 0); + } + + SUBCASE("[SceneTree][CodeEdit] new line") { + // Add a new line. + code_edit->set_text("test new line"); + code_edit->set_caret_line(0); + code_edit->set_caret_column(13); + SEND_GUI_ACTION("ui_text_newline"); + CHECK(code_edit->get_line(0) == "test new line"); + CHECK(code_edit->get_line(1) == ""); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 0); + + // Split line with new line. + code_edit->set_text("test new line"); + code_edit->set_caret_line(0); + code_edit->set_caret_column(5); + SEND_GUI_ACTION("ui_text_newline"); + CHECK(code_edit->get_line(0) == "test "); + CHECK(code_edit->get_line(1) == "new line"); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 0); + + // Delete selection and split with new line. + code_edit->set_text("test new line"); + code_edit->select(0, 0, 0, 5); + SEND_GUI_ACTION("ui_text_newline"); + CHECK(code_edit->get_line(0) == ""); + CHECK(code_edit->get_line(1) == "new line"); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 0); + + // Blank new line below with selection should not split. + code_edit->set_text("test new line"); + code_edit->select(0, 0, 0, 5); + SEND_GUI_ACTION("ui_text_newline_blank"); + CHECK(code_edit->get_line(0) == "test new line"); + CHECK(code_edit->get_line(1) == ""); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 0); + + // Blank new line above with selection should not split. + code_edit->set_text("test new line"); + code_edit->select(0, 0, 0, 5); + SEND_GUI_ACTION("ui_text_newline_above"); + CHECK(code_edit->get_line(0) == ""); + CHECK(code_edit->get_line(1) == "test new line"); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 0); + + // Multiple new lines with multiple carets. + code_edit->set_text("test new line"); + code_edit->set_caret_line(0); + code_edit->set_caret_column(5); + code_edit->add_caret(0, 8); + SEND_GUI_ACTION("ui_text_newline"); + CHECK(code_edit->get_line(0) == "test "); + CHECK(code_edit->get_line(1) == "new"); + CHECK(code_edit->get_line(2) == " line"); + CHECK(code_edit->get_caret_count() == 2); + CHECK(code_edit->get_caret_line(0) == 1); + CHECK(code_edit->get_caret_column(0) == 0); + CHECK(code_edit->get_caret_line(1) == 2); + CHECK(code_edit->get_caret_column(1) == 0); + + // Multiple blank new lines with multiple carets. + code_edit->set_text("test new line"); + code_edit->remove_secondary_carets(); + code_edit->set_caret_line(0); + code_edit->set_caret_column(5); + code_edit->add_caret(0, 8); + SEND_GUI_ACTION("ui_text_newline_blank"); + CHECK(code_edit->get_line(0) == "test new line"); + CHECK(code_edit->get_line(1) == ""); + CHECK(code_edit->get_line(2) == ""); + CHECK(code_edit->get_caret_count() == 2); + CHECK(code_edit->get_caret_line(0) == 2); + CHECK(code_edit->get_caret_column(0) == 0); + CHECK(code_edit->get_caret_line(1) == 1); + CHECK(code_edit->get_caret_column(1) == 0); + + // Multiple new lines above with multiple carets. + code_edit->set_text("test new line"); + code_edit->remove_secondary_carets(); + code_edit->set_caret_line(0); + code_edit->set_caret_column(5); + code_edit->add_caret(0, 8); + SEND_GUI_ACTION("ui_text_newline_above"); + CHECK(code_edit->get_line(0) == ""); + CHECK(code_edit->get_line(1) == ""); + CHECK(code_edit->get_line(2) == "test new line"); + CHECK(code_edit->get_caret_count() == 2); + CHECK(code_edit->get_caret_line(0) == 0); + CHECK(code_edit->get_caret_column(0) == 0); + CHECK(code_edit->get_caret_line(1) == 1); + CHECK(code_edit->get_caret_column(1) == 0); + + // See '[CodeEdit] auto indent' tests for tests about new line with indentation. + } + + SUBCASE("[SceneTree][CodeEdit] move lines up") { + code_edit->set_text("test\nlines\nto\n\nmove\naround"); + + // Move line up with caret on it. + code_edit->set_caret_line(2); + code_edit->set_caret_column(1); + code_edit->move_lines_up(); + CHECK(code_edit->get_text() == "test\nto\nlines\n\nmove\naround"); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 1); + + // Undo. + code_edit->undo(); + CHECK(code_edit->get_text() == "test\nlines\nto\n\nmove\naround"); + CHECK(code_edit->get_caret_line() == 2); + CHECK(code_edit->get_caret_column() == 1); + + // Redo. + code_edit->redo(); + CHECK(code_edit->get_text() == "test\nto\nlines\n\nmove\naround"); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 1); + + // Does nothing at the first line. + code_edit->set_text("test\nlines\nto\n\nmove\naround"); + code_edit->set_caret_line(0); + code_edit->set_caret_column(1); + code_edit->move_lines_up(); + CHECK(code_edit->get_text() == "test\nlines\nto\n\nmove\naround"); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 1); + + // Works on empty line. + code_edit->set_text("test\nlines\nto\n\nmove\naround"); + code_edit->set_caret_line(3); + code_edit->set_caret_column(0); + code_edit->move_lines_up(); + CHECK(code_edit->get_text() == "test\nlines\n\nto\nmove\naround"); + CHECK(code_edit->get_caret_line() == 2); + CHECK(code_edit->get_caret_column() == 0); + + // Move multiple lines up with selection. + code_edit->set_text("test\nlines\nto\n\nmove\naround"); + code_edit->select(4, 0, 5, 1); + code_edit->move_lines_up(); + CHECK(code_edit->get_text() == "test\nlines\nto\nmove\naround\n"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 3); + CHECK(code_edit->get_selection_origin_column() == 0); + CHECK(code_edit->get_caret_line() == 4); + CHECK(code_edit->get_caret_column() == 1); + + // Does not affect line with selection end at column 0. + code_edit->set_text("test\nlines\nto\n\nmove\naround"); + code_edit->select(4, 0, 5, 0); + code_edit->move_lines_up(); + CHECK(code_edit->get_text() == "test\nlines\nto\nmove\n\naround"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 3); + CHECK(code_edit->get_selection_origin_column() == 0); + CHECK(code_edit->get_caret_line() == 4); + CHECK(code_edit->get_caret_column() == 0); + + // Move multiple lines up with selection, right to left selection. + code_edit->set_text("test\nlines\nto\n\nmove\naround"); + code_edit->select(5, 2, 4, 1); + code_edit->move_lines_up(); + CHECK(code_edit->get_text() == "test\nlines\nto\nmove\naround\n"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 4); + CHECK(code_edit->get_selection_origin_column() == 2); + CHECK(code_edit->get_caret_line() == 3); + CHECK(code_edit->get_caret_column() == 1); + + // Move multiple lines with multiple carets. A line with multiple carets is only moved once. + code_edit->set_text("test\nlines\nto\n\nmove\naround"); + code_edit->select(5, 2, 5, 4); + code_edit->add_caret(4, 0); + code_edit->add_caret(4, 4); + code_edit->move_lines_up(); + CHECK(code_edit->get_text() == "test\nlines\nto\nmove\naround\n"); + CHECK(code_edit->get_caret_count() == 3); + CHECK(code_edit->has_selection(0)); + CHECK(code_edit->get_selection_origin_line(0) == 4); + CHECK(code_edit->get_selection_origin_column(0) == 2); + CHECK(code_edit->get_caret_line(0) == 4); + CHECK(code_edit->get_caret_column(0) == 4); + CHECK_FALSE(code_edit->has_selection(1)); + CHECK(code_edit->get_caret_line(1) == 3); + CHECK(code_edit->get_caret_column(1) == 0); + CHECK_FALSE(code_edit->has_selection(2)); + CHECK(code_edit->get_caret_line(2) == 3); + CHECK(code_edit->get_caret_column(2) == 4); + + // Move multiple separate lines with multiple selections. + code_edit->remove_secondary_carets(); + code_edit->set_text("test\nlines\nto\n\nmove\naround"); + code_edit->select(2, 2, 1, 4); + code_edit->add_caret(5, 0); + code_edit->select(5, 0, 5, 1, 1); + code_edit->move_lines_up(); + CHECK(code_edit->get_text() == "lines\nto\ntest\n\naround\nmove"); + CHECK(code_edit->get_caret_count() == 2); + CHECK(code_edit->has_selection(0)); + CHECK(code_edit->get_selection_origin_line(0) == 1); + CHECK(code_edit->get_selection_origin_column(0) == 2); + CHECK(code_edit->get_caret_line(0) == 0); + CHECK(code_edit->get_caret_column(0) == 4); + CHECK(code_edit->has_selection(1)); + CHECK(code_edit->get_selection_origin_line(1) == 4); + CHECK(code_edit->get_selection_origin_column(1) == 0); + CHECK(code_edit->get_caret_line(1) == 4); + CHECK(code_edit->get_caret_column(1) == 1); + } + + SUBCASE("[SceneTree][CodeEdit] move lines down") { + code_edit->set_text("test\nlines\nto\n\nmove\naround"); + + // Move line down with caret on it. + code_edit->set_caret_line(1); + code_edit->set_caret_column(1); + code_edit->move_lines_down(); + CHECK(code_edit->get_text() == "test\nto\nlines\n\nmove\naround"); + CHECK(code_edit->get_caret_line() == 2); + CHECK(code_edit->get_caret_column() == 1); + + // Undo. + code_edit->undo(); + CHECK(code_edit->get_text() == "test\nlines\nto\n\nmove\naround"); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 1); + + // Redo. + code_edit->redo(); + CHECK(code_edit->get_text() == "test\nto\nlines\n\nmove\naround"); + CHECK(code_edit->get_caret_line() == 2); + CHECK(code_edit->get_caret_column() == 1); + + // Does nothing at the last line. + code_edit->set_text("test\nlines\nto\n\nmove\naround"); + code_edit->set_caret_line(5); + code_edit->set_caret_column(1); + code_edit->move_lines_down(); + CHECK(code_edit->get_text() == "test\nlines\nto\n\nmove\naround"); + CHECK(code_edit->get_caret_line() == 5); + CHECK(code_edit->get_caret_column() == 1); + + // Works on empty line. + code_edit->set_text("test\nlines\nto\n\nmove\naround"); + code_edit->set_caret_line(3); + code_edit->set_caret_column(0); + code_edit->move_lines_down(); + CHECK(code_edit->get_text() == "test\nlines\nto\nmove\n\naround"); + CHECK(code_edit->get_caret_line() == 4); + CHECK(code_edit->get_caret_column() == 0); + + // Move multiple lines down with selection. + code_edit->set_text("test\nlines\nto\n\nmove\naround"); + code_edit->select(1, 0, 2, 1); + code_edit->move_lines_down(); + CHECK(code_edit->get_text() == "test\n\nlines\nto\nmove\naround"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 2); + CHECK(code_edit->get_selection_origin_column() == 0); + CHECK(code_edit->get_caret_line() == 3); + CHECK(code_edit->get_caret_column() == 1); + + // Does not affect line with selection end at column 0. + code_edit->set_text("test\nlines\nto\n\nmove\naround"); + code_edit->select(1, 0, 2, 0); + code_edit->move_lines_down(); + CHECK(code_edit->get_text() == "test\nto\nlines\n\nmove\naround"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 2); + CHECK(code_edit->get_selection_origin_column() == 0); + CHECK(code_edit->get_caret_line() == 3); + CHECK(code_edit->get_caret_column() == 0); + + // Move multiple lines down with selection, right to left selection. + code_edit->set_text("test\nlines\nto\n\nmove\naround"); + code_edit->select(2, 2, 1, 1); + code_edit->move_lines_down(); + CHECK(code_edit->get_text() == "test\n\nlines\nto\nmove\naround"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 3); + CHECK(code_edit->get_selection_origin_column() == 2); + CHECK(code_edit->get_caret_line() == 2); + CHECK(code_edit->get_caret_column() == 1); + + // Move multiple lines with multiple carets. A line with multiple carets is only moved once. + code_edit->set_text("test\nlines\nto\n\nmove\naround"); + code_edit->select(1, 2, 1, 4); + code_edit->add_caret(0, 0); + code_edit->add_caret(0, 1); + code_edit->move_lines_down(); + CHECK(code_edit->get_text() == "to\ntest\nlines\n\nmove\naround"); + CHECK(code_edit->get_caret_count() == 3); + CHECK(code_edit->has_selection(0)); + CHECK(code_edit->get_selection_origin_line(0) == 2); + CHECK(code_edit->get_selection_origin_column(0) == 2); + CHECK(code_edit->get_caret_line(0) == 2); + CHECK(code_edit->get_caret_column(0) == 4); + CHECK_FALSE(code_edit->has_selection(1)); + CHECK(code_edit->get_caret_line(1) == 1); + CHECK(code_edit->get_caret_column(1) == 0); + CHECK_FALSE(code_edit->has_selection(2)); + CHECK(code_edit->get_caret_line(2) == 1); + CHECK(code_edit->get_caret_column(2) == 1); + + // Move multiple separate lines with multiple selections. + code_edit->remove_secondary_carets(); + code_edit->set_text("test\nlines\nto\n\nmove\naround"); + code_edit->select(0, 2, 1, 4); + code_edit->add_caret(4, 0); + code_edit->select(4, 0, 4, 2, 1); + code_edit->move_lines_down(); + CHECK(code_edit->get_text() == "to\ntest\nlines\n\naround\nmove"); + CHECK(code_edit->get_caret_count() == 2); + CHECK(code_edit->has_selection(0)); + CHECK(code_edit->get_selection_origin_line(0) == 1); + CHECK(code_edit->get_selection_origin_column(0) == 2); + CHECK(code_edit->get_caret_line(0) == 2); + CHECK(code_edit->get_caret_column(0) == 4); + CHECK(code_edit->has_selection(1)); + CHECK(code_edit->get_selection_origin_line(1) == 5); + CHECK(code_edit->get_selection_origin_column(1) == 0); + CHECK(code_edit->get_caret_line(1) == 5); + CHECK(code_edit->get_caret_column(1) == 2); + } + + SUBCASE("[SceneTree][CodeEdit] delete lines") { + code_edit->set_text("test\nlines\nto\n\ndelete"); + + // Delete line with caret on it. + code_edit->set_caret_line(1); + code_edit->set_caret_column(1); + code_edit->delete_lines(); + CHECK(code_edit->get_text() == "test\nto\n\ndelete"); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 1); + + // Undo. + code_edit->undo(); + CHECK(code_edit->get_text() == "test\nlines\nto\n\ndelete"); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 1); + + // Redo. + code_edit->redo(); + CHECK(code_edit->get_text() == "test\nto\n\ndelete"); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 1); + + // Delete empty line. + code_edit->set_caret_line(2); + code_edit->set_caret_column(0); + code_edit->delete_lines(); + CHECK(code_edit->get_text() == "test\nto\ndelete"); + CHECK(code_edit->get_caret_line() == 2); + CHECK(code_edit->get_caret_column() == 0); + + // Deletes only one line when there are multiple carets on it. Carets move down and the column gets clamped. + code_edit->set_caret_line(0); + code_edit->set_caret_column(0); + code_edit->add_caret(0, 1); + code_edit->add_caret(0, 4); + code_edit->delete_lines(); + CHECK(code_edit->get_text() == "to\ndelete"); + CHECK(code_edit->get_caret_count() == 3); + CHECK(code_edit->get_caret_line(0) == 0); + CHECK(code_edit->get_caret_column(0) == 0); + CHECK(code_edit->get_caret_line(1) == 0); + CHECK(code_edit->get_caret_column(1) == 1); + CHECK(code_edit->get_caret_line(2) == 0); + CHECK(code_edit->get_caret_column(2) == 2); + + // Delete multiple lines with selection. + code_edit->remove_secondary_carets(); + code_edit->set_text("test\nlines\nto\n\ndelete"); + code_edit->select(0, 1, 2, 1); + code_edit->delete_lines(); + CHECK(code_edit->get_text() == "\ndelete"); + CHECK_FALSE(code_edit->has_selection()); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 0); + + // Does not affect line with selection end at column 0. + code_edit->set_text("test\nlines\nto\n\ndelete"); + code_edit->select(0, 1, 1, 0); + code_edit->delete_lines(); + CHECK(code_edit->get_text() == "lines\nto\n\ndelete"); + CHECK_FALSE(code_edit->has_selection()); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 0); + + // Delete multiple lines with multiple carets. + code_edit->set_text("test\nlines\nto\n\ndelete"); + code_edit->set_caret_line(0); + code_edit->set_caret_column(2); + code_edit->add_caret(1, 0); + code_edit->add_caret(4, 5); + code_edit->delete_lines(); + CHECK(code_edit->get_text() == "to\n"); + CHECK(code_edit->get_caret_count() == 2); + CHECK(code_edit->get_caret_line(0) == 0); + CHECK(code_edit->get_caret_column(0) == 0); + CHECK(code_edit->get_caret_line(1) == 1); + CHECK(code_edit->get_caret_column(1) == 0); + + // Delete multiple separate lines with multiple selections. + code_edit->remove_secondary_carets(); + code_edit->set_text("test\nlines\nto\n\ndelete"); + code_edit->add_caret(4, 5); + code_edit->select(0, 1, 1, 1); + code_edit->select(5, 5, 4, 0, 1); + code_edit->delete_lines(); + CHECK(code_edit->get_text() == "to\n"); + CHECK_FALSE(code_edit->has_selection()); + CHECK(code_edit->get_caret_count() == 2); + CHECK(code_edit->get_caret_line(0) == 0); + CHECK(code_edit->get_caret_column(0) == 1); + CHECK(code_edit->get_caret_line(1) == 1); + CHECK(code_edit->get_caret_column(1) == 0); + + // Deletes contents when there is only one line. + code_edit->remove_secondary_carets(); + code_edit->set_text("test"); + code_edit->set_caret_line(0); + code_edit->set_caret_column(4); + code_edit->delete_lines(); + CHECK(code_edit->get_text() == ""); + CHECK_FALSE(code_edit->has_selection()); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_caret_column() == 0); + } + + SUBCASE("[SceneTree][CodeEdit] duplicate selection") { + code_edit->set_text("test\nlines\nto\n\nduplicate"); + + // Duplicate selected text. + code_edit->select(0, 1, 1, 2); + code_edit->duplicate_selection(); + CHECK(code_edit->get_text() == "test\nliest\nlines\nto\n\nduplicate"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 1); + CHECK(code_edit->get_selection_origin_column() == 2); + CHECK(code_edit->get_caret_line() == 2); + CHECK(code_edit->get_caret_column() == 2); + + // Undo. + code_edit->undo(); + CHECK(code_edit->get_text() == "test\nlines\nto\n\nduplicate"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_selection_origin_column() == 1); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 2); + + // Redo. + code_edit->redo(); + CHECK(code_edit->get_text() == "test\nliest\nlines\nto\n\nduplicate"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 1); + CHECK(code_edit->get_selection_origin_column() == 2); + CHECK(code_edit->get_caret_line() == 2); + CHECK(code_edit->get_caret_column() == 2); + + // Duplicate selected text, right to left selection. + code_edit->set_text("test\nlines\nto\n\nduplicate"); + code_edit->select(1, 1, 0, 2); + code_edit->duplicate_selection(); + CHECK(code_edit->get_text() == "test\nlst\nlines\nto\n\nduplicate"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 2); + CHECK(code_edit->get_selection_origin_column() == 1); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 1); + + // Duplicate line if there is no selection. + code_edit->deselect(); + code_edit->set_text("test\nlines\nto\n\nduplicate"); + code_edit->set_caret_line(1); + code_edit->set_caret_column(2); + code_edit->duplicate_selection(); + CHECK(code_edit->get_text() == "test\nlines\nlines\nto\n\nduplicate"); + CHECK_FALSE(code_edit->has_selection()); + CHECK(code_edit->get_caret_line() == 2); + CHECK(code_edit->get_caret_column() == 2); + + // Duplicate multiple lines. + code_edit->deselect(); + code_edit->set_text("test\nlines\nto\n\nduplicate"); + code_edit->set_caret_line(1); + code_edit->set_caret_column(2); + code_edit->add_caret(5, 0); + code_edit->add_caret(0, 4); + code_edit->duplicate_selection(); + CHECK(code_edit->get_text() == "test\ntest\nlines\nlines\nto\n\nduplicate\nduplicate"); + CHECK(code_edit->get_caret_count() == 3); + CHECK_FALSE(code_edit->has_selection()); + CHECK(code_edit->get_caret_line(0) == 3); + CHECK(code_edit->get_caret_column(0) == 2); + CHECK(code_edit->get_caret_line(1) == 7); + CHECK(code_edit->get_caret_column(1) == 0); + CHECK(code_edit->get_caret_line(2) == 1); + CHECK(code_edit->get_caret_column(2) == 4); + + // Duplicate multiple separate selections. + code_edit->remove_secondary_carets(); + code_edit->set_text("test\nlines\nto\n\nduplicate"); + code_edit->add_caret(4, 4); + code_edit->add_caret(0, 1); + code_edit->add_caret(0, 4); + code_edit->select(2, 0, 2, 1, 0); + code_edit->select(3, 0, 4, 4, 1); + code_edit->select(0, 1, 0, 0, 2); + code_edit->select(0, 2, 0, 4, 3); + code_edit->duplicate_selection(); + CHECK(code_edit->get_text() == "ttestst\nlines\ntto\n\ndupl\nduplicate"); + CHECK(code_edit->get_caret_count() == 4); + CHECK(code_edit->has_selection(0)); + CHECK(code_edit->get_selection_origin_line(0) == 2); + CHECK(code_edit->get_selection_origin_column(0) == 1); + CHECK(code_edit->get_caret_line(0) == 2); + CHECK(code_edit->get_caret_column(0) == 2); + CHECK(code_edit->has_selection(1)); + CHECK(code_edit->get_selection_origin_line(1) == 4); + CHECK(code_edit->get_selection_origin_column(1) == 4); + CHECK(code_edit->get_caret_line(1) == 5); + CHECK(code_edit->get_caret_column(1) == 4); + CHECK(code_edit->has_selection(2)); + CHECK(code_edit->get_selection_origin_line(2) == 0); + CHECK(code_edit->get_selection_origin_column(2) == 2); + CHECK(code_edit->get_caret_line(2) == 0); + CHECK(code_edit->get_caret_column(2) == 1); + CHECK(code_edit->has_selection(3)); + CHECK(code_edit->get_selection_origin_line(3) == 0); + CHECK(code_edit->get_selection_origin_column(3) == 5); + CHECK(code_edit->get_caret_line(3) == 0); + CHECK(code_edit->get_caret_column(3) == 7); + + // Duplicate adjacent selections. + code_edit->remove_secondary_carets(); + code_edit->set_text("test\nlines\nto\n\nduplicate"); + code_edit->add_caret(1, 2); + code_edit->select(1, 0, 1, 1, 0); + code_edit->select(1, 1, 1, 4, 1); + code_edit->duplicate_selection(); + CHECK(code_edit->get_text() == "test\nllineines\nto\n\nduplicate"); + CHECK(code_edit->get_caret_count() == 2); + CHECK(code_edit->has_selection(0)); + CHECK(code_edit->get_selection_origin_line(0) == 1); + CHECK(code_edit->get_selection_origin_column(0) == 1); + CHECK(code_edit->get_caret_line(0) == 1); + CHECK(code_edit->get_caret_column(0) == 2); + CHECK(code_edit->has_selection(1)); + CHECK(code_edit->get_selection_origin_line(1) == 1); + CHECK(code_edit->get_selection_origin_column(1) == 5); + CHECK(code_edit->get_caret_line(1) == 1); + CHECK(code_edit->get_caret_column(1) == 8); + + // Duplicate lines then duplicate selections when there are both selections and non-selections. + code_edit->remove_secondary_carets(); + code_edit->set_text("test duplicate"); + code_edit->select(0, 14, 0, 13, 0); + code_edit->add_caret(0, 8); + code_edit->add_caret(0, 4); + code_edit->select(0, 2, 0, 4, 2); + code_edit->duplicate_selection(); + CHECK(code_edit->get_text() == "test duplicate\ntestst duplicatee"); + CHECK(code_edit->get_caret_count() == 3); + CHECK(code_edit->has_selection(0)); + CHECK(code_edit->get_selection_origin_line(0) == 1); + CHECK(code_edit->get_selection_origin_column(0) == 17); + CHECK(code_edit->get_caret_line(0) == 1); + CHECK(code_edit->get_caret_column(0) == 16); + CHECK_FALSE(code_edit->has_selection(1)); + CHECK(code_edit->get_caret_line(1) == 1); + CHECK(code_edit->get_caret_column(1) == 10); + CHECK(code_edit->has_selection(2)); + CHECK(code_edit->get_selection_origin_line(2) == 1); + CHECK(code_edit->get_selection_origin_column(2) == 4); + CHECK(code_edit->get_caret_line(2) == 1); + CHECK(code_edit->get_caret_column(2) == 6); + } + + SUBCASE("[SceneTree][CodeEdit] duplicate lines") { + String reset_text = R"(extends Node + +func _ready(): + var a := len(OS.get_cmdline_args()) + var b := get_child_count() + var c := a + b + for i in range(c): + print("This is the solution: ", sin(i)) + var pos = get_index() - 1 + print("Make sure this exits: %b" % pos) +)"; + + code_edit->set_text(reset_text); - code_edit->set_text(R"(extends Node + // Duplicate a single line without selection. + code_edit->set_caret_line(0); + code_edit->duplicate_lines(); + CHECK(code_edit->get_line(0) == "extends Node"); + CHECK(code_edit->get_line(1) == "extends Node"); + CHECK(code_edit->get_line(2) == ""); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 0); + + // Duplicate multiple lines with selection. + code_edit->set_text(reset_text); + code_edit->select(4, 8, 6, 15); + code_edit->duplicate_lines(); + CHECK(code_edit->get_text() == R"(extends Node + +func _ready(): + var a := len(OS.get_cmdline_args()) + var b := get_child_count() + var c := a + b + for i in range(c): + var b := get_child_count() + var c := a + b + for i in range(c): + print("This is the solution: ", sin(i)) + var pos = get_index() - 1 + print("Make sure this exits: %b" % pos) +)"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 7); + CHECK(code_edit->get_selection_origin_column() == 8); + CHECK(code_edit->get_caret_line() == 9); + CHECK(code_edit->get_caret_column() == 15); + + // Duplicate multiple lines with right to left selection. + code_edit->set_text(reset_text); + code_edit->select(6, 15, 4, 8); + code_edit->duplicate_lines(); + CHECK(code_edit->get_text() == R"(extends Node func _ready(): var a := len(OS.get_cmdline_args()) var b := get_child_count() var c := a + b for i in range(c): + var b := get_child_count() + var c := a + b + for i in range(c): print("This is the solution: ", sin(i)) var pos = get_index() - 1 print("Make sure this exits: %b" % pos) )"); + CHECK(code_edit->has_selection()); + CHECK(code_edit->get_selection_origin_line() == 9); + CHECK(code_edit->get_selection_origin_column() == 15); + CHECK(code_edit->get_caret_line() == 7); + CHECK(code_edit->get_caret_column() == 8); + + // Duplicate single lines with multiple carets. Multiple carets on a single line only duplicate once. + code_edit->remove_secondary_carets(); + code_edit->deselect(); + code_edit->set_text(reset_text); + code_edit->set_caret_line(3); + code_edit->set_caret_column(1); + code_edit->add_caret(5, 1); + code_edit->add_caret(5, 5); + code_edit->add_caret(4, 2); + code_edit->duplicate_lines(); + CHECK(code_edit->get_text() == R"(extends Node - /* Duplicate a single line without selection. */ - code_edit->set_caret_line(0); - code_edit->duplicate_lines(); - CHECK(code_edit->get_line(0) == "extends Node"); - CHECK(code_edit->get_line(1) == "extends Node"); - CHECK(code_edit->get_line(2) == ""); - - /* Duplicate multiple lines with selection. */ - code_edit->set_caret_line(6); - code_edit->set_caret_column(15); - code_edit->select(4, 8, 6, 15); - code_edit->duplicate_lines(); - CHECK(code_edit->get_line(6) == "\tvar c := a + b"); - CHECK(code_edit->get_line(7) == "\tvar a := len(OS.get_cmdline_args())"); - CHECK(code_edit->get_line(8) == "\tvar b := get_child_count()"); - CHECK(code_edit->get_line(9) == "\tvar c := a + b"); - CHECK(code_edit->get_line(10) == "\tfor i in range(c):"); - - /* Duplicate single lines with multiple carets. */ - code_edit->deselect(); - code_edit->set_caret_line(10); - code_edit->set_caret_column(1); - code_edit->add_caret(11, 2); - code_edit->add_caret(12, 1); - code_edit->duplicate_lines(); - CHECK(code_edit->get_line(9) == "\tvar c := a + b"); - CHECK(code_edit->get_line(10) == "\tfor i in range(c):"); - CHECK(code_edit->get_line(11) == "\tfor i in range(c):"); - CHECK(code_edit->get_line(12) == "\t\tprint(\"This is the solution: \", sin(i))"); - CHECK(code_edit->get_line(13) == "\t\tprint(\"This is the solution: \", sin(i))"); - CHECK(code_edit->get_line(14) == "\tvar pos = get_index() - 1"); - CHECK(code_edit->get_line(15) == "\tvar pos = get_index() - 1"); - CHECK(code_edit->get_line(16) == "\tprint(\"Make sure this exits: %b\" % pos)"); - - /* Duplicate multiple lines with multiple carets. */ - code_edit->select(0, 0, 1, 2, 0); - code_edit->select(3, 0, 4, 2, 1); - code_edit->select(16, 0, 17, 0, 2); - code_edit->set_caret_line(1, false, true, 0, 0); - code_edit->set_caret_column(2, false, 0); - code_edit->set_caret_line(4, false, true, 0, 1); - code_edit->set_caret_column(2, false, 1); - code_edit->set_caret_line(17, false, true, 0, 2); - code_edit->set_caret_column(0, false, 2); - code_edit->duplicate_lines(); - CHECK(code_edit->get_line(1) == "extends Node"); - CHECK(code_edit->get_line(2) == "extends Node"); - CHECK(code_edit->get_line(3) == "extends Node"); - CHECK(code_edit->get_line(4) == ""); - CHECK(code_edit->get_line(6) == "\tvar a := len(OS.get_cmdline_args())"); - CHECK(code_edit->get_line(7) == "func _ready():"); - CHECK(code_edit->get_line(8) == "\tvar a := len(OS.get_cmdline_args())"); - CHECK(code_edit->get_line(9) == "\tvar b := get_child_count()"); - CHECK(code_edit->get_line(20) == "\tprint(\"Make sure this exits: %b\" % pos)"); - CHECK(code_edit->get_line(21) == ""); - CHECK(code_edit->get_line(22) == "\tprint(\"Make sure this exits: %b\" % pos)"); - CHECK(code_edit->get_line(23) == ""); +func _ready(): + var a := len(OS.get_cmdline_args()) + var a := len(OS.get_cmdline_args()) + var b := get_child_count() + var b := get_child_count() + var c := a + b + var c := a + b + for i in range(c): + print("This is the solution: ", sin(i)) + var pos = get_index() - 1 + print("Make sure this exits: %b" % pos) +)"); + CHECK(code_edit->get_caret_count() == 4); + CHECK_FALSE(code_edit->has_selection(0)); + CHECK(code_edit->get_caret_line(0) == 4); + CHECK(code_edit->get_caret_column(0) == 1); + CHECK_FALSE(code_edit->has_selection(1)); + CHECK(code_edit->get_caret_line(1) == 8); + CHECK(code_edit->get_caret_column(1) == 1); + CHECK_FALSE(code_edit->has_selection(2)); + CHECK(code_edit->get_caret_line(2) == 8); + CHECK(code_edit->get_caret_column(2) == 5); + CHECK_FALSE(code_edit->has_selection(3)); + CHECK(code_edit->get_caret_line(3) == 6); + CHECK(code_edit->get_caret_column(3) == 2); + + // Duplicate multiple lines with multiple selections. + code_edit->remove_secondary_carets(); + code_edit->set_text(reset_text); + code_edit->add_caret(4, 2); + code_edit->add_caret(6, 0); + code_edit->add_caret(7, 8); + code_edit->select(0, 0, 2, 5, 0); + code_edit->select(3, 0, 4, 2, 1); + code_edit->select(7, 1, 6, 0, 2); + code_edit->select(7, 3, 7, 8, 3); + code_edit->duplicate_lines(); + CHECK(code_edit->get_text() == R"(extends Node + +func _ready(): +extends Node + +func _ready(): + var a := len(OS.get_cmdline_args()) + var b := get_child_count() + var a := len(OS.get_cmdline_args()) + var b := get_child_count() + var c := a + b + for i in range(c): + print("This is the solution: ", sin(i)) + for i in range(c): + print("This is the solution: ", sin(i)) + var pos = get_index() - 1 + print("Make sure this exits: %b" % pos) +)"); + CHECK(code_edit->get_caret_count() == 4); + CHECK(code_edit->has_selection(0)); + CHECK(code_edit->get_selection_origin_line(0) == 3); + CHECK(code_edit->get_selection_origin_column(0) == 0); + CHECK(code_edit->get_caret_line(0) == 5); + CHECK(code_edit->get_caret_column(0) == 5); + + CHECK(code_edit->has_selection(1)); + CHECK(code_edit->get_selection_origin_line(1) == 8); + CHECK(code_edit->get_selection_origin_column(1) == 0); + CHECK(code_edit->get_caret_line(1) == 9); + CHECK(code_edit->get_caret_column(1) == 2); + + CHECK(code_edit->has_selection(2)); + CHECK(code_edit->get_selection_origin_line(2) == 14); + CHECK(code_edit->get_selection_origin_column(2) == 1); + CHECK(code_edit->get_caret_line(2) == 13); + CHECK(code_edit->get_caret_column(2) == 0); + + CHECK(code_edit->has_selection(3)); + CHECK(code_edit->get_selection_origin_line(3) == 14); + CHECK(code_edit->get_selection_origin_column(3) == 3); + CHECK(code_edit->get_caret_line(3) == 14); + CHECK(code_edit->get_caret_column(3) == 8); + } memdelete(code_edit); } diff --git a/tests/scene/test_curve_2d.h b/tests/scene/test_curve_2d.h index 099f6fefa9..1248632630 100644 --- a/tests/scene/test_curve_2d.h +++ b/tests/scene/test_curve_2d.h @@ -147,13 +147,19 @@ TEST_CASE("[Curve2D] Sampling") { CHECK(curve->samplef(1) == Vector2(0, 50)); } - SUBCASE("sample_baked") { + SUBCASE("sample_baked, cubic = false") { CHECK(curve->sample_baked(curve->get_closest_offset(Vector2(0, 0))) == Vector2(0, 0)); CHECK(curve->sample_baked(curve->get_closest_offset(Vector2(0, 25))) == Vector2(0, 25)); CHECK(curve->sample_baked(curve->get_closest_offset(Vector2(0, 50))) == Vector2(0, 50)); } - SUBCASE("sample_baked_with_rotation") { + SUBCASE("sample_baked, cubic = true") { + CHECK(curve->sample_baked(curve->get_closest_offset(Vector2(0, 0)), true) == Vector2(0, 0)); + CHECK(curve->sample_baked(curve->get_closest_offset(Vector2(0, 25)), true) == Vector2(0, 25)); + CHECK(curve->sample_baked(curve->get_closest_offset(Vector2(0, 50)), true) == Vector2(0, 50)); + } + + SUBCASE("sample_baked_with_rotation, cubic = false") { const real_t pi = 3.14159; const real_t half_pi = pi * 0.5; Ref<Curve2D> rot_curve = memnew(Curve2D); @@ -188,6 +194,41 @@ TEST_CASE("[Curve2D] Sampling") { CHECK(Math::is_equal_approx(t.get_rotation(), -half_pi)); } + SUBCASE("sample_baked_with_rotation, cubic = true") { + const real_t pi = 3.14159; + const real_t half_pi = pi * 0.5; + Ref<Curve2D> rot_curve = memnew(Curve2D); + Transform2D t; + + rot_curve->clear_points(); + rot_curve->add_point(Vector2()); + rot_curve->add_point(Vector2(50, 0)); + t = rot_curve->sample_baked_with_rotation(25, true); + CHECK(t.get_origin() == Vector2(25, 0)); + CHECK(Math::is_equal_approx(t.get_rotation(), 0)); + + rot_curve->clear_points(); + rot_curve->add_point(Vector2()); + rot_curve->add_point(Vector2(0, 50)); + t = rot_curve->sample_baked_with_rotation(25, true); + CHECK(t.get_origin() == Vector2(0, 25)); + CHECK(Math::is_equal_approx(t.get_rotation(), half_pi)); + + rot_curve->clear_points(); + rot_curve->add_point(Vector2()); + rot_curve->add_point(Vector2(-50, 0)); + t = rot_curve->sample_baked_with_rotation(25, true); + CHECK(t.get_origin() == Vector2(-25, 0)); + CHECK(Math::is_equal_approx(t.get_rotation(), pi)); + + rot_curve->clear_points(); + rot_curve->add_point(Vector2()); + rot_curve->add_point(Vector2(0, -50)); + t = rot_curve->sample_baked_with_rotation(25, true); + CHECK(t.get_origin() == Vector2(0, -25)); + CHECK(Math::is_equal_approx(t.get_rotation(), -half_pi)); + } + SUBCASE("get_closest_point") { CHECK(curve->get_closest_point(Vector2(0, 0)) == Vector2(0, 0)); CHECK(curve->get_closest_point(Vector2(0, 25)) == Vector2(0, 25)); diff --git a/tests/scene/test_curve_3d.h b/tests/scene/test_curve_3d.h index d73bb1ad35..2e60a9c6e6 100644 --- a/tests/scene/test_curve_3d.h +++ b/tests/scene/test_curve_3d.h @@ -177,12 +177,30 @@ TEST_CASE("[Curve3D] Sampling") { CHECK(curve->sample_baked(curve->get_closest_offset(Vector3(0, 50, 0)), true) == Vector3(0, 50, 0)); } - SUBCASE("sample_baked_with_rotation") { + SUBCASE("sample_baked_with_rotation, cubic = false, p_apply_tilt = false") { CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 0, 0))) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 0, 0))); CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 25, 0))) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 25, 0))); CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 50, 0))) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 50, 0))); } + SUBCASE("sample_baked_with_rotation, cubic = false, p_apply_tilt = true") { + CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 0, 0)), false, true) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 0, 0))); + CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 25, 0)), false, true) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 25, 0))); + CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 50, 0)), false, true) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 50, 0))); + } + + SUBCASE("sample_baked_with_rotation, cubic = true, p_apply_tilt = false") { + CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 0, 0)), true) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 0, 0))); + CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 25, 0)), true) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 25, 0))); + CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 50, 0)), true) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 50, 0))); + } + + SUBCASE("sample_baked_with_rotation, cubic = true, p_apply_tilt = true") { + CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 0, 0)), true, true) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 0, 0))); + CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 25, 0)), true, true) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 25, 0))); + CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 50, 0)), true, true) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 50, 0))); + } + SUBCASE("sample_baked_tilt") { CHECK(curve->sample_baked_tilt(curve->get_closest_offset(Vector3(0, 0, 0))) == 0); CHECK(curve->sample_baked_tilt(curve->get_closest_offset(Vector3(0, 25, 0))) == 0); diff --git a/tests/scene/test_graph_node.h b/tests/scene/test_graph_node.h new file mode 100644 index 0000000000..72b8b682c9 --- /dev/null +++ b/tests/scene/test_graph_node.h @@ -0,0 +1,59 @@ +/**************************************************************************/ +/* test_graph_node.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_GRAPH_NODE_H +#define TEST_GRAPH_NODE_H + +#include "scene/gui/graph_node.h" +#include "scene/main/window.h" + +#include "tests/test_macros.h" + +namespace TestGraphNode { + +TEST_CASE("[GraphNode][SceneTree]") { + SUBCASE("[GraphNode] Graph Node only child on delete should not cause error.") { + // Setup. + GraphNode *test_node = memnew(GraphNode); + test_child->set_name("Graph Node"); + Control *test_child = memnew(Control); + test_child->set_name("child"); + test_node->add_child(test_child); + + // Test. + CHECK_NOTHROW_MESSAGE(test_node->remove_child(test_child)); + + memdelete(test_node); + } +} + +} // namespace TestGraphNode + +#endif // TEST_GRAPH_NODE_H diff --git a/tests/scene/test_instance_placeholder.h b/tests/scene/test_instance_placeholder.h new file mode 100644 index 0000000000..8e8cf7c9df --- /dev/null +++ b/tests/scene/test_instance_placeholder.h @@ -0,0 +1,532 @@ +/**************************************************************************/ +/* test_instance_placeholder.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef TEST_INSTANCE_PLACEHOLDER_H +#define TEST_INSTANCE_PLACEHOLDER_H + +#include "scene/main/instance_placeholder.h" +#include "scene/resources/packed_scene.h" + +#include "tests/test_macros.h" + +class _TestInstancePlaceholderNode : public Node { + GDCLASS(_TestInstancePlaceholderNode, Node); + +protected: + static void _bind_methods() { + ClassDB::bind_method(D_METHOD("set_int_property", "int_property"), &_TestInstancePlaceholderNode::set_int_property); + ClassDB::bind_method(D_METHOD("get_int_property"), &_TestInstancePlaceholderNode::get_int_property); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "int_property"), "set_int_property", "get_int_property"); + + ClassDB::bind_method(D_METHOD("set_reference_property", "reference_property"), &_TestInstancePlaceholderNode::set_reference_property); + ClassDB::bind_method(D_METHOD("get_reference_property"), &_TestInstancePlaceholderNode::get_reference_property); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "reference_property", PROPERTY_HINT_NODE_TYPE), "set_reference_property", "get_reference_property"); + + ClassDB::bind_method(D_METHOD("set_reference_array_property", "reference_array_property"), &_TestInstancePlaceholderNode::set_reference_array_property); + ClassDB::bind_method(D_METHOD("get_reference_array_property"), &_TestInstancePlaceholderNode::get_reference_array_property); + + // The hint string value "24/34:Node" is determined from existing PackedScenes with typed Array properties. + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "reference_array_property", PROPERTY_HINT_TYPE_STRING, "24/34:Node"), "set_reference_array_property", "get_reference_array_property"); + } + +public: + int int_property = 0; + + void set_int_property(int p_int) { + int_property = p_int; + } + + int get_int_property() const { + return int_property; + } + + Variant reference_property; + + void set_reference_property(const Variant &p_node) { + reference_property = p_node; + } + + Variant get_reference_property() const { + return reference_property; + } + + Array reference_array_property; + + void set_reference_array_property(const Array &p_array) { + reference_array_property = p_array; + } + + Array get_reference_array_property() const { + return reference_array_property; + } + + _TestInstancePlaceholderNode() { + reference_array_property.set_typed(Variant::OBJECT, "Node", Variant()); + } +}; + +namespace TestInstancePlaceholder { + +TEST_CASE("[SceneTree][InstancePlaceholder] Instantiate from placeholder with no overrides") { + GDREGISTER_CLASS(_TestInstancePlaceholderNode); + + SUBCASE("with non-node values") { + InstancePlaceholder *ip = memnew(InstancePlaceholder); + ip->set_name("TestScene"); + Node *root = memnew(Node); + SceneTree::get_singleton()->get_root()->add_child(root); + + root->add_child(ip); + // Create a scene to instance. + _TestInstancePlaceholderNode *scene = memnew(_TestInstancePlaceholderNode); + scene->set_int_property(12); + + // Pack the scene. + PackedScene *packed_scene = memnew(PackedScene); + const Error err = packed_scene->pack(scene); + REQUIRE(err == OK); + + // Instantiate the scene. + _TestInstancePlaceholderNode *created = Object::cast_to<_TestInstancePlaceholderNode>(ip->create_instance(true, packed_scene)); + REQUIRE(created != nullptr); + CHECK(created->get_name() == "TestScene"); + CHECK(created->get_int_property() == 12); + + root->queue_free(); + memdelete(scene); + } + + SUBCASE("with node value") { + InstancePlaceholder *ip = memnew(InstancePlaceholder); + ip->set_name("TestScene"); + Node *root = memnew(Node); + SceneTree::get_singleton()->get_root()->add_child(root); + + root->add_child(ip); + // Create a scene to instance. + _TestInstancePlaceholderNode *scene = memnew(_TestInstancePlaceholderNode); + Node *referenced = memnew(Node); + scene->add_child(referenced); + referenced->set_owner(scene); + scene->set_reference_property(referenced); + // Pack the scene. + PackedScene *packed_scene = memnew(PackedScene); + const Error err = packed_scene->pack(scene); + REQUIRE(err == OK); + + // Instantiate the scene. + _TestInstancePlaceholderNode *created = Object::cast_to<_TestInstancePlaceholderNode>(ip->create_instance(true, packed_scene)); + REQUIRE(created != nullptr); + CHECK(created->get_name() == "TestScene"); + CHECK(created->get_child_count() == 1); + CHECK(created->get_reference_property().identity_compare(created->get_child(0, false))); + CHECK_FALSE(created->get_reference_property().identity_compare(referenced)); + + root->queue_free(); + memdelete(scene); + } + + SUBCASE("with node-array value") { + InstancePlaceholder *ip = memnew(InstancePlaceholder); + ip->set_name("TestScene"); + Node *root = memnew(Node); + SceneTree::get_singleton()->get_root()->add_child(root); + + root->add_child(ip); + // Create a scene to instance. + _TestInstancePlaceholderNode *scene = memnew(_TestInstancePlaceholderNode); + Node *referenced1 = memnew(Node); + Node *referenced2 = memnew(Node); + scene->add_child(referenced1); + scene->add_child(referenced2); + referenced1->set_owner(scene); + referenced2->set_owner(scene); + Array node_array; + node_array.set_typed(Variant::OBJECT, "Node", Variant()); + node_array.push_back(referenced1); + node_array.push_back(referenced2); + scene->set_reference_array_property(node_array); + // Pack the scene. + PackedScene *packed_scene = memnew(PackedScene); + const Error err = packed_scene->pack(scene); + REQUIRE(err == OK); + + // Instantiate the scene. + _TestInstancePlaceholderNode *created = Object::cast_to<_TestInstancePlaceholderNode>(ip->create_instance(true, packed_scene)); + REQUIRE(created != nullptr); + CHECK(created->get_name() == "TestScene"); + CHECK(created->get_child_count() == 2); + Array created_array = created->get_reference_array_property(); + REQUIRE(created_array.size() == node_array.size()); + REQUIRE(created_array.size() == created->get_child_count()); + + // Iterate over all nodes, since the ordering is not guaranteed. + for (int i = 0; i < node_array.size(); i++) { + bool node_found = false; + for (int j = 0; j < created->get_child_count(); j++) { + if (created_array[i].identity_compare(created->get_child(j, true))) { + node_found = true; + } + } + CHECK(node_found); + } + root->queue_free(); + memdelete(scene); + } +} + +TEST_CASE("[SceneTree][InstancePlaceholder] Instantiate from placeholder with overrides") { + GDREGISTER_CLASS(_TestInstancePlaceholderNode); + + SUBCASE("with non-node values") { + InstancePlaceholder *ip = memnew(InstancePlaceholder); + Node *root = memnew(Node); + SceneTree::get_singleton()->get_root()->add_child(root); + + root->add_child(ip); + ip->set_name("TestScene"); + ip->set("int_property", 45); + // Create a scene to pack. + _TestInstancePlaceholderNode *scene = memnew(_TestInstancePlaceholderNode); + scene->set_int_property(12); + + // Pack the scene. + PackedScene *packed_scene = memnew(PackedScene); + packed_scene->pack(scene); + + // Instantiate the scene. + _TestInstancePlaceholderNode *created = Object::cast_to<_TestInstancePlaceholderNode>(ip->create_instance(true, packed_scene)); + REQUIRE(created != nullptr); + CHECK(created->get_int_property() == 45); + + root->queue_free(); + memdelete(scene); + } + + SUBCASE("with node values") { + InstancePlaceholder *ip = memnew(InstancePlaceholder); + ip->set_name("TestScene"); + Node *root = memnew(Node); + Node *overriding = memnew(Node); + SceneTree::get_singleton()->get_root()->add_child(root); + + root->add_child(ip); + root->add_child(overriding); + ip->set("reference_property", overriding); + // Create a scene to instance. + _TestInstancePlaceholderNode *scene = memnew(_TestInstancePlaceholderNode); + Node *referenced = memnew(Node); + scene->add_child(referenced); + referenced->set_owner(scene); + scene->set_reference_property(referenced); + // Pack the scene. + PackedScene *packed_scene = memnew(PackedScene); + const Error err = packed_scene->pack(scene); + REQUIRE(err == OK); + + // Instantiate the scene. + _TestInstancePlaceholderNode *created = Object::cast_to<_TestInstancePlaceholderNode>(ip->create_instance(true, packed_scene)); + REQUIRE(created != nullptr); + CHECK(created->get_name() == "TestScene"); + CHECK(created->get_child_count() == 1); + CHECK(created->get_reference_property().identity_compare(overriding)); + CHECK_FALSE(created->get_reference_property().identity_compare(referenced)); + + root->queue_free(); + memdelete(scene); + } + + SUBCASE("with node-array value") { + InstancePlaceholder *ip = memnew(InstancePlaceholder); + ip->set_name("TestScene"); + Node *root = memnew(Node); + SceneTree::get_singleton()->get_root()->add_child(root); + + Node *override1 = memnew(Node); + Node *override2 = memnew(Node); + Node *override3 = memnew(Node); + root->add_child(ip); + root->add_child(override1); + root->add_child(override2); + root->add_child(override3); + + Array override_node_array; + override_node_array.set_typed(Variant::OBJECT, "Node", Variant()); + override_node_array.push_back(override1); + override_node_array.push_back(override2); + override_node_array.push_back(override3); + + ip->set("reference_array_property", override_node_array); + + // Create a scene to instance. + _TestInstancePlaceholderNode *scene = memnew(_TestInstancePlaceholderNode); + Node *referenced1 = memnew(Node); + Node *referenced2 = memnew(Node); + + scene->add_child(referenced1); + scene->add_child(referenced2); + + referenced1->set_owner(scene); + referenced2->set_owner(scene); + Array referenced_array; + referenced_array.set_typed(Variant::OBJECT, "Node", Variant()); + referenced_array.push_back(referenced1); + referenced_array.push_back(referenced2); + + scene->set_reference_array_property(referenced_array); + // Pack the scene. + PackedScene *packed_scene = memnew(PackedScene); + const Error err = packed_scene->pack(scene); + REQUIRE(err == OK); + + // Instantiate the scene. + _TestInstancePlaceholderNode *created = Object::cast_to<_TestInstancePlaceholderNode>(ip->create_instance(true, packed_scene)); + REQUIRE(created != nullptr); + CHECK(created->get_name() == "TestScene"); + CHECK(created->get_child_count() == 2); + Array created_array = created->get_reference_array_property(); + REQUIRE_FALSE(created_array.size() == referenced_array.size()); + REQUIRE(created_array.size() == override_node_array.size()); + REQUIRE_FALSE(created_array.size() == created->get_child_count()); + + // Iterate over all nodes, since the ordering is not guaranteed. + for (int i = 0; i < override_node_array.size(); i++) { + bool node_found = false; + for (int j = 0; j < created_array.size(); j++) { + if (override_node_array[i].identity_compare(created_array[j])) { + node_found = true; + } + } + CHECK(node_found); + } + root->queue_free(); + memdelete(scene); + } +} + +TEST_CASE("[SceneTree][InstancePlaceholder] Instance a PackedScene containing an InstancePlaceholder with no overrides") { + GDREGISTER_CLASS(_TestInstancePlaceholderNode); + + // Create the internal scene. + _TestInstancePlaceholderNode *internal = memnew(_TestInstancePlaceholderNode); + internal->set_name("InternalNode"); + Node *referenced = memnew(Node); + referenced->set_name("OriginalReference"); + internal->add_child(referenced); + referenced->set_owner(internal); + internal->set_reference_property(referenced); + + // Pack the internal scene. + PackedScene *internal_scene = memnew(PackedScene); + Error err = internal_scene->pack(internal); + REQUIRE(err == OK); + + const String internal_path = OS::get_singleton()->get_cache_path().path_join("instance_placeholder_test_internal.tscn"); + err = ResourceSaver::save(internal_scene, internal_path); + REQUIRE(err == OK); + + Ref<PackedScene> internal_scene_loaded = ResourceLoader::load(internal_path, "PackedScene", ResourceFormatLoader::CacheMode::CACHE_MODE_IGNORE, &err); + REQUIRE(err == OK); + + // Create the main scene. + Node *root = memnew(Node); + root->set_name("MainNode"); + Node *overriding = memnew(Node); + overriding->set_name("OverridingReference"); + + _TestInstancePlaceholderNode *internal_created = Object::cast_to<_TestInstancePlaceholderNode>(internal_scene_loaded->instantiate(PackedScene::GEN_EDIT_STATE_MAIN_INHERITED)); + internal_created->set_scene_instance_load_placeholder(true); + root->add_child(internal_created); + internal_created->set_owner(root); + + root->add_child(overriding); + overriding->set_owner(root); + // Here we introduce an error, we override the property with an internal node to the instance placeholder. + // The InstancePlaceholder is now forced to properly resolve the Node. + internal_created->set("reference_property", NodePath("OriginalReference")); + + // Pack the main scene. + PackedScene *main_scene = memnew(PackedScene); + err = main_scene->pack(root); + REQUIRE(err == OK); + + const String main_path = OS::get_singleton()->get_cache_path().path_join("instance_placeholder_test_main.tscn"); + err = ResourceSaver::save(main_scene, main_path); + REQUIRE(err == OK); + + // // Instantiate the scene. + Ref<PackedScene> main_scene_loaded = ResourceLoader::load(main_path, "PackedScene", ResourceFormatLoader::CacheMode::CACHE_MODE_IGNORE, &err); + REQUIRE(err == OK); + + Node *instanced_main_node = main_scene_loaded->instantiate(); + REQUIRE(instanced_main_node != nullptr); + SceneTree::get_singleton()->get_root()->add_child(instanced_main_node); + CHECK(instanced_main_node->get_name() == "MainNode"); + REQUIRE(instanced_main_node->get_child_count() == 2); + InstancePlaceholder *instanced_placeholder = Object::cast_to<InstancePlaceholder>(instanced_main_node->get_child(0, true)); + REQUIRE(instanced_placeholder != nullptr); + + _TestInstancePlaceholderNode *final_node = Object::cast_to<_TestInstancePlaceholderNode>(instanced_placeholder->create_instance(true)); + REQUIRE(final_node != nullptr); + REQUIRE(final_node->get_child_count() == 1); + REQUIRE(final_node->get_reference_property().identity_compare(final_node->get_child(0, true))); + + instanced_main_node->queue_free(); + memdelete(overriding); + memdelete(root); + memdelete(internal); + DirAccess::remove_file_or_error(internal_path); + DirAccess::remove_file_or_error(main_path); +} + +TEST_CASE("[SceneTree][InstancePlaceholder] Instance a PackedScene containing an InstancePlaceholder with overrides") { + GDREGISTER_CLASS(_TestInstancePlaceholderNode); + + // Create the internal scene. + _TestInstancePlaceholderNode *internal = memnew(_TestInstancePlaceholderNode); + internal->set_name("InternalNode"); + Node *referenced = memnew(Node); + referenced->set_name("OriginalReference"); + internal->add_child(referenced); + referenced->set_owner(internal); + internal->set_reference_property(referenced); + + Node *array_ref1 = memnew(Node); + array_ref1->set_name("ArrayRef1"); + internal->add_child(array_ref1); + array_ref1->set_owner(internal); + Node *array_ref2 = memnew(Node); + array_ref2->set_name("ArrayRef2"); + internal->add_child(array_ref2); + array_ref2->set_owner(internal); + Array referenced_array; + referenced_array.set_typed(Variant::OBJECT, "Node", Variant()); + referenced_array.push_back(array_ref1); + referenced_array.push_back(array_ref2); + internal->set_reference_array_property(referenced_array); + + // Pack the internal scene. + PackedScene *internal_scene = memnew(PackedScene); + Error err = internal_scene->pack(internal); + REQUIRE(err == OK); + + const String internal_path = OS::get_singleton()->get_cache_path().path_join("instance_placeholder_test_internal_override.tscn"); + err = ResourceSaver::save(internal_scene, internal_path); + REQUIRE(err == OK); + + Ref<PackedScene> internal_scene_loaded = ResourceLoader::load(internal_path, "PackedScene", ResourceFormatLoader::CacheMode::CACHE_MODE_IGNORE, &err); + REQUIRE(err == OK); + + // Create the main scene. + Node *root = memnew(Node); + root->set_name("MainNode"); + Node *overriding = memnew(Node); + overriding->set_name("OverridingReference"); + Node *array_ext = memnew(Node); + array_ext->set_name("ExternalArrayMember"); + + _TestInstancePlaceholderNode *internal_created = Object::cast_to<_TestInstancePlaceholderNode>(internal_scene_loaded->instantiate(PackedScene::GEN_EDIT_STATE_MAIN_INHERITED)); + internal_created->set_scene_instance_load_placeholder(true); + root->add_child(internal_created); + internal_created->set_owner(root); + + root->add_child(overriding); + overriding->set_owner(root); + root->add_child(array_ext); + array_ext->set_owner(root); + // Here we introduce an error, we override the property with an internal node to the instance placeholder. + // The InstancePlaceholder is now forced to properly resolve the Node. + internal_created->set_reference_property(overriding); + Array internal_array = internal_created->get_reference_array_property(); + Array override_array; + override_array.set_typed(Variant::OBJECT, "Node", Variant()); + for (int i = 0; i < internal_array.size(); i++) { + override_array.push_back(internal_array[i]); + } + override_array.push_back(array_ext); + internal_created->set_reference_array_property(override_array); + + // Pack the main scene. + PackedScene *main_scene = memnew(PackedScene); + err = main_scene->pack(root); + REQUIRE(err == OK); + + const String main_path = OS::get_singleton()->get_cache_path().path_join("instance_placeholder_test_main_override.tscn"); + err = ResourceSaver::save(main_scene, main_path); + REQUIRE(err == OK); + + // // Instantiate the scene. + Ref<PackedScene> main_scene_loaded = ResourceLoader::load(main_path, "PackedScene", ResourceFormatLoader::CacheMode::CACHE_MODE_IGNORE, &err); + REQUIRE(err == OK); + + Node *instanced_main_node = main_scene_loaded->instantiate(); + REQUIRE(instanced_main_node != nullptr); + SceneTree::get_singleton()->get_root()->add_child(instanced_main_node); + CHECK(instanced_main_node->get_name() == "MainNode"); + REQUIRE(instanced_main_node->get_child_count() == 3); + InstancePlaceholder *instanced_placeholder = Object::cast_to<InstancePlaceholder>(instanced_main_node->get_child(0, true)); + REQUIRE(instanced_placeholder != nullptr); + + _TestInstancePlaceholderNode *final_node = Object::cast_to<_TestInstancePlaceholderNode>(instanced_placeholder->create_instance(true)); + REQUIRE(final_node != nullptr); + REQUIRE(final_node->get_child_count() == 3); + REQUIRE(final_node->get_reference_property().identity_compare(instanced_main_node->get_child(1, true))); + Array final_array = final_node->get_reference_array_property(); + REQUIRE(final_array.size() == 3); + Array wanted_node_array; + wanted_node_array.push_back(instanced_main_node->get_child(2, true)); // ExternalArrayMember + wanted_node_array.push_back(final_node->get_child(1, true)); // ArrayRef1 + wanted_node_array.push_back(final_node->get_child(2, true)); // ArrayRef2 + + // Iterate over all nodes, since the ordering is not guaranteed. + for (int i = 0; i < wanted_node_array.size(); i++) { + bool node_found = false; + for (int j = 0; j < final_array.size(); j++) { + if (wanted_node_array[i].identity_compare(final_array[j])) { + node_found = true; + } + } + CHECK(node_found); + } + + instanced_main_node->queue_free(); + memdelete(array_ext); + memdelete(overriding); + memdelete(root); + memdelete(internal); + DirAccess::remove_file_or_error(internal_path); + DirAccess::remove_file_or_error(main_path); +} + +} //namespace TestInstancePlaceholder + +#endif // TEST_INSTANCE_PLACEHOLDER_H diff --git a/tests/scene/test_navigation_region_2d.h b/tests/scene/test_navigation_region_2d.h index 4574893c8d..fcb5aeacbe 100644 --- a/tests/scene/test_navigation_region_2d.h +++ b/tests/scene/test_navigation_region_2d.h @@ -41,7 +41,7 @@ namespace TestNavigationRegion2D { TEST_SUITE("[Navigation]") { TEST_CASE("[SceneTree][NavigationRegion2D] New region should have valid RID") { NavigationRegion2D *region_node = memnew(NavigationRegion2D); - CHECK(region_node->get_region_rid().is_valid()); + CHECK(region_node->get_rid().is_valid()); memdelete(region_node); } } diff --git a/tests/scene/test_navigation_region_3d.h b/tests/scene/test_navigation_region_3d.h index 372f6dc505..f3d7f27361 100644 --- a/tests/scene/test_navigation_region_3d.h +++ b/tests/scene/test_navigation_region_3d.h @@ -43,7 +43,7 @@ namespace TestNavigationRegion3D { TEST_SUITE("[Navigation]") { TEST_CASE("[SceneTree][NavigationRegion3D] New region should have valid RID") { NavigationRegion3D *region_node = memnew(NavigationRegion3D); - CHECK(region_node->get_region_rid().is_valid()); + CHECK(region_node->get_rid().is_valid()); memdelete(region_node); } @@ -65,21 +65,14 @@ TEST_SUITE("[Navigation]") { CHECK_EQ(navigation_mesh->get_vertices().size(), 0); SUBCASE("Synchronous bake should have immediate effects") { + ERR_PRINT_OFF; // Suppress warning about baking from visual meshes as source geometry. navigation_region->bake_navigation_mesh(false); + ERR_PRINT_ON; CHECK_FALSE(navigation_region->is_baking()); CHECK_NE(navigation_mesh->get_polygon_count(), 0); CHECK_NE(navigation_mesh->get_vertices().size(), 0); } - // Race condition is present in the below subcase, but baking should take many - // orders of magnitude longer than basic checks on the main thread, so it's fine. - SUBCASE("Asynchronous bake should not be immediate") { - navigation_region->bake_navigation_mesh(true); - CHECK(navigation_region->is_baking()); - CHECK_EQ(navigation_mesh->get_polygon_count(), 0); - CHECK_EQ(navigation_mesh->get_vertices().size(), 0); - } - memdelete(mesh_instance); memdelete(navigation_region); memdelete(node_3d); diff --git a/tests/scene/test_sprite_frames.h b/tests/scene/test_sprite_frames.h index bf127cd42c..55854b90e4 100644 --- a/tests/scene/test_sprite_frames.h +++ b/tests/scene/test_sprite_frames.h @@ -74,9 +74,10 @@ TEST_CASE("[SpriteFrames] Animation addition, list getter, renaming, removal, an sname_list.size() == test_names.size(), "StringName List getter returned list of expected size"); - for (int i = 0; i < test_names.size(); i++) { + int idx = 0; + for (List<StringName>::ConstIterator itr = sname_list.begin(); itr != sname_list.end(); ++itr, ++idx) { CHECK_MESSAGE( - sname_list[i] == StringName(test_names[i]), + *itr == StringName(test_names[idx]), "StringName List getter returned expected values"); } diff --git a/tests/scene/test_text_edit.h b/tests/scene/test_text_edit.h index 8577dd7148..b2d9f5100e 100644 --- a/tests/scene/test_text_edit.h +++ b/tests/scene/test_text_edit.h @@ -36,6 +36,23 @@ #include "tests/test_macros.h" namespace TestTextEdit { +static inline Array build_array() { + return Array(); +} +template <typename... Targs> +static inline Array build_array(Variant item, Targs... Fargs) { + Array a = build_array(Fargs...); + a.push_front(item); + return a; +} +static inline Array reverse_nested(Array array) { + Array reversed_array = array.duplicate(true); + reversed_array.reverse(); + for (int i = 0; i < reversed_array.size(); i++) { + ((Array)reversed_array[i]).reverse(); + } + return reversed_array; +} TEST_CASE("[SceneTree][TextEdit] text entry") { SceneTree::get_singleton()->get_root()->set_physics_object_picking(false); @@ -52,12 +69,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_WATCH(text_edit, "lines_edited_from"); SIGNAL_WATCH(text_edit, "caret_changed"); - Array args1; - args1.push_back(0); - args1.push_back(0); - Array lines_edited_args; - lines_edited_args.push_back(args1); - lines_edited_args.push_back(args1.duplicate()); + Array lines_edited_args = build_array(build_array(0, 0), build_array(0, 0)); SUBCASE("[TextEdit] clear and set text") { // "text_changed" should not be emitted on clear / set. @@ -119,13 +131,10 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_set"); - // Clear. + // Can clear even if not editable. text_edit->set_editable(false); - Array lines_edited_clear_args; - Array new_args = args1.duplicate(); - new_args[0] = 1; - lines_edited_clear_args.push_back(new_args); + Array lines_edited_clear_args = build_array(build_array(1, 0)); text_edit->clear(); MessageQueue::get_singleton()->flush(); @@ -210,6 +219,321 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_CHECK_FALSE("text_changed"); } + SUBCASE("[TextEdit] insert text") { + // insert_text is 0 indexed. + ERR_PRINT_OFF; + text_edit->insert_text("test", 1, 0); + ERR_PRINT_ON; + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == ""); + SIGNAL_CHECK_FALSE("lines_edited_from"); + SIGNAL_CHECK_FALSE("text_changed"); + SIGNAL_CHECK_FALSE("caret_changed"); + SIGNAL_CHECK_FALSE("text_set"); + + // Insert text when there is no text. + lines_edited_args = build_array(build_array(0, 0)); + + text_edit->insert_text("tes", 0, 0); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "tes"); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 3); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_set"); + + // Insert multiple lines. + lines_edited_args = build_array(build_array(0, 1)); + + text_edit->insert_text("t\ninserting text", 0, 3); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "test\ninserting text"); + CHECK(text_edit->get_caret_line() == 1); + CHECK(text_edit->get_caret_column() == 14); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_set"); + + // Can insert even if not editable. + lines_edited_args = build_array(build_array(1, 1)); + + text_edit->set_editable(false); + text_edit->insert_text("mid", 1, 2); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "test\ninmidserting text"); + CHECK(text_edit->get_caret_line() == 1); + CHECK(text_edit->get_caret_column() == 17); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_set"); + text_edit->set_editable(true); + + // Undo insert. + text_edit->undo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "test\ninserting text"); + CHECK(text_edit->get_caret_line() == 1); + CHECK(text_edit->get_caret_column() == 14); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_set"); + + // Redo insert. + text_edit->redo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "test\ninmidserting text"); + CHECK(text_edit->get_caret_line() == 1); + CHECK(text_edit->get_caret_column() == 17); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_set"); + + // Insert offsets carets after the edit. + text_edit->add_caret(1, 1); + text_edit->add_caret(1, 4); + text_edit->select(1, 4, 1, 6, 2); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(1, 2)); + + text_edit->insert_text("\n ", 1, 2); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "test\nin\n midserting text"); + CHECK(text_edit->get_caret_count() == 3); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 2); + CHECK(text_edit->get_caret_column(0) == 16); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 1); + CHECK(text_edit->has_selection(2)); + CHECK(text_edit->get_caret_line(2) == 2); + CHECK(text_edit->get_caret_column(2) == 5); + CHECK(text_edit->get_selection_origin_line(2) == 2); + CHECK(text_edit->get_selection_origin_column(2) == 3); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_set"); + text_edit->remove_secondary_carets(); + text_edit->deselect(); + + // Insert text outside of selections. + text_edit->set_text("test text"); + text_edit->add_caret(0, 8); + text_edit->select(0, 1, 0, 4, 0); + text_edit->select(0, 4, 0, 8, 1); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(0, 0)); + + text_edit->insert_text("a", 0, 4, true, false); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "testa text"); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 4); + CHECK(text_edit->get_selection_origin_line(0) == 0); + CHECK(text_edit->get_selection_origin_column(0) == 1); + CHECK(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 0); + CHECK(text_edit->get_caret_column(1) == 9); + CHECK(text_edit->get_selection_origin_line(1) == 0); + CHECK(text_edit->get_selection_origin_column(1) == 5); + + // Insert text to beginning of selections. + text_edit->set_text("test text"); + text_edit->add_caret(0, 8); + text_edit->select(0, 1, 0, 4, 0); + text_edit->select(0, 4, 0, 8, 1); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(0, 0)); + + text_edit->insert_text("a", 0, 4, false, false); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "testa text"); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 4); + CHECK(text_edit->get_selection_origin_line(0) == 0); + CHECK(text_edit->get_selection_origin_column(0) == 1); + CHECK(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 0); + CHECK(text_edit->get_caret_column(1) == 9); + CHECK(text_edit->get_selection_origin_line(1) == 0); + CHECK(text_edit->get_selection_origin_column(1) == 4); + + // Insert text to end of selections. + text_edit->set_text("test text"); + text_edit->add_caret(0, 8); + text_edit->select(0, 1, 0, 4, 0); + text_edit->select(0, 4, 0, 8, 1); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(0, 0)); + + text_edit->insert_text("a", 0, 4, true, true); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "testa text"); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 5); + CHECK(text_edit->get_selection_origin_line(0) == 0); + CHECK(text_edit->get_selection_origin_column(0) == 1); + CHECK(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 0); + CHECK(text_edit->get_caret_column(1) == 9); + CHECK(text_edit->get_selection_origin_line(1) == 0); + CHECK(text_edit->get_selection_origin_column(1) == 5); + + // Insert text inside of selections. + text_edit->set_text("test text"); + text_edit->add_caret(0, 8); + text_edit->select(0, 1, 0, 4, 0); + text_edit->select(0, 4, 0, 8, 1); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(0, 0)); + + text_edit->insert_text("a", 0, 4, false, true); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "testa text"); + CHECK(text_edit->get_caret_count() == 1); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 9); + CHECK(text_edit->get_selection_origin_line(0) == 0); + CHECK(text_edit->get_selection_origin_column(0) == 1); + } + + SUBCASE("[TextEdit] remove text") { + lines_edited_args = build_array(build_array(0, 0), build_array(0, 2)); + + text_edit->set_text("test\nremoveing text\nthird line"); + MessageQueue::get_singleton()->flush(); + SIGNAL_CHECK("text_set", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_changed"); + + // remove_text is 0 indexed. + ERR_PRINT_OFF; + text_edit->remove_text(3, 0, 3, 4); + ERR_PRINT_ON; + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "test\nremoveing text\nthird line"); + SIGNAL_CHECK_FALSE("lines_edited_from"); + SIGNAL_CHECK_FALSE("text_changed"); + SIGNAL_CHECK_FALSE("caret_changed"); + SIGNAL_CHECK_FALSE("text_set"); + + // Remove multiple lines. + text_edit->set_caret_line(2); + text_edit->set_caret_column(10); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(2, 1)); + + text_edit->remove_text(1, 9, 2, 2); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "test\nremoveingird line"); + CHECK(text_edit->get_caret_line() == 1); + CHECK(text_edit->get_caret_column() == 17); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_set"); + + // Can remove even if not editable. + lines_edited_args = build_array(build_array(1, 1)); + + text_edit->set_editable(false); + text_edit->remove_text(1, 5, 1, 6); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "test\nremovingird line"); + CHECK(text_edit->get_caret_line() == 1); + CHECK(text_edit->get_caret_column() == 16); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_set"); + text_edit->set_editable(true); + + // Undo remove. + text_edit->undo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "test\nremoveingird line"); + CHECK(text_edit->get_caret_line() == 1); + CHECK(text_edit->get_caret_column() == 17); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_set"); + + // Redo remove. + text_edit->redo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "test\nremovingird line"); + CHECK(text_edit->get_caret_line() == 1); + CHECK(text_edit->get_caret_column() == 16); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_set"); + + // Remove collapses carets and offsets carets after the edit. + text_edit->set_caret_line(1); + text_edit->set_caret_column(9); + text_edit->add_caret(1, 10); + text_edit->select(1, 10, 1, 13, 1); + text_edit->add_caret(1, 14); + text_edit->add_caret(1, 2); + MessageQueue::get_singleton()->flush(); + SIGNAL_CHECK("caret_changed", empty_signal_args); + + text_edit->remove_text(1, 8, 1, 11); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "test\nremoving line"); + // Caret 0 was merged into the selection. + CHECK(text_edit->get_caret_count() == 3); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 10); + CHECK(text_edit->get_selection_origin_line(0) == 1); + CHECK(text_edit->get_selection_origin_column(0) == 8); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 11); + CHECK(text_edit->get_caret_line(2) == 1); + CHECK(text_edit->get_caret_column(2) == 2); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_set"); + text_edit->remove_secondary_carets(); + } + SUBCASE("[TextEdit] set and get line") { // Set / Get line is 0 indexed. text_edit->set_line(1, "test"); @@ -225,6 +549,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->get_text() == "test"); CHECK(text_edit->get_line(0) == "test"); CHECK(text_edit->get_line(1) == ""); + CHECK(text_edit->get_line_count() == 1); SIGNAL_CHECK("lines_edited_from", lines_edited_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_set"); @@ -233,14 +558,15 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { // Setting to a longer line, caret and selections should be preserved. text_edit->select_all(); MessageQueue::get_singleton()->flush(); - CHECK(text_edit->has_selection()); - SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_DISCARD("caret_changed"); text_edit->set_line(0, "test text"); MessageQueue::get_singleton()->flush(); CHECK(text_edit->get_line(0) == "test text"); CHECK(text_edit->has_selection()); CHECK(text_edit->get_selected_text() == "test"); + CHECK(text_edit->get_selection_origin_column() == 0); + CHECK(text_edit->get_caret_column() == 4); SIGNAL_CHECK("lines_edited_from", lines_edited_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK_FALSE("caret_changed"); @@ -299,12 +625,84 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("text_set"); - ERR_PRINT_ON; + + // Both ends of selection are adjusted and deselects. + text_edit->set_text("test text"); + text_edit->select(0, 8, 0, 6); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("caret_changed"); + + text_edit->set_line(0, "test"); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_line(0) == "test"); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_column() == 4); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_set"); + + // Multiple carets adjust to keep visual position. + text_edit->set_text("test text"); + text_edit->set_caret_column(2); + text_edit->add_caret(0, 0); + text_edit->add_caret(0, 1); + text_edit->add_caret(0, 6); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("caret_changed"); + + text_edit->set_line(0, "\tset line"); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_line(0) == "\tset line"); + CHECK(text_edit->get_caret_count() == 3); + CHECK_FALSE(text_edit->has_selection()); + // In the default font, these are the same positions. + CHECK(text_edit->get_caret_column(0) == 1); + CHECK(text_edit->get_caret_column(1) == 0); + // The previous caret at index 2 was merged. + CHECK(text_edit->get_caret_column(2) == 4); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_set"); + text_edit->remove_secondary_carets(); + + // Insert multiple lines. + text_edit->set_text("test text\nsecond line"); + text_edit->set_caret_column(5); + text_edit->add_caret(1, 6); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(0, 0), build_array(0, 1)); + + text_edit->set_line(0, "multiple\nlines"); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "multiple\nlines\nsecond line"); + CHECK(text_edit->get_caret_count() == 2); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 3); // In the default font, this is the same position. + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 6); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_set"); + text_edit->remove_secondary_carets(); } SUBCASE("[TextEdit] swap lines") { - ((Array)lines_edited_args[1])[1] = 1; + lines_edited_args = build_array(build_array(0, 0), build_array(0, 1)); text_edit->set_text("testing\nswap"); MessageQueue::get_singleton()->flush(); @@ -317,15 +715,10 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->set_caret_column(text_edit->get_line(0).length()); MessageQueue::get_singleton()->flush(); SIGNAL_CHECK("caret_changed", empty_signal_args); + // Emitted twice for each line. + lines_edited_args = build_array(build_array(0, 0), build_array(0, 0), build_array(1, 1), build_array(1, 1)); - ((Array)lines_edited_args[1])[1] = 0; - Array swap_args; - swap_args.push_back(1); - swap_args.push_back(1); - lines_edited_args.push_back(swap_args); - lines_edited_args.push_back(swap_args); - - // Order does not matter. Should also work if not editable. + // Order does not matter. Works when not editable. text_edit->set_editable(false); text_edit->swap_lines(1, 0); MessageQueue::get_singleton()->flush(); @@ -336,19 +729,15 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_CHECK_FALSE("text_set"); text_edit->set_editable(true); - lines_edited_args.reverse(); - - // Single undo/redo action + // Single undo/redo action. text_edit->undo(); MessageQueue::get_singleton()->flush(); CHECK(text_edit->get_text() == "testing\nswap"); - SIGNAL_CHECK("lines_edited_from", lines_edited_args); + SIGNAL_CHECK("lines_edited_from", reverse_nested(lines_edited_args)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_set"); - lines_edited_args.reverse(); - text_edit->redo(); MessageQueue::get_singleton()->flush(); CHECK(text_edit->get_text() == "swap\ntesting"); @@ -361,36 +750,70 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { ERR_PRINT_OFF; text_edit->swap_lines(-1, 0); CHECK(text_edit->get_text() == "swap\ntesting"); - SIGNAL_CHECK_FALSE("lines_edited_from"); - SIGNAL_CHECK_FALSE("caret_changed"); - SIGNAL_CHECK_FALSE("text_changed"); - SIGNAL_CHECK_FALSE("text_set"); - text_edit->swap_lines(0, -1); CHECK(text_edit->get_text() == "swap\ntesting"); - SIGNAL_CHECK_FALSE("lines_edited_from"); - SIGNAL_CHECK_FALSE("caret_changed"); - SIGNAL_CHECK_FALSE("text_changed"); - SIGNAL_CHECK_FALSE("text_set"); - text_edit->swap_lines(2, 0); CHECK(text_edit->get_text() == "swap\ntesting"); + text_edit->swap_lines(0, 2); + CHECK(text_edit->get_text() == "swap\ntesting"); + MessageQueue::get_singleton()->flush(); SIGNAL_CHECK_FALSE("lines_edited_from"); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("text_set"); + ERR_PRINT_ON; + + // Carets are also swapped. + text_edit->set_caret_line(0); + text_edit->set_caret_column(2); + text_edit->select(0, 0, 0, 2); + text_edit->add_caret(1, 6); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(1, 1), build_array(1, 1), build_array(0, 0), build_array(0, 0)); + + text_edit->swap_lines(0, 1); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "testing\nswap"); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 2); + CHECK(text_edit->get_selection_origin_line(0) == 1); + CHECK(text_edit->get_selection_origin_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 0); + CHECK(text_edit->get_caret_column(1) == 6); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_set"); + text_edit->remove_secondary_carets(); + + // Swap non adjacent lines. + text_edit->insert_line_at(1, "new line"); + text_edit->set_caret_line(1); + text_edit->set_caret_column(5); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "testing\nnew line\nswap"); + SIGNAL_DISCARD("caret_changed"); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("text_changed"); + lines_edited_args = build_array(build_array(2, 2), build_array(2, 2), build_array(0, 0), build_array(0, 0)); text_edit->swap_lines(0, 2); - CHECK(text_edit->get_text() == "swap\ntesting"); - SIGNAL_CHECK_FALSE("lines_edited_from"); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "swap\nnew line\ntesting"); + CHECK(text_edit->get_caret_line() == 1); + CHECK(text_edit->get_caret_column() == 5); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); SIGNAL_CHECK_FALSE("caret_changed"); - SIGNAL_CHECK_FALSE("text_changed"); + SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_set"); - ERR_PRINT_ON; } SUBCASE("[TextEdit] insert line at") { - ((Array)lines_edited_args[1])[1] = 1; + lines_edited_args = build_array(build_array(0, 0), build_array(0, 1)); text_edit->set_text("testing\nswap"); MessageQueue::get_singleton()->flush(); @@ -407,9 +830,9 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->get_selection_to_line() == 1); SIGNAL_CHECK("caret_changed", empty_signal_args); - // Insert before should move caret and selection, and works when not editable. + // Insert line at inserts a line before and moves caret and selection. Works when not editable. text_edit->set_editable(false); - lines_edited_args.remove_at(0); + lines_edited_args = build_array(build_array(0, 1)); text_edit->insert_line_at(0, "new"); MessageQueue::get_singleton()->flush(); CHECK(text_edit->get_text() == "new\ntesting\nswap"); @@ -417,7 +840,9 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->get_caret_column() == text_edit->get_line(2).size() - 1); CHECK(text_edit->has_selection()); CHECK(text_edit->get_selection_from_line() == 1); + CHECK(text_edit->get_selection_from_column() == 0); CHECK(text_edit->get_selection_to_line() == 2); + CHECK(text_edit->get_selection_to_column() == 4); SIGNAL_CHECK("lines_edited_from", lines_edited_args); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); @@ -425,19 +850,15 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->set_editable(true); // Can undo/redo as single action. - ((Array)lines_edited_args[0])[0] = 1; - ((Array)lines_edited_args[0])[1] = 0; text_edit->undo(); MessageQueue::get_singleton()->flush(); CHECK(text_edit->get_text() == "testing\nswap"); CHECK(text_edit->has_selection()); - SIGNAL_CHECK("lines_edited_from", lines_edited_args); + SIGNAL_CHECK("lines_edited_from", reverse_nested(lines_edited_args)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_set"); - ((Array)lines_edited_args[0])[0] = 0; - ((Array)lines_edited_args[0])[1] = 1; text_edit->redo(); MessageQueue::get_singleton()->flush(); CHECK(text_edit->get_text() == "new\ntesting\nswap"); @@ -454,9 +875,8 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->get_selection_from_line() == 0); CHECK(text_edit->get_selection_to_line() == 2); SIGNAL_CHECK_FALSE("caret_changed"); + lines_edited_args = build_array(build_array(2, 3)); - ((Array)lines_edited_args[0])[0] = 2; - ((Array)lines_edited_args[0])[1] = 3; text_edit->insert_line_at(2, "after"); MessageQueue::get_singleton()->flush(); CHECK(text_edit->get_text() == "new\ntesting\nafter\nswap"); @@ -474,24 +894,222 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { ERR_PRINT_OFF; text_edit->insert_line_at(-1, "after"); CHECK(text_edit->get_text() == "new\ntesting\nafter\nswap"); + text_edit->insert_line_at(4, "after"); + CHECK(text_edit->get_text() == "new\ntesting\nafter\nswap"); + MessageQueue::get_singleton()->flush(); SIGNAL_CHECK_FALSE("lines_edited_from"); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("text_set"); + ERR_PRINT_ON; - text_edit->insert_line_at(4, "after"); - CHECK(text_edit->get_text() == "new\ntesting\nafter\nswap"); + // Can insert multiple lines. + text_edit->select(0, 1, 2, 2); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(2, 4)); + + text_edit->insert_line_at(2, "multiple\nlines"); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "new\ntesting\nmultiple\nlines\nafter\nswap"); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 4); + CHECK(text_edit->get_caret_column() == 2); + CHECK(text_edit->get_selection_origin_line() == 0); + CHECK(text_edit->get_selection_origin_column() == 1); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_set"); + } + + SUBCASE("[TextEdit] remove line at") { + lines_edited_args = build_array(build_array(0, 0), build_array(0, 5)); + text_edit->set_text("testing\nremove line at\n\tremove\nlines\n\ntest"); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "testing\nremove line at\n\tremove\nlines\n\ntest"); + SIGNAL_CHECK("text_set", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_changed"); + + // Remove line handles multiple carets. + text_edit->set_caret_line(2); + text_edit->set_caret_column(0); + text_edit->add_caret(2, 7); + text_edit->select(2, 1, 2, 7, 1); + text_edit->add_caret(3, 1); + text_edit->add_caret(4, 5); + text_edit->add_caret(1, 5); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(3, 2)); + + text_edit->remove_line_at(2, true); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "testing\nremove line at\nlines\n\ntest"); + CHECK(text_edit->get_caret_count() == 5); + CHECK_FALSE(text_edit->has_selection(0)); // Same line. + CHECK(text_edit->get_caret_line(0) == 2); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK(text_edit->has_selection(1)); // Same line, clamped. + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 5); + CHECK(text_edit->get_selection_origin_line(1) == 2); + CHECK(text_edit->get_selection_origin_column(1) == 3); // In the default font, this is the same position. + CHECK_FALSE(text_edit->has_selection(2)); // Moved up. + CHECK(text_edit->get_caret_line(2) == 2); + CHECK(text_edit->get_caret_column(2) == 1); + CHECK_FALSE(text_edit->has_selection(3)); // Moved up. + CHECK(text_edit->get_caret_line(3) == 3); + CHECK(text_edit->get_caret_column(3) == 0); + CHECK_FALSE(text_edit->has_selection(4)); // Didn't move. + CHECK(text_edit->get_caret_line(4) == 1); + CHECK(text_edit->get_caret_column(4) == 5); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_set"); + text_edit->remove_secondary_carets(); + + // Remove first line. + text_edit->set_caret_line(0); + text_edit->set_caret_column(5); + text_edit->add_caret(4, 4); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(1, 0)); + + text_edit->remove_line_at(0, false); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "remove line at\nlines\n\ntest"); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK(text_edit->get_caret_line(1) == 3); + CHECK(text_edit->get_caret_column(1) == 4); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_set"); + text_edit->remove_secondary_carets(); + + // Remove empty line. + text_edit->set_caret_line(2); + text_edit->set_caret_column(0); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(3, 2)); + + text_edit->remove_line_at(2, false); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "remove line at\nlines\ntest"); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 0); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_set"); + + // Remove last line. + text_edit->set_caret_line(2); + text_edit->set_caret_column(2); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(2, 1)); + + text_edit->remove_line_at(2, true); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "remove line at\nlines"); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 5); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_set"); + + // Out of bounds. + text_edit->set_caret_line(0); + text_edit->set_caret_column(2); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + + ERR_PRINT_OFF + text_edit->remove_line_at(2, true); + ERR_PRINT_ON + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "remove line at\nlines"); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 2); SIGNAL_CHECK_FALSE("lines_edited_from"); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("text_set"); - ERR_PRINT_ON; + + // Remove regular line with move caret up and not editable. + text_edit->set_editable(false); + text_edit->set_caret_line(1); + text_edit->set_caret_column(2); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(1, 0)); + + text_edit->remove_line_at(1, false); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "remove line at"); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 1); // In the default font, this is the same position. + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_set"); + text_edit->set_editable(true); + + // Undo. + text_edit->undo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "remove line at\nlines"); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 2); + SIGNAL_CHECK("lines_edited_from", reverse_nested(lines_edited_args)); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_set"); + + // Redo. + text_edit->redo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "remove line at"); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 1); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_set"); + + // Remove only line removes line content. + text_edit->set_caret_line(0); + text_edit->set_caret_column(10); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(0, 0)); + + text_edit->remove_line_at(0); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == ""); + CHECK(text_edit->get_line_count() == 1); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_set"); } - SUBCASE("[TextEdit] insert line at caret") { - lines_edited_args.pop_back(); - ((Array)lines_edited_args[0])[1] = 1; + SUBCASE("[TextEdit] insert text at caret") { + lines_edited_args = build_array(build_array(0, 1)); + // Insert text at caret can insert multiple lines. text_edit->insert_text_at_caret("testing\nswap"); MessageQueue::get_singleton()->flush(); CHECK(text_edit->get_text() == "testing\nswap"); @@ -502,11 +1120,13 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_set"); + // Text is inserted at caret. text_edit->set_caret_line(0, false); text_edit->set_caret_column(2); + MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("caret_changed"); - ((Array)lines_edited_args[0])[1] = 0; + lines_edited_args = build_array(build_array(0, 0)); text_edit->insert_text_at_caret("mid"); MessageQueue::get_singleton()->flush(); CHECK(text_edit->get_text() == "temidsting\nswap"); @@ -517,9 +1137,10 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_set"); + // Selections are deleted then text is inserted. It also works even if not editable. text_edit->select(0, 0, 0, text_edit->get_line(0).length()); CHECK(text_edit->has_selection()); - lines_edited_args.push_back(args1.duplicate()); + lines_edited_args = build_array(build_array(0, 0), build_array(0, 0)); text_edit->set_editable(false); text_edit->insert_text_at_caret("new line"); @@ -534,12 +1155,15 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_CHECK_FALSE("text_set"); text_edit->set_editable(true); + // Undo restores text and selection. text_edit->undo(); MessageQueue::get_singleton()->flush(); CHECK(text_edit->get_text() == "temidsting\nswap"); CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 5); + CHECK(text_edit->get_caret_column() == text_edit->get_line(0).length()); CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selection_origin_line() == 0); + CHECK(text_edit->get_selection_origin_column() == 0); SIGNAL_CHECK("lines_edited_from", lines_edited_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("caret_changed", empty_signal_args); @@ -589,24 +1213,19 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_WATCH(text_edit, "lines_edited_from"); SIGNAL_WATCH(text_edit, "caret_changed"); - Array args1; - args1.push_back(0); - args1.push_back(0); - Array lines_edited_args; - lines_edited_args.push_back(args1); - lines_edited_args.push_back(args1.duplicate()); + Array lines_edited_args = build_array(build_array(0, 0), build_array(0, 0)); SUBCASE("[TextEdit] select all") { + // Select when there is no text does not select. text_edit->select_all(); CHECK_FALSE(text_edit->has_selection()); - ERR_PRINT_OFF; - CHECK(text_edit->get_selection_from_line() == -1); - CHECK(text_edit->get_selection_from_column() == -1); - CHECK(text_edit->get_selection_to_line() == -1); - CHECK(text_edit->get_selection_to_column() == -1); + CHECK(text_edit->get_selection_from_line() == 0); + CHECK(text_edit->get_selection_from_column() == 0); + CHECK(text_edit->get_selection_to_line() == 0); + CHECK(text_edit->get_selection_to_column() == 0); CHECK(text_edit->get_selected_text() == ""); - ERR_PRINT_ON; + // Select all selects all text. text_edit->set_text("test\nselection"); SEND_GUI_ACTION("ui_text_select_all"); CHECK(text_edit->get_viewport()->is_input_handled()); @@ -618,10 +1237,12 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->get_selection_to_line() == 1); CHECK(text_edit->get_selection_to_column() == 9); CHECK(text_edit->get_selection_mode() == TextEdit::SelectionMode::SELECTION_MODE_SHIFT); + CHECK(text_edit->is_caret_after_selection_origin()); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 9); SIGNAL_CHECK("caret_changed", empty_signal_args); + // Cannot select when disabled. text_edit->set_caret_line(0); text_edit->set_caret_column(0); text_edit->set_selecting_enabled(false); @@ -654,6 +1275,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); + // Select word under caret with multiple carets. text_edit->select_word_under_caret(); CHECK(text_edit->has_selection(0)); CHECK(text_edit->get_selected_text(0) == "test"); @@ -675,6 +1297,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->get_caret_count() == 2); + // Select word under caret disables selection if there is already a selection. text_edit->select_word_under_caret(); CHECK_FALSE(text_edit->has_selection()); CHECK(text_edit->get_selected_text() == ""); @@ -703,6 +1326,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->get_selected_text() == "test\ntest"); SIGNAL_CHECK("caret_changed", empty_signal_args); + // Cannot select when disabled. text_edit->set_selecting_enabled(false); text_edit->select_word_under_caret(); CHECK_FALSE(text_edit->has_selection()); @@ -714,10 +1338,10 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_CHECK_FALSE("caret_changed"); text_edit->set_selecting_enabled(true); - text_edit->set_caret_line(1, false, true, 0, 0); + // Select word under caret when there is no word does not select. + text_edit->set_caret_line(1, false, true, -1, 0); text_edit->set_caret_column(5, false, 0); - - text_edit->set_caret_line(2, false, true, 0, 1); + text_edit->set_caret_line(2, false, true, -1, 1); text_edit->set_caret_column(5, false, 1); text_edit->select_word_under_caret(); @@ -739,7 +1363,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->set_caret_column(0); text_edit->set_caret_line(1); - // First selection made by the implicit select_word_under_caret call + // First selection made by the implicit select_word_under_caret call. text_edit->add_selection_for_next_occurrence(); CHECK(text_edit->get_caret_count() == 1); CHECK(text_edit->get_selected_text(0) == "test"); @@ -780,7 +1404,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->get_caret_line(3) == 3); CHECK(text_edit->get_caret_column(3) == 9); - // A different word with a new manually added caret + // A different word with a new manually added caret. text_edit->add_caret(2, 1); text_edit->select(2, 0, 2, 4, 4); CHECK(text_edit->get_selected_text(4) == "rand"); @@ -795,7 +1419,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->get_caret_line(5) == 3); CHECK(text_edit->get_caret_column(5) == 22); - // Make sure the previous selections are still active + // Make sure the previous selections are still active. CHECK(text_edit->get_selected_text(0) == "test"); CHECK(text_edit->get_selected_text(1) == "test"); CHECK(text_edit->get_selected_text(2) == "test"); @@ -987,6 +1611,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SEND_GUI_KEY_EVENT(Key::RIGHT | KeyModifierMask::SHIFT) CHECK(text_edit->has_selection()); CHECK(text_edit->get_selected_text() == "t"); + CHECK(text_edit->is_caret_after_selection_origin()); #ifdef MACOS_ENABLED SEND_GUI_KEY_EVENT(Key::RIGHT | KeyModifierMask::SHIFT | KeyModifierMask::ALT) @@ -995,10 +1620,12 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { #endif CHECK(text_edit->has_selection()); CHECK(text_edit->get_selected_text() == "test"); + CHECK(text_edit->is_caret_after_selection_origin()); SEND_GUI_KEY_EVENT(Key::LEFT | KeyModifierMask::SHIFT) CHECK(text_edit->has_selection()); CHECK(text_edit->get_selected_text() == "tes"); + CHECK(text_edit->is_caret_after_selection_origin()); #ifdef MACOS_ENABLED SEND_GUI_KEY_EVENT(Key::LEFT | KeyModifierMask::SHIFT | KeyModifierMask::ALT) @@ -1019,11 +1646,13 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SEND_GUI_KEY_EVENT(Key::LEFT | KeyModifierMask::SHIFT) CHECK(text_edit->has_selection()); CHECK(text_edit->get_selected_text() == "t"); + CHECK_FALSE(text_edit->is_caret_after_selection_origin()); SEND_GUI_KEY_EVENT(Key::LEFT) CHECK_FALSE(text_edit->has_selection()); CHECK(text_edit->get_selected_text() == ""); + // Cannot select when disabled. text_edit->set_selecting_enabled(false); SEND_GUI_KEY_EVENT(Key::RIGHT | KeyModifierMask::SHIFT) CHECK_FALSE(text_edit->has_selection()); @@ -1032,46 +1661,120 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { } SUBCASE("[TextEdit] mouse drag select") { - /* Set size for mouse input. */ + // Set size for mouse input. text_edit->set_size(Size2(200, 200)); text_edit->set_text("this is some text\nfor selection"); text_edit->grab_focus(); MessageQueue::get_singleton()->flush(); - SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_pos_at_line_column(0, 1), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); - SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_pos_at_line_column(0, 7), MouseButtonMask::LEFT, Key::NONE); + // Click and drag to make a selection. + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(1, 0).get_center(), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + // Add (2,0) to bring it past the center point of the grapheme and account for integer division flooring. + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(1, 5).get_center() + Point2i(2, 0), MouseButtonMask::LEFT, Key::NONE); CHECK(text_edit->has_selection()); CHECK(text_edit->get_selected_text() == "for s"); CHECK(text_edit->get_selection_mode() == TextEdit::SELECTION_MODE_POINTER); - CHECK(text_edit->get_selection_from_line() == 1); - CHECK(text_edit->get_selection_from_column() == 0); - CHECK(text_edit->get_selection_to_line() == 1); - CHECK(text_edit->get_selection_to_column() == 5); + CHECK(text_edit->get_selection_origin_line() == 1); + CHECK(text_edit->get_selection_origin_column() == 0); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 5); + CHECK(text_edit->is_caret_after_selection_origin()); + CHECK(text_edit->is_dragging_cursor()); - SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_pos_at_line_column(0, 9), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + // Releasing finishes. + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(text_edit->get_rect_at_line_column(1, 5).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text() == "for s"); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(1, 9).get_center() + Point2i(2, 0), MouseButtonMask::NONE, Key::NONE); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text() == "for s"); + CHECK(text_edit->get_selection_origin_line() == 1); + CHECK(text_edit->get_selection_origin_column() == 0); + CHECK(text_edit->get_caret_line() == 1); + CHECK(text_edit->get_caret_column() == 5); + CHECK(text_edit->is_caret_after_selection_origin()); + + // Clicking clears selection. + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(0, 7).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 7); + // Cannot select when disabled, but caret still moves. text_edit->set_selecting_enabled(false); - SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_pos_at_line_column(0, 1), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); - SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_pos_at_line_column(0, 7), MouseButtonMask::LEFT, Key::NONE); + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(1, 0).get_center(), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->get_caret_line() == 1); + CHECK(text_edit->get_caret_column() == 0); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(1, 5).get_center() + Point2i(2, 0), MouseButtonMask::LEFT, Key::NONE); CHECK_FALSE(text_edit->has_selection()); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 5); text_edit->set_selecting_enabled(true); + + // Only last caret is moved when adding a selection. + text_edit->set_caret_line(0); + text_edit->set_caret_column(4); + text_edit->add_caret(0, 15); + text_edit->select(0, 11, 0, 15, 1); + MessageQueue::get_singleton()->flush(); + + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(1, 5).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE | KeyModifierMask::ALT); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(1, 0).get_center(), MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->get_caret_count() == 3); + CHECK(text_edit->get_selection_mode() == TextEdit::SELECTION_MODE_POINTER); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 4); + + CHECK(text_edit->has_selection(1)); + CHECK(text_edit->get_selection_origin_line(1) == 0); + CHECK(text_edit->get_selection_origin_column(1) == 11); + CHECK(text_edit->get_caret_line(1) == 0); + CHECK(text_edit->get_caret_column(1) == 15); + + CHECK(text_edit->has_selection(2)); + CHECK(text_edit->get_selected_text(2) == "for s"); + CHECK(text_edit->get_selection_origin_line(2) == 1); + CHECK(text_edit->get_selection_origin_column(2) == 5); + CHECK(text_edit->get_caret_line(2) == 1); + CHECK(text_edit->get_caret_column(2) == 0); + CHECK_FALSE(text_edit->is_caret_after_selection_origin(2)); + + // Overlapping carets and selections merges them. + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(0, 3).get_center() + Point2i(2, 0), MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->get_caret_count() == 1); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text() == "s is some text\nfor s"); + CHECK(text_edit->get_selection_mode() == TextEdit::SELECTION_MODE_POINTER); + CHECK(text_edit->get_selection_origin_line() == 1); + CHECK(text_edit->get_selection_origin_column() == 5); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 3); + CHECK_FALSE(text_edit->is_caret_after_selection_origin()); + + // Entering text stops selecting. + text_edit->insert_text_at_caret("a"); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_text() == "thiaelection"); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 4); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(0, 10).get_center() + Point2i(2, 0), MouseButtonMask::NONE, Key::NONE); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 4); } SUBCASE("[TextEdit] mouse word select") { - /* Set size for mouse input. */ + // Set size for mouse input. text_edit->set_size(Size2(200, 200)); - text_edit->set_text("this is some text\nfor selection"); + text_edit->set_text("this is some text\nfor selection\n"); MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("caret_changed"); - SEND_GUI_DOUBLE_CLICK(text_edit->get_pos_at_line_column(0, 2), Key::NONE); + // Double click to select word. + SEND_GUI_DOUBLE_CLICK(text_edit->get_rect_at_line_column(1, 2).get_center() + Point2i(2, 0), Key::NONE); CHECK(text_edit->has_selection()); CHECK(text_edit->get_selected_text() == "for"); CHECK(text_edit->get_selection_mode() == TextEdit::SELECTION_MODE_WORD); @@ -1081,9 +1784,11 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->get_selection_to_column() == 3); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 3); + CHECK(text_edit->is_caret_after_selection_origin()); SIGNAL_CHECK("caret_changed", empty_signal_args); - SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_pos_at_line_column(0, 7), MouseButtonMask::LEFT, Key::NONE); + // Moving mouse selects entire words at a time. + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(1, 6).get_center() + Point2i(2, 0), MouseButtonMask::LEFT, Key::NONE); CHECK(text_edit->has_selection()); CHECK(text_edit->get_selected_text() == "for selection"); CHECK(text_edit->get_selection_mode() == TextEdit::SELECTION_MODE_WORD); @@ -1093,15 +1798,116 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->get_selection_to_column() == 13); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 13); + CHECK(text_edit->is_caret_after_selection_origin()); + CHECK(text_edit->is_dragging_cursor()); + SIGNAL_CHECK("caret_changed", empty_signal_args); + + // Moving to a word before the initial selected word reverses selection direction and keeps the initial word selected. + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(0, 10).get_center(), MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text() == "some text\nfor"); + CHECK(text_edit->get_selection_mode() == TextEdit::SELECTION_MODE_WORD); + CHECK(text_edit->get_selection_from_line() == 0); + CHECK(text_edit->get_selection_from_column() == 8); + CHECK(text_edit->get_selection_to_line() == 1); + CHECK(text_edit->get_selection_to_column() == 3); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 8); + CHECK_FALSE(text_edit->is_caret_after_selection_origin()); SIGNAL_CHECK("caret_changed", empty_signal_args); - Point2i line_0 = text_edit->get_pos_at_line_column(0, 0); - line_0.y /= 2; - SEND_GUI_MOUSE_BUTTON_EVENT(line_0, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + // Releasing finishes. + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(text_edit->get_rect_at_line_column(0, 10).get_center(), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text() == "some text\nfor"); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(1, 2).get_center(), MouseButtonMask::NONE, Key::NONE); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text() == "some text\nfor"); + text_edit->deselect(); + + // Can start word select mode on an empty line. + SEND_GUI_DOUBLE_CLICK(text_edit->get_rect_at_line_column(2, 0).get_center() + Point2i(2, 0), Key::NONE); CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_selection_mode() == TextEdit::SELECTION_MODE_WORD); + CHECK(text_edit->get_caret_line() == 2); + CHECK(text_edit->get_caret_column() == 0); + + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(1, 9).get_center(), MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text() == "selection\n"); + CHECK(text_edit->get_selection_mode() == TextEdit::SELECTION_MODE_WORD); + CHECK(text_edit->get_caret_line() == 1); + CHECK(text_edit->get_caret_column() == 4); + CHECK(text_edit->get_selection_origin_line() == 2); + CHECK(text_edit->get_selection_origin_column() == 0); + + // Clicking clears selection. + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(0, 0).get_center(), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 0); + // Can start word select mode when not on a word. + SEND_GUI_DOUBLE_CLICK(text_edit->get_rect_at_line_column(0, 12).get_center() + Point2i(2, 0), Key::NONE); + CHECK(text_edit->get_selection_mode() == TextEdit::SELECTION_MODE_WORD); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 12); + + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(1, 9).get_center(), MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->get_selection_mode() == TextEdit::SELECTION_MODE_WORD); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text() == " text\nfor selection"); + CHECK(text_edit->get_caret_line() == 1); + CHECK(text_edit->get_caret_column() == 13); + CHECK(text_edit->get_selection_origin_line() == 0); + CHECK(text_edit->get_selection_origin_column() == 12); + + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(0, 10).get_center(), MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->get_selection_mode() == TextEdit::SELECTION_MODE_WORD); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text() == "some"); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 8); + CHECK(text_edit->get_selection_origin_line() == 0); + CHECK(text_edit->get_selection_origin_column() == 12); + + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(text_edit->get_rect_at_line_column(0, 15).get_center(), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + + // Add a new selection without affecting the old one. + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(1, 5).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE | KeyModifierMask::ALT); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(1, 8).get_center() + Point2i(2, 0), MouseButtonMask::LEFT, Key::NONE | KeyModifierMask::ALT); + CHECK(text_edit->get_selection_mode() == TextEdit::SELECTION_MODE_POINTER); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selected_text(0) == "some"); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 8); + CHECK(text_edit->get_selection_origin_line(0) == 0); + CHECK(text_edit->get_selection_origin_column(0) == 12); + + CHECK(text_edit->has_selection(1)); + CHECK(text_edit->get_selected_text(1) == "ele"); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 8); + CHECK(text_edit->get_selection_origin_line(1) == 1); + CHECK(text_edit->get_selection_origin_column(1) == 5); + + // Shift + double click to extend selection and start word select mode. + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(text_edit->get_rect_at_line_column(1, 8).get_center(), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + text_edit->remove_secondary_carets(); + SEND_GUI_DOUBLE_CLICK(text_edit->get_rect_at_line_column(1, 7).get_center() + Point2i(2, 0), Key::NONE | KeyModifierMask::SHIFT); + CHECK(text_edit->get_selection_mode() == TextEdit::SELECTION_MODE_WORD); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text() == " text\nfor selection"); + CHECK(text_edit->get_caret_line() == 1); + CHECK(text_edit->get_caret_column() == 13); + CHECK(text_edit->get_selection_origin_line() == 0); + CHECK(text_edit->get_selection_origin_column() == 12); + + // Cannot select when disabled, but caret still moves to end of word. text_edit->set_selecting_enabled(false); - SEND_GUI_DOUBLE_CLICK(text_edit->get_pos_at_line_column(0, 2), Key::NONE); + SEND_GUI_DOUBLE_CLICK(text_edit->get_rect_at_line_column(1, 1).get_center() + Point2i(2, 0), Key::NONE); CHECK_FALSE(text_edit->has_selection()); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 3); @@ -1109,32 +1915,149 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { } SUBCASE("[TextEdit] mouse line select") { - /* Set size for mouse input. */ + // Set size for mouse input. text_edit->set_size(Size2(200, 200)); - text_edit->set_text("this is some text\nfor selection"); + text_edit->set_text("this is some text\nfor selection\nwith 3 lines"); MessageQueue::get_singleton()->flush(); - SEND_GUI_DOUBLE_CLICK(text_edit->get_pos_at_line_column(0, 2), Key::NONE); - SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_pos_at_line_column(0, 2), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + // Triple click to select line. + SEND_GUI_DOUBLE_CLICK(text_edit->get_rect_at_line_column(1, 2).get_center(), Key::NONE); + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(1, 2).get_center(), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); CHECK(text_edit->has_selection()); - CHECK(text_edit->get_selected_text() == "for selection"); + CHECK(text_edit->get_selected_text() == "for selection\n"); CHECK(text_edit->get_selection_mode() == TextEdit::SELECTION_MODE_LINE); CHECK(text_edit->get_selection_from_line() == 1); CHECK(text_edit->get_selection_from_column() == 0); + CHECK(text_edit->get_selection_to_line() == 2); + CHECK(text_edit->get_selection_to_column() == 0); + CHECK(text_edit->get_caret_line() == 2); + CHECK(text_edit->get_caret_column() == 0); + CHECK(text_edit->is_caret_after_selection_origin()); + + // Moving mouse selects entire lines at a time. Selecting above reverses the selection direction. + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(0, 10).get_center(), MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text() == "this is some text\nfor selection"); + CHECK(text_edit->get_selection_mode() == TextEdit::SELECTION_MODE_LINE); + CHECK(text_edit->get_selection_from_line() == 0); + CHECK(text_edit->get_selection_from_column() == 0); CHECK(text_edit->get_selection_to_line() == 1); CHECK(text_edit->get_selection_to_column() == 13); - CHECK(text_edit->get_caret_line() == 1); + CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 0); + CHECK_FALSE(text_edit->is_caret_after_selection_origin()); + CHECK(text_edit->is_dragging_cursor()); - Point2i line_0 = text_edit->get_pos_at_line_column(0, 0); - line_0.y /= 2; - SEND_GUI_MOUSE_BUTTON_EVENT(line_0, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + // Selecting to the last line puts the caret at end of the line. + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(2, 10).get_center(), MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text() == "for selection\nwith 3 lines"); + CHECK(text_edit->get_selection_mode() == TextEdit::SELECTION_MODE_LINE); + CHECK(text_edit->get_selection_from_line() == 1); + CHECK(text_edit->get_selection_from_column() == 0); + CHECK(text_edit->get_selection_to_line() == 2); + CHECK(text_edit->get_selection_to_column() == 12); + CHECK(text_edit->get_caret_line() == 2); + CHECK(text_edit->get_caret_column() == 12); + CHECK(text_edit->is_caret_after_selection_origin()); + + // Releasing finishes. + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(text_edit->get_rect_at_line_column(2, 10).get_center(), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text() == "for selection\nwith 3 lines"); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(1, 2).get_center(), MouseButtonMask::NONE, Key::NONE); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text() == "for selection\nwith 3 lines"); + + // Clicking clears selection. + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(0, 0).get_center(), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 0); + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(text_edit->get_rect_at_line_column(0, 0).get_center(), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + + // Can start line select mode on an empty line. + text_edit->set_text("this is some text\n\nfor selection\nwith 4 lines"); + MessageQueue::get_singleton()->flush(); + SEND_GUI_DOUBLE_CLICK(text_edit->get_rect_at_line_column(1, 0).get_center() + Point2i(2, 0), Key::NONE); + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(1, 0).get_center(), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text() == "\n"); + CHECK(text_edit->get_selection_mode() == TextEdit::SELECTION_MODE_LINE); + CHECK(text_edit->get_caret_line() == 2); + CHECK(text_edit->get_caret_column() == 0); + CHECK(text_edit->get_selection_origin_line() == 1); + CHECK(text_edit->get_selection_origin_column() == 0); + + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(2, 9).get_center(), MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text() == "\nfor selection\n"); + CHECK(text_edit->get_selection_mode() == TextEdit::SELECTION_MODE_LINE); + CHECK(text_edit->get_caret_line() == 3); + CHECK(text_edit->get_caret_column() == 0); + CHECK(text_edit->get_selection_origin_line() == 1); + CHECK(text_edit->get_selection_origin_column() == 0); + + // Add a new selection without affecting the old one. + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(0, 3).get_center(), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE | KeyModifierMask::ALT); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(0, 4).get_center() + Point2i(2, 0), MouseButtonMask::LEFT, Key::NONE | KeyModifierMask::ALT); + CHECK(text_edit->get_selection_mode() == TextEdit::SELECTION_MODE_POINTER); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selected_text(0) == "\nfor selection\n"); + CHECK(text_edit->get_caret_line(0) == 3); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK(text_edit->get_selection_origin_line(0) == 1); + CHECK(text_edit->get_selection_origin_column(0) == 0); + + CHECK(text_edit->has_selection(1)); + CHECK(text_edit->get_selected_text(1) == "is"); + CHECK(text_edit->get_caret_line(1) == 0); + CHECK(text_edit->get_caret_column(1) == 4); + CHECK(text_edit->get_selection_origin_line(1) == 0); + CHECK(text_edit->get_selection_origin_column(1) == 2); + text_edit->remove_secondary_carets(); + text_edit->deselect(); + + // Selecting the last line puts caret at the end. + SEND_GUI_DOUBLE_CLICK(text_edit->get_rect_at_line_column(3, 3).get_center(), Key::NONE); + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(3, 3).get_center(), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->get_selection_mode() == TextEdit::SELECTION_MODE_LINE); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text() == "with 4 lines"); + CHECK(text_edit->get_caret_line() == 3); + CHECK(text_edit->get_caret_column() == 12); + CHECK(text_edit->get_selection_origin_line() == 3); + CHECK(text_edit->get_selection_origin_column() == 0); + + // Selecting above reverses direction. + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(2, 10).get_center(), MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->get_selection_mode() == TextEdit::SELECTION_MODE_LINE); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text() == "for selection\nwith 4 lines"); + CHECK(text_edit->get_caret_line() == 2); + CHECK(text_edit->get_caret_column() == 0); + CHECK(text_edit->get_selection_origin_line() == 3); + CHECK(text_edit->get_selection_origin_column() == 12); + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(text_edit->get_rect_at_line_column(2, 10).get_center(), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + + // Shift + triple click to extend selection and restart line select mode. + SEND_GUI_DOUBLE_CLICK(text_edit->get_rect_at_line_column(0, 9).get_center() + Point2i(2, 0), Key::NONE | KeyModifierMask::SHIFT); + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(0, 9).get_center(), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE | KeyModifierMask::SHIFT); + CHECK(text_edit->get_selection_mode() == TextEdit::SELECTION_MODE_LINE); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text() == "this is some text\n\nfor selection\nwith 4 lines"); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 0); + CHECK(text_edit->get_selection_origin_line() == 3); + CHECK(text_edit->get_selection_origin_column() == 12); + + // Cannot select when disabled, but caret still moves to the start of the next line. text_edit->set_selecting_enabled(false); - SEND_GUI_DOUBLE_CLICK(text_edit->get_pos_at_line_column(0, 2), Key::NONE); - SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_pos_at_line_column(0, 2), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + SEND_GUI_DOUBLE_CLICK(text_edit->get_rect_at_line_column(0, 2).get_center(), Key::NONE); + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(0, 2).get_center(), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); CHECK_FALSE(text_edit->has_selection()); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 0); @@ -1142,30 +2065,47 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { } SUBCASE("[TextEdit] mouse shift click select") { - /* Set size for mouse input. */ + // Set size for mouse input. text_edit->set_size(Size2(200, 200)); text_edit->set_text("this is some text\nfor selection"); MessageQueue::get_singleton()->flush(); - SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_pos_at_line_column(0, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); - SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_pos_at_line_column(0, 7), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE | KeyModifierMask::SHIFT); + // Shift click to make a selection from the previous caret position. + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(1, 1).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(1, 5).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE | KeyModifierMask::SHIFT); CHECK(text_edit->has_selection()); - CHECK(text_edit->get_selected_text() == "for s"); + CHECK(text_edit->get_selected_text() == "or s"); CHECK(text_edit->get_selection_mode() == TextEdit::SELECTION_MODE_POINTER); - CHECK(text_edit->get_selection_from_line() == 1); - CHECK(text_edit->get_selection_from_column() == 0); - CHECK(text_edit->get_selection_to_line() == 1); - CHECK(text_edit->get_selection_to_column() == 5); + CHECK(text_edit->get_selection_origin_line() == 1); + CHECK(text_edit->get_selection_origin_column() == 1); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 5); + CHECK(text_edit->is_caret_after_selection_origin()); - SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_pos_at_line_column(0, 9), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + // Shift click above to switch selection direction. Uses original selection position. + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(0, 6).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE | KeyModifierMask::SHIFT); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text() == "s some text\nf"); + CHECK(text_edit->get_selection_mode() == TextEdit::SELECTION_MODE_POINTER); + CHECK(text_edit->get_selection_origin_line() == 1); + CHECK(text_edit->get_selection_origin_column() == 1); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 6); + CHECK_FALSE(text_edit->is_caret_after_selection_origin()); + + // Clicking clears selection. + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(1, 7).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 1); + CHECK(text_edit->get_caret_column() == 7); + // Cannot select when disabled, but caret still moves. text_edit->set_selecting_enabled(false); - SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_pos_at_line_column(0, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); - SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_pos_at_line_column(0, 7), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE | KeyModifierMask::SHIFT); + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(1, 0).get_center(), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->get_caret_line() == 1); + CHECK(text_edit->get_caret_column() == 0); + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(1, 5).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE | KeyModifierMask::SHIFT); CHECK_FALSE(text_edit->has_selection()); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 5); @@ -1175,89 +2115,166 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SUBCASE("[TextEdit] select and deselect") { text_edit->set_text("this is some text\nfor selection"); MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + // Select clamps input to full text. text_edit->select(-1, -1, 500, 500); + MessageQueue::get_singleton()->flush(); CHECK(text_edit->has_selection()); CHECK(text_edit->get_selected_text() == "this is some text\nfor selection"); + CHECK(text_edit->is_caret_after_selection_origin(0)); + CHECK(text_edit->get_selection_origin_line(0) == 0); + CHECK(text_edit->get_selection_origin_column(0) == 0); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 13); + CHECK(text_edit->get_selection_from_line(0) == text_edit->get_selection_origin_line(0)); + CHECK(text_edit->get_selection_from_column(0) == text_edit->get_selection_origin_column(0)); + CHECK(text_edit->get_selection_to_line(0) == text_edit->get_caret_line(0)); + CHECK(text_edit->get_selection_to_column(0) == text_edit->get_caret_column(0)); + SIGNAL_CHECK("caret_changed", empty_signal_args); text_edit->deselect(); + MessageQueue::get_singleton()->flush(); CHECK_FALSE(text_edit->has_selection()); + SIGNAL_CHECK_FALSE("caret_changed"); + // Select works in the other direction. text_edit->select(500, 500, -1, -1); + MessageQueue::get_singleton()->flush(); CHECK(text_edit->has_selection()); CHECK(text_edit->get_selected_text() == "this is some text\nfor selection"); + CHECK_FALSE(text_edit->is_caret_after_selection_origin(0)); + CHECK(text_edit->get_selection_origin_line(0) == 1); + CHECK(text_edit->get_selection_origin_column(0) == 13); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK(text_edit->get_selection_from_line(0) == text_edit->get_caret_line(0)); + CHECK(text_edit->get_selection_from_column(0) == text_edit->get_caret_column(0)); + CHECK(text_edit->get_selection_to_line(0) == text_edit->get_selection_origin_line(0)); + CHECK(text_edit->get_selection_to_column(0) == text_edit->get_selection_origin_column(0)); + SIGNAL_CHECK("caret_changed", empty_signal_args); text_edit->deselect(); + MessageQueue::get_singleton()->flush(); CHECK_FALSE(text_edit->has_selection()); + SIGNAL_CHECK_FALSE("caret_changed"); + // Select part of a line. text_edit->select(0, 4, 0, 8); + MessageQueue::get_singleton()->flush(); CHECK(text_edit->has_selection()); CHECK(text_edit->get_selected_text() == " is "); + CHECK(text_edit->is_caret_after_selection_origin(0)); + CHECK(text_edit->get_selection_origin_line(0) == 0); + CHECK(text_edit->get_selection_origin_column(0) == 4); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 8); + CHECK(text_edit->get_selection_from_line(0) == text_edit->get_selection_origin_line(0)); + CHECK(text_edit->get_selection_from_column(0) == text_edit->get_selection_origin_column(0)); + CHECK(text_edit->get_selection_to_line(0) == text_edit->get_caret_line(0)); + CHECK(text_edit->get_selection_to_column(0) == text_edit->get_caret_column(0)); + SIGNAL_CHECK("caret_changed", empty_signal_args); text_edit->deselect(); + MessageQueue::get_singleton()->flush(); CHECK_FALSE(text_edit->has_selection()); + SIGNAL_CHECK_FALSE("caret_changed"); + // Select part of a line in the other direction. text_edit->select(0, 8, 0, 4); + MessageQueue::get_singleton()->flush(); CHECK(text_edit->has_selection()); CHECK(text_edit->get_selected_text() == " is "); + CHECK_FALSE(text_edit->is_caret_after_selection_origin(0)); + CHECK(text_edit->get_selection_origin_line(0) == 0); + CHECK(text_edit->get_selection_origin_column(0) == 8); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 4); + CHECK(text_edit->get_selection_from_line(0) == text_edit->get_caret_line(0)); + CHECK(text_edit->get_selection_from_column(0) == text_edit->get_caret_column(0)); + CHECK(text_edit->get_selection_to_line(0) == text_edit->get_selection_origin_line(0)); + CHECK(text_edit->get_selection_to_column(0) == text_edit->get_selection_origin_column(0)); + SIGNAL_CHECK("caret_changed", empty_signal_args); + // Cannot select when disabled. text_edit->set_selecting_enabled(false); CHECK_FALSE(text_edit->has_selection()); text_edit->select(0, 8, 0, 4); + MessageQueue::get_singleton()->flush(); CHECK_FALSE(text_edit->has_selection()); + SIGNAL_CHECK_FALSE("caret_changed"); text_edit->set_selecting_enabled(true); + } - text_edit->select(0, 8, 0, 4); - CHECK(text_edit->has_selection()); - SEND_GUI_ACTION("ui_text_caret_right"); + SUBCASE("[TextEdit] delete selection") { + text_edit->set_text("this is some text\nfor selection"); + MessageQueue::get_singleton()->flush(); + + // Delete selection does nothing if there is no selection. + text_edit->set_caret_line(0); + text_edit->set_caret_column(8); CHECK_FALSE(text_edit->has_selection()); text_edit->delete_selection(); CHECK(text_edit->get_text() == "this is some text\nfor selection"); + CHECK_FALSE(text_edit->has_selection()); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 8); - text_edit->select(0, 8, 0, 4); + // Backspace removes selection. + text_edit->select(0, 4, 0, 8); CHECK(text_edit->has_selection()); SEND_GUI_ACTION("ui_text_backspace"); + CHECK_FALSE(text_edit->has_selection()); CHECK(text_edit->get_text() == "thissome text\nfor selection"); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 4); + // Undo restores previous selection. text_edit->undo(); - CHECK(text_edit->has_selection()); CHECK(text_edit->get_text() == "this is some text\nfor selection"); + CHECK(text_edit->has_selection()); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 8); + CHECK(text_edit->get_selection_origin_line() == 0); + CHECK(text_edit->get_selection_origin_column() == 4); + // Redo restores caret. text_edit->redo(); - CHECK_FALSE(text_edit->has_selection()); CHECK(text_edit->get_text() == "thissome text\nfor selection"); + CHECK_FALSE(text_edit->has_selection()); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 4); text_edit->undo(); - CHECK(text_edit->has_selection()); CHECK(text_edit->get_text() == "this is some text\nfor selection"); + CHECK(text_edit->has_selection()); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 8); - text_edit->select(0, 8, 0, 4); + text_edit->select(0, 4, 0, 8); CHECK(text_edit->has_selection()); + // Delete selection removes text, deselects, and moves caret. text_edit->delete_selection(); - CHECK_FALSE(text_edit->has_selection()); CHECK(text_edit->get_text() == "thissome text\nfor selection"); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 4); + // Undo delete works. text_edit->undo(); - CHECK(text_edit->has_selection()); CHECK(text_edit->get_text() == "this is some text\nfor selection"); + CHECK(text_edit->has_selection()); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 8); + CHECK(text_edit->get_selection_origin_line() == 0); + CHECK(text_edit->get_selection_origin_column() == 4); + // Redo delete works. text_edit->redo(); - CHECK_FALSE(text_edit->has_selection()); CHECK(text_edit->get_text() == "thissome text\nfor selection"); + CHECK_FALSE(text_edit->has_selection()); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 4); @@ -1267,19 +2284,227 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 8); + // Can still delete if not editable. text_edit->set_editable(false); text_edit->delete_selection(); text_edit->set_editable(false); CHECK_FALSE(text_edit->has_selection()); CHECK(text_edit->get_text() == "thissome text\nfor selection"); + // Cannot undo since it was not editable. text_edit->undo(); CHECK_FALSE(text_edit->has_selection()); CHECK(text_edit->get_text() == "thissome text\nfor selection"); + + // Delete multiple adjacent selections on the same line. + text_edit->select(0, 0, 0, 5); + text_edit->add_caret(0, 8); + text_edit->select(0, 5, 0, 8, 1); + CHECK(text_edit->get_caret_count() == 2); + text_edit->delete_selection(); + CHECK(text_edit->get_text() == " text\nfor selection"); + CHECK(text_edit->get_caret_count() == 1); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 0); + + // Delete mulitline selection. Ignore non selections. + text_edit->remove_secondary_carets(); + text_edit->select(1, 3, 0, 2); + text_edit->add_caret(1, 7); + text_edit->delete_selection(); + CHECK(text_edit->get_text() == " t selection"); + CHECK(text_edit->get_caret_count() == 2); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 2); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 0); + CHECK(text_edit->get_caret_column(1) == 6); } - // Add readonly test? SUBCASE("[TextEdit] text drag") { + text_edit->set_size(Size2(200, 200)); + text_edit->set_text("drag test\ndrop here ''"); + text_edit->grab_click_focus(); + MessageQueue::get_singleton()->flush(); + + // Drag and drop selected text to mouse position. + text_edit->select(0, 0, 0, 4); + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(0, 2).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->is_mouse_over_selection()); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(0, 7).get_center() + Point2i(2, 0), MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->get_viewport()->gui_is_dragging()); + CHECK(text_edit->get_viewport()->gui_get_drag_data() == "drag"); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 4); + CHECK(text_edit->get_selection_origin_line() == 0); + CHECK(text_edit->get_selection_origin_column() == 0); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(1, 11).get_center() + Point2i(2, 0), MouseButtonMask::LEFT, Key::NONE); + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(text_edit->get_rect_at_line_column(1, 11).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE); + CHECK_FALSE(text_edit->get_viewport()->gui_is_dragging()); + CHECK(text_edit->get_text() == " test\ndrop here 'drag'"); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 1); + CHECK(text_edit->get_caret_column() == 15); + CHECK(text_edit->get_selection_origin_line() == 1); + CHECK(text_edit->get_selection_origin_column() == 11); + + // Undo. + text_edit->undo(); + CHECK(text_edit->get_text() == "drag test\ndrop here ''"); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 4); + CHECK(text_edit->get_selection_origin_line() == 0); + CHECK(text_edit->get_selection_origin_column() == 0); + + // Redo. + text_edit->redo(); + CHECK(text_edit->get_text() == " test\ndrop here 'drag'"); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 1); + CHECK(text_edit->get_caret_column() == 15); + CHECK(text_edit->get_selection_origin_line() == 1); + CHECK(text_edit->get_selection_origin_column() == 11); + + // Hold control when dropping to not delete selected text. + text_edit->select(1, 10, 1, 16); + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(1, 12).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->is_mouse_over_selection()); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(1, 7).get_center() + Point2i(2, 0), MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->get_viewport()->gui_is_dragging()); + CHECK(text_edit->get_viewport()->gui_get_drag_data() == "'drag'"); + CHECK(text_edit->has_selection()); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(0, 0).get_center(), MouseButtonMask::LEFT, Key::NONE); + SEND_GUI_KEY_EVENT(Key::CMD_OR_CTRL); + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(text_edit->get_rect_at_line_column(0, 0).get_center(), MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE); + SEND_GUI_KEY_UP_EVENT(Key::CMD_OR_CTRL); + CHECK_FALSE(text_edit->get_viewport()->gui_is_dragging()); + CHECK(text_edit->get_text() == "'drag' test\ndrop here 'drag'"); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 6); + CHECK(text_edit->get_selection_origin_line() == 0); + CHECK(text_edit->get_selection_origin_column() == 0); + + // Multiple caret drags entire selection. + text_edit->select(0, 11, 0, 7, 0); + text_edit->add_caret(1, 2); + text_edit->select(1, 2, 1, 4, 1); + text_edit->add_caret(1, 12); + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(1, 3).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->is_mouse_over_selection(true, 1)); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(1, 12).get_center() + Point2i(2, 0), MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->get_viewport()->gui_is_dragging()); + CHECK(text_edit->get_viewport()->gui_get_drag_data() == "test\nop"); + // Carets aren't removed from dragging, only dropping. + CHECK(text_edit->get_caret_count() == 3); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 7); + CHECK(text_edit->get_selection_origin_line(0) == 0); + CHECK(text_edit->get_selection_origin_column(0) == 11); + CHECK(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 4); + CHECK(text_edit->get_selection_origin_line(1) == 1); + CHECK(text_edit->get_selection_origin_column(1) == 2); + CHECK_FALSE(text_edit->has_selection(2)); + CHECK(text_edit->get_caret_line(2) == 1); + CHECK(text_edit->get_caret_column(2) == 12); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(1, 9).get_center() + Point2i(2, 0), MouseButtonMask::LEFT, Key::NONE); + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(text_edit->get_rect_at_line_column(1, 9).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE); + CHECK_FALSE(text_edit->get_viewport()->gui_is_dragging()); + CHECK(text_edit->get_text() == "'drag' \ndr heretest\nop 'drag'"); + CHECK(text_edit->get_caret_count() == 1); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 2); + CHECK(text_edit->get_caret_column() == 2); + CHECK(text_edit->get_selection_origin_line() == 1); + CHECK(text_edit->get_selection_origin_column() == 7); + + // Drop onto same selection should do effectively nothing. + text_edit->select(1, 3, 1, 7); + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(1, 6).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->is_mouse_over_selection()); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(0, 1).get_center() + Point2i(2, 0), MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->get_viewport()->gui_is_dragging()); + CHECK(text_edit->get_viewport()->gui_get_drag_data() == "here"); + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(text_edit->get_rect_at_line_column(1, 7).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE); + CHECK_FALSE(text_edit->get_viewport()->gui_is_dragging()); + CHECK(text_edit->get_text() == "'drag' \ndr heretest\nop 'drag'"); + CHECK(text_edit->get_caret_count() == 1); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 1); + CHECK(text_edit->get_caret_column() == 7); + CHECK(text_edit->get_selection_origin_line() == 1); + CHECK(text_edit->get_selection_origin_column() == 3); + + // Cannot drag when drag and drop selection is disabled. It becomes regular drag to select. + text_edit->set_drag_and_drop_selection_enabled(false); + text_edit->select(0, 1, 0, 5); + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(0, 2).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + CHECK_FALSE(text_edit->is_mouse_over_selection()); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(1, 7).get_center() + Point2i(2, 0), MouseButtonMask::LEFT, Key::NONE); + CHECK_FALSE(text_edit->get_viewport()->gui_is_dragging()); + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(text_edit->get_rect_at_line_column(1, 7).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE); + CHECK_FALSE(text_edit->get_viewport()->gui_is_dragging()); + CHECK(text_edit->get_text() == "'drag' \ndr heretest\nop 'drag'"); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 1); + CHECK(text_edit->get_caret_column() == 7); + CHECK(text_edit->get_selection_origin_line() == 0); + CHECK(text_edit->get_selection_origin_column() == 2); + text_edit->set_drag_and_drop_selection_enabled(true); + + // Cancel drag and drop from Escape key. + text_edit->select(0, 1, 0, 5); + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(0, 3).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->is_mouse_over_selection()); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(0, 1).get_center() + Point2i(2, 0), MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->get_viewport()->gui_is_dragging()); + CHECK(text_edit->get_viewport()->gui_get_drag_data() == "drag"); + SEND_GUI_KEY_EVENT(Key::ESCAPE); + CHECK_FALSE(text_edit->get_viewport()->gui_is_dragging()); + CHECK(text_edit->get_text() == "'drag' \ndr heretest\nop 'drag'"); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 5); + CHECK(text_edit->get_selection_origin_line() == 0); + CHECK(text_edit->get_selection_origin_column() == 1); + + // Cancel drag and drop from caret move key input. + text_edit->select(0, 1, 0, 5); + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(0, 3).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->is_mouse_over_selection()); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(0, 1).get_center() + Point2i(2, 0), MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->get_viewport()->gui_is_dragging()); + CHECK(text_edit->get_viewport()->gui_get_drag_data() == "drag"); + SEND_GUI_KEY_EVENT(Key::RIGHT); + CHECK_FALSE(text_edit->get_viewport()->gui_is_dragging()); + CHECK(text_edit->get_text() == "'drag' \ndr heretest\nop 'drag'"); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 5); + + // Cancel drag and drop from text key input. + text_edit->select(0, 1, 0, 5); + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(0, 3).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->is_mouse_over_selection()); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(0, 1).get_center() + Point2i(2, 0), MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->get_viewport()->gui_is_dragging()); + CHECK(text_edit->get_viewport()->gui_get_drag_data() == "drag"); + SEND_GUI_KEY_EVENT(Key::A); + CHECK_FALSE(text_edit->get_viewport()->gui_is_dragging()); + CHECK(text_edit->get_text() == "'A' \ndr heretest\nop 'drag'"); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 2); + } + + SUBCASE("[TextEdit] text drag to another text edit") { TextEdit *target_text_edit = memnew(TextEdit); SceneTree::get_singleton()->get_root()->add_child(target_text_edit); @@ -1292,27 +2517,223 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->set_text("drag me"); text_edit->select_all(); text_edit->grab_click_focus(); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 7); + CHECK(text_edit->get_selection_origin_line() == 0); + CHECK(text_edit->get_selection_origin_column() == 0); MessageQueue::get_singleton()->flush(); - Point2i line_0 = text_edit->get_pos_at_line_column(0, 0); - line_0.y /= 2; - SEND_GUI_MOUSE_BUTTON_EVENT(line_0, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + // Drag text between text edits. + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(0, 0).get_center(), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); CHECK(text_edit->is_mouse_over_selection()); - SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_pos_at_line_column(0, 7), MouseButtonMask::LEFT, Key::NONE); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(0, 7).get_center(), MouseButtonMask::LEFT, Key::NONE); CHECK(text_edit->get_viewport()->gui_is_dragging()); CHECK(text_edit->get_viewport()->gui_get_drag_data() == "drag me"); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 7); + CHECK(text_edit->get_selection_origin_line() == 0); + CHECK(text_edit->get_selection_origin_column() == 0); - line_0 = target_text_edit->get_pos_at_line_column(0, 0); - line_0.y /= 2; - line_0.x += 401; // As empty add one. - SEND_GUI_MOUSE_MOTION_EVENT(line_0, MouseButtonMask::LEFT, Key::NONE); + Point2i target_line0 = target_text_edit->get_position() + Point2i(1, target_text_edit->get_line_height() / 2); + SEND_GUI_MOUSE_MOTION_EVENT(target_line0, MouseButtonMask::LEFT, Key::NONE); CHECK(text_edit->get_viewport()->gui_is_dragging()); + CHECK(target_text_edit->get_caret_line() == 0); + CHECK(target_text_edit->get_caret_column() == 0); + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(target_line0, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE); + CHECK_FALSE(text_edit->get_viewport()->gui_is_dragging()); + CHECK(text_edit->get_text() == ""); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 0); + CHECK(target_text_edit->get_text() == "drag me"); + CHECK(target_text_edit->has_selection()); + CHECK(target_text_edit->get_caret_line() == 0); + CHECK(target_text_edit->get_caret_column() == 7); + CHECK(target_text_edit->get_selection_origin_line() == 0); + CHECK(target_text_edit->get_selection_origin_column() == 0); - SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(line_0, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE); + // Undo is separate per TextEdit. + text_edit->undo(); + CHECK(text_edit->get_text() == "drag me"); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 7); + CHECK(text_edit->get_selection_origin_line() == 0); + CHECK(text_edit->get_selection_origin_column() == 0); + CHECK(target_text_edit->get_text() == "drag me"); + CHECK(target_text_edit->has_selection()); + CHECK(target_text_edit->get_caret_line() == 0); + CHECK(target_text_edit->get_caret_column() == 7); + CHECK(target_text_edit->get_selection_origin_line() == 0); + CHECK(target_text_edit->get_selection_origin_column() == 0); + + target_text_edit->undo(); + CHECK(text_edit->get_text() == "drag me"); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 7); + CHECK(text_edit->get_selection_origin_line() == 0); + CHECK(text_edit->get_selection_origin_column() == 0); + CHECK(target_text_edit->get_text() == ""); + CHECK_FALSE(target_text_edit->has_selection()); + CHECK(target_text_edit->get_caret_line() == 0); + CHECK(target_text_edit->get_caret_column() == 0); + + // Redo is also separate. + text_edit->redo(); + CHECK(text_edit->get_text() == ""); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 0); + CHECK(target_text_edit->get_text() == ""); + CHECK_FALSE(target_text_edit->has_selection()); + CHECK(target_text_edit->get_caret_line() == 0); + CHECK(target_text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->get_viewport()->gui_is_dragging()); + target_text_edit->redo(); CHECK(text_edit->get_text() == ""); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 0); CHECK(target_text_edit->get_text() == "drag me"); + CHECK(target_text_edit->has_selection()); + CHECK(target_text_edit->get_caret_line() == 0); + CHECK(target_text_edit->get_caret_column() == 7); + CHECK(target_text_edit->get_selection_origin_line() == 0); + CHECK(target_text_edit->get_selection_origin_column() == 0); + + // Hold control to not remove selected text. + text_edit->set_text("drag test\ndrop test"); + MessageQueue::get_singleton()->flush(); + target_text_edit->select(0, 0, 0, 3, 0); + target_text_edit->add_caret(0, 5); + text_edit->select(0, 5, 0, 7, 0); + text_edit->add_caret(0, 1); + text_edit->select(0, 1, 0, 0, 1); + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(0, 5).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->is_mouse_over_selection(true, 0)); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(1, 6).get_center(), MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->get_viewport()->gui_is_dragging()); + CHECK(text_edit->get_viewport()->gui_get_drag_data() == "d\nte"); + CHECK(text_edit->has_selection()); + SEND_GUI_KEY_EVENT(Key::CMD_OR_CTRL); + SEND_GUI_MOUSE_MOTION_EVENT(target_text_edit->get_position() + target_text_edit->get_rect_at_line_column(0, 6).get_center() + Point2i(2, 0), MouseButtonMask::LEFT, Key::NONE); + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(target_text_edit->get_position() + target_text_edit->get_rect_at_line_column(0, 6).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE); + SEND_GUI_KEY_UP_EVENT(Key::CMD_OR_CTRL); + CHECK_FALSE(text_edit->get_viewport()->gui_is_dragging()); + CHECK(text_edit->get_text() == "drag test\ndrop test"); + CHECK(text_edit->get_caret_count() == 2); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 7); + CHECK(text_edit->get_caret_line(1) == 0); + CHECK(text_edit->get_caret_column(1) == 0); + CHECK(target_text_edit->get_text() == "drag md\ntee"); + CHECK(target_text_edit->get_caret_count() == 1); + CHECK(target_text_edit->has_selection()); + CHECK(target_text_edit->get_caret_line() == 1); + CHECK(target_text_edit->get_caret_column() == 2); + CHECK(target_text_edit->get_selection_origin_line() == 0); + CHECK(target_text_edit->get_selection_origin_column() == 6); + + // Drop onto selected text deletes the selected text first. + text_edit->set_deselect_on_focus_loss_enabled(false); + target_text_edit->set_deselect_on_focus_loss_enabled(false); + text_edit->remove_secondary_carets(); + text_edit->select(0, 5, 0, 9); + target_text_edit->select(0, 6, 0, 8); + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(0, 6).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->is_mouse_over_selection(true, 0)); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(1, 7).get_center(), MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->get_viewport()->gui_is_dragging()); + CHECK(text_edit->get_viewport()->gui_get_drag_data() == "test"); + CHECK(text_edit->has_selection()); + SEND_GUI_MOUSE_MOTION_EVENT(target_text_edit->get_position() + target_text_edit->get_rect_at_line_column(0, 7).get_center() + Point2i(2, 0), MouseButtonMask::LEFT, Key::NONE); + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(target_text_edit->get_position() + target_text_edit->get_rect_at_line_column(0, 7).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE); + CHECK_FALSE(text_edit->get_viewport()->gui_is_dragging()); + CHECK(text_edit->get_text() == "drag \ndrop test"); + CHECK(target_text_edit->get_caret_count() == 1); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 5); + CHECK(target_text_edit->get_text() == "drag mdtest\ntee"); + CHECK(target_text_edit->has_selection()); + CHECK(target_text_edit->get_caret_line() == 0); + CHECK(target_text_edit->get_caret_column() == 11); + CHECK(target_text_edit->get_selection_origin_line() == 0); + CHECK(target_text_edit->get_selection_origin_column() == 7); + text_edit->set_deselect_on_focus_loss_enabled(true); + target_text_edit->set_deselect_on_focus_loss_enabled(true); + + // Can drop even when drag and drop selection is disabled. + target_text_edit->set_drag_and_drop_selection_enabled(false); + text_edit->select(0, 4, 0, 5); + target_text_edit->deselect(); + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(0, 4).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->is_mouse_over_selection()); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(1, 7).get_center() + Point2i(2, 0), MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->get_viewport()->gui_is_dragging()); + CHECK(text_edit->get_viewport()->gui_get_drag_data() == " "); + CHECK(text_edit->has_selection()); + SEND_GUI_MOUSE_MOTION_EVENT(target_text_edit->get_position() + target_text_edit->get_rect_at_line_column(0, 2).get_center() + Point2i(2, 0), MouseButtonMask::LEFT, Key::NONE); + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(target_text_edit->get_position() + target_text_edit->get_rect_at_line_column(0, 7).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE); + CHECK_FALSE(text_edit->get_viewport()->gui_is_dragging()); + CHECK(text_edit->get_text() == "drag\ndrop test"); + CHECK(target_text_edit->get_text() == "drag md test\ntee"); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 4); + CHECK(target_text_edit->has_selection()); + CHECK(target_text_edit->get_caret_line() == 0); + CHECK(target_text_edit->get_caret_column() == 8); + CHECK(target_text_edit->get_selection_origin_line() == 0); + CHECK(target_text_edit->get_selection_origin_column() == 7); + target_text_edit->set_drag_and_drop_selection_enabled(true); + + // Cannot drop when not editable. + target_text_edit->set_editable(false); + text_edit->select(0, 1, 0, 4); + target_text_edit->deselect(); + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(0, 2).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->is_mouse_over_selection()); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(1, 7).get_center() + Point2i(2, 0), MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->get_viewport()->gui_is_dragging()); + CHECK(text_edit->get_viewport()->gui_get_drag_data() == "rag"); + CHECK(text_edit->has_selection()); + SEND_GUI_MOUSE_MOTION_EVENT(target_text_edit->get_position() + target_text_edit->get_rect_at_line_column(0, 2).get_center() + Point2i(2, 0), MouseButtonMask::LEFT, Key::NONE); + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(target_text_edit->get_position() + target_text_edit->get_rect_at_line_column(0, 2).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE); + CHECK_FALSE(text_edit->get_viewport()->gui_is_dragging()); + CHECK(text_edit->get_text() == "drag\ndrop test"); + CHECK(target_text_edit->get_text() == "drag md test\ntee"); + CHECK(text_edit->has_selection()); + CHECK_FALSE(target_text_edit->has_selection()); + target_text_edit->set_editable(true); + + // Can drag when not editable, but text will not be removed. + text_edit->set_editable(false); + text_edit->select(0, 0, 0, 4); + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(0, 2).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->is_mouse_over_selection()); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(1, 7).get_center() + Point2i(2, 0), MouseButtonMask::LEFT, Key::NONE); + CHECK(text_edit->get_viewport()->gui_is_dragging()); + CHECK(text_edit->get_viewport()->gui_get_drag_data() == "drag"); + CHECK(text_edit->has_selection()); + SEND_GUI_MOUSE_MOTION_EVENT(target_text_edit->get_position() + target_text_edit->get_rect_at_line_column(0, 4).get_center() + Point2i(2, 0), MouseButtonMask::LEFT, Key::NONE); + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(target_text_edit->get_position() + target_text_edit->get_rect_at_line_column(0, 4).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE); + CHECK_FALSE(text_edit->get_viewport()->gui_is_dragging()); + CHECK(text_edit->get_text() == "drag\ndrop test"); + CHECK(target_text_edit->get_text() == "dragdrag md test\ntee"); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 4); + CHECK(target_text_edit->has_selection()); + CHECK(target_text_edit->get_caret_line() == 0); + CHECK(target_text_edit->get_caret_column() == 8); + CHECK(target_text_edit->get_selection_origin_line() == 0); + CHECK(target_text_edit->get_selection_origin_column() == 4); + text_edit->set_editable(true); memdelete(target_text_edit); } @@ -1324,44 +2745,41 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { } SUBCASE("[TextEdit] overridable actions") { + DisplayServerMock *DS = (DisplayServerMock *)(DisplayServer::get_singleton()); + SIGNAL_WATCH(text_edit, "text_set"); SIGNAL_WATCH(text_edit, "text_changed"); SIGNAL_WATCH(text_edit, "lines_edited_from"); SIGNAL_WATCH(text_edit, "caret_changed"); - Array args1; - args1.push_back(0); - args1.push_back(0); - Array lines_edited_args; - lines_edited_args.push_back(args1); + Array lines_edited_args = build_array(build_array(0, 0)); SUBCASE("[TextEdit] backspace") { text_edit->set_text("this is\nsome\n"); text_edit->set_caret_line(0); text_edit->set_caret_column(0); MessageQueue::get_singleton()->flush(); - SIGNAL_DISCARD("text_set"); SIGNAL_DISCARD("text_changed"); SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); + // Cannot backspace at start of text. text_edit->backspace(); MessageQueue::get_singleton()->flush(); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); + // Backspace at start of line removes the line. text_edit->set_caret_line(2); text_edit->set_caret_column(0); MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(2, 1)); - ((Array)lines_edited_args[0])[0] = 2; - ((Array)lines_edited_args[0])[1] = 1; text_edit->backspace(); MessageQueue::get_singleton()->flush(); - CHECK(text_edit->get_text() == "this is\nsome"); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 4); @@ -1369,10 +2787,10 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); - ((Array)lines_edited_args[0])[0] = 1; + // Backspace removes a character. + lines_edited_args = build_array(build_array(1, 1)); text_edit->backspace(); MessageQueue::get_singleton()->flush(); - CHECK(text_edit->get_text() == "this is\nsom"); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 3); @@ -1380,11 +2798,11 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); + // Backspace when text is selected removes the selection. text_edit->end_complex_operation(); text_edit->select(1, 0, 1, 3); text_edit->backspace(); MessageQueue::get_singleton()->flush(); - CHECK(text_edit->get_text() == "this is\n"); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 0); @@ -1392,11 +2810,11 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); + // Cannot backspace if not editable. text_edit->set_editable(false); text_edit->backspace(); text_edit->set_editable(true); MessageQueue::get_singleton()->flush(); - CHECK(text_edit->get_text() == "this is\n"); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 0); @@ -1404,6 +2822,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); + // Undo restores text to the previous end of complex operation. text_edit->undo(); MessageQueue::get_singleton()->flush(); CHECK(text_edit->get_text() == "this is\nsom"); @@ -1412,98 +2831,736 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); + + // Redo. + text_edit->redo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "this is\n"); + CHECK(text_edit->get_caret_line() == 1); + CHECK(text_edit->get_caret_column() == 0); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + + // See ui_text_backspace for more backspace tests. } SUBCASE("[TextEdit] cut") { + // Cut without a selection removes the entire line. 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"); + lines_edited_args = build_array(build_array(1, 0)); - ERR_PRINT_OFF; text_edit->cut(); MessageQueue::get_singleton()->flush(); - ERR_PRINT_ON; // Can't check display server content. - - ((Array)lines_edited_args[0])[0] = 1; + CHECK(DS->clipboard_get() == "this is\n"); CHECK(text_edit->get_text() == "some\n"); CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 4); + CHECK(text_edit->get_caret_column() == 3); // In the default font, this is the same position. SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); - ((Array)lines_edited_args[0])[0] = 0; - ((Array)lines_edited_args[0])[1] = 1; + // Undo restores the cut text. text_edit->undo(); MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get() == "this is\n"); 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("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); - SIGNAL_CHECK("lines_edited_from", lines_edited_args); + SIGNAL_CHECK("lines_edited_from", reverse_nested(lines_edited_args)); - ((Array)lines_edited_args[0])[0] = 1; - ((Array)lines_edited_args[0])[1] = 0; + // Redo. text_edit->redo(); MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get() == "this is\n"); CHECK(text_edit->get_text() == "some\n"); CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 4); + CHECK(text_edit->get_caret_column() == 3); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); + // Cut with a selection removes just the selection. text_edit->set_text("this is\nsome\n"); + text_edit->select(0, 5, 0, 7); MessageQueue::get_singleton()->flush(); - SIGNAL_DISCARD("text_set"); SIGNAL_DISCARD("text_changed"); SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(0, 0)); - ((Array)lines_edited_args[0])[0] = 0; - text_edit->select(0, 5, 0, 7); - ERR_PRINT_OFF; SEND_GUI_ACTION("ui_cut"); CHECK(text_edit->get_viewport()->is_input_handled()); MessageQueue::get_singleton()->flush(); - ERR_PRINT_ON; // Can't check display server content. + CHECK(DS->clipboard_get() == "is"); CHECK(text_edit->get_text() == "this \nsome\n"); + CHECK_FALSE(text_edit->get_caret_line()); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 5); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); + // Cut does not change the text if not editable. Text is still added to clipboard. + text_edit->set_text("this is\nsome\n"); + text_edit->set_caret_line(0); + text_edit->set_caret_column(5); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("caret_changed"); + text_edit->set_editable(false); text_edit->cut(); MessageQueue::get_singleton()->flush(); text_edit->set_editable(true); - CHECK(text_edit->get_text() == "this \nsome\n"); + CHECK(DS->clipboard_get() == "this is\n"); + CHECK(text_edit->get_text() == "this is\nsome\n"); CHECK(text_edit->get_caret_line() == 0); CHECK(text_edit->get_caret_column() == 5); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); + + // Cut line with multiple carets. + text_edit->set_text("this is\nsome\n"); + text_edit->set_caret_line(0); + text_edit->set_caret_column(3); + text_edit->add_caret(0, 2); + text_edit->add_caret(0, 4); + text_edit->add_caret(2, 0); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(1, 0), build_array(1, 0)); + + text_edit->cut(); + MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get() == "this is\n\n"); + CHECK(text_edit->get_text() == "some"); + CHECK(text_edit->get_caret_count() == 3); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 2); // In the default font, this is the same position. + // The previous caret at index 1 was merged. + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 0); + CHECK(text_edit->get_caret_column(1) == 3); // In the default font, this is the same position. + CHECK_FALSE(text_edit->has_selection(2)); + CHECK(text_edit->get_caret_line(2) == 0); + CHECK(text_edit->get_caret_column(2) == 4); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + text_edit->remove_secondary_carets(); + + // Cut on the only line removes the contents. + text_edit->set_caret_line(0); + text_edit->set_caret_column(2); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(0, 0)); + + text_edit->cut(); + MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get() == "some\n"); + CHECK(text_edit->get_text() == ""); + CHECK(text_edit->get_line_count() == 1); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 0); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + + // Cut empty line. + text_edit->cut(); + MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get() == "\n"); + CHECK(text_edit->get_text() == ""); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 0); + SIGNAL_CHECK_FALSE("caret_changed"); + // These signals are emitted even if there is no change. + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + + // Cut multiple lines, in order. + text_edit->set_text("this is\nsome\ntext to\nbe\n\ncut"); + text_edit->set_caret_line(2); + text_edit->set_caret_column(7); + text_edit->add_caret(3, 0); + text_edit->add_caret(0, 2); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(1, 0), build_array(3, 2), build_array(2, 1)); + + text_edit->cut(); + MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get() == "this is\ntext to\nbe\n"); + CHECK(text_edit->get_text() == "some\n\ncut"); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK(text_edit->get_caret_line(1) == 0); + CHECK(text_edit->get_caret_column(1) == 2); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + text_edit->remove_secondary_carets(); + + // Cut multiple selections, in order. Ignores regular carets. + text_edit->set_text("this is\nsome\ntext to\nbe\n\ncut"); + text_edit->add_caret(3, 0); + text_edit->add_caret(0, 2); + text_edit->add_caret(2, 0); + text_edit->select(1, 0, 1, 2, 0); + text_edit->select(3, 0, 4, 0, 1); + text_edit->select(0, 5, 0, 3, 2); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(1, 1), build_array(4, 3), build_array(0, 0)); + + text_edit->cut(); + MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get() == "s \nso\nbe\n"); + CHECK(text_edit->get_text() == "thiis\nme\ntext to\n\ncut"); + CHECK(text_edit->get_caret_count() == 4); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK(text_edit->get_caret_line(1) == 3); + CHECK(text_edit->get_caret_column(1) == 0); + CHECK(text_edit->get_caret_line(2) == 0); + CHECK(text_edit->get_caret_column(2) == 3); + CHECK(text_edit->get_caret_line(3) == 2); + CHECK(text_edit->get_caret_column(3) == 0); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); } SUBCASE("[TextEdit] copy") { - // TODO: Cannot test need display server support. + text_edit->set_text("this is\nsome\ntest\n\ntext"); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("caret_changed"); + + // Copy selected text. + text_edit->select(0, 0, 1, 2, 0); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + DS->clipboard_set_primary(""); + + text_edit->copy(); + MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get() == "this is\nso"); + CHECK(DS->clipboard_get_primary() == ""); + CHECK(text_edit->get_text() == "this is\nsome\ntest\n\ntext"); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selection_origin_line() == 0); + CHECK(text_edit->get_selection_origin_column() == 0); + CHECK(text_edit->get_caret_line() == 1); + CHECK(text_edit->get_caret_column() == 2); + SIGNAL_CHECK_FALSE("caret_changed"); + SIGNAL_CHECK_FALSE("text_changed"); + SIGNAL_CHECK_FALSE("lines_edited_from"); + + // Copy with GUI action. + text_edit->select(0, 0, 0, 2, 0); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + + SEND_GUI_ACTION("ui_copy"); + MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get() == "th"); + SIGNAL_CHECK_FALSE("caret_changed"); + SIGNAL_CHECK_FALSE("text_changed"); + SIGNAL_CHECK_FALSE("lines_edited_from"); + + // Can copy even if not editable. + text_edit->select(2, 4, 1, 2, 0); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + + text_edit->set_editable(false); + text_edit->copy(); + text_edit->set_editable(true); + MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get() == "me\ntest"); + SIGNAL_CHECK_FALSE("caret_changed"); + SIGNAL_CHECK_FALSE("text_changed"); + SIGNAL_CHECK_FALSE("lines_edited_from"); + text_edit->deselect(); + + // Copy full line when there is no selection. + text_edit->set_caret_line(0); + text_edit->set_caret_column(2); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + + text_edit->copy(); + MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get() == "this is\n"); + SIGNAL_CHECK_FALSE("caret_changed"); + SIGNAL_CHECK_FALSE("text_changed"); + SIGNAL_CHECK_FALSE("lines_edited_from"); + + // Copy empty line. + text_edit->set_caret_line(3); + text_edit->set_caret_column(0); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + + text_edit->copy(); + MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get() == "\n"); + SIGNAL_CHECK_FALSE("caret_changed"); + SIGNAL_CHECK_FALSE("text_changed"); + SIGNAL_CHECK_FALSE("lines_edited_from"); + text_edit->deselect(); + + // Copy full line with multiple carets on that line only copies once. + text_edit->set_caret_line(1); + text_edit->set_caret_column(2); + text_edit->add_caret(1, 0); + text_edit->add_caret(1, 4); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + + text_edit->copy(); + MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get() == "some\n"); + SIGNAL_CHECK_FALSE("caret_changed"); + SIGNAL_CHECK_FALSE("text_changed"); + SIGNAL_CHECK_FALSE("lines_edited_from"); + text_edit->remove_secondary_carets(); + + // Copy selected text from all selections with `\n` in between, in order. Ignore regular carets. + text_edit->set_caret_line(2); + text_edit->set_caret_column(4); + text_edit->add_caret(4, 0); + text_edit->add_caret(0, 4); + text_edit->add_caret(1, 0); + text_edit->select(1, 3, 2, 4, 0); + text_edit->select(4, 4, 4, 0, 1); + text_edit->select(0, 5, 0, 4, 2); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + + text_edit->copy(); + MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get() == " \ne\ntest\ntext"); + SIGNAL_CHECK_FALSE("caret_changed"); + SIGNAL_CHECK_FALSE("text_changed"); + SIGNAL_CHECK_FALSE("lines_edited_from"); + text_edit->remove_secondary_carets(); + text_edit->deselect(); + + // Copy multiple lines with multiple carets, in order. + text_edit->set_caret_line(3); + text_edit->set_caret_column(0); + text_edit->add_caret(4, 2); + text_edit->add_caret(0, 4); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + + text_edit->copy(); + MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get() == "this is\n\ntext\n"); + SIGNAL_CHECK_FALSE("caret_changed"); + SIGNAL_CHECK_FALSE("text_changed"); + SIGNAL_CHECK_FALSE("lines_edited_from"); } SUBCASE("[TextEdit] paste") { - // TODO: Cannot test need display server support. + // Paste text from clipboard at caret. + text_edit->set_text("this is\nsome\n\ntext"); + text_edit->set_caret_line(1); + text_edit->set_caret_column(2); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(1, 1)); + DS->clipboard_set("paste"); + + text_edit->paste(); + MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get() == "paste"); + CHECK(text_edit->get_text() == "this is\nsopasteme\n\ntext"); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 1); + CHECK(text_edit->get_caret_column() == 7); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + + // Undo. + text_edit->undo(); + MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get() == "paste"); + CHECK(text_edit->get_text() == "this is\nsome\n\ntext"); + CHECK(text_edit->get_caret_line() == 1); + CHECK(text_edit->get_caret_column() == 2); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + + // Redo. + text_edit->redo(); + MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get() == "paste"); + CHECK(text_edit->get_text() == "this is\nsopasteme\n\ntext"); + CHECK(text_edit->get_caret_line() == 1); + CHECK(text_edit->get_caret_column() == 7); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + + // Paste on empty line. Use GUI action. + text_edit->set_text("this is\nsome\n\ntext"); + text_edit->set_caret_line(2); + text_edit->set_caret_column(0); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(2, 2)); + DS->clipboard_set("paste2"); + + SEND_GUI_ACTION("ui_paste"); + MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get() == "paste2"); + CHECK(text_edit->get_text() == "this is\nsome\npaste2\ntext"); + CHECK(text_edit->get_caret_line() == 2); + CHECK(text_edit->get_caret_column() == 6); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + + // Paste removes selection before pasting. + text_edit->set_text("this is\nsome\n\ntext"); + text_edit->select(0, 5, 1, 3); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(1, 0), build_array(0, 0)); + DS->clipboard_set("paste"); + + text_edit->paste(); + MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get() == "paste"); + CHECK(text_edit->get_text() == "this pastee\n\ntext"); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 10); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + + // Paste multiple lines. + text_edit->set_text("this is\nsome\n\ntext"); + text_edit->set_caret_line(0); + text_edit->set_caret_column(1); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(0, 3)); + DS->clipboard_set("multi\n\nline\npaste"); + + text_edit->paste(); + MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get() == "multi\n\nline\npaste"); + CHECK(text_edit->get_text() == "tmulti\n\nline\npastehis is\nsome\n\ntext"); + CHECK(text_edit->get_caret_line() == 3); + CHECK(text_edit->get_caret_column() == 5); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + + // Paste full line after copying it. + text_edit->set_text("this is\nsome\n\ntext"); + text_edit->set_caret_line(1); + text_edit->set_caret_column(2); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(1, 2)); + DS->clipboard_set(""); + text_edit->copy(); + text_edit->set_caret_column(3); + CHECK(DS->clipboard_get() == "some\n"); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + + text_edit->paste(); + MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get() == "some\n"); + CHECK(text_edit->get_text() == "this is\nsome\nsome\n\ntext"); + CHECK(text_edit->get_caret_line() == 2); + CHECK(text_edit->get_caret_column() == 3); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + + // Do not paste as line since it wasn't copied. + text_edit->set_text("this is\nsome\n\ntext"); + text_edit->set_caret_line(0); + text_edit->set_caret_column(4); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(0, 1)); + DS->clipboard_set("paste\n"); + + text_edit->paste(); + MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get() == "paste\n"); + CHECK(text_edit->get_text() == "thispaste\n is\nsome\n\ntext"); + CHECK(text_edit->get_caret_line() == 1); + CHECK(text_edit->get_caret_column() == 0); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + + // Paste text at each caret. + text_edit->set_text("this is\nsome\n\ntext"); + text_edit->set_caret_line(1); + text_edit->set_caret_column(2); + text_edit->add_caret(3, 4); + text_edit->add_caret(0, 4); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(0, 1), build_array(2, 3), build_array(5, 6)); + DS->clipboard_set("paste\ntest"); + + text_edit->paste(); + MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get() == "paste\ntest"); + CHECK(text_edit->get_text() == "thispaste\ntest is\nsopaste\ntestme\n\ntextpaste\ntest"); + CHECK(text_edit->get_caret_count() == 3); + CHECK(text_edit->get_caret_line(0) == 3); + CHECK(text_edit->get_caret_column(0) == 4); + CHECK(text_edit->get_caret_line(1) == 6); + CHECK(text_edit->get_caret_column(1) == 4); + CHECK(text_edit->get_caret_line(2) == 1); + CHECK(text_edit->get_caret_column(2) == 4); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + text_edit->remove_secondary_carets(); + + // Paste line per caret when the amount of lines is equal to the number of carets. + text_edit->set_text("this is\nsome\n\ntext"); + text_edit->set_caret_line(1); + text_edit->set_caret_column(2); + text_edit->add_caret(3, 4); + text_edit->add_caret(0, 4); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(0, 0), build_array(1, 1), build_array(3, 3)); + DS->clipboard_set("paste\ntest\n1"); + + text_edit->paste(); + MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get() == "paste\ntest\n1"); + CHECK(text_edit->get_text() == "thispaste is\nsotestme\n\ntext1"); + CHECK(text_edit->get_caret_count() == 3); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 6); + CHECK(text_edit->get_caret_line(1) == 3); + CHECK(text_edit->get_caret_column(1) == 5); + CHECK(text_edit->get_caret_line(2) == 0); + CHECK(text_edit->get_caret_column(2) == 9); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + text_edit->remove_secondary_carets(); + + // Cannot paste when not editable. + text_edit->set_text("this is\nsome\n\ntext"); + text_edit->set_caret_line(0); + text_edit->set_caret_column(4); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("caret_changed"); + DS->clipboard_set("no paste"); + + text_edit->set_editable(false); + text_edit->paste(); + text_edit->set_editable(true); + MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get() == "no paste"); + CHECK(text_edit->get_text() == "this is\nsome\n\ntext"); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 4); + SIGNAL_CHECK_FALSE("caret_changed"); + SIGNAL_CHECK_FALSE("text_changed"); + SIGNAL_CHECK_FALSE("lines_edited_from"); } SUBCASE("[TextEdit] paste primary") { - // TODO: Cannot test need display server support. + // Set size for mouse input. + text_edit->set_size(Size2(200, 200)); + + text_edit->grab_focus(); + DS->clipboard_set(""); + DS->clipboard_set_primary(""); + CHECK(DS->clipboard_get_primary() == ""); + + // Select text with mouse to put into primary clipboard. + text_edit->set_text("this is\nsome\n\ntext"); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("caret_changed"); + + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(0, 2).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(1, 3).get_center() + Point2i(2, 0), MouseButtonMask::LEFT, Key::NONE); + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(text_edit->get_rect_at_line_column(1, 3).get_center() + Point2i(2, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + CHECK(DS->clipboard_get() == ""); + CHECK(DS->clipboard_get_primary() == "is is\nsom"); + CHECK(text_edit->get_text() == "this is\nsome\n\ntext"); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_selected_text() == "is is\nsom"); + CHECK(text_edit->get_selection_origin_line() == 0); + CHECK(text_edit->get_selection_origin_column() == 2); + CHECK(text_edit->get_caret_line() == 1); + CHECK(text_edit->get_caret_column() == 3); + SIGNAL_CHECK_FALSE("text_set"); + SIGNAL_CHECK_FALSE("text_changed"); + SIGNAL_CHECK_FALSE("lines_edited_from"); + + // Middle click to paste at mouse. + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(3, 4)); + + SEND_GUI_MOUSE_BUTTON_EVENT(text_edit->get_rect_at_line_column(3, 2).get_center() + Point2i(2, 0), MouseButton::MIDDLE, MouseButtonMask::MIDDLE, Key::NONE); + CHECK(DS->clipboard_get_primary() == "is is\nsom"); + CHECK(text_edit->get_text() == "this is\nsome\n\nteis is\nsomxt"); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 4); + CHECK(text_edit->get_caret_column() == 3); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + + // Paste at mouse position if there is only one caret. + text_edit->set_text("this is\nsome\n\ntext"); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(0, 1).get_center() + Point2i(2, 0), MouseButtonMask::NONE, Key::NONE); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("caret_changed"); + DS->clipboard_set_primary("paste"); + lines_edited_args = build_array(build_array(0, 0)); + + text_edit->paste_primary_clipboard(); + MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get_primary() == "paste"); + CHECK(text_edit->get_text() == "tpastehis is\nsome\n\ntext"); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 6); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + + // Paste at all carets if there are multiple carets. + text_edit->set_text("this is\nsome\n\ntext"); + text_edit->set_caret_line(1); + text_edit->set_caret_column(0); + text_edit->add_caret(2, 0); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(0, 1).get_center() + Point2i(2, 0), MouseButtonMask::NONE, Key::NONE); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("caret_changed"); + DS->clipboard_set_primary("paste"); + lines_edited_args = build_array(build_array(1, 1), build_array(2, 2)); + + text_edit->paste_primary_clipboard(); + MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get_primary() == "paste"); + CHECK(text_edit->get_text() == "this is\npastesome\npaste\ntext"); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 5); + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 5); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + + // Cannot paste if not editable. + text_edit->set_text("this is\nsome\n\ntext"); + text_edit->set_caret_line(0); + text_edit->set_caret_column(4); + SEND_GUI_MOUSE_MOTION_EVENT(text_edit->get_rect_at_line_column(1, 3).get_center() + Point2i(2, 0), MouseButtonMask::NONE, Key::NONE); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("caret_changed"); + DS->clipboard_set("no paste"); + + text_edit->set_editable(false); + text_edit->paste_primary_clipboard(); + text_edit->set_editable(true); + MessageQueue::get_singleton()->flush(); + CHECK(DS->clipboard_get() == "no paste"); + CHECK(text_edit->get_text() == "this is\nsome\n\ntext"); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 4); + SIGNAL_CHECK_FALSE("caret_changed"); + SIGNAL_CHECK_FALSE("text_changed"); + SIGNAL_CHECK_FALSE("lines_edited_from"); } SIGNAL_UNWATCH(text_edit, "text_set"); @@ -1512,60 +3569,77 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_UNWATCH(text_edit, "caret_changed"); } - // Add undo / redo tests? SUBCASE("[TextEdit] input") { SIGNAL_WATCH(text_edit, "text_set"); SIGNAL_WATCH(text_edit, "text_changed"); SIGNAL_WATCH(text_edit, "lines_edited_from"); SIGNAL_WATCH(text_edit, "caret_changed"); - Array args1; - args1.push_back(0); - args1.push_back(0); - Array lines_edited_args; - lines_edited_args.push_back(args1); + Array lines_edited_args = build_array(build_array(0, 0)); SUBCASE("[TextEdit] ui_text_newline_above") { text_edit->set_text("this is some test text.\nthis is some test text."); - text_edit->select(0, 0, 0, 4); - text_edit->set_caret_column(4); - - text_edit->add_caret(1, 4); - text_edit->select(1, 0, 1, 4, 1); - CHECK(text_edit->get_caret_count() == 2); - MessageQueue::get_singleton()->flush(); - SIGNAL_DISCARD("text_set"); SIGNAL_DISCARD("text_changed"); SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); - // For the second caret. - Array args2; - args2.push_back(0); - args2.push_back(1); - lines_edited_args.push_front(args2); + // Insert new line above. + text_edit->select(0, 0, 0, 4); + text_edit->add_caret(1, 4); + CHECK(text_edit->get_caret_count() == 2); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(0, 1), build_array(2, 3)); - ((Array)lines_edited_args[1])[1] = 1; SEND_GUI_ACTION("ui_text_newline_above"); CHECK(text_edit->get_viewport()->is_input_handled()); CHECK(text_edit->get_text() == "\nthis is some test text.\n\nthis is some test text."); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 0); CHECK_FALSE(text_edit->has_selection(0)); - + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); CHECK(text_edit->get_caret_line(1) == 2); CHECK(text_edit->get_caret_column(1) == 0); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + + // Undo. + text_edit->undo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "this is some test text.\nthis is some test text."); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 4); + CHECK(text_edit->get_selection_origin_line(0) == 0); + CHECK(text_edit->get_selection_origin_column(0) == 0); CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 4); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", reverse_nested(lines_edited_args)); + + // Redo. + text_edit->redo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "\nthis is some test text.\n\nthis is some test text."); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 0); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); + // Does not work if not editable. text_edit->set_caret_line(1); text_edit->set_caret_column(4); - - text_edit->set_caret_line(3, false, true, 0, 1); + text_edit->set_caret_line(3, false, true, -1, 1); text_edit->set_caret_column(4, false, 1); MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("caret_changed"); @@ -1574,31 +3648,57 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SEND_GUI_ACTION("ui_text_newline_above"); CHECK(text_edit->get_viewport()->is_input_handled()); CHECK(text_edit->get_text() == "\nthis is some test text.\n\nthis is some test text."); - CHECK(text_edit->get_caret_line() == 1); - CHECK(text_edit->get_caret_column() == 4); CHECK_FALSE(text_edit->has_selection(0)); - + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 4); + CHECK_FALSE(text_edit->has_selection(1)); CHECK(text_edit->get_caret_line(1) == 3); CHECK(text_edit->get_caret_column(1) == 4); - CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); text_edit->set_editable(true); - ((Array)lines_edited_args[0])[0] = 2; - ((Array)lines_edited_args[0])[1] = 3; + // Works on first line, empty lines, and only happens at caret for selections. + text_edit->select(1, 10, 0, 0); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(0, 1), build_array(4, 5)); SEND_GUI_ACTION("ui_text_newline_above"); CHECK(text_edit->get_viewport()->is_input_handled()); CHECK(text_edit->get_text() == "\n\nthis is some test text.\n\n\nthis is some test text."); - CHECK(text_edit->get_caret_line() == 1); - CHECK(text_edit->get_caret_column() == 0); CHECK_FALSE(text_edit->has_selection(0)); - + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); CHECK(text_edit->get_caret_line(1) == 4); CHECK(text_edit->get_caret_column(1) == 0); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + + // Insert multiple new lines above from one line. + text_edit->set_text("test"); + text_edit->set_caret_line(0); + text_edit->set_caret_column(1); + text_edit->add_caret(0, 3); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(0, 1), build_array(1, 2)); + + SEND_GUI_ACTION("ui_text_newline_above"); + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK(text_edit->get_text() == "\n\ntest"); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 0); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); @@ -1606,34 +3706,55 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SUBCASE("[TextEdit] ui_text_newline_blank") { text_edit->set_text("this is some test text.\nthis is some test text."); - text_edit->select(0, 0, 0, 4); - text_edit->set_caret_column(4); - - text_edit->add_caret(1, 4); - text_edit->select(1, 0, 1, 4, 1); - CHECK(text_edit->get_caret_count() == 2); - MessageQueue::get_singleton()->flush(); - SIGNAL_DISCARD("text_set"); SIGNAL_DISCARD("text_changed"); SIGNAL_DISCARD("lines_edited_from"); - SIGNAL_DISCARD("caret_changed"); - // For the second caret. - Array args2; - args2.push_back(1); - args2.push_back(2); - lines_edited_args.push_front(args2); + // Insert new line below. + text_edit->select(0, 0, 0, 4); + text_edit->add_caret(1, 4); + CHECK(text_edit->get_caret_count() == 2); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(0, 1), build_array(2, 3)); - ((Array)lines_edited_args[1])[1] = 1; SEND_GUI_ACTION("ui_text_newline_blank"); CHECK(text_edit->get_viewport()->is_input_handled()); CHECK(text_edit->get_text() == "this is some test text.\n\nthis is some test text.\n"); - CHECK(text_edit->get_caret_line() == 1); - CHECK(text_edit->get_caret_column() == 0); CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 3); + CHECK(text_edit->get_caret_column(1) == 0); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + + // Undo. + text_edit->undo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "this is some test text.\nthis is some test text."); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 4); + CHECK(text_edit->get_selection_origin_line(0) == 0); + CHECK(text_edit->get_selection_origin_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 4); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", reverse_nested(lines_edited_args)); + // Redo. + text_edit->redo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "this is some test text.\n\nthis is some test text.\n"); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(0)); CHECK(text_edit->get_caret_line(1) == 3); CHECK(text_edit->get_caret_column(1) == 0); CHECK_FALSE(text_edit->has_selection(1)); @@ -1641,75 +3762,119 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); + // Does not work if not editable. text_edit->set_editable(false); SEND_GUI_ACTION("ui_text_newline_blank"); CHECK(text_edit->get_viewport()->is_input_handled()); CHECK(text_edit->get_text() == "this is some test text.\n\nthis is some test text.\n"); - CHECK(text_edit->get_caret_line() == 1); - CHECK(text_edit->get_caret_column() == 0); CHECK_FALSE(text_edit->has_selection(0)); - + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); CHECK(text_edit->get_caret_line(1) == 3); CHECK(text_edit->get_caret_column(1) == 0); - CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); text_edit->set_editable(true); + + // Insert multiple new lines below from one line. + text_edit->set_text("test"); + text_edit->set_caret_line(0); + text_edit->set_caret_column(1); + text_edit->add_caret(0, 3); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(0, 1), build_array(0, 1)); + + SEND_GUI_ACTION("ui_text_newline_blank"); + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK(text_edit->get_text() == "test\n\n"); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 2); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 0); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); } SUBCASE("[TextEdit] ui_text_newline") { text_edit->set_text("this is some test text.\nthis is some test text."); - text_edit->select(0, 0, 0, 4); - text_edit->set_caret_column(4); - - text_edit->add_caret(1, 4); - text_edit->select(1, 0, 1, 4, 1); - CHECK(text_edit->get_caret_count() == 2); - MessageQueue::get_singleton()->flush(); - SIGNAL_DISCARD("text_set"); SIGNAL_DISCARD("text_changed"); SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); - // For the second caret. - Array args2; - args2.push_back(1); - args2.push_back(1); - lines_edited_args.push_front(args2); - lines_edited_args.push_front(args2.duplicate()); - ((Array)lines_edited_args[1])[1] = 2; - - lines_edited_args.push_back(lines_edited_args[2].duplicate()); - ((Array)lines_edited_args[3])[1] = 1; + // Insert new line at caret. + text_edit->select(0, 0, 0, 4); + text_edit->add_caret(1, 4); + CHECK(text_edit->get_caret_count() == 2); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + // Lines edited: deletion, insert line, insert line. + lines_edited_args = build_array(build_array(0, 0), build_array(0, 1), build_array(2, 3)); SEND_GUI_ACTION("ui_text_newline"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "\n is some test text.\n\n is some test text."); - CHECK(text_edit->get_caret_line() == 1); - CHECK(text_edit->get_caret_column() == 0); + CHECK(text_edit->get_text() == "\n is some test text.\nthis\n is some test text."); CHECK_FALSE(text_edit->has_selection(0)); - + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); CHECK(text_edit->get_caret_line(1) == 3); CHECK(text_edit->get_caret_column(1) == 0); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + + // Undo. + text_edit->undo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "this is some test text.\nthis is some test text."); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 4); + CHECK(text_edit->get_selection_origin_line(0) == 0); + CHECK(text_edit->get_selection_origin_column(0) == 0); CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 4); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", reverse_nested(lines_edited_args)); + + // Redo. + text_edit->redo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "\n is some test text.\nthis\n is some test text."); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 3); + CHECK(text_edit->get_caret_column(1) == 0); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); + // Does not work if not editable. text_edit->set_editable(false); SEND_GUI_ACTION("ui_text_newline"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "\n is some test text.\n\n is some test text."); - CHECK(text_edit->get_caret_line() == 1); - CHECK(text_edit->get_caret_column() == 0); + CHECK(text_edit->get_text() == "\n is some test text.\nthis\n is some test text."); CHECK_FALSE(text_edit->has_selection(0)); - + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); CHECK(text_edit->get_caret_line(1) == 3); CHECK(text_edit->get_caret_column(1) == 0); - CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); @@ -1717,255 +3882,399 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { } SUBCASE("[TextEdit] ui_text_backspace_all_to_left") { - text_edit->set_text("\nthis is some test text.\n\nthis is some test text."); - text_edit->select(1, 0, 1, 4); - text_edit->set_caret_line(1); - text_edit->set_caret_column(4); - - text_edit->add_caret(3, 4); - text_edit->select(3, 0, 3, 4, 1); - CHECK(text_edit->get_caret_count() == 2); - - MessageQueue::get_singleton()->flush(); - Ref<InputEvent> tmpevent = InputEventKey::create_reference(Key::BACKSPACE | KeyModifierMask::ALT | KeyModifierMask::CMD_OR_CTRL); InputMap::get_singleton()->action_add_event("ui_text_backspace_all_to_left", tmpevent); + text_edit->set_text("\nthis is some test text.\n\nthis is some test text."); + MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("text_set"); SIGNAL_DISCARD("text_changed"); SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); - // For the second caret. - Array args2; - args2.push_back(3); - args2.push_back(3); - lines_edited_args.push_front(args2); - - // With selection should be a normal backspace. - ((Array)lines_edited_args[1])[0] = 1; - ((Array)lines_edited_args[1])[1] = 1; + // Remove all text to the left. + text_edit->set_caret_line(1); + text_edit->set_caret_column(5); + text_edit->add_caret(1, 2); + text_edit->add_caret(1, 8); + lines_edited_args = build_array(build_array(1, 1)); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); SEND_GUI_ACTION("ui_text_backspace_all_to_left"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "\n is some test text.\n\n is some test text."); + CHECK(text_edit->get_text() == "\nsome test text.\n\nthis is some test text."); + CHECK(text_edit->get_caret_count() == 1); + CHECK_FALSE(text_edit->has_selection()); CHECK(text_edit->get_caret_line() == 1); CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection(0)); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); - CHECK(text_edit->get_caret_line(1) == 3); - CHECK(text_edit->get_caret_column(1) == 0); + // Undo. + text_edit->undo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "\nthis is some test text.\n\nthis is some test text."); + CHECK(text_edit->get_caret_count() == 3); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 5); CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 2); + CHECK_FALSE(text_edit->has_selection(2)); + CHECK(text_edit->get_caret_line(2) == 1); + CHECK(text_edit->get_caret_column(2) == 8); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); - ((Array)lines_edited_args[0])[1] = 2; - ((Array)lines_edited_args[1])[1] = 0; + // Redo. + text_edit->redo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "\nsome test text.\n\nthis is some test text."); + CHECK(text_edit->get_caret_count() == 1); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 1); + CHECK(text_edit->get_caret_column() == 0); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + + // Acts as a normal backspace with selections. + text_edit->select(1, 5, 1, 9, 0); + text_edit->add_caret(3, 4); + text_edit->select(3, 7, 3, 4, 1); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(3, 3), build_array(1, 1)); - // Start of line should also be a normal backspace. SEND_GUI_ACTION("ui_text_backspace_all_to_left"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " is some test text.\n is some test text."); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 0); + CHECK(text_edit->get_text() == "\nsome text.\n\nthis some test text."); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 5); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 3); + CHECK(text_edit->get_caret_column(1) == 4); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + + // Acts as a normal backspace when at the start of a line. + text_edit->set_caret_column(0); + text_edit->set_caret_column(0, false, 1); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(3, 2), build_array(1, 0)); + SEND_GUI_ACTION("ui_text_backspace_all_to_left"); + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK(text_edit->get_text() == "some text.\nthis some test text."); + CHECK(text_edit->get_caret_count() == 2); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); CHECK(text_edit->get_caret_line(1) == 1); CHECK(text_edit->get_caret_column(1) == 0); - CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); + // Does not work if not editable. text_edit->set_caret_column(text_edit->get_line(0).length()); text_edit->set_caret_column(text_edit->get_line(1).length(), false, 1); MessageQueue::get_singleton()->flush(); - - SIGNAL_DISCARD("text_set"); - SIGNAL_DISCARD("text_changed"); - SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); text_edit->set_editable(false); SEND_GUI_ACTION("ui_text_backspace_all_to_left"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " is some test text.\n is some test text."); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == text_edit->get_line(0).length()); + CHECK(text_edit->get_text() == "some text.\nthis some test text."); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); - + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == text_edit->get_line(0).length()); + CHECK_FALSE(text_edit->has_selection(1)); CHECK(text_edit->get_caret_line(1) == 1); CHECK(text_edit->get_caret_column(1) == text_edit->get_line(1).length()); - CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); text_edit->set_editable(true); - ((Array)lines_edited_args[0])[0] = 1; - ((Array)lines_edited_args[0])[1] = 1; - ((Array)lines_edited_args[1])[0] = 0; + // Remove entire line content when at the end of the line. + lines_edited_args = build_array(build_array(1, 1), build_array(0, 0)); SEND_GUI_ACTION("ui_text_backspace_all_to_left"); CHECK(text_edit->get_viewport()->is_input_handled()); CHECK(text_edit->get_text() == "\n"); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 0); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); - + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); CHECK(text_edit->get_caret_line(1) == 1); CHECK(text_edit->get_caret_column(1) == 0); - CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); + text_edit->remove_secondary_carets(); + + // Removing newline effectively happens after removing text. + text_edit->set_text("test\nlines"); + text_edit->set_caret_line(1); + text_edit->set_caret_column(0); + text_edit->add_caret(1, 4); + + SEND_GUI_ACTION("ui_text_backspace_all_to_left"); + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_text() == "tests"); + CHECK(text_edit->get_caret_count() == 1); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 4); + text_edit->remove_secondary_carets(); + + // Removing newline effectively happens after removing text, reverse caret order. + text_edit->set_text("test\nlines"); + text_edit->set_caret_line(1); + text_edit->set_caret_column(4); + text_edit->add_caret(1, 0); + + SEND_GUI_ACTION("ui_text_backspace_all_to_left"); + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_text() == "tests"); + CHECK(text_edit->get_caret_count() == 1); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 4); + text_edit->remove_secondary_carets(); InputMap::get_singleton()->action_erase_event("ui_text_backspace_all_to_left", tmpevent); } SUBCASE("[TextEdit] ui_text_backspace_word") { text_edit->set_text("\nthis is some test text.\n\nthis is some test text."); - text_edit->select(1, 0, 1, 4); - text_edit->set_caret_line(1); - text_edit->set_caret_column(4); - - text_edit->add_caret(3, 4); - text_edit->select(3, 0, 3, 4, 1); - CHECK(text_edit->get_caret_count() == 2); MessageQueue::get_singleton()->flush(); - SIGNAL_DISCARD("text_set"); SIGNAL_DISCARD("text_changed"); SIGNAL_DISCARD("lines_edited_from"); - SIGNAL_DISCARD("caret_changed"); - - // For the second caret. - Array args2; - args2.push_back(3); - args2.push_back(3); - lines_edited_args.push_front(args2); - // With selection should be a normal backspace. - ((Array)lines_edited_args[1])[0] = 1; - ((Array)lines_edited_args[1])[1] = 1; + // Acts as a normal backspace with selections. + text_edit->select(1, 8, 1, 15); + text_edit->add_caret(3, 6); + text_edit->select(3, 10, 3, 6, 1); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(3, 3), build_array(1, 1)); SEND_GUI_ACTION("ui_text_backspace_word"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "\n is some test text.\n\n is some test text."); - CHECK(text_edit->get_caret_line() == 1); - CHECK(text_edit->get_caret_column() == 0); + CHECK(text_edit->get_text() == "\nthis is st text.\n\nthis ime test text."); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); - - CHECK(text_edit->get_caret_line(1) == 3); - CHECK(text_edit->get_caret_column(1) == 0); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 8); CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 3); + CHECK(text_edit->get_caret_column(1) == 6); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); text_edit->end_complex_operation(); - ((Array)lines_edited_args[0])[1] = 2; - ((Array)lines_edited_args[1])[1] = 0; + lines_edited_args = build_array(build_array(3, 2), build_array(1, 0)); // Start of line should also be a normal backspace. + text_edit->set_caret_column(0); + text_edit->set_caret_column(0, false, 1); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + SEND_GUI_ACTION("ui_text_backspace_word"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " is some test text.\n is some test text."); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 0); + CHECK(text_edit->get_text() == "this is st text.\nthis ime test text."); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); - + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); CHECK(text_edit->get_caret_line(1) == 1); CHECK(text_edit->get_caret_column(1) == 0); - CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); + // Does not work if not editable. text_edit->set_editable(false); SEND_GUI_ACTION("ui_text_backspace_word"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " is some test text.\n is some test text."); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 0); + CHECK(text_edit->get_text() == "this is st text.\nthis ime test text."); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); - + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); CHECK(text_edit->get_caret_line(1) == 1); CHECK(text_edit->get_caret_column(1) == 0); - CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); text_edit->set_editable(true); + // FIXME: Remove after GH-77101 is fixed. + text_edit->start_action(TextEdit::ACTION_NONE); + + // Remove text to the start of the word to the left of the caret. text_edit->set_caret_column(text_edit->get_line(0).length()); - text_edit->set_caret_column(text_edit->get_line(1).length(), false, 1); + text_edit->set_caret_column(12, false, 1); MessageQueue::get_singleton()->flush(); - - SIGNAL_DISCARD("text_set"); - SIGNAL_DISCARD("text_changed"); - SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); - - ((Array)lines_edited_args[0])[0] = 1; - ((Array)lines_edited_args[0])[1] = 1; - ((Array)lines_edited_args[1])[0] = 0; + lines_edited_args = build_array(build_array(1, 1), build_array(0, 0)); SEND_GUI_ACTION("ui_text_backspace_word"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " is some test \n is some test "); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 14); + CHECK(text_edit->get_text() == "this is st \nthis ime t text."); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 11); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 9); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + // Undo. + text_edit->undo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "this is st text.\nthis ime test text."); + CHECK(text_edit->get_caret_count() == 2); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 16); + CHECK_FALSE(text_edit->has_selection(1)); CHECK(text_edit->get_caret_line(1) == 1); - CHECK(text_edit->get_caret_column(1) == 14); + CHECK(text_edit->get_caret_column(1) == 12); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", reverse_nested(lines_edited_args)); + + // Redo. + text_edit->redo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "this is st \nthis ime t text."); + CHECK(text_edit->get_caret_count() == 2); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 11); CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 9); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); - } - SUBCASE("[TextEdit] ui_text_backspace_word same line") { - text_edit->set_text("test test test"); - text_edit->set_caret_column(4); - text_edit->add_caret(0, 9); - text_edit->add_caret(0, 15); + // Removing newline effectively happens after removing text. + text_edit->set_text("test\nlines"); + text_edit->set_caret_line(1); + text_edit->set_caret_column(0); + text_edit->add_caret(1, 4); - // For the second caret. - Array args2; - args2.push_back(0); - lines_edited_args.push_front(args2); + 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() == "tests"); + CHECK(text_edit->get_caret_count() == 1); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 4); + text_edit->remove_secondary_carets(); - // For the third caret. - Array args3; - args2.push_back(0); - lines_edited_args.push_front(args2); + // Removing newline effectively happens after removing text, reverse caret order. + text_edit->set_text("test\nlines"); + text_edit->set_caret_line(1); + text_edit->set_caret_column(4); + text_edit->add_caret(1, 0); - CHECK(text_edit->get_caret_count() == 3); - MessageQueue::get_singleton()->flush(); + 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() == "tests"); + CHECK(text_edit->get_caret_count() == 1); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 4); + text_edit->remove_secondary_carets(); + } + SUBCASE("[TextEdit] ui_text_backspace_word same line") { + text_edit->set_text("test longwordtest test"); + MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("text_set"); SIGNAL_DISCARD("text_changed"); SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); + // Multiple carets on the same line is handled. + text_edit->set_caret_line(0); + text_edit->set_caret_column(4); + text_edit->add_caret(0, 11); + text_edit->add_caret(0, 15); + text_edit->add_caret(0, 9); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + + lines_edited_args = build_array(build_array(0, 0), build_array(0, 0)); + SEND_GUI_ACTION("ui_text_backspace_word"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " "); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 0); + CHECK(text_edit->get_text() == " st test"); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); - + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); CHECK(text_edit->get_caret_line(1) == 0); CHECK(text_edit->get_caret_column(1) == 1); - CHECK_FALSE(text_edit->has_selection(1)); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); - CHECK(text_edit->get_caret_line(2) == 0); - CHECK(text_edit->get_caret_column(2) == 2); + text_edit->undo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "test longwordtest test"); + CHECK(text_edit->get_caret_count() == 4); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 4); CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 0); + CHECK(text_edit->get_caret_column(1) == 11); + CHECK_FALSE(text_edit->has_selection(2)); + CHECK(text_edit->get_caret_line(2) == 0); + CHECK(text_edit->get_caret_column(2) == 15); + CHECK_FALSE(text_edit->has_selection(3)); + CHECK(text_edit->get_caret_line(3) == 0); + CHECK(text_edit->get_caret_column(3) == 9); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", reverse_nested(lines_edited_args)); + text_edit->redo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == " st test"); + CHECK(text_edit->get_caret_count() == 2); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 0); + CHECK(text_edit->get_caret_column(1) == 1); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); @@ -1973,130 +4282,267 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SUBCASE("[TextEdit] ui_text_backspace") { text_edit->set_text("\nthis is some test text.\n\nthis is some test text."); - text_edit->select(1, 0, 1, 4); - text_edit->set_caret_line(1); - text_edit->set_caret_column(4); - - text_edit->add_caret(3, 4); - text_edit->select(3, 0, 3, 4, 1); - CHECK(text_edit->get_caret_count() == 2); - MessageQueue::get_singleton()->flush(); - SIGNAL_DISCARD("text_set"); SIGNAL_DISCARD("text_changed"); SIGNAL_DISCARD("lines_edited_from"); - SIGNAL_DISCARD("caret_changed"); - // For the second caret. - Array args2; - args2.push_back(3); - args2.push_back(3); - lines_edited_args.push_front(args2); - - // With selection should be a normal backspace. - ((Array)lines_edited_args[1])[0] = 1; - ((Array)lines_edited_args[1])[1] = 1; + // Remove selected text when there are selections. + text_edit->select(1, 0, 1, 4); + text_edit->add_caret(3, 4); + text_edit->select(3, 5, 3, 2, 1); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(3, 3), build_array(1, 1)); SEND_GUI_ACTION("ui_text_backspace"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "\n is some test text.\n\n is some test text."); - CHECK(text_edit->get_caret_line() == 1); - CHECK(text_edit->get_caret_column() == 0); + CHECK(text_edit->get_text() == "\n is some test text.\n\nthis some test text."); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 3); + CHECK(text_edit->get_caret_column(1) == 2); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + // Undo remove selection. + text_edit->undo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->get_text() == "\nthis is some test text.\n\nthis is some test text."); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 4); + CHECK(text_edit->get_selection_origin_line(0) == 1); + CHECK(text_edit->get_selection_origin_column(0) == 0); + CHECK(text_edit->has_selection(1)); CHECK(text_edit->get_caret_line(1) == 3); - CHECK(text_edit->get_caret_column(1) == 0); + CHECK(text_edit->get_caret_column(1) == 2); + CHECK(text_edit->get_selection_origin_line(1) == 3); + CHECK(text_edit->get_selection_origin_column(1) == 5); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", reverse_nested(lines_edited_args)); + + // Redo remove selection. + text_edit->redo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "\n is some test text.\n\nthis some test text."); + CHECK(text_edit->get_caret_count() == 2); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 0); CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 3); + CHECK(text_edit->get_caret_column(1) == 2); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); - ((Array)lines_edited_args[0])[1] = 2; - ((Array)lines_edited_args[1])[1] = 0; + // Remove the newline when at start of line. + text_edit->set_caret_column(0, false, 1); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(3, 2), build_array(1, 0)); - // Start of line should also be a normal backspace. SEND_GUI_ACTION("ui_text_backspace"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " is some test text.\n is some test text."); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 0); + CHECK(text_edit->get_text() == " is some test text.\nthis some test text."); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); - + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); CHECK(text_edit->get_caret_line(1) == 1); CHECK(text_edit->get_caret_column(1) == 0); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + + // Undo remove newline. + text_edit->undo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "\n is some test text.\n\nthis some test text."); + CHECK(text_edit->get_caret_count() == 2); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 3); + CHECK(text_edit->get_caret_column(1) == 0); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", reverse_nested(lines_edited_args)); + + // Redo remove newline. + text_edit->redo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == " is some test text.\nthis some test text."); + CHECK(text_edit->get_caret_count() == 2); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 0); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); + // Does not work if not editable. text_edit->set_caret_column(text_edit->get_line(0).length()); - text_edit->set_caret_column(text_edit->get_line(1).length(), false, 1); + text_edit->set_caret_column(15, false, 1); MessageQueue::get_singleton()->flush(); - - SIGNAL_DISCARD("text_set"); - SIGNAL_DISCARD("text_changed"); - SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); text_edit->set_editable(false); SEND_GUI_ACTION("ui_text_backspace"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " is some test text.\n is some test text."); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == text_edit->get_line(0).length()); + CHECK(text_edit->get_text() == " is some test text.\nthis some test text."); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); - - CHECK(text_edit->get_caret_line(1) == 1); - CHECK(text_edit->get_caret_column(1) == text_edit->get_line(1).length()); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == text_edit->get_line(0).length()); CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 15); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); text_edit->set_editable(true); - ((Array)lines_edited_args[0])[0] = 1; - ((Array)lines_edited_args[0])[1] = 1; - ((Array)lines_edited_args[1])[0] = 0; + // FIXME: Remove after GH-77101 is fixed. + text_edit->start_action(TextEdit::ACTION_NONE); + + // Backspace removes character to the left. + lines_edited_args = build_array(build_array(1, 1), build_array(0, 0)); SEND_GUI_ACTION("ui_text_backspace"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " is some test text\n is some test text"); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 18); + CHECK(text_edit->get_text() == " is some test text\nthis some testtext."); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 18); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 14); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + // Backspace another character without changing caret. + SEND_GUI_ACTION("ui_text_backspace"); + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK(text_edit->get_text() == " is some test tex\nthis some testext."); + CHECK(text_edit->get_caret_count() == 2); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 17); + CHECK_FALSE(text_edit->has_selection(1)); CHECK(text_edit->get_caret_line(1) == 1); - CHECK(text_edit->get_caret_column(1) == 18); + CHECK(text_edit->get_caret_column(1) == 13); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + + // Undo both backspaces. + lines_edited_args = build_array(build_array(1, 1), build_array(0, 0), build_array(1, 1), build_array(0, 0)); + + text_edit->undo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == " is some test text.\nthis some test text."); + CHECK(text_edit->get_caret_count() == 2); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 19); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 15); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", reverse_nested(lines_edited_args)); + + // Redo both backspaces. + text_edit->redo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == " is some test tex\nthis some testext."); + CHECK(text_edit->get_caret_count() == 2); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 17); CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 13); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); - // Select the entire text, from right to left - text_edit->select(0, 18, 0, 0); + // Backspace with multiple carets that will overlap. + text_edit->remove_secondary_carets(); text_edit->set_caret_line(0); - text_edit->set_caret_column(0); - - text_edit->select(1, 18, 1, 0, 1); - text_edit->set_caret_line(1, false, true, 0, 1); - text_edit->set_caret_column(0, false, 1); + text_edit->set_caret_column(8); + text_edit->add_caret(0, 7); + text_edit->add_caret(0, 9); MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(0, 0), build_array(0, 0), build_array(0, 0)); - SIGNAL_DISCARD("text_set"); - SIGNAL_DISCARD("text_changed"); - SIGNAL_DISCARD("lines_edited_from"); + SEND_GUI_ACTION("ui_text_backspace"); + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK(text_edit->get_text() == " is sotest tex\nthis some testext."); + CHECK(text_edit->get_caret_count() == 1); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 6); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + + // Select each line of text, from right to left. Remove selection to column 0. + text_edit->select(0, text_edit->get_line(0).length(), 0, 0); + text_edit->add_caret(1, 0); + text_edit->select(1, text_edit->get_line(1).length(), 1, 0, 1); + MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(1, 1), build_array(0, 0)); SEND_GUI_ACTION("ui_text_backspace"); CHECK(text_edit->get_text() == "\n"); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 0); + CHECK(text_edit->get_caret_count() == 2); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); CHECK(text_edit->get_caret_line(1) == 1); CHECK(text_edit->get_caret_column(1) == 0); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); + + // Backspace at start of first line does nothing. + text_edit->remove_secondary_carets(); + text_edit->deselect(); + text_edit->set_caret_line(0); + text_edit->set_caret_column(0); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + + SEND_GUI_ACTION("ui_text_backspace"); + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK(text_edit->get_text() == "\n"); + CHECK(text_edit->get_caret_count() == 1); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 0); + SIGNAL_CHECK_FALSE("caret_changed"); + SIGNAL_CHECK_FALSE("text_changed"); + SIGNAL_CHECK_FALSE("lines_edited_from"); } SUBCASE("[TextEdit] ui_text_delete_all_to_right") { @@ -2104,101 +4550,138 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { InputMap::get_singleton()->action_add_event("ui_text_delete_all_to_right", tmpevent); text_edit->set_text("this is some test text.\nthis is some test text.\n"); - text_edit->select(0, 0, 0, 4); - text_edit->set_caret_line(0); - text_edit->set_caret_column(4); - - text_edit->add_caret(1, 4); - text_edit->select(1, 0, 1, 4, 1); - CHECK(text_edit->get_caret_count() == 2); - MessageQueue::get_singleton()->flush(); - SIGNAL_DISCARD("text_set"); SIGNAL_DISCARD("text_changed"); SIGNAL_DISCARD("lines_edited_from"); - SIGNAL_DISCARD("caret_changed"); - // For the second caret. - Array args2; - args2.push_back(1); - args2.push_back(1); - lines_edited_args.push_front(args2); + // Remove all text to right of caret. + text_edit->set_caret_line(0); + text_edit->set_caret_column(18); + text_edit->add_caret(0, 16); + text_edit->add_caret(0, 20); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(0, 0)); - // With selection should be a normal delete. SEND_GUI_ACTION("ui_text_delete_all_to_right"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " is some test text.\n is some test text.\n"); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 0); + CHECK(text_edit->get_text() == "this is some tes\nthis is some test text.\n"); + CHECK(text_edit->get_caret_count() == 1); CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 16); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); - CHECK(text_edit->get_caret_line(1) == 1); - CHECK(text_edit->get_caret_column(1) == 0); + // Undo. + lines_edited_args = build_array(build_array(0, 0)); + + text_edit->undo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "this is some test text.\nthis is some test text.\n"); + CHECK(text_edit->get_caret_count() == 3); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 18); CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 0); + CHECK(text_edit->get_caret_column(1) == 16); + CHECK_FALSE(text_edit->has_selection(2)); + CHECK(text_edit->get_caret_line(2) == 0); + CHECK(text_edit->get_caret_column(2) == 20); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); - // End of line should not do anything. - text_edit->set_caret_column(text_edit->get_line(0).length()); - text_edit->set_caret_column(text_edit->get_line(1).length(), false, 1); + // Redo. + text_edit->redo(); MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "this is some tes\nthis is some test text.\n"); + CHECK(text_edit->get_caret_count() == 1); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 16); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); - SIGNAL_DISCARD("text_set"); - SIGNAL_DISCARD("text_changed"); - SIGNAL_DISCARD("lines_edited_from"); + // Acts as a normal delete with selections. + text_edit->select(0, 0, 0, 4); + text_edit->add_caret(1, 4); + text_edit->select(1, 8, 1, 4, 1); + MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(0, 0), build_array(1, 1)); SEND_GUI_ACTION("ui_text_delete_all_to_right"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " is some test text.\n is some test text.\n"); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == text_edit->get_line(0).length()); + CHECK(text_edit->get_text() == " is some tes\nthissome test text.\n"); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 4); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + // Does nothing when caret is at end of line. + text_edit->set_caret_column(text_edit->get_line(0).length()); + text_edit->set_caret_column(text_edit->get_line(1).length(), false, 1); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + + SEND_GUI_ACTION("ui_text_delete_all_to_right"); + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK(text_edit->get_text() == " is some tes\nthissome test text.\n"); + CHECK(text_edit->get_caret_count() == 2); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == text_edit->get_line(0).length()); + CHECK_FALSE(text_edit->has_selection(1)); CHECK(text_edit->get_caret_line(1) == 1); CHECK(text_edit->get_caret_column(1) == text_edit->get_line(1).length()); - CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); + // Does not work if not editable. text_edit->set_caret_column(0); text_edit->set_caret_column(0, false, 1); MessageQueue::get_singleton()->flush(); - - SIGNAL_DISCARD("text_set"); - SIGNAL_DISCARD("text_changed"); - SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); text_edit->set_editable(false); SEND_GUI_ACTION("ui_text_delete_all_to_right"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " is some test text.\n is some test text.\n"); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 0); + CHECK(text_edit->get_text() == " is some tes\nthissome test text.\n"); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); - + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); CHECK(text_edit->get_caret_line(1) == 1); CHECK(text_edit->get_caret_column(1) == 0); - CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); text_edit->set_editable(true); + // Delete entire line. SEND_GUI_ACTION("ui_text_delete_all_to_right"); CHECK(text_edit->get_viewport()->is_input_handled()); CHECK(text_edit->get_text() == "\n\n"); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 0); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); - + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); CHECK(text_edit->get_caret_line(1) == 1); CHECK(text_edit->get_caret_column(1) == 0); - CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); @@ -2210,302 +4693,589 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->set_caret_mid_grapheme_enabled(true); CHECK(text_edit->is_caret_mid_grapheme_enabled()); - text_edit->set_text("this ffi some test text.\n\nthis ffi some test text.\n"); - text_edit->select(0, 0, 0, 4); - text_edit->set_caret_line(0); - text_edit->set_caret_column(4); - - text_edit->add_caret(2, 4); - text_edit->select(2, 0, 2, 4, 1); - CHECK(text_edit->get_caret_count() == 2); - + text_edit->set_text("this is some test text.\n\nthis is some test text.\n"); MessageQueue::get_singleton()->flush(); - SIGNAL_DISCARD("text_set"); SIGNAL_DISCARD("text_changed"); SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); - // For the second caret. - Array args2; - args2.push_back(2); - args2.push_back(2); - lines_edited_args.push_front(args2); + // Acts as a normal delete with selections. + text_edit->select(0, 8, 0, 15); + text_edit->add_caret(2, 6); + text_edit->select(2, 10, 2, 6, 1); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(0, 0), build_array(2, 2)); - // With selection should be a normal delete. SEND_GUI_ACTION("ui_text_delete_word"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " ffi some test text.\n\n ffi some test text.\n"); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 0); + CHECK(text_edit->get_text() == "this is st text.\n\nthis ime test text.\n"); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); - - CHECK(text_edit->get_caret_line(1) == 2); - CHECK(text_edit->get_caret_column(1) == 0); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 8); CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 6); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); - // With selection should be a normal delete. - ((Array)lines_edited_args[0])[0] = 3; - ((Array)lines_edited_args[1])[0] = 1; + // Removes newlines when at end of line. text_edit->set_caret_column(text_edit->get_line(0).length()); text_edit->set_caret_column(text_edit->get_line(2).length(), false, 1); MessageQueue::get_singleton()->flush(); - - SIGNAL_DISCARD("text_set"); - SIGNAL_DISCARD("text_changed"); - SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(1, 0), build_array(2, 1)); SEND_GUI_ACTION("ui_text_delete_word"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " ffi some test text.\n ffi some test text."); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == text_edit->get_line(0).length()); - CHECK_FALSE(text_edit->has_selection()); - + CHECK(text_edit->get_text() == "this is st text.\nthis ime test text."); + CHECK(text_edit->get_caret_count() == 2); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == text_edit->get_line(0).length()); + CHECK_FALSE(text_edit->has_selection(1)); CHECK(text_edit->get_caret_line(1) == 1); CHECK(text_edit->get_caret_column(1) == text_edit->get_line(1).length()); - CHECK_FALSE(text_edit->has_selection(0)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); - ((Array)lines_edited_args[1])[0] = 0; - ((Array)lines_edited_args[0])[0] = 1; - ((Array)lines_edited_args[0])[1] = 1; + // Does not work if not editable. text_edit->set_caret_column(0); - text_edit->set_caret_column(0, false, 1); + text_edit->set_caret_column(10, false, 1); MessageQueue::get_singleton()->flush(); - - SIGNAL_DISCARD("text_set"); - SIGNAL_DISCARD("text_changed"); - SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); text_edit->set_editable(false); SEND_GUI_ACTION("ui_text_delete_word"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " ffi some test text.\n ffi some test text."); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 0); + CHECK(text_edit->get_text() == "this is st text.\nthis ime test text."); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); - - CHECK(text_edit->get_caret_line(1) == 1); - CHECK(text_edit->get_caret_column(1) == 0); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 10); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); text_edit->set_editable(true); + // FIXME: Remove after GH-77101 is fixed. + text_edit->start_action(TextEdit::ACTION_NONE); + + // Delete to the end of the word right of the caret. + lines_edited_args = build_array(build_array(0, 0), build_array(1, 1)); + SEND_GUI_ACTION("ui_text_delete_word"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " some test text.\n some test text."); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 0); + CHECK(text_edit->get_text() == " is st text.\nthis ime t text."); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 10); + SIGNAL_CHECK_FALSE("caret_changed"); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + // Undo. + text_edit->undo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "this is st text.\nthis ime test text."); + CHECK(text_edit->get_caret_count() == 2); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); CHECK(text_edit->get_caret_line(1) == 1); - CHECK(text_edit->get_caret_column(1) == 0); + CHECK(text_edit->get_caret_column(1) == 10); + SIGNAL_CHECK_FALSE("caret_changed"); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", reverse_nested(lines_edited_args)); + + // Redo. + text_edit->redo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == " is st text.\nthis ime t text."); + CHECK(text_edit->get_caret_count() == 2); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 10); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); - } - SUBCASE("[TextEdit] ui_text_delete") { - text_edit->set_caret_mid_grapheme_enabled(true); - CHECK(text_edit->is_caret_mid_grapheme_enabled()); + // Delete one word with multiple carets. + text_edit->remove_secondary_carets(); + text_edit->set_text("onelongword test"); + text_edit->set_caret_line(0); + text_edit->set_caret_column(6); + text_edit->add_caret(0, 9); + text_edit->add_caret(0, 3); + lines_edited_args = build_array(build_array(0, 0)); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("text_set"); + SIGNAL_DISCARD("text_changed"); + SIGNAL_DISCARD("lines_edited_from"); + SIGNAL_DISCARD("caret_changed"); - text_edit->set_text("this ffi some test text.\nthis ffi some test text."); - text_edit->select(0, 0, 0, 4); + SEND_GUI_ACTION("ui_text_delete_word"); + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK(text_edit->get_text() == "one test"); + CHECK(text_edit->get_caret_count() == 1); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 3); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + + // Removing newline effectively happens after removing text. + text_edit->set_text("test\nlines"); + text_edit->set_caret_line(0); + text_edit->set_caret_column(2); + text_edit->add_caret(0, 4); + + 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() == "telines"); + CHECK(text_edit->get_caret_count() == 1); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 2); + text_edit->remove_secondary_carets(); + + // Removing newline effectively happens after removing text, reverse caret order. + text_edit->set_text("test\nlines"); text_edit->set_caret_line(0); text_edit->set_caret_column(4); + text_edit->add_caret(0, 2); - text_edit->add_caret(1, 4); - text_edit->select(1, 0, 1, 4, 1); - CHECK(text_edit->get_caret_count() == 2); + 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() == "telines"); + CHECK(text_edit->get_caret_count() == 1); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 2); + text_edit->remove_secondary_carets(); + } + SUBCASE("[TextEdit] ui_text_delete_word same line") { + text_edit->set_text("test longwordtest test"); MessageQueue::get_singleton()->flush(); - SIGNAL_DISCARD("text_set"); SIGNAL_DISCARD("text_changed"); SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); - // For the second caret. - Array args2; - args2.push_back(1); - args2.push_back(1); - lines_edited_args.push_front(args2); + // Multiple carets on the same line is handled. + text_edit->set_caret_line(0); + text_edit->set_caret_column(0); + text_edit->add_caret(0, 11); + text_edit->add_caret(0, 15); + text_edit->add_caret(0, 9); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); - // With selection should be a normal delete. - SEND_GUI_ACTION("ui_text_delete"); + lines_edited_args = build_array(build_array(0, 0), build_array(0, 0)); + + SEND_GUI_ACTION("ui_text_delete_word"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " ffi some test text.\n ffi some test text."); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 0); + CHECK(text_edit->get_text() == " long test"); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); - - CHECK(text_edit->get_caret_line(1) == 1); - CHECK(text_edit->get_caret_column(1) == 0); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 0); + CHECK(text_edit->get_caret_column(1) == 5); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); - // With selection should be a normal delete. - lines_edited_args.remove_at(0); - ((Array)lines_edited_args[0])[0] = 1; - text_edit->set_caret_column(text_edit->get_line(1).length(), false, 1); - text_edit->set_caret_column(text_edit->get_line(0).length()); + lines_edited_args = build_array(build_array(0, 0), build_array(0, 0)); + + text_edit->undo(); MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "test longwordtest test"); + CHECK(text_edit->get_caret_count() == 4); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 0); + CHECK(text_edit->get_caret_column(1) == 11); + CHECK_FALSE(text_edit->has_selection(2)); + CHECK(text_edit->get_caret_line(2) == 0); + CHECK(text_edit->get_caret_column(2) == 15); + CHECK_FALSE(text_edit->has_selection(3)); + CHECK(text_edit->get_caret_line(3) == 0); + CHECK(text_edit->get_caret_column(3) == 9); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", reverse_nested(lines_edited_args)); + text_edit->redo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == " long test"); + CHECK(text_edit->get_caret_count() == 2); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 0); + CHECK(text_edit->get_caret_column(1) == 5); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + } + + SUBCASE("[TextEdit] ui_text_delete") { + text_edit->set_caret_mid_grapheme_enabled(true); + CHECK(text_edit->is_caret_mid_grapheme_enabled()); + + text_edit->set_text("this is some test text.\n\nthis is some test text.\n"); + MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("text_set"); SIGNAL_DISCARD("text_changed"); SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); + // Remove selected text when there are selections. + text_edit->select(0, 0, 0, 4); + text_edit->add_caret(2, 2); + text_edit->select(2, 5, 2, 2, 1); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(0, 0), build_array(2, 2)); + SEND_GUI_ACTION("ui_text_delete"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " ffi some test text. ffi some test text."); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 20); + CHECK(text_edit->get_text() == " is some test text.\n\nthis some test text.\n"); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 2); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); - // Caret should be removed due to column preservation. - CHECK(text_edit->get_caret_count() == 1); - - // Lets add it back. - text_edit->set_caret_column(0); - text_edit->add_caret(0, 20); + // Undo remove selection. + text_edit->undo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->get_text() == "this is some test text.\n\nthis is some test text.\n"); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 4); + CHECK(text_edit->get_selection_origin_line(0) == 0); + CHECK(text_edit->get_selection_origin_column(0) == 0); + CHECK(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 2); + CHECK(text_edit->get_selection_origin_line(1) == 2); + CHECK(text_edit->get_selection_origin_column(1) == 5); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", reverse_nested(lines_edited_args)); - ((Array)lines_edited_args[0])[0] = 0; - lines_edited_args.push_back(args2); - ((Array)lines_edited_args[1])[0] = 0; - ((Array)lines_edited_args[1])[1] = 0; + // Redo remove selection. + text_edit->redo(); MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == " is some test text.\n\nthis some test text.\n"); + CHECK(text_edit->get_caret_count() == 2); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 2); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); - SIGNAL_DISCARD("text_set"); - SIGNAL_DISCARD("text_changed"); - SIGNAL_DISCARD("lines_edited_from"); + // Remove newline when at end of line. + text_edit->set_caret_column(text_edit->get_line(0).length()); + text_edit->set_caret_column(text_edit->get_line(2).length(), false, 1); + MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("caret_changed"); + lines_edited_args = build_array(build_array(1, 0), build_array(2, 1)); - text_edit->set_editable(false); SEND_GUI_ACTION("ui_text_delete"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == " ffi some test text. ffi some test text."); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 0); + CHECK(text_edit->get_text() == " is some test text.\nthis some test text."); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 19); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 20); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); - CHECK(text_edit->get_caret_line(1) == 0); + // Undo remove newline. + text_edit->undo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == " is some test text.\n\nthis some test text.\n"); + CHECK(text_edit->get_caret_count() == 2); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 19); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 2); CHECK(text_edit->get_caret_column(1) == 20); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", reverse_nested(lines_edited_args)); + + // Redo remove newline. + text_edit->redo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == " is some test text.\nthis some test text."); + CHECK(text_edit->get_caret_count() == 2); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 19); CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 20); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + + // Does not work if not editable. + text_edit->set_caret_column(0); + text_edit->set_caret_column(15, false, 1); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + + text_edit->set_editable(false); + SEND_GUI_ACTION("ui_text_delete"); + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK(text_edit->get_text() == " is some test text.\nthis some test text."); + CHECK(text_edit->get_caret_count() == 2); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 15); SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); text_edit->set_editable(true); + // FIXME: Remove after GH-77101 is fixed. text_edit->start_action(TextEdit::EditAction::ACTION_NONE); + // Delete removes character to the right. + lines_edited_args = build_array(build_array(0, 0), build_array(1, 1)); + SEND_GUI_ACTION("ui_text_delete"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "ffi some test text.ffi some test text."); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 0); + CHECK(text_edit->get_text() == "is some test text.\nthis some test ext."); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 15); + SIGNAL_CHECK_FALSE("caret_changed"); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); - CHECK(text_edit->get_caret_line(1) == 0); - CHECK(text_edit->get_caret_column(1) == 19); + // Delete another character without changing caret. + SEND_GUI_ACTION("ui_text_delete"); + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK(text_edit->get_text() == "s some test text.\nthis some test xt."); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); - SIGNAL_CHECK("caret_changed", empty_signal_args); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 15); + SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); - text_edit->start_action(TextEdit::EditAction::ACTION_NONE); + // Undo both deletes. + lines_edited_args = build_array(build_array(0, 0), build_array(1, 1), build_array(0, 0), build_array(1, 1)); - SEND_GUI_ACTION("ui_text_delete"); - CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_text() == "fi some test text.fi some test text."); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 0); + text_edit->undo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == " is some test text.\nthis some test text."); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 15); + SIGNAL_CHECK_FALSE("caret_changed"); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", reverse_nested(lines_edited_args)); - CHECK(text_edit->get_caret_line(1) == 0); - CHECK(text_edit->get_caret_column(1) == 18); + // Redo both deletes. + text_edit->redo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "s some test text.\nthis some test xt."); + CHECK(text_edit->get_caret_count() == 2); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); CHECK_FALSE(text_edit->has_selection(1)); - SIGNAL_CHECK("caret_changed", empty_signal_args); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 15); + SIGNAL_CHECK_FALSE("caret_changed"); SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); + + // Delete at end of last line does nothing. + text_edit->remove_secondary_carets(); + text_edit->set_caret_line(1); + text_edit->set_caret_column(18); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + + SEND_GUI_ACTION("ui_text_delete"); + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK(text_edit->get_text() == "s some test text.\nthis some test xt."); + CHECK(text_edit->get_caret_count() == 1); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 18); + SIGNAL_CHECK_FALSE("caret_changed"); + SIGNAL_CHECK_FALSE("text_changed"); + SIGNAL_CHECK_FALSE("lines_edited_from"); } SUBCASE("[TextEdit] ui_text_caret_word_left") { text_edit->set_text("\nthis is some test text.\nthis is some test text."); text_edit->set_caret_line(1); - text_edit->set_caret_column(7); - - text_edit->add_caret(2, 7); - CHECK(text_edit->get_caret_count() == 2); + text_edit->set_caret_column(15); + text_edit->add_caret(2, 10); + text_edit->select(1, 10, 1, 15); + text_edit->select(2, 15, 2, 10, 1); MessageQueue::get_singleton()->flush(); - SIGNAL_DISCARD("text_set"); SIGNAL_DISCARD("text_changed"); SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); - // Shift should select. + // Deselect to start of previous word when selection is right to left. + // Select to start of next word when selection is left to right. #ifdef MACOS_ENABLED SEND_GUI_KEY_EVENT(Key::LEFT | KeyModifierMask::ALT | KeyModifierMask::SHIFT); #else SEND_GUI_KEY_EVENT(Key::LEFT | KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT); #endif CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_caret_line() == 1); - CHECK(text_edit->get_caret_column() == 5); - CHECK(text_edit->get_selected_text(0) == "is"); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selected_text(0) == "me "); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 13); + CHECK(text_edit->get_selection_origin_line(0) == 1); + CHECK(text_edit->get_selection_origin_column(0) == 10); + CHECK(text_edit->is_caret_after_selection_origin(0)); - CHECK(text_edit->get_caret_line(1) == 2); - CHECK(text_edit->get_caret_column(1) == 5); - CHECK(text_edit->get_selected_text(1) == "is"); CHECK(text_edit->has_selection(1)); + CHECK(text_edit->get_selected_text(1) == "some te"); + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 8); + CHECK(text_edit->get_selection_origin_line(1) == 2); + CHECK(text_edit->get_selection_origin_column(1) == 15); + CHECK_FALSE(text_edit->is_caret_after_selection_origin(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); - // Should still move caret with selection. - SEND_GUI_ACTION("ui_text_caret_word_left"); + // Select to start of word with shift. + text_edit->deselect(); + text_edit->set_caret_column(7); + text_edit->set_caret_column(16, false, 1); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + +#ifdef MACOS_ENABLED + SEND_GUI_KEY_EVENT(Key::LEFT | KeyModifierMask::ALT | KeyModifierMask::SHIFT); +#else + SEND_GUI_KEY_EVENT(Key::LEFT | KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT); +#endif CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_caret_line() == 1); - CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selected_text(0) == "is"); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 5); + CHECK(text_edit->get_selection_origin_line(0) == 1); + CHECK(text_edit->get_selection_origin_column(0) == 7); + CHECK_FALSE(text_edit->is_caret_after_selection_origin(0)); + + CHECK(text_edit->has_selection(1)); + CHECK(text_edit->get_selected_text(1) == "tes"); CHECK(text_edit->get_caret_line(1) == 2); - CHECK(text_edit->get_caret_column(1) == 0); - CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_column(1) == 13); + CHECK(text_edit->get_selection_origin_line(1) == 2); + CHECK(text_edit->get_selection_origin_column(1) == 16); + CHECK_FALSE(text_edit->is_caret_after_selection_origin(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); - // Normal word left. + // Deselect and move caret to start of next word without shift. SEND_GUI_ACTION("ui_text_caret_word_left"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 0); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 8); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_changed"); + SIGNAL_CHECK_FALSE("lines_edited_from"); + + // Moves to end of previous line when at start of line. Does nothing at start of text. + text_edit->set_caret_line(0); + text_edit->set_caret_column(0); + text_edit->set_caret_column(0, false, 1); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + SEND_GUI_ACTION("ui_text_caret_word_left"); + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK(text_edit->get_caret_count() == 2); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); CHECK(text_edit->get_caret_line(1) == 1); CHECK(text_edit->get_caret_column(1) == 23); - CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); @@ -2515,249 +5285,417 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->set_text("\nthis is some test text.\nthis is some test text."); text_edit->set_caret_line(1); text_edit->set_caret_column(7); - text_edit->select(1, 2, 1, 7); - - text_edit->add_caret(2, 7); - text_edit->select(2, 2, 2, 7, 1); - CHECK(text_edit->get_caret_count() == 2); - + text_edit->select(1, 3, 1, 7); + text_edit->add_caret(2, 3); + text_edit->select(2, 7, 2, 3, 1); MessageQueue::get_singleton()->flush(); - SIGNAL_DISCARD("text_set"); SIGNAL_DISCARD("text_changed"); SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); - // Normal left should deselect and place at selection start. - SEND_GUI_ACTION("ui_text_caret_left"); + // Remove one character from selection when selection is left to right. + // Add one character to selection when selection is right to left. + SEND_GUI_KEY_EVENT(Key::LEFT | KeyModifierMask::SHIFT); CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selected_text(0) == "s i"); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 6); + CHECK(text_edit->get_selection_origin_line(0) == 1); + CHECK(text_edit->get_selection_origin_column(0) == 3); + CHECK(text_edit->is_caret_after_selection_origin(0)); - CHECK(text_edit->get_caret_line() == 1); - CHECK(text_edit->get_caret_column() == 2); - CHECK_FALSE(text_edit->has_selection(0)); - + CHECK(text_edit->has_selection(1)); + CHECK(text_edit->get_selected_text(1) == "is is"); CHECK(text_edit->get_caret_line(1) == 2); CHECK(text_edit->get_caret_column(1) == 2); - CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_selection_origin_line(1) == 2); + CHECK(text_edit->get_selection_origin_column(1) == 7); + CHECK_FALSE(text_edit->is_caret_after_selection_origin(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); - // With shift should select. - SEND_GUI_KEY_EVENT(Key::LEFT | KeyModifierMask::SHIFT); + // Deselect and put caret at selection start without shift. + SEND_GUI_ACTION("ui_text_caret_left"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_caret_line() == 1); - CHECK(text_edit->get_caret_column() == 1); - CHECK(text_edit->get_selected_text(0) == "h"); - CHECK(text_edit->has_selection(0)); - + CHECK(text_edit->get_caret_count() == 2); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 3); + CHECK_FALSE(text_edit->has_selection(1)); CHECK(text_edit->get_caret_line(1) == 2); - CHECK(text_edit->get_caret_column(1) == 1); - CHECK(text_edit->get_selected_text(1) == "h"); - CHECK(text_edit->has_selection(1)); - + CHECK(text_edit->get_caret_column(1) == 2); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); - // All ready at select left, should only deselect. + // Move caret one character to the left. SEND_GUI_ACTION("ui_text_caret_left"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_caret_line() == 1); - CHECK(text_edit->get_caret_column() == 1); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); - + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 2); + CHECK_FALSE(text_edit->has_selection(1)); CHECK(text_edit->get_caret_line(1) == 2); CHECK(text_edit->get_caret_column(1) == 1); - CHECK_FALSE(text_edit->has_selection(1)); - - SIGNAL_CHECK_FALSE("caret_changed"); + SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); - // Normal left. - SEND_GUI_ACTION("ui_text_caret_left"); + // Select one character to the left with shift and no existing selection. + SEND_GUI_KEY_EVENT(Key::LEFT | KeyModifierMask::SHIFT); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_caret_line() == 1); - CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selected_text(0) == "h"); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 1); + CHECK(text_edit->get_selection_origin_line(0) == 1); + CHECK(text_edit->get_selection_origin_column(0) == 2); + CHECK_FALSE(text_edit->is_caret_after_selection_origin(0)); + CHECK(text_edit->has_selection(1)); + CHECK(text_edit->get_selected_text(1) == "t"); CHECK(text_edit->get_caret_line(1) == 2); CHECK(text_edit->get_caret_column(1) == 0); - CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_selection_origin_line(1) == 2); + CHECK(text_edit->get_selection_origin_column(1) == 1); + CHECK_FALSE(text_edit->is_caret_after_selection_origin(1)); + SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); - // Left at col 0 should go up a line. + // Moves to end of previous line when at start of line. Does nothing at start of text. + text_edit->deselect(); + text_edit->set_caret_line(0); + text_edit->set_caret_column(0); + text_edit->set_caret_column(0, false, 1); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + SEND_GUI_ACTION("ui_text_caret_left"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 0); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); - + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); CHECK(text_edit->get_caret_line(1) == 1); CHECK(text_edit->get_caret_column(1) == 23); - CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); - } - SUBCASE("[TextEdit] ui_text_caret_word_right") { - text_edit->set_text("this is some test text\n\nthis is some test text\n"); - text_edit->set_caret_line(0); - text_edit->set_caret_column(13); + // Selects to end of previous line when at start of line. + text_edit->remove_secondary_carets(); + text_edit->set_caret_line(1); + text_edit->set_caret_column(0); + text_edit->select(1, 1, 1, 0); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); - text_edit->add_caret(2, 13); - CHECK(text_edit->get_caret_count() == 2); + SEND_GUI_KEY_EVENT(Key::LEFT | KeyModifierMask::SHIFT); + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK(text_edit->get_caret_count() == 1); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selected_text(0) == "\nt"); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK(text_edit->get_selection_origin_line(0) == 1); + CHECK(text_edit->get_selection_origin_column(0) == 1); + CHECK_FALSE(text_edit->is_caret_after_selection_origin(0)); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_changed"); + SIGNAL_CHECK_FALSE("lines_edited_from"); + // Merge selections when they overlap. + text_edit->set_caret_line(1); + text_edit->set_caret_column(4); + text_edit->select(1, 6, 1, 4); + text_edit->add_caret(1, 8); + text_edit->select(1, 8, 1, 6, 1); MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + CHECK(text_edit->get_caret_count() == 2); + + SEND_GUI_KEY_EVENT(Key::LEFT | KeyModifierMask::SHIFT); + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK(text_edit->get_caret_count() == 1); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selected_text(0) == "s is "); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 3); + CHECK(text_edit->get_selection_origin_line(0) == 1); + CHECK(text_edit->get_selection_origin_column(0) == 8); + CHECK_FALSE(text_edit->is_caret_after_selection_origin(0)); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_changed"); + SIGNAL_CHECK_FALSE("lines_edited_from"); + } + SUBCASE("[TextEdit] ui_text_caret_word_right") { + text_edit->set_text("this is some test text\n\nthis is some test text"); + text_edit->set_caret_line(0); + text_edit->set_caret_column(15); + text_edit->add_caret(2, 10); + text_edit->select(0, 10, 0, 15); + text_edit->select(2, 15, 2, 10, 1); + MessageQueue::get_singleton()->flush(); SIGNAL_DISCARD("text_set"); SIGNAL_DISCARD("text_changed"); SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); - // Shift should select. + // Select to end of next word when selection is right to left. + // Deselect to end of previous word when selection is left to right. #ifdef MACOS_ENABLED SEND_GUI_KEY_EVENT(Key::RIGHT | KeyModifierMask::ALT | KeyModifierMask::SHIFT); #else SEND_GUI_KEY_EVENT(Key::RIGHT | KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT); #endif CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 17); - CHECK(text_edit->get_selected_text(0) == "test"); + CHECK(text_edit->get_caret_count() == 2); CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selected_text(0) == "me test"); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 17); + CHECK(text_edit->get_selection_origin_line(0) == 0); + CHECK(text_edit->get_selection_origin_column(0) == 10); + CHECK(text_edit->is_caret_after_selection_origin(0)); + CHECK(text_edit->has_selection(1)); + CHECK(text_edit->get_selected_text(1) == " te"); CHECK(text_edit->get_caret_line(1) == 2); - CHECK(text_edit->get_caret_column(1) == 17); - CHECK(text_edit->get_selected_text(1) == "test"); + CHECK(text_edit->get_caret_column(1) == 12); + CHECK(text_edit->get_selection_origin_line(1) == 2); + CHECK(text_edit->get_selection_origin_column(1) == 15); + CHECK_FALSE(text_edit->is_caret_after_selection_origin(1)); + + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_changed"); + SIGNAL_CHECK_FALSE("lines_edited_from"); + + // Select to end of word with shift. + text_edit->deselect(); + text_edit->set_caret_column(13); + text_edit->set_caret_column(15, false, 1); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); +#ifdef MACOS_ENABLED + SEND_GUI_KEY_EVENT(Key::RIGHT | KeyModifierMask::ALT | KeyModifierMask::SHIFT); +#else + SEND_GUI_KEY_EVENT(Key::RIGHT | KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT); +#endif + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selected_text(0) == "test"); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 17); + CHECK(text_edit->get_selection_origin_line(0) == 0); + CHECK(text_edit->get_selection_origin_column(0) == 13); + CHECK(text_edit->is_caret_after_selection_origin(0)); + CHECK(text_edit->has_selection(1)); + CHECK(text_edit->get_selected_text(1) == "st"); + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 17); + CHECK(text_edit->get_selection_origin_line(1) == 2); + CHECK(text_edit->get_selection_origin_column(1) == 15); + CHECK(text_edit->is_caret_after_selection_origin(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); - // Should still move caret with selection. + // Deselect and move caret to end of next word without shift. SEND_GUI_ACTION("ui_text_caret_word_right"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 22); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); - + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 22); + CHECK_FALSE(text_edit->has_selection(1)); CHECK(text_edit->get_caret_line(1) == 2); CHECK(text_edit->get_caret_column(1) == 22); - CHECK_FALSE(text_edit->has_selection(1)); - SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); - // Normal word right. + // Moves to start of next line when at end of line. Does nothing at end of text. SEND_GUI_ACTION("ui_text_caret_word_right"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_caret_line() == 1); - CHECK(text_edit->get_caret_column() == 0); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); - - CHECK(text_edit->get_caret_line(1) == 3); - CHECK(text_edit->get_caret_column(1) == 0); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 0); CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 22); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); } SUBCASE("[TextEdit] ui_text_caret_right") { - text_edit->set_text("this is some test text\n\nthis is some test text\n"); + text_edit->set_text("this is some test text\n\nthis is some test text"); text_edit->set_caret_line(0); - text_edit->set_caret_column(16); - text_edit->select(0, 16, 0, 20); - - text_edit->add_caret(2, 16); - text_edit->select(2, 16, 2, 20, 1); - CHECK(text_edit->get_caret_count() == 2); - + text_edit->set_caret_column(19); + text_edit->select(0, 15, 0, 19); + text_edit->add_caret(2, 15); + text_edit->select(2, 19, 2, 15, 1); MessageQueue::get_singleton()->flush(); - SIGNAL_DISCARD("text_set"); SIGNAL_DISCARD("text_changed"); SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); - // Normal right should deselect and place at selection start. - SEND_GUI_ACTION("ui_text_caret_right"); + // Remove one character from selection when selection is right to left. + // Add one character to selection when selection is left to right. + SEND_GUI_KEY_EVENT(Key::RIGHT | KeyModifierMask::SHIFT); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 20); - CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selected_text(0) == "st te"); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 20); + CHECK(text_edit->get_selection_origin_line(0) == 0); + CHECK(text_edit->get_selection_origin_column(0) == 15); + CHECK(text_edit->is_caret_after_selection_origin(0)); + CHECK(text_edit->has_selection(1)); + CHECK(text_edit->get_selected_text(1) == "t t"); CHECK(text_edit->get_caret_line(1) == 2); - CHECK(text_edit->get_caret_column(1) == 20); - CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_column(1) == 16); + CHECK(text_edit->get_selection_origin_line(1) == 2); + CHECK(text_edit->get_selection_origin_column(1) == 19); + CHECK_FALSE(text_edit->is_caret_after_selection_origin(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); - // With shift should select. - SEND_GUI_KEY_EVENT(Key::RIGHT | KeyModifierMask::SHIFT); + // Deselect and put caret at selection end without shift. + SEND_GUI_ACTION("ui_text_caret_right"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 21); - CHECK(text_edit->get_selected_text(0) == "x"); - CHECK(text_edit->has_selection(0)); - + CHECK(text_edit->get_caret_count() == 2); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 20); + CHECK_FALSE(text_edit->has_selection(1)); CHECK(text_edit->get_caret_line(1) == 2); - CHECK(text_edit->get_caret_column(1) == 21); - CHECK(text_edit->get_selected_text(1) == "x"); - CHECK(text_edit->has_selection(1)); - + CHECK(text_edit->get_caret_column(1) == 19); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); - // All ready at select right, should only deselect. + // Move caret one character to the right. SEND_GUI_ACTION("ui_text_caret_right"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 21); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 21); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 20); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_changed"); + SIGNAL_CHECK_FALSE("lines_edited_from"); + // Select one character to the right with shift and no existing selection. + SEND_GUI_KEY_EVENT(Key::RIGHT | KeyModifierMask::SHIFT); + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selected_text(0) == "t"); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 22); + CHECK(text_edit->get_selection_origin_line(0) == 0); + CHECK(text_edit->get_selection_origin_column(0) == 21); + CHECK(text_edit->is_caret_after_selection_origin(0)); + + CHECK(text_edit->has_selection(1)); + CHECK(text_edit->get_selected_text(1) == "x"); CHECK(text_edit->get_caret_line(1) == 2); CHECK(text_edit->get_caret_column(1) == 21); - CHECK_FALSE(text_edit->has_selection(1)); - SIGNAL_CHECK_FALSE("caret_changed"); + CHECK(text_edit->get_selection_origin_line(1) == 2); + CHECK(text_edit->get_selection_origin_column(1) == 20); + CHECK(text_edit->is_caret_after_selection_origin(1)); + + SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); - // Normal right. + // Moves to start of next line when at end of line. Does nothing at end of text. + text_edit->deselect(); + text_edit->set_caret_line(0); + text_edit->set_caret_column(22); + text_edit->set_caret_column(22, false, 1); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + SEND_GUI_ACTION("ui_text_caret_right"); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_caret_line() == 0); - CHECK(text_edit->get_caret_column() == 22); + CHECK(text_edit->get_caret_count() == 2); CHECK_FALSE(text_edit->has_selection(0)); - + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK_FALSE(text_edit->has_selection(1)); CHECK(text_edit->get_caret_line(1) == 2); CHECK(text_edit->get_caret_column(1) == 22); - CHECK_FALSE(text_edit->has_selection(1)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); - // Right at end col should go down a line. - SEND_GUI_ACTION("ui_text_caret_right"); + // Selects to start of next line when at end of line. + text_edit->remove_secondary_carets(); + text_edit->set_caret_line(0); + text_edit->set_caret_column(22); + text_edit->select(0, 21, 0, 22); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + + SEND_GUI_KEY_EVENT(Key::RIGHT | KeyModifierMask::SHIFT); CHECK(text_edit->get_viewport()->is_input_handled()); - CHECK(text_edit->get_caret_line() == 1); - CHECK(text_edit->get_caret_column() == 0); - CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_count() == 1); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selected_text(0) == "t\n"); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK(text_edit->get_selection_origin_line(0) == 0); + CHECK(text_edit->get_selection_origin_column(0) == 21); + CHECK(text_edit->is_caret_after_selection_origin(0)); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK_FALSE("text_changed"); + SIGNAL_CHECK_FALSE("lines_edited_from"); - CHECK(text_edit->get_caret_line(1) == 3); - CHECK(text_edit->get_caret_column(1) == 0); - CHECK_FALSE(text_edit->has_selection(1)); + // Merge selections when they overlap. + text_edit->set_caret_line(0); + text_edit->set_caret_column(4); + text_edit->select(0, 4, 0, 6); + text_edit->add_caret(0, 8); + text_edit->select(0, 6, 0, 8, 1); + MessageQueue::get_singleton()->flush(); + SIGNAL_DISCARD("caret_changed"); + CHECK(text_edit->get_caret_count() == 2); + + SEND_GUI_KEY_EVENT(Key::RIGHT | KeyModifierMask::SHIFT); + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK(text_edit->get_caret_count() == 1); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selected_text(0) == " is s"); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 9); + CHECK(text_edit->get_selection_origin_line(0) == 0); + CHECK(text_edit->get_selection_origin_column(0) == 4); + CHECK(text_edit->is_caret_after_selection_origin(0)); SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); @@ -2775,7 +5713,6 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->get_caret_count() == 2); MessageQueue::get_singleton()->flush(); - CHECK(text_edit->is_line_wrapped(0)); SIGNAL_DISCARD("text_set"); SIGNAL_DISCARD("text_changed"); @@ -3156,11 +6093,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_DISCARD("lines_edited_from"); SIGNAL_DISCARD("caret_changed"); - // For the second caret. - Array args2; - args2.push_back(1); - args2.push_back(1); - lines_edited_args.push_front(args2); + lines_edited_args = build_array(build_array(0, 0), build_array(1, 1)); SEND_GUI_KEY_EVENT(Key::A); CHECK(text_edit->get_viewport()->is_input_handled()); @@ -3171,6 +6104,27 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_CHECK("text_changed", empty_signal_args); SIGNAL_CHECK("lines_edited_from", lines_edited_args); + // Undo reverts both carets. + text_edit->undo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "a\na"); + CHECK(text_edit->get_caret_column() == 1); + CHECK(text_edit->get_caret_column(1) == 1); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", reverse_nested(lines_edited_args)); + + // Redo. + text_edit->redo(); + MessageQueue::get_singleton()->flush(); + CHECK(text_edit->get_text() == "aA\naA"); + CHECK(text_edit->get_caret_column() == 2); + CHECK(text_edit->get_caret_column(1) == 2); + SIGNAL_CHECK("caret_changed", empty_signal_args); + SIGNAL_CHECK("text_changed", empty_signal_args); + SIGNAL_CHECK("lines_edited_from", lines_edited_args); + + // Does not work if not editable. text_edit->set_editable(false); SEND_GUI_KEY_EVENT(Key::A); CHECK_FALSE(text_edit->get_viewport()->is_input_handled()); // Should this be handled? @@ -3182,8 +6136,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_CHECK_FALSE("lines_edited_from"); text_edit->set_editable(true); - lines_edited_args.push_back(lines_edited_args[1].duplicate()); - lines_edited_args.push_front(args2.duplicate()); + lines_edited_args = build_array(build_array(0, 0), build_array(0, 0), build_array(1, 1), build_array(1, 1)); text_edit->select(0, 0, 0, 1); text_edit->select(1, 0, 1, 1, 1); @@ -3220,8 +6173,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { text_edit->set_overtype_mode_enabled(false); CHECK_FALSE(text_edit->is_overtype_mode_enabled()); - lines_edited_args.remove_at(0); - lines_edited_args.remove_at(1); + lines_edited_args = build_array(build_array(0, 0), build_array(1, 1)); SEND_GUI_KEY_EVENT(Key::TAB); CHECK(text_edit->get_viewport()->is_input_handled()); @@ -3576,6 +6528,11 @@ TEST_CASE("[SceneTree][TextEdit] caret") { text_edit->set_caret_column(4); CHECK(text_edit->get_word_under_caret() == "Lorem"); + text_edit->set_caret_column(1); + text_edit->add_caret(0, 15); + CHECK(text_edit->get_word_under_caret() == "Lorem\ndolor"); + text_edit->remove_secondary_carets(); + // Should this work? text_edit->set_caret_column(5); CHECK(text_edit->get_word_under_caret() == ""); @@ -3616,18 +6573,20 @@ TEST_CASE("[SceneTree][TextEdit] multicaret") { SIGNAL_DISCARD("caret_changed"); SUBCASE("[TextEdit] add remove caret") { - // Overlapping + // Overlapping. CHECK(text_edit->add_caret(0, 0) == -1); MessageQueue::get_singleton()->flush(); SIGNAL_CHECK_FALSE("caret_changed"); - // Selection - text_edit->select(0, 0, 2, 4); + // Select. + text_edit->select(2, 4, 0, 0); + + // Cannot add in selection. CHECK(text_edit->add_caret(0, 0) == -1); CHECK(text_edit->add_caret(2, 4) == -1); CHECK(text_edit->add_caret(1, 2) == -1); - // Out of bounds + // Cannot add when out of bounds. CHECK(text_edit->add_caret(-1, 0) == -1); CHECK(text_edit->add_caret(5, 0) == -1); CHECK(text_edit->add_caret(0, 100) == -1); @@ -3670,23 +6629,276 @@ TEST_CASE("[SceneTree][TextEdit] multicaret") { ERR_PRINT_ON; } - SUBCASE("[TextEdit] caret index edit order") { - Vector<int> caret_index_get_order; - caret_index_get_order.push_back(1); - caret_index_get_order.push_back(0); + SUBCASE("[TextEdit] sort carets") { + Vector<int> sorted_carets = { 0, 1, 2 }; - CHECK(text_edit->add_caret(1, 0)); - CHECK(text_edit->get_caret_count() == 2); - CHECK(text_edit->get_caret_index_edit_order() == caret_index_get_order); + // Ascending order. + text_edit->remove_secondary_carets(); + text_edit->add_caret(0, 1); + text_edit->add_caret(1, 0); + CHECK(text_edit->get_sorted_carets() == sorted_carets); + // Descending order. + sorted_carets = { 2, 1, 0 }; text_edit->remove_secondary_carets(); text_edit->set_caret_line(1); - CHECK(text_edit->add_caret(0, 0)); + text_edit->add_caret(0, 1); + text_edit->add_caret(0, 0); + CHECK(text_edit->get_sorted_carets() == sorted_carets); + + // Mixed order. + sorted_carets = { 0, 2, 1, 3 }; + text_edit->remove_secondary_carets(); + text_edit->set_caret_line(0); + text_edit->add_caret(1, 0); + text_edit->add_caret(0, 1); + text_edit->add_caret(1, 1); + CHECK(text_edit->get_sorted_carets() == sorted_carets); + + // Overlapping carets. + sorted_carets = { 0, 1, 3, 2 }; + text_edit->remove_secondary_carets(); + text_edit->add_caret(0, 1); + text_edit->add_caret(1, 2); + text_edit->add_caret(0, 2); + text_edit->set_caret_column(1, false, 3); + CHECK(text_edit->get_sorted_carets() == sorted_carets); + + // Sorted by selection start. + sorted_carets = { 1, 0 }; + text_edit->remove_secondary_carets(); + text_edit->select(1, 3, 1, 5); + text_edit->add_caret(2, 0); + text_edit->select(1, 0, 2, 0, 1); + CHECK(text_edit->get_sorted_carets() == sorted_carets); + } + + SUBCASE("[TextEdit] merge carets") { + text_edit->set_text("this is some text\nfor selection"); + MessageQueue::get_singleton()->flush(); + + // Don't merge carets that are not overlapping. + text_edit->set_caret_line(0); + text_edit->set_caret_column(4); + text_edit->add_caret(0, 6); + text_edit->add_caret(1, 6); + text_edit->merge_overlapping_carets(); + CHECK(text_edit->get_caret_count() == 3); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 4); + CHECK(text_edit->get_caret_line(1) == 0); + CHECK(text_edit->get_caret_column(1) == 6); + CHECK(text_edit->get_caret_line(2) == 1); + CHECK(text_edit->get_caret_column(2) == 6); + text_edit->remove_secondary_carets(); + + // Don't merge when in a multicaret edit. + text_edit->begin_multicaret_edit(); + text_edit->set_caret_line(0); + text_edit->set_caret_column(4); + text_edit->add_caret(0, 4); + text_edit->merge_overlapping_carets(); + CHECK(text_edit->is_in_mulitcaret_edit()); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 4); + CHECK(text_edit->get_caret_line(1) == 0); + CHECK(text_edit->get_caret_column(1) == 4); + + // Merge overlapping carets. Merge at the end of the multicaret edit. + text_edit->end_multicaret_edit(); + CHECK_FALSE(text_edit->is_in_mulitcaret_edit()); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_count() == 1); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 4); + + // Don't merge selections that are not overlapping. + text_edit->set_caret_line(0); + text_edit->set_caret_column(4); + text_edit->add_caret(0, 2); + text_edit->add_caret(1, 4); + text_edit->select(0, 4, 1, 2, 0); + text_edit->select(0, 2, 0, 3, 1); + text_edit->select(1, 4, 1, 8, 2); + text_edit->merge_overlapping_carets(); + CHECK(text_edit->get_caret_count() == 3); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->has_selection(1)); + CHECK(text_edit->has_selection(2)); + text_edit->remove_secondary_carets(); + text_edit->deselect(); + + // Don't merge selections that are only touching. + text_edit->set_caret_line(0); + text_edit->set_caret_column(4); + text_edit->add_caret(1, 2); + text_edit->select(0, 4, 1, 2, 0); + text_edit->select(1, 2, 1, 5, 1); + text_edit->merge_overlapping_carets(); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->has_selection(1)); + text_edit->remove_secondary_carets(); + text_edit->deselect(); + + // Merge carets into selection. + text_edit->set_caret_line(0); + text_edit->set_caret_column(3); + text_edit->add_caret(0, 2); + text_edit->add_caret(1, 4); + text_edit->add_caret(1, 8); + text_edit->add_caret(1, 10); + text_edit->select(0, 2, 1, 8, 0); + text_edit->merge_overlapping_carets(); CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selection_from_line(0) == 0); + CHECK(text_edit->get_selection_from_column(0) == 2); + CHECK(text_edit->get_selection_to_line(0) == 1); + CHECK(text_edit->get_selection_to_column(0) == 8); + CHECK(text_edit->is_caret_after_selection_origin(0)); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 10); + text_edit->remove_secondary_carets(); + text_edit->deselect(); - caret_index_get_order.write[0] = 0; - caret_index_get_order.write[1] = 1; - CHECK(text_edit->get_caret_index_edit_order() == caret_index_get_order); + // Merge partially overlapping selections. + text_edit->set_caret_line(0); + text_edit->set_caret_column(1); + text_edit->add_caret(0, 2); + text_edit->add_caret(0, 3); + text_edit->select(0, 2, 0, 6, 0); + text_edit->select(0, 4, 1, 3, 1); + text_edit->select(1, 0, 1, 5, 2); + text_edit->merge_overlapping_carets(); + CHECK(text_edit->get_caret_count() == 1); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selection_from_line(0) == 0); + CHECK(text_edit->get_selection_from_column(0) == 2); + CHECK(text_edit->get_selection_to_line(0) == 1); + CHECK(text_edit->get_selection_to_column(0) == 5); + CHECK(text_edit->is_caret_after_selection_origin(0)); + text_edit->remove_secondary_carets(); + text_edit->deselect(); + + // Merge smaller overlapping selection into a bigger one. + text_edit->set_caret_line(0); + text_edit->set_caret_column(1); + text_edit->add_caret(0, 2); + text_edit->add_caret(0, 3); + text_edit->select(0, 2, 0, 6, 0); + text_edit->select(0, 8, 1, 3, 1); + text_edit->select(0, 2, 1, 5, 2); + text_edit->merge_overlapping_carets(); + CHECK(text_edit->get_caret_count() == 1); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selection_from_line(0) == 0); + CHECK(text_edit->get_selection_from_column(0) == 2); + CHECK(text_edit->get_selection_to_line(0) == 1); + CHECK(text_edit->get_selection_to_column(0) == 5); + CHECK(text_edit->is_caret_after_selection_origin(0)); + text_edit->remove_secondary_carets(); + text_edit->deselect(); + + // Merge equal overlapping selections. + text_edit->set_caret_line(0); + text_edit->set_caret_column(1); + text_edit->add_caret(0, 2); + text_edit->select(0, 2, 1, 6, 0); + text_edit->select(0, 2, 1, 6, 1); + text_edit->merge_overlapping_carets(); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selection_from_line(0) == 0); + CHECK(text_edit->get_selection_from_column(0) == 2); + CHECK(text_edit->get_selection_to_line(0) == 1); + CHECK(text_edit->get_selection_to_column(0) == 6); + CHECK(text_edit->is_caret_after_selection_origin(0)); + } + + SUBCASE("[TextEdit] collapse carets") { + text_edit->set_text("this is some text\nfor selection"); + + // Collapse carets in range, dont affect other carets. + text_edit->add_caret(0, 9); + text_edit->add_caret(1, 0); + text_edit->add_caret(1, 2); + text_edit->add_caret(1, 6); + text_edit->begin_multicaret_edit(); + + text_edit->collapse_carets(0, 8, 1, 2); + CHECK(text_edit->get_caret_count() == 5); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK(text_edit->get_caret_line(1) == 0); + CHECK(text_edit->get_caret_column(1) == 8); + CHECK(text_edit->get_caret_line(2) == 0); + CHECK(text_edit->get_caret_column(2) == 8); + CHECK(text_edit->get_caret_line(3) == 1); + CHECK(text_edit->get_caret_column(3) == 2); + CHECK(text_edit->get_caret_line(4) == 1); + CHECK(text_edit->get_caret_column(4) == 6); + CHECK_FALSE(text_edit->multicaret_edit_ignore_caret(0)); + CHECK(text_edit->multicaret_edit_ignore_caret(1)); + CHECK(text_edit->multicaret_edit_ignore_caret(2)); + CHECK_FALSE(text_edit->multicaret_edit_ignore_caret(3)); + CHECK_FALSE(text_edit->multicaret_edit_ignore_caret(4)); + + // Collapsed carets get merged at the end of the edit. + text_edit->end_multicaret_edit(); + CHECK(text_edit->get_caret_count() == 4); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + CHECK(text_edit->get_caret_line(1) == 0); + CHECK(text_edit->get_caret_column(1) == 8); + CHECK(text_edit->get_caret_line(2) == 1); + CHECK(text_edit->get_caret_column(2) == 2); + CHECK(text_edit->get_caret_line(3) == 1); + CHECK(text_edit->get_caret_column(3) == 6); + text_edit->remove_secondary_carets(); + + // Collapse inclusive. + text_edit->set_caret_line(0); + text_edit->set_caret_column(3); + text_edit->add_caret(1, 2); + text_edit->collapse_carets(0, 3, 1, 2, true); + CHECK(text_edit->get_caret_count() == 1); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 3); + text_edit->remove_secondary_carets(); + + // Deselect if selection was encompassed. + text_edit->select(0, 5, 0, 7); + text_edit->collapse_carets(0, 3, 1, 2); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 3); + + // Clamp only caret end of selection. + text_edit->select(0, 1, 0, 7); + text_edit->collapse_carets(0, 3, 1, 2); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 3); + CHECK(text_edit->get_selection_origin_line() == 0); + CHECK(text_edit->get_selection_origin_column() == 1); + text_edit->deselect(); + + // Clamp only selection origin end of selection. + text_edit->select(0, 7, 0, 1); + text_edit->collapse_carets(0, 3, 1, 2); + CHECK(text_edit->has_selection()); + CHECK(text_edit->get_caret_line() == 0); + CHECK(text_edit->get_caret_column() == 1); + CHECK(text_edit->get_selection_origin_line() == 0); + CHECK(text_edit->get_selection_origin_column() == 3); + text_edit->deselect(); } SUBCASE("[TextEdit] add caret at carets") { @@ -3694,36 +6906,325 @@ TEST_CASE("[SceneTree][TextEdit] multicaret") { text_edit->set_caret_line(1); text_edit->set_caret_column(9); + // Add caret below. Column will clamp. text_edit->add_caret_at_carets(true); CHECK(text_edit->get_caret_count() == 2); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 9); CHECK(text_edit->get_caret_line(1) == 2); CHECK(text_edit->get_caret_column(1) == 4); + // Cannot add below when at last line. text_edit->add_caret_at_carets(true); CHECK(text_edit->get_caret_count() == 2); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 9); + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 4); + // Add caret above. Column will clamp. text_edit->add_caret_at_carets(false); CHECK(text_edit->get_caret_count() == 3); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 9); + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 4); CHECK(text_edit->get_caret_line(2) == 0); CHECK(text_edit->get_caret_column(2) == 7); + // Cannot add above when at first line. + text_edit->add_caret_at_carets(false); + CHECK(text_edit->get_caret_count() == 3); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 9); + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 4); + CHECK(text_edit->get_caret_line(2) == 0); + CHECK(text_edit->get_caret_column(2) == 7); + + // Cannot add below when at the last line for selection. + text_edit->remove_secondary_carets(); + text_edit->select(2, 1, 2, 4); + text_edit->add_caret_at_carets(true); + CHECK(text_edit->get_caret_count() == 1); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selection_origin_line(0) == 2); + CHECK(text_edit->get_selection_origin_column(0) == 1); + CHECK(text_edit->get_caret_line(0) == 2); + CHECK(text_edit->get_caret_column(0) == 4); + + // Cannot add above when at the first line for selection. + text_edit->select(0, 1, 0, 4); + text_edit->add_caret_at_carets(false); + CHECK(text_edit->get_caret_count() == 1); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selection_origin_line(0) == 0); + CHECK(text_edit->get_selection_origin_column(0) == 1); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 4); + + // Add selection below. + text_edit->select(0, 0, 0, 4); + text_edit->add_caret_at_carets(true); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selection_origin_line(0) == 0); + CHECK(text_edit->get_selection_origin_column(0) == 0); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 4); + CHECK(text_edit->has_selection(1)); + CHECK(text_edit->get_selection_origin_line(1) == 1); + CHECK(text_edit->get_selection_origin_column(1) == 0); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 3); // In the default font, this is the same position. + + // Add selection below again. + text_edit->add_caret_at_carets(true); + CHECK(text_edit->get_caret_count() == 3); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selection_origin_line(0) == 0); + CHECK(text_edit->get_selection_origin_column(0) == 0); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 4); + CHECK(text_edit->has_selection(1)); + CHECK(text_edit->get_selection_origin_line(1) == 1); + CHECK(text_edit->get_selection_origin_column(1) == 0); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 3); + CHECK(text_edit->has_selection(2)); + CHECK(text_edit->get_selection_origin_line(2) == 2); + CHECK(text_edit->get_selection_origin_column(2) == 0); + CHECK(text_edit->get_caret_line(2) == 2); + CHECK(text_edit->get_caret_column(2) == 4); + + text_edit->set_text("\tthis is\nsome\n\ttest text"); + MessageQueue::get_singleton()->flush(); + + // Last fit x is preserved when adding below. + text_edit->remove_secondary_carets(); + text_edit->deselect(); + text_edit->set_caret_line(0); + text_edit->set_caret_column(6); + text_edit->add_caret_at_carets(true); + text_edit->add_caret_at_carets(true); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_count() == 3); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 6); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 4); + CHECK(text_edit->get_caret_line(2) == 2); + CHECK(text_edit->get_caret_column(2) == 6); + + // Last fit x is preserved when adding above. + text_edit->remove_secondary_carets(); + text_edit->deselect(); + text_edit->set_caret_line(2); + text_edit->set_caret_column(9); + text_edit->add_caret_at_carets(false); + text_edit->add_caret_at_carets(false); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_count() == 3); + CHECK(text_edit->get_caret_line(0) == 2); + CHECK(text_edit->get_caret_column(0) == 9); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 4); + CHECK(text_edit->get_caret_line(2) == 0); + CHECK(text_edit->get_caret_column(2) == 8); + + // Last fit x is preserved when selection adding below. + text_edit->remove_secondary_carets(); + text_edit->deselect(); + text_edit->select(0, 8, 0, 5); + text_edit->add_caret_at_carets(true); + text_edit->add_caret_at_carets(true); + CHECK(text_edit->get_caret_count() == 3); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selection_origin_line(0) == 0); + CHECK(text_edit->get_selection_origin_column(0) == 8); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 5); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 4); + CHECK(text_edit->has_selection(2)); + CHECK(text_edit->get_selection_origin_line(2) == 2); + CHECK(text_edit->get_selection_origin_column(2) == 7); + CHECK(text_edit->get_caret_line(2) == 2); + CHECK(text_edit->get_caret_column(2) == 5); + + // Last fit x is preserved when selection adding above. text_edit->remove_secondary_carets(); + text_edit->deselect(); + text_edit->select(2, 9, 2, 5); + text_edit->add_caret_at_carets(false); + text_edit->add_caret_at_carets(false); + CHECK(text_edit->get_caret_count() == 3); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selection_origin_line(0) == 2); + CHECK(text_edit->get_selection_origin_column(0) == 9); + CHECK(text_edit->get_caret_line(0) == 2); + CHECK(text_edit->get_caret_column(0) == 5); + CHECK_FALSE(text_edit->has_selection(1)); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 4); + CHECK(text_edit->has_selection(2)); + CHECK(text_edit->get_selection_origin_line(2) == 0); + CHECK(text_edit->get_selection_origin_column(2) == 8); + CHECK(text_edit->get_caret_line(2) == 0); + CHECK(text_edit->get_caret_column(2) == 5); + + // Selections are merged when they overlap. + text_edit->remove_secondary_carets(); + text_edit->deselect(); + text_edit->select(0, 1, 0, 5); + text_edit->add_caret(1, 0); + text_edit->select(1, 1, 1, 3, 1); + text_edit->add_caret_at_carets(true); + CHECK(text_edit->get_caret_count() == 3); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selection_origin_line(0) == 0); + CHECK(text_edit->get_selection_origin_column(0) == 1); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 5); + CHECK(text_edit->has_selection(1)); + CHECK(text_edit->get_selection_origin_line(1) == 1); + CHECK(text_edit->get_selection_origin_column(1) == 1); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 4); + CHECK(text_edit->has_selection(2)); + CHECK(text_edit->get_selection_origin_line(2) == 2); + CHECK(text_edit->get_selection_origin_column(2) == 0); + CHECK(text_edit->get_caret_line(2) == 2); + CHECK(text_edit->get_caret_column(2) == 3); + + // Multiline selection. + text_edit->remove_secondary_carets(); + text_edit->deselect(); + text_edit->set_caret_line(0); + text_edit->set_caret_column(1); + text_edit->select(0, 3, 1, 1); + text_edit->add_caret_at_carets(true); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selection_origin_line(0) == 0); + CHECK(text_edit->get_selection_origin_column(0) == 3); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 1); + CHECK(text_edit->has_selection(1)); + CHECK(text_edit->get_selection_origin_line(1) == 1); + CHECK(text_edit->get_selection_origin_column(1) == 3); + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 0); + + text_edit->set_line_wrapping_mode(TextEdit::LineWrappingMode::LINE_WRAPPING_BOUNDARY); + text_edit->set_size(Size2(50, 100)); + // Line wraps: `\t,this, is\nso,me\n\t,test, ,text`. + CHECK(text_edit->is_line_wrapped(0)); + MessageQueue::get_singleton()->flush(); + + // Add caret below on next line wrap. + text_edit->remove_secondary_carets(); + text_edit->deselect(); text_edit->set_caret_line(0); text_edit->set_caret_column(4); - text_edit->select(0, 0, 0, 4); text_edit->add_caret_at_carets(true); + CHECK_FALSE(text_edit->has_selection()); CHECK(text_edit->get_caret_count() == 2); - CHECK(text_edit->get_selection_from_line(1) == 1); - CHECK(text_edit->get_selection_to_line(1) == 1); - CHECK(text_edit->get_selection_from_column(1) == 0); - CHECK(text_edit->get_selection_to_column(1) == 3); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 4); + CHECK(text_edit->get_caret_line(1) == 0); + CHECK(text_edit->get_caret_column(1) == 8); + // Add caret below from end of line wrap. text_edit->add_caret_at_carets(true); + CHECK_FALSE(text_edit->has_selection()); CHECK(text_edit->get_caret_count() == 3); - CHECK(text_edit->get_selection_from_line(2) == 2); - CHECK(text_edit->get_selection_to_line(2) == 2); - CHECK(text_edit->get_selection_from_column(2) == 0); - CHECK(text_edit->get_selection_to_column(2) == 4); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 4); + CHECK(text_edit->get_caret_line(1) == 0); + CHECK(text_edit->get_caret_column(1) == 8); + CHECK(text_edit->get_caret_line(2) == 1); + CHECK(text_edit->get_caret_column(2) == 1); + + // Add caret below from last line and not last line wrap. + text_edit->remove_secondary_carets(); + text_edit->deselect(); + text_edit->set_caret_line(2); + text_edit->set_caret_column(5); + text_edit->add_caret_at_carets(true); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_count() == 2); + 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); + + // Cannot add caret below from last line last line wrap. + text_edit->add_caret_at_carets(true); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_count() == 2); + 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); + + // Add caret above from not first line wrap. + text_edit->remove_secondary_carets(); + text_edit->deselect(); + text_edit->set_caret_line(1); + text_edit->set_caret_column(4); + text_edit->add_caret_at_carets(false); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 4); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 1); + + // Add caret above from first line wrap. + text_edit->add_caret_at_carets(false); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_count() == 3); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 4); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 1); + CHECK(text_edit->get_caret_line(2) == 0); + CHECK(text_edit->get_caret_column(2) == 8); + + // Add caret above from first line and not first line wrap. + text_edit->add_caret_at_carets(false); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_count() == 4); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 4); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 1); + CHECK(text_edit->get_caret_line(2) == 0); + CHECK(text_edit->get_caret_column(2) == 8); + CHECK(text_edit->get_caret_line(3) == 0); + CHECK(text_edit->get_caret_column(3) == 4); + + // Cannot add caret above from first line first line wrap. + text_edit->remove_secondary_carets(); + text_edit->deselect(); + text_edit->set_caret_line(0); + text_edit->set_caret_column(0); + text_edit->add_caret_at_carets(false); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_caret_count() == 1); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); + + // Does nothing if multiple carets are disabled. + text_edit->set_multiple_carets_enabled(false); + text_edit->add_caret_at_carets(true); + CHECK(text_edit->get_caret_count() == 1); } memdelete(text_edit); @@ -3992,7 +7493,7 @@ TEST_CASE("[SceneTree][TextEdit] viewport") { CHECK(text_edit->get_last_full_visible_line() == visible_lines - 1); CHECK(text_edit->get_last_full_visible_line_wrap_index() == 0); - // Wrap + // Wrap. text_edit->set_line_wrapping_mode(TextEdit::LineWrappingMode::LINE_WRAPPING_BOUNDARY); MessageQueue::get_singleton()->flush(); CHECK(text_edit->get_total_visible_line_count() > total_visible_lines); @@ -4242,7 +7743,7 @@ TEST_CASE("[SceneTree][TextEdit] viewport") { CHECK(text_edit->get_last_full_visible_line_wrap_index() == 0); CHECK(text_edit->get_caret_wrap_index() == 0); - // Typing and undo / redo should adjust viewport + // Typing and undo / redo should adjust viewport. text_edit->set_caret_line(0); text_edit->set_caret_column(0); text_edit->set_line_as_first_visible(5); diff --git a/tests/scene/test_timer.h b/tests/scene/test_timer.h new file mode 100644 index 0000000000..913ed92de5 --- /dev/null +++ b/tests/scene/test_timer.h @@ -0,0 +1,217 @@ +/**************************************************************************/ +/* test_timer.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_TIMER_H +#define TEST_TIMER_H + +#include "scene/main/timer.h" + +#include "tests/test_macros.h" + +namespace TestTimer { + +TEST_CASE("[SceneTree][Timer] Check Timer Setters and Getters") { + Timer *test_timer = memnew(Timer); + + SUBCASE("[Timer] Timer set and get wait time") { + // check default + CHECK(Math::is_equal_approx(test_timer->get_wait_time(), 1.0)); + + test_timer->set_wait_time(50.0); + CHECK(Math::is_equal_approx(test_timer->get_wait_time(), 50.0)); + + test_timer->set_wait_time(42.0); + CHECK(Math::is_equal_approx(test_timer->get_wait_time(), 42.0)); + + // wait time remains unchanged if we attempt to set it negative or zero + ERR_PRINT_OFF; + test_timer->set_wait_time(-22.0); + ERR_PRINT_ON; + CHECK(Math::is_equal_approx(test_timer->get_wait_time(), 42.0)); + + ERR_PRINT_OFF; + test_timer->set_wait_time(0.0); + ERR_PRINT_ON; + CHECK(Math::is_equal_approx(test_timer->get_wait_time(), 42.0)); + } + + SUBCASE("[Timer] Timer set and get one shot") { + // check default + CHECK(test_timer->is_one_shot() == false); + + test_timer->set_one_shot(true); + CHECK(test_timer->is_one_shot() == true); + + test_timer->set_one_shot(false); + CHECK(test_timer->is_one_shot() == false); + } + + SUBCASE("[Timer] Timer set and get autostart") { + // check default + CHECK(test_timer->has_autostart() == false); + + test_timer->set_autostart(true); + CHECK(test_timer->has_autostart() == true); + + test_timer->set_autostart(false); + CHECK(test_timer->has_autostart() == false); + } + + SUBCASE("[Timer] Timer start and stop") { + test_timer->set_autostart(false); + } + + SUBCASE("[Timer] Timer set and get paused") { + // check default + CHECK(test_timer->is_paused() == false); + + test_timer->set_paused(true); + CHECK(test_timer->is_paused() == true); + + test_timer->set_paused(false); + CHECK(test_timer->is_paused() == false); + } + + memdelete(test_timer); +} + +TEST_CASE("[SceneTree][Timer] Check Timer Start and Stop") { + Timer *test_timer = memnew(Timer); + + SUBCASE("[Timer] Timer start and stop") { + SceneTree::get_singleton()->get_root()->add_child(test_timer); + + test_timer->start(5.0); + + CHECK(Math::is_equal_approx(test_timer->get_wait_time(), 5.0)); + CHECK(Math::is_equal_approx(test_timer->get_time_left(), 5.0)); + + test_timer->start(-2.0); + + // the wait time and time left remains unchanged when started with a negative start time + CHECK(Math::is_equal_approx(test_timer->get_wait_time(), 5.0)); + CHECK(Math::is_equal_approx(test_timer->get_time_left(), 5.0)); + + test_timer->stop(); + CHECK(test_timer->is_processing() == false); + CHECK(test_timer->has_autostart() == false); + } + + memdelete(test_timer); +} + +TEST_CASE("[SceneTree][Timer] Check Timer process callback") { + Timer *test_timer = memnew(Timer); + + SUBCASE("[Timer] Timer process callback") { + // check default + CHECK(test_timer->get_timer_process_callback() == Timer::TimerProcessCallback::TIMER_PROCESS_IDLE); + + test_timer->set_timer_process_callback(Timer::TimerProcessCallback::TIMER_PROCESS_PHYSICS); + CHECK(test_timer->get_timer_process_callback() == Timer::TimerProcessCallback::TIMER_PROCESS_PHYSICS); + + test_timer->set_timer_process_callback(Timer::TimerProcessCallback::TIMER_PROCESS_IDLE); + CHECK(test_timer->get_timer_process_callback() == Timer::TimerProcessCallback::TIMER_PROCESS_IDLE); + } + + memdelete(test_timer); +} + +TEST_CASE("[SceneTree][Timer] Check Timer timeout signal") { + Timer *test_timer = memnew(Timer); + SceneTree::get_singleton()->get_root()->add_child(test_timer); + + test_timer->set_process(true); + test_timer->set_physics_process(true); + + SUBCASE("[Timer] Timer process timeout signal must be emitted") { + SIGNAL_WATCH(test_timer, SNAME("timeout")); + test_timer->start(0.1); + + SceneTree::get_singleton()->process(0.2); + + Array signal_args; + signal_args.push_back(Array()); + + SIGNAL_CHECK(SNAME("timeout"), signal_args); + + SIGNAL_UNWATCH(test_timer, SNAME("timeout")); + } + + SUBCASE("[Timer] Timer process timeout signal must not be emitted") { + SIGNAL_WATCH(test_timer, SNAME("timeout")); + test_timer->start(0.1); + + SceneTree::get_singleton()->process(0.05); + + Array signal_args; + signal_args.push_back(Array()); + + SIGNAL_CHECK_FALSE(SNAME("timeout")); + + SIGNAL_UNWATCH(test_timer, SNAME("timeout")); + } + + test_timer->set_timer_process_callback(Timer::TimerProcessCallback::TIMER_PROCESS_PHYSICS); + + SUBCASE("[Timer] Timer physics process timeout signal must be emitted") { + SIGNAL_WATCH(test_timer, SNAME("timeout")); + test_timer->start(0.1); + + SceneTree::get_singleton()->physics_process(0.2); + + Array signal_args; + signal_args.push_back(Array()); + + SIGNAL_CHECK(SNAME("timeout"), signal_args); + + SIGNAL_UNWATCH(test_timer, SNAME("timeout")); + } + + SUBCASE("[Timer] Timer physics process timeout signal must not be emitted") { + SIGNAL_WATCH(test_timer, SNAME("timeout")); + test_timer->start(0.1); + + SceneTree::get_singleton()->physics_process(0.05); + + Array signal_args; + signal_args.push_back(Array()); + + SIGNAL_CHECK_FALSE(SNAME("timeout")); + + SIGNAL_UNWATCH(test_timer, SNAME("timeout")); + } + + memdelete(test_timer); +} + +} // namespace TestTimer + +#endif // TEST_TIMER_H diff --git a/tests/scene/test_viewport.h b/tests/scene/test_viewport.h index 66b0f438cc..1341cc0332 100644 --- a/tests/scene/test_viewport.h +++ b/tests/scene/test_viewport.h @@ -548,8 +548,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { } SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification propagation when moving into child.") { - SIGNAL_WATCH(node_i, SNAME("mouse_entered")); - SIGNAL_WATCH(node_i, SNAME("mouse_exited")); + SIGNAL_WATCH(node_i, SceneStringName(mouse_entered)); + SIGNAL_WATCH(node_i, SceneStringName(mouse_exited)); Array signal_args; signal_args.push_back(Array()); @@ -568,8 +568,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK(node_i->mouse_over_self); CHECK_FALSE(node_j->mouse_over); CHECK_FALSE(node_j->mouse_over_self); - SIGNAL_CHECK(SNAME("mouse_entered"), signal_args); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK(SceneStringName(mouse_entered), signal_args); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); // Move to child Control node_j. node_i should not receive any new Mouse Enter signals. SEND_GUI_MOUSE_MOTION_EVENT(on_j, MouseButtonMask::NONE, Key::NONE); @@ -577,8 +577,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK(node_j->mouse_over); CHECK(node_j->mouse_over_self); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); // Move to parent Control node_i. node_i should not receive any new Mouse Enter signals. SEND_GUI_MOUSE_MOTION_EVENT(on_i, MouseButtonMask::NONE, Key::NONE); @@ -586,8 +586,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK(node_i->mouse_over_self); CHECK_FALSE(node_j->mouse_over); CHECK_FALSE(node_j->mouse_over_self); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); // Move to background. SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE); @@ -595,16 +595,16 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK_FALSE(node_j->mouse_over); CHECK_FALSE(node_j->mouse_over_self); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK(SNAME("mouse_exited"), signal_args); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK(SceneStringName(mouse_exited), signal_args); CHECK_FALSE(node_i->invalid_order); CHECK_FALSE(node_j->invalid_order); node_j->set_mouse_filter(Control::MOUSE_FILTER_STOP); - SIGNAL_UNWATCH(node_i, SNAME("mouse_entered")); - SIGNAL_UNWATCH(node_i, SNAME("mouse_exited")); + SIGNAL_UNWATCH(node_i, SceneStringName(mouse_entered)); + SIGNAL_UNWATCH(node_i, SceneStringName(mouse_exited)); } SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification propagation with top level.") { @@ -756,8 +756,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { } SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification when changing top level.") { - SIGNAL_WATCH(node_i, SNAME("mouse_entered")); - SIGNAL_WATCH(node_i, SNAME("mouse_exited")); + SIGNAL_WATCH(node_i, SceneStringName(mouse_entered)); + SIGNAL_WATCH(node_i, SceneStringName(mouse_exited)); Array signal_args; signal_args.push_back(Array()); @@ -794,8 +794,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK(node_j->mouse_over); CHECK(node_j->mouse_over_self); - SIGNAL_CHECK(SNAME("mouse_entered"), signal_args); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK(SceneStringName(mouse_entered), signal_args); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); // Change node_i to top level. node_h should receive Mouse Exit. node_i should not receive any new signals. node_i->set_as_top_level(true); @@ -805,8 +805,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK(node_j->mouse_over); CHECK(node_j->mouse_over_self); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); // Change node_i to not top level. node_h should receive Mouse Enter. node_i should not receive any new signals. node_i->set_as_top_level(false); @@ -816,8 +816,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK(node_j->mouse_over); CHECK(node_j->mouse_over_self); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); CHECK_FALSE(node_b->invalid_order); CHECK_FALSE(node_d->invalid_order); @@ -830,13 +830,13 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP); node_j->set_mouse_filter(Control::MOUSE_FILTER_STOP); - SIGNAL_UNWATCH(node_i, SNAME("mouse_entered")); - SIGNAL_UNWATCH(node_i, SNAME("mouse_exited")); + SIGNAL_UNWATCH(node_i, SceneStringName(mouse_entered)); + SIGNAL_UNWATCH(node_i, SceneStringName(mouse_exited)); } SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification when changing the mouse filter to stop.") { - SIGNAL_WATCH(node_i, SNAME("mouse_entered")); - SIGNAL_WATCH(node_i, SNAME("mouse_exited")); + SIGNAL_WATCH(node_i, SceneStringName(mouse_entered)); + SIGNAL_WATCH(node_i, SceneStringName(mouse_exited)); Array signal_args; signal_args.push_back(Array()); @@ -851,8 +851,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK(node_j->mouse_over); CHECK(node_j->mouse_over_self); - SIGNAL_CHECK(SNAME("mouse_entered"), signal_args); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK(SceneStringName(mouse_entered), signal_args); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); // Change node_i to MOUSE_FILTER_STOP. node_h should receive Mouse Exit. node_i should not receive any new signals. node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP); @@ -862,8 +862,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK(node_j->mouse_over); CHECK(node_j->mouse_over_self); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); // Change node_i to MOUSE_FILTER_PASS. node_h should receive Mouse Enter. node_i should not receive any new signals. node_i->set_mouse_filter(Control::MOUSE_FILTER_PASS); @@ -873,8 +873,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK(node_j->mouse_over); CHECK(node_j->mouse_over_self); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); CHECK_FALSE(node_h->invalid_order); CHECK_FALSE(node_i->invalid_order); @@ -883,13 +883,13 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP); node_j->set_mouse_filter(Control::MOUSE_FILTER_STOP); - SIGNAL_UNWATCH(node_i, SNAME("mouse_entered")); - SIGNAL_UNWATCH(node_i, SNAME("mouse_exited")); + SIGNAL_UNWATCH(node_i, SceneStringName(mouse_entered)); + SIGNAL_UNWATCH(node_i, SceneStringName(mouse_exited)); } SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification when changing the mouse filter to ignore.") { - SIGNAL_WATCH(node_i, SNAME("mouse_entered")); - SIGNAL_WATCH(node_i, SNAME("mouse_exited")); + SIGNAL_WATCH(node_i, SceneStringName(mouse_entered)); + SIGNAL_WATCH(node_i, SceneStringName(mouse_exited)); Array signal_args; signal_args.push_back(Array()); @@ -904,8 +904,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK(node_j->mouse_over); CHECK(node_j->mouse_over_self); - SIGNAL_CHECK(SNAME("mouse_entered"), signal_args); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK(SceneStringName(mouse_entered), signal_args); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); // Change node_i to MOUSE_FILTER_IGNORE. node_i should receive Mouse Exit. node_i->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); @@ -915,8 +915,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK(node_j->mouse_over); CHECK(node_j->mouse_over_self); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK(SNAME("mouse_exited"), signal_args); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK(SceneStringName(mouse_exited), signal_args); // Change node_i to MOUSE_FILTER_PASS. node_i should receive Mouse Enter. node_i->set_mouse_filter(Control::MOUSE_FILTER_PASS); @@ -926,8 +926,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK(node_j->mouse_over); CHECK(node_j->mouse_over_self); - SIGNAL_CHECK(SNAME("mouse_entered"), signal_args); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK(SceneStringName(mouse_entered), signal_args); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); // Change node_j to MOUSE_FILTER_IGNORE. After updating the mouse motion, node_i should now have mouse_over_self. node_j->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); @@ -938,8 +938,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK(node_i->mouse_over_self); CHECK_FALSE(node_j->mouse_over); CHECK_FALSE(node_j->mouse_over_self); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); // Change node_j to MOUSE_FILTER_PASS. After updating the mouse motion, node_j should now have mouse_over_self. node_j->set_mouse_filter(Control::MOUSE_FILTER_PASS); @@ -950,8 +950,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK(node_j->mouse_over); CHECK(node_j->mouse_over_self); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); CHECK_FALSE(node_h->invalid_order); CHECK_FALSE(node_i->invalid_order); @@ -960,13 +960,13 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP); node_j->set_mouse_filter(Control::MOUSE_FILTER_STOP); - SIGNAL_UNWATCH(node_i, SNAME("mouse_entered")); - SIGNAL_UNWATCH(node_i, SNAME("mouse_exited")); + SIGNAL_UNWATCH(node_i, SceneStringName(mouse_entered)); + SIGNAL_UNWATCH(node_i, SceneStringName(mouse_exited)); } SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification when removing the hovered Control.") { - SIGNAL_WATCH(node_h, SNAME("mouse_entered")); - SIGNAL_WATCH(node_h, SNAME("mouse_exited")); + SIGNAL_WATCH(node_h, SceneStringName(mouse_entered)); + SIGNAL_WATCH(node_h, SceneStringName(mouse_exited)); Array signal_args; signal_args.push_back(Array()); @@ -981,8 +981,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK(node_j->mouse_over); CHECK(node_j->mouse_over_self); - SIGNAL_CHECK(SNAME("mouse_entered"), signal_args); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK(SceneStringName(mouse_entered), signal_args); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); // Remove node_i from the tree. node_i and node_j should receive Mouse Exit. node_h should not receive any new signals. node_h->remove_child(node_i); @@ -992,8 +992,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK_FALSE(node_j->mouse_over); CHECK_FALSE(node_j->mouse_over_self); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); // Add node_i to the tree and update the mouse. node_i and node_j should receive Mouse Enter. node_h should not receive any new signals. node_h->add_child(node_i); @@ -1004,8 +1004,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK(node_j->mouse_over); CHECK(node_j->mouse_over_self); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); CHECK_FALSE(node_h->invalid_order); CHECK_FALSE(node_i->invalid_order); @@ -1014,13 +1014,13 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP); node_j->set_mouse_filter(Control::MOUSE_FILTER_STOP); - SIGNAL_UNWATCH(node_h, SNAME("mouse_entered")); - SIGNAL_UNWATCH(node_h, SNAME("mouse_exited")); + SIGNAL_UNWATCH(node_h, SceneStringName(mouse_entered)); + SIGNAL_UNWATCH(node_h, SceneStringName(mouse_exited)); } SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification when hiding the hovered Control.") { - SIGNAL_WATCH(node_h, SNAME("mouse_entered")); - SIGNAL_WATCH(node_h, SNAME("mouse_exited")); + SIGNAL_WATCH(node_h, SceneStringName(mouse_entered)); + SIGNAL_WATCH(node_h, SceneStringName(mouse_exited)); Array signal_args; signal_args.push_back(Array()); @@ -1035,8 +1035,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK(node_j->mouse_over); CHECK(node_j->mouse_over_self); - SIGNAL_CHECK(SNAME("mouse_entered"), signal_args); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK(SceneStringName(mouse_entered), signal_args); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); // Hide node_i. node_i and node_j should receive Mouse Exit. node_h should not receive any new signals. node_i->hide(); @@ -1046,8 +1046,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK_FALSE(node_j->mouse_over); CHECK_FALSE(node_j->mouse_over_self); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); // Show node_i and update the mouse. node_i and node_j should receive Mouse Enter. node_h should not receive any new signals. node_i->show(); @@ -1058,8 +1058,8 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { CHECK_FALSE(node_i->mouse_over_self); CHECK(node_j->mouse_over); CHECK(node_j->mouse_over_self); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); CHECK_FALSE(node_h->invalid_order); CHECK_FALSE(node_i->invalid_order); @@ -1068,26 +1068,26 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP); node_j->set_mouse_filter(Control::MOUSE_FILTER_STOP); - SIGNAL_UNWATCH(node_h, SNAME("mouse_entered")); - SIGNAL_UNWATCH(node_h, SNAME("mouse_exited")); + SIGNAL_UNWATCH(node_h, SceneStringName(mouse_entered)); + SIGNAL_UNWATCH(node_h, SceneStringName(mouse_exited)); } SUBCASE("[Viewport][GuiInputEvent] Window Mouse Enter/Exit signals.") { - SIGNAL_WATCH(root, SNAME("mouse_entered")); - SIGNAL_WATCH(root, SNAME("mouse_exited")); + SIGNAL_WATCH(root, SceneStringName(mouse_entered)); + SIGNAL_WATCH(root, SceneStringName(mouse_exited)); Array signal_args; signal_args.push_back(Array()); SEND_GUI_MOUSE_MOTION_EVENT(on_outside, MouseButtonMask::NONE, Key::NONE); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK(SNAME("mouse_exited"), signal_args); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK(SceneStringName(mouse_exited), signal_args); SEND_GUI_MOUSE_MOTION_EVENT(on_a, MouseButtonMask::NONE, Key::NONE); - SIGNAL_CHECK(SNAME("mouse_entered"), signal_args); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK(SceneStringName(mouse_entered), signal_args); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); - SIGNAL_UNWATCH(root, SNAME("mouse_entered")); - SIGNAL_UNWATCH(root, SNAME("mouse_exited")); + SIGNAL_UNWATCH(root, SceneStringName(mouse_entered)); + SIGNAL_UNWATCH(root, SceneStringName(mouse_exited)); } SUBCASE("[Viewport][GuiInputEvent] Process-Mode affects, if GUI Mouse Motion Events are processed.") { @@ -1415,9 +1415,9 @@ public: Ref<InputEvent> last_input_event; void init_signals() { - connect(SNAME("mouse_entered"), callable_mp(this, &TestArea2D::_on_mouse_entered)); - connect(SNAME("mouse_exited"), callable_mp(this, &TestArea2D::_on_mouse_exited)); - connect(SNAME("input_event"), callable_mp(this, &TestArea2D::_on_input_event)); + connect(SceneStringName(mouse_entered), callable_mp(this, &TestArea2D::_on_mouse_entered)); + connect(SceneStringName(mouse_exited), callable_mp(this, &TestArea2D::_on_mouse_exited)); + connect(SceneStringName(input_event), callable_mp(this, &TestArea2D::_on_input_event)); } void test_reset() { @@ -1459,8 +1459,8 @@ TEST_CASE("[SceneTree][Viewport] Physics Picking 2D") { pc.a->set_name("A" + itos(i)); pc.c->set_name("C" + itos(i)); v.push_back(pc); - SIGNAL_WATCH(pc.a, SNAME("mouse_entered")); - SIGNAL_WATCH(pc.a, SNAME("mouse_exited")); + SIGNAL_WATCH(pc.a, SceneStringName(mouse_entered)); + SIGNAL_WATCH(pc.a, SceneStringName(mouse_exited)); } Node2D *node_a = memnew(Node2D); @@ -1499,8 +1499,8 @@ TEST_CASE("[SceneTree][Viewport] Physics Picking 2D") { SUBCASE("[Viewport][Picking2D] Mouse Motion") { SEND_GUI_MOUSE_MOTION_EVENT(on_all, MouseButtonMask::NONE, Key::NONE); tree->physics_process(1); - SIGNAL_CHECK(SNAME("mouse_entered"), empty_signal_args_4); - SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + SIGNAL_CHECK(SceneStringName(mouse_entered), empty_signal_args_4); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_exited)); for (PickingCollider E : v) { CHECK(E.a->enter_id); CHECK_FALSE(E.a->exit_id); @@ -1509,8 +1509,8 @@ TEST_CASE("[SceneTree][Viewport] Physics Picking 2D") { SEND_GUI_MOUSE_MOTION_EVENT(on_01, MouseButtonMask::NONE, Key::NONE); tree->physics_process(1); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK(SNAME("mouse_exited"), empty_signal_args_2); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK(SceneStringName(mouse_exited), empty_signal_args_2); for (int i = 0; i < v.size(); i++) { CHECK_FALSE(v[i].a->enter_id); @@ -1524,8 +1524,8 @@ TEST_CASE("[SceneTree][Viewport] Physics Picking 2D") { SEND_GUI_MOUSE_MOTION_EVENT(on_outside, MouseButtonMask::NONE, Key::NONE); tree->physics_process(1); - SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); - SIGNAL_CHECK(SNAME("mouse_exited"), empty_signal_args_2); + SIGNAL_CHECK_FALSE(SceneStringName(mouse_entered)); + SIGNAL_CHECK(SceneStringName(mouse_exited), empty_signal_args_2); for (int i = 0; i < v.size(); i++) { CHECK_FALSE(v[i].a->enter_id); if (i < 2) { @@ -1788,8 +1788,8 @@ TEST_CASE("[SceneTree][Viewport] Physics Picking 2D") { } for (PickingCollider E : v) { - SIGNAL_UNWATCH(E.a, SNAME("mouse_entered")); - SIGNAL_UNWATCH(E.a, SNAME("mouse_exited")); + SIGNAL_UNWATCH(E.a, SceneStringName(mouse_entered)); + SIGNAL_UNWATCH(E.a, SceneStringName(mouse_exited)); memdelete(E.c); memdelete(E.a); } diff --git a/tests/servers/test_navigation_server_3d.h b/tests/servers/test_navigation_server_3d.h index 827a1bed17..8778ea86a6 100644 --- a/tests/servers/test_navigation_server_3d.h +++ b/tests/servers/test_navigation_server_3d.h @@ -764,6 +764,8 @@ TEST_SUITE("[Navigation]") { navigation_server->process(0.0); // Give server some cycles to commit. } + // FIXME: The race condition mentioned below is actually a problem and fails on CI (GH-90613). + /* TEST_CASE("[NavigationServer3D] Server should be able to bake asynchronously") { NavigationServer3D *navigation_server = NavigationServer3D::get_singleton(); Ref<NavigationMesh> navigation_mesh = memnew(NavigationMesh); @@ -781,6 +783,7 @@ TEST_SUITE("[Navigation]") { CHECK_EQ(navigation_mesh->get_polygon_count(), 0); CHECK_EQ(navigation_mesh->get_vertices().size(), 0); } + */ } } //namespace TestNavigationServer3D diff --git a/tests/servers/test_text_server.h b/tests/servers/test_text_server.h index 334c642d26..d982102a03 100644 --- a/tests/servers/test_text_server.h +++ b/tests/servers/test_text_server.h @@ -637,6 +637,97 @@ TEST_SUITE("[TextServer]") { } } + SUBCASE("[TextServer] Unicode letters") { + 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."); + + struct ul_testcase { + int fail_index = -1; // Expecting failure at given index. + char32_t text[10]; // Using 0 as the terminator. + }; + ul_testcase cases[14] = { + { + 0, + { 0x2D, 0x33, 0x30, 0, 0, 0, 0, 0, 0, 0 }, // "-30" + }, + { + 1, + { 0x61, 0x2E, 0x31, 0, 0, 0, 0, 0, 0, 0 }, // "a.1" + }, + { + 1, + { 0x61, 0x2C, 0x31, 0, 0, 0, 0, 0, 0, 0 }, // "a,1" + }, + { + 0, + { 0x31, 0x65, 0x2D, 0x32, 0, 0, 0, 0, 0, 0 }, // "1e-2" + }, + { + 0, + { 0xAB, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // "Left-Pointing Double Angle Quotation Mark" + }, + { + -1, + { 0x41, 0x42, 0, 0, 0, 0, 0, 0, 0, 0 }, // "AB" + }, + { + 4, + { 0x54, 0x65, 0x73, 0x74, 0x31, 0, 0, 0, 0, 0 }, // "Test1" + }, + { + 2, + { 0x54, 0x65, 0x2A, 0x73, 0x74, 0, 0, 0, 0, 0 }, // "Te*st" + }, + { + 4, + { 0x74, 0x65, 0x73, 0x74, 0x5F, 0x74, 0x65, 0x73, 0x74, 0x65 }, // "test_teste" + }, + { + 4, + { 0x74, 0x65, 0x73, 0x74, 0x20, 0x74, 0x65, 0x73, 0x74, 0 }, // "test test" + }, + { + -1, + { 0x643, 0x402, 0x716, 0xB05, 0, 0, 0, 0, 0, 0 }, // "كЂܖଅ" (arabic letters), + }, + { + -1, + { 0x643, 0x402, 0x716, 0xB05, 0x54, 0x65, 0x73, 0x74, 0x30AA, 0x4E21 }, // 0-3 arabic letters, 4-7 latin letters, 8-9 CJK letters + }, + { + -1, + { 0x4D2, 0x4D6, 0x4DA, 0x4DC, 0, 0, 0, 0, 0, 0 }, // "ӒӖӚӜ" cyrillic letters + }, + { + -1, + { 0xC2, 0xC3, 0xC4, 0xC5, 0x100, 0x102, 0x104, 0xC7, 0x106, 0x108 }, // "ÂÃÄÅĀĂĄÇĆĈ" rarer latin letters + }, + }; + + for (int j = 0; j < 14; j++) { + ul_testcase test = cases[j]; + int failed_on_index = -1; + for (int k = 0; k < 10; k++) { + char32_t character = test.text[k]; + if (character == 0) { + break; + } + if (!ts->is_valid_letter(character)) { + failed_on_index = k; + break; + } + } + + if (test.fail_index == -1) { + CHECK_MESSAGE(test.fail_index == failed_on_index, "In interface ", ts->get_name() + ": In test case ", j, ", the character at index ", failed_on_index, " should have been a letter."); + } else { + CHECK_MESSAGE(test.fail_index == failed_on_index, "In interface ", ts->get_name() + ": In test case ", j, ", expected first non-letter at index ", test.fail_index, ", but found at index ", failed_on_index); + } + } + } + } + SUBCASE("[TextServer] Strip Diacritics") { for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) { Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i); diff --git a/tests/test_macros.h b/tests/test_macros.h index a173b37a2d..10f4c59a90 100644 --- a/tests/test_macros.h +++ b/tests/test_macros.h @@ -119,6 +119,7 @@ DOCTEST_STRINGIFY_VARIANT(PackedStringArray); DOCTEST_STRINGIFY_VARIANT(PackedVector2Array); DOCTEST_STRINGIFY_VARIANT(PackedVector3Array); DOCTEST_STRINGIFY_VARIANT(PackedColorArray); +DOCTEST_STRINGIFY_VARIANT(PackedVector4Array); // Register test commands to be launched from the command-line. // For instance: REGISTER_TEST_COMMAND("gdscript-parser" &test_parser_func). @@ -136,6 +137,7 @@ int register_test_command(String p_command, TestFunc p_function); // Requires Message Queue and InputMap to be setup. // SEND_GUI_ACTION - takes an input map key. e.g SEND_GUI_ACTION("ui_text_newline"). // SEND_GUI_KEY_EVENT - takes a keycode set. e.g SEND_GUI_KEY_EVENT(Key::A | KeyModifierMask::META). +// SEND_GUI_KEY_UP_EVENT - takes a keycode set. e.g SEND_GUI_KEY_UP_EVENT(Key::A | KeyModifierMask::META). // SEND_GUI_MOUSE_BUTTON_EVENT - takes a position, mouse button, mouse mask and modifiers e.g SEND_GUI_MOUSE_BUTTON_EVENT(Vector2(50, 50), MOUSE_BUTTON_NONE, MOUSE_BUTTON_NONE, Key::None); // SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT - takes a position, mouse button, mouse mask and modifiers e.g SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(Vector2(50, 50), MOUSE_BUTTON_NONE, MOUSE_BUTTON_NONE, Key::None); // SEND_GUI_MOUSE_MOTION_EVENT - takes a position, mouse mask and modifiers e.g SEND_GUI_MOUSE_MOTION_EVENT(Vector2(50, 50), MouseButtonMask::LEFT, KeyModifierMask::META); @@ -161,6 +163,14 @@ int register_test_command(String p_command, TestFunc p_function); MessageQueue::get_singleton()->flush(); \ } +#define SEND_GUI_KEY_UP_EVENT(m_input) \ + { \ + Ref<InputEventKey> event = InputEventKey::create_reference(m_input); \ + event->set_pressed(false); \ + _SEND_DISPLAYSERVER_EVENT(event); \ + 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); \ @@ -396,4 +406,71 @@ public: #define SIGNAL_CHECK_FALSE(m_signal) CHECK(SignalWatcher::get_singleton()->check_false(m_signal)); #define SIGNAL_DISCARD(m_signal) SignalWatcher::get_singleton()->discard_signal(m_signal); +#define MULTICHECK_STRING_EQ(m_obj, m_func, m_param1, m_eq) \ + CHECK(m_obj.m_func(m_param1) == m_eq); \ + CHECK(m_obj.m_func(U##m_param1) == m_eq); \ + CHECK(m_obj.m_func(L##m_param1) == m_eq); \ + CHECK(m_obj.m_func(String(m_param1)) == m_eq); + +#define MULTICHECK_STRING_INT_EQ(m_obj, m_func, m_param1, m_param2, m_eq) \ + CHECK(m_obj.m_func(m_param1, m_param2) == m_eq); \ + CHECK(m_obj.m_func(U##m_param1, m_param2) == m_eq); \ + CHECK(m_obj.m_func(L##m_param1, m_param2) == m_eq); \ + CHECK(m_obj.m_func(String(m_param1), m_param2) == m_eq); + +#define MULTICHECK_STRING_INT_INT_EQ(m_obj, m_func, m_param1, m_param2, m_param3, m_eq) \ + CHECK(m_obj.m_func(m_param1, m_param2, m_param3) == m_eq); \ + CHECK(m_obj.m_func(U##m_param1, m_param2, m_param3) == m_eq); \ + CHECK(m_obj.m_func(L##m_param1, m_param2, m_param3) == m_eq); \ + CHECK(m_obj.m_func(String(m_param1), m_param2, m_param3) == m_eq); + +#define MULTICHECK_STRING_STRING_EQ(m_obj, m_func, m_param1, m_param2, m_eq) \ + CHECK(m_obj.m_func(m_param1, m_param2) == m_eq); \ + CHECK(m_obj.m_func(U##m_param1, U##m_param2) == m_eq); \ + CHECK(m_obj.m_func(L##m_param1, L##m_param2) == m_eq); \ + CHECK(m_obj.m_func(String(m_param1), String(m_param2)) == m_eq); + +#define MULTICHECK_GET_SLICE(m_obj, m_param1, m_slices) \ + for (int i = 0; i < m_obj.get_slice_count(m_param1); ++i) { \ + CHECK(m_obj.get_slice(m_param1, i) == m_slices[i]); \ + } \ + for (int i = 0; i < m_obj.get_slice_count(U##m_param1); ++i) { \ + CHECK(m_obj.get_slice(U##m_param1, i) == m_slices[i]); \ + } \ + for (int i = 0; i < m_obj.get_slice_count(L##m_param1); ++i) { \ + CHECK(m_obj.get_slice(L##m_param1, i) == m_slices[i]); \ + } \ + for (int i = 0; i < m_obj.get_slice_count(String(m_param1)); ++i) { \ + CHECK(m_obj.get_slice(String(m_param1), i) == m_slices[i]); \ + } + +#define MULTICHECK_SPLIT(m_obj, m_func, m_param1, m_param2, m_param3, m_slices, m_expected_size) \ + do { \ + Vector<String> string_list; \ + \ + string_list = m_obj.m_func(m_param1, m_param2, m_param3); \ + CHECK(m_expected_size == string_list.size()); \ + for (int i = 0; i < string_list.size(); ++i) { \ + CHECK(string_list[i] == m_slices[i]); \ + } \ + \ + string_list = m_obj.m_func(U##m_param1, m_param2, m_param3); \ + CHECK(m_expected_size == string_list.size()); \ + for (int i = 0; i < string_list.size(); ++i) { \ + CHECK(string_list[i] == m_slices[i]); \ + } \ + \ + string_list = m_obj.m_func(L##m_param1, m_param2, m_param3); \ + CHECK(m_expected_size == string_list.size()); \ + for (int i = 0; i < string_list.size(); ++i) { \ + CHECK(string_list[i] == m_slices[i]); \ + } \ + \ + string_list = m_obj.m_func(String(m_param1), m_param2, m_param3); \ + CHECK(m_expected_size == string_list.size()); \ + for (int i = 0; i < string_list.size(); ++i) { \ + CHECK(string_list[i] == m_slices[i]); \ + } \ + } while (0) + #endif // TEST_MACROS_H diff --git a/tests/test_main.cpp b/tests/test_main.cpp index bb6837c965..041231888b 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -100,7 +100,6 @@ #include "tests/core/variant/test_variant.h" #include "tests/core/variant/test_variant_utility.h" #include "tests/scene/test_animation.h" -#include "tests/scene/test_arraymesh.h" #include "tests/scene/test_audio_stream_wav.h" #include "tests/scene/test_bit_map.h" #include "tests/scene/test_camera_2d.h" @@ -112,6 +111,7 @@ #include "tests/scene/test_curve_3d.h" #include "tests/scene/test_gradient.h" #include "tests/scene/test_image_texture.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" @@ -119,6 +119,7 @@ #include "tests/scene/test_sprite_frames.h" #include "tests/scene/test_text_edit.h" #include "tests/scene/test_theme.h" +#include "tests/scene/test_timer.h" #include "tests/scene/test_viewport.h" #include "tests/scene/test_visual_shader.h" #include "tests/scene/test_window.h" @@ -127,6 +128,7 @@ #include "tests/test_validate_testing.h" #ifndef _3D_DISABLED +#include "tests/scene/test_arraymesh.h" #include "tests/scene/test_camera_3d.h" #include "tests/scene/test_navigation_agent_2d.h" #include "tests/scene/test_navigation_agent_3d.h" @@ -187,7 +189,7 @@ int test_main(int argc, char *argv[]) { } // Doctest runner. doctest::Context test_context; - List<String> test_args; + LocalVector<String> test_args; // Clean arguments of "--test" from the args. for (int x = 0; x < argc; x++) { @@ -200,7 +202,7 @@ int test_main(int argc, char *argv[]) { if (test_args.size() > 0) { // Convert Godot command line arguments back to standard arguments. char **doctest_args = new char *[test_args.size()]; - for (int x = 0; x < test_args.size(); x++) { + for (uint32_t x = 0; x < test_args.size(); x++) { // Operation to convert Godot string to non wchar string. CharString cs = test_args[x].utf8(); const char *str = cs.get_data(); @@ -212,7 +214,7 @@ int test_main(int argc, char *argv[]) { test_context.applyCommandLine(test_args.size(), doctest_args); - for (int x = 0; x < test_args.size(); x++) { + for (uint32_t x = 0; x < test_args.size(); x++) { delete[] doctest_args[x]; } delete[] doctest_args; @@ -241,7 +243,7 @@ struct GodotTestCaseListener : public doctest::IReporter { String name = String(p_in.m_name); String suite_name = String(p_in.m_test_suite); - if (name.find("[SceneTree]") != -1 || name.find("[Editor]") != -1) { + if (name.contains("[SceneTree]") || name.contains("[Editor]")) { memnew(MessageQueue); memnew(Input); @@ -251,7 +253,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, err); + DisplayServer::create(i, "", DisplayServer::WindowMode::WINDOW_MODE_MINIMIZED, DisplayServer::VSyncMode::VSYNC_ENABLED, 0, nullptr, Vector2i(0, 0), DisplayServer::SCREEN_PRIMARY, DisplayServer::CONTEXT_EDITOR, err); break; } } @@ -290,7 +292,7 @@ struct GodotTestCaseListener : public doctest::IReporter { } #ifdef TOOLS_ENABLED - if (name.find("[Editor]") != -1) { + if (name.contains("[Editor]")) { Engine::get_singleton()->set_editor_hint(true); EditorPaths::create(); EditorSettings::create(); @@ -300,7 +302,7 @@ struct GodotTestCaseListener : public doctest::IReporter { return; } - if (name.find("Audio") != -1) { + 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); @@ -310,7 +312,7 @@ struct GodotTestCaseListener : public doctest::IReporter { } #ifndef _3D_DISABLED - if (suite_name.find("[Navigation]") != -1 && navigation_server_2d == nullptr && navigation_server_3d == nullptr) { + if (suite_name.contains("[Navigation]") && navigation_server_2d == nullptr && navigation_server_3d == nullptr) { ERR_PRINT_OFF; navigation_server_3d = NavigationServer3DManager::new_default_server(); navigation_server_2d = NavigationServer2DManager::new_default_server(); diff --git a/tests/test_validate_testing.h b/tests/test_validate_testing.h index 4673d42b5a..f2e3bf3bb4 100644 --- a/tests/test_validate_testing.h +++ b/tests/test_validate_testing.h @@ -181,6 +181,13 @@ TEST_SUITE("Validate tests") { color_arr.push_back(Color(2, 2, 2)); INFO(color_arr); + PackedVector4Array vec4_arr; + vec4_arr.push_back(Vector4(0, 0, 0, 0)); + vec4_arr.push_back(Vector4(1, 1, 1, 1)); + vec4_arr.push_back(Vector4(2, 2, 2, 2)); + vec4_arr.push_back(Vector4(3, 3, 3, 3)); + INFO(vec4_arr); + // doctest string concatenation. CHECK_MESSAGE(true, var, " ", vec2, " ", rect2, " ", color); } |