diff --git a/tests/scene/test_aspect_ratio_container.cpp b/tests/scene/test_aspect_ratio_container.cpp new file mode 100644 index 0000000000..7fdf937ba5 --- /dev/null +++ b/tests/scene/test_aspect_ratio_container.cpp @@ -0,0 +1,358 @@ +/**************************************************************************/ +/* test_aspect_ratio_container.cpp */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#include "tests/test_macros.h" + +TEST_FORCE_LINK(test_aspect_ratio_container) + +#include "scene/gui/aspect_ratio_container.h" +#include "scene/gui/control.h" +#include "scene/main/scene_tree.h" +#include "scene/main/window.h" + +namespace TestAspectRatioContainer { + +TEST_CASE("[SceneTree][AspectRatioContainer] Default Properties") { + AspectRatioContainer *aspect_ratio_container = memnew(AspectRatioContainer); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(aspect_ratio_container); + Control *child_control = memnew(Control); + aspect_ratio_container->add_child(child_control); + + CHECK_MESSAGE( + aspect_ratio_container->get_stretch_mode() == AspectRatioContainer::STRETCH_FIT, + "AspectRatioContainer stretch mode is set to STRETCH_FIT by default."); + + CHECK_MESSAGE( + Math::is_equal_approx(aspect_ratio_container->get_ratio(), 1.0f), + "AspectRatioContainer ratio is set to 1.0 by default."); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 0)), + "Child control is positioned at the top-left corner of the AspectRatioContainer by default."); + + CHECK_MESSAGE( + child_control->get_h_size_flags() == Control::SIZE_FILL, + "Child control horizontal size flags are set to SIZE_FILL by default."); + + CHECK_MESSAGE( + child_control->get_v_size_flags() == Control::SIZE_FILL, + "Child control vertical size flags are set to SIZE_FILL by default."); + + memdelete(child_control); + memdelete(aspect_ratio_container); +} + +TEST_CASE("[SceneTree][AspectRatioContainer] Aspect Ratio") { + AspectRatioContainer *aspect_ratio_container = memnew(AspectRatioContainer); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(aspect_ratio_container); + Control *child_control = memnew(Control); + aspect_ratio_container->add_child(child_control); + + aspect_ratio_container->set_size(Size2(200, 100)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_size().is_equal_approx(Size2(100, 100)), + "Child control is resized to maintain aspect ratio when AspectRatioContainer is resized."); + + aspect_ratio_container->set_size(Size2(100, 200)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_size().is_equal_approx(Size2(100, 100)), + "Child control is resized to maintain aspect ratio when AspectRatioContainer is resized."); + + aspect_ratio_container->set_ratio(2.0f); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_size().is_equal_approx(Size2(100, 50)), + "Child control is resized to maintain aspect ratio when AspectRatioContainer ratio is changed."); + + aspect_ratio_container->set_ratio(0.5f); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_size().is_equal_approx(Size2(100, 200)), + "Child control is resized to maintain aspect ratio when AspectRatioContainer ratio is changed."); + + aspect_ratio_container->set_size(Size2(200, 100)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_size().is_equal_approx(Size2(50, 100)), + "Child control is resized to maintain aspect ratio when AspectRatioContainer is resized after ratio change."); + + memdelete(child_control); + memdelete(aspect_ratio_container); +} + +TEST_CASE("[SceneTree][AspectRatioContainer] Stretch Modes") { + AspectRatioContainer *aspect_ratio_container = memnew(AspectRatioContainer); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(aspect_ratio_container); + Control *child_control = memnew(Control); + aspect_ratio_container->add_child(child_control); + + aspect_ratio_container->set_size(Size2(200, 100)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_size().is_equal_approx(Size2(100, 100)), + "Child control is resized to maintain aspect ratio in STRETCH_FIT mode."); + + aspect_ratio_container->set_stretch_mode(AspectRatioContainer::STRETCH_WIDTH_CONTROLS_HEIGHT); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_size().is_equal_approx(Size2(200, 200)), + "Child control height is adjusted to maintain aspect ratio in STRETCH_WIDTH_CONTROLS_HEIGHT mode."); + + aspect_ratio_container->set_stretch_mode(AspectRatioContainer::STRETCH_HEIGHT_CONTROLS_WIDTH); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_size().is_equal_approx(Size2(100, 100)), + "Child control width is adjusted to maintain aspect ratio in STRETCH_HEIGHT_CONTROLS_WIDTH mode."); + + aspect_ratio_container->set_stretch_mode(AspectRatioContainer::STRETCH_COVER); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_size().is_equal_approx(Size2(200, 200)), + "Child control is resized to cover the entire AspectRatioContainer in STRETCH_COVER mode."); + + aspect_ratio_container->set_size(Size2(100, 200)); + aspect_ratio_container->set_stretch_mode(AspectRatioContainer::STRETCH_FIT); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + aspect_ratio_container->get_stretch_mode() == AspectRatioContainer::STRETCH_FIT, + "AspectRatioContainer stretch mode is set to STRETCH_FIT by default."); + + CHECK_MESSAGE( + child_control->get_size().is_equal_approx(Size2(100, 100)), + "Child control is resized to maintain aspect ratio in STRETCH_FIT mode."); + + aspect_ratio_container->set_stretch_mode(AspectRatioContainer::STRETCH_WIDTH_CONTROLS_HEIGHT); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_size().is_equal_approx(Size2(100, 100)), + "Child control height is adjusted to maintain aspect ratio in STRETCH_WIDTH_CONTROLS_HEIGHT mode."); + + aspect_ratio_container->set_stretch_mode(AspectRatioContainer::STRETCH_HEIGHT_CONTROLS_WIDTH); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_size().is_equal_approx(Size2(200, 200)), + "Child control width is adjusted to maintain aspect ratio in STRETCH_HEIGHT_CONTROLS_WIDTH mode."); + + aspect_ratio_container->set_stretch_mode(AspectRatioContainer::STRETCH_COVER); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_size().is_equal_approx(Size2(200, 200)), + "Child control is resized to cover the entire AspectRatioContainer in STRETCH_COVER mode."); + + memdelete(child_control); + memdelete(aspect_ratio_container); +} + +TEST_CASE("[SceneTree][AspectRatioContainer] Alignment modes") { + AspectRatioContainer *aspect_ratio_container = memnew(AspectRatioContainer); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(aspect_ratio_container); + Control *child_control = memnew(Control); + aspect_ratio_container->add_child(child_control); + + SUBCASE("Horizontal Alignment") { + aspect_ratio_container->set_size(Size2(200, 100)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + aspect_ratio_container->get_alignment_horizontal() == AspectRatioContainer::ALIGNMENT_CENTER, + "AspectRatioContainer horizontal alignment is set to ALIGNMENT_CENTER by default."); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(50, 0)), + "Child control is aligned to the center in ALIGNMENT_CENTER horizontal alignment mode."); + + aspect_ratio_container->set_alignment_horizontal(AspectRatioContainer::ALIGNMENT_BEGIN); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 0)), + "Child control is aligned to the left in ALIGNMENT_BEGIN horizontal alignment mode."); + + aspect_ratio_container->set_alignment_horizontal(AspectRatioContainer::ALIGNMENT_END); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(100, 0)), + "Child control is aligned to the right in ALIGNMENT_END horizontal alignment mode."); + } + + SUBCASE("Vertical Alignment") { + aspect_ratio_container->set_size(Size2(100, 200)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + aspect_ratio_container->get_alignment_vertical() == AspectRatioContainer::ALIGNMENT_CENTER, + "AspectRatioContainer vertical alignment is set to ALIGNMENT_CENTER by default."); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 50)), + "Child control is aligned to the center in ALIGNMENT_CENTER vertical alignment mode."); + + aspect_ratio_container->set_alignment_vertical(AspectRatioContainer::ALIGNMENT_BEGIN); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 0)), + "Child control is aligned to the top in ALIGNMENT_BEGIN vertical alignment mode."); + + aspect_ratio_container->set_alignment_vertical(AspectRatioContainer::ALIGNMENT_END); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 100)), + "Child control is aligned to the bottom in ALIGNMENT_END vertical alignment mode."); + } + + memdelete(child_control); + memdelete(aspect_ratio_container); +} + +TEST_CASE("[SceneTree][AspectRatioContainer] Container Sizing Flags") { + AspectRatioContainer *aspect_ratio_container = memnew(AspectRatioContainer); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(aspect_ratio_container); + Control *child_control = memnew(Control); + aspect_ratio_container->add_child(child_control); + + SUBCASE("Horizontal Flags") { + aspect_ratio_container->set_size(Size2(200, 100)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(50, 0)), + "Child control is vertically centered by default with SIZE_FILL."); + + child_control->set_h_size_flags(Control::SIZE_SHRINK_BEGIN); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(50, 0)), + "Child control is aligned to the left of its would-be area with SIZE_FILL in SIZE_SHRINK_BEGIN horizontal size flag mode."); + + child_control->set_h_size_flags(Control::SIZE_SHRINK_CENTER); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(100, 0)), + "Child control is centered in its would-be area with SIZE_FILL in SIZE_SHRINK_CENTER horizontal size flag mode."); + + child_control->set_h_size_flags(Control::SIZE_SHRINK_END); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(150, 0)), + "Child control is aligned to the right of its would-be area with SIZE_FILL in SIZE_SHRINK_END horizontal size flag mode."); + } + + SUBCASE("Horizontal Flags RTL") { + aspect_ratio_container->set_size(Size2(200, 100)); + aspect_ratio_container->set_layout_direction(Control::LAYOUT_DIRECTION_RTL); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(50, 0)), + "Child control is vertically centered by default with SIZE_FILL."); + + child_control->set_h_size_flags(Control::SIZE_SHRINK_BEGIN); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(150, 0)), + "Child control is aligned to the right of its would-be area with SIZE_FILL in RTL SIZE_SHRINK_BEGIN horizontal size flag mode."); + + child_control->set_h_size_flags(Control::SIZE_SHRINK_CENTER); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(100, 0)), + "Child control is centered in its would-be area with SIZE_FILL in SIZE_SHRINK_CENTER horizontal size flag mode."); + + child_control->set_h_size_flags(Control::SIZE_SHRINK_END); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(50, 0)), + "Child control is aligned to the left of its would-be area with SIZE_FILL in RTL SIZE_SHRINK_END horizontal size flag mode."); + } + + SUBCASE("Vertical Flags") { + aspect_ratio_container->set_size(Size2(100, 200)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 50)), + "Child control is horizontally centered by default with SIZE_FILL."); + + child_control->set_v_size_flags(Control::SIZE_SHRINK_BEGIN); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 50)), + "Child control is aligned to the top of its would-be area with SIZE_FILL in SIZE_SHRINK_BEGIN vertical size flag mode."); + + child_control->set_v_size_flags(Control::SIZE_SHRINK_CENTER); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 100)), + "Child control is centered in its would-be area with SIZE_FILL in SIZE_SHRINK_CENTER vertical size flag mode."); + + child_control->set_v_size_flags(Control::SIZE_SHRINK_END); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 150)), + "Child control is aligned to the bottom of its would-be area with SIZE_FILL in SIZE_SHRINK_END vertical size flag mode."); + } + + memdelete(child_control); + memdelete(aspect_ratio_container); +} + +} // namespace TestAspectRatioContainer diff --git a/tests/scene/test_box_container.cpp b/tests/scene/test_box_container.cpp new file mode 100644 index 0000000000..8f086e7e0f --- /dev/null +++ b/tests/scene/test_box_container.cpp @@ -0,0 +1,625 @@ +/**************************************************************************/ +/* test_box_container.cpp */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#include "tests/test_macros.h" + +TEST_FORCE_LINK(test_box_container) + +#include "scene/gui/box_container.h" +#include "scene/gui/control.h" +#include "scene/main/scene_tree.h" +#include "scene/main/window.h" + +namespace TestBoxContainer { + +TEST_CASE("[SceneTree][BoxContainer] HBoxContainer") { + HBoxContainer *hbox_container = memnew(HBoxContainer); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(hbox_container); + + hbox_container->set_size(Size2(100, 100)); + SceneTree::get_singleton()->process(0); + + SUBCASE("Default behavior") { + Control *child_control = memnew(Control); + hbox_container->add_child(child_control); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + hbox_container->get_alignment() == BoxContainer::ALIGNMENT_BEGIN, + "HBoxContainer alignment is set to ALIGNMENT_BEGIN by default."); + + CHECK_MESSAGE( + !hbox_container->is_vertical(), + "HBoxContainer orientation is horizontal."); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 0)), + "Child control is aligned to the left in ALIGNMENT_BEGIN mode."); + + CHECK_MESSAGE( + child_control->get_h_size_flags() == Control::SIZE_FILL, + "Child control horizontal size flags are set to SIZE_FILL by default."); + + CHECK_MESSAGE( + child_control->get_v_size_flags() == Control::SIZE_FILL, + "Child control vertical size flags are set to SIZE_FILL by default."); + + CHECK_MESSAGE( + child_control->get_size().is_equal_approx(Size2(0, 100)), + "Child control takes up minimum horizontal space and matches container height by default."); + + memdelete(child_control); + } + + SUBCASE("Child Horizontal Size Flags") { + Control *child_control = memnew(Control); + hbox_container->add_child(child_control); + child_control->set_custom_minimum_size(Size2(20, 20)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 0)), + "Child control is aligned to the left by default with SIZE_FILL."); + + child_control->set_h_size_flags(Control::SIZE_SHRINK_BEGIN); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 0)), + "Child control is aligned to the left of in SIZE_SHRINK_BEGIN horizontal size flag mode."); + + child_control->set_h_size_flags(Control::SIZE_SHRINK_CENTER); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 0)), + "Child control is aligned to the left of in SIZE_SHRINK_CENTER horizontal size flag mode."); + + child_control->set_h_size_flags(Control::SIZE_SHRINK_END); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 0)), + "Child control is aligned to the left of in SIZE_SHRINK_END horizontal size flag mode."); + + child_control->set_h_size_flags(Control::SIZE_EXPAND | Control::SIZE_SHRINK_BEGIN); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 0)), + "Child control is aligned to the left in SIZE_EXPAND | SIZE_SHRINK_BEGIN horizontal size flag mode."); + + child_control->set_h_size_flags(Control::SIZE_EXPAND | Control::SIZE_SHRINK_CENTER); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(40, 0)), + "Child control is aligned to the center in SIZE_EXPAND | SIZE_SHRINK_CENTER horizontal size flag mode."); + + child_control->set_h_size_flags(Control::SIZE_EXPAND | Control::SIZE_SHRINK_END); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(80, 0)), + "Child control is aligned to the right in SIZE_EXPAND | SIZE_SHRINK_END horizontal size flag mode."); + + memdelete(child_control); + } + + SUBCASE("Child Vertical Size Flags") { + Control *child_control = memnew(Control); + hbox_container->add_child(child_control); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 0)), + "Child control is aligned to the top by default with SIZE_FILL."); + + child_control->set_v_size_flags(Control::SIZE_SHRINK_BEGIN); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 0)), + "Child control is aligned to the top of in SIZE_SHRINK_BEGIN vertical size flag mode."); + + child_control->set_v_size_flags(Control::SIZE_SHRINK_CENTER); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 50)), + "Child control is aligned to the center of in SIZE_SHRINK_CENTER vertical size flag mode."); + + child_control->set_v_size_flags(Control::SIZE_SHRINK_END); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 100)), + "Child control is aligned to the bottom of in SIZE_SHRINK_END vertical size flag mode."); + + memdelete(child_control); + } + + SUBCASE("Child Alignment") { + Control *child_control = memnew(Control); + hbox_container->add_child(child_control); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 0)), + "Child control is aligned to the left in ALIGNMENT_BEGIN mode by default."); + + hbox_container->set_alignment(BoxContainer::ALIGNMENT_CENTER); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(50, 0)), + "Child control is aligned to the center in ALIGNMENT_CENTER mode."); + + hbox_container->set_alignment(BoxContainer::ALIGNMENT_END); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(100, 0)), + "Child control is aligned to the right in ALIGNMENT_END mode."); + + memdelete(child_control); + } + + SUBCASE("Child Alignment RTL") { + Control *child_control = memnew(Control); + hbox_container->add_child(child_control); + hbox_container->set_layout_direction(Control::LAYOUT_DIRECTION_RTL); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(100, 0)), + "Child control is aligned to the right in RTL ALIGNMENT_BEGIN mode by default."); + + hbox_container->set_alignment(BoxContainer::ALIGNMENT_CENTER); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(50, 0)), + "Child control is aligned to the center in RTL ALIGNMENT_CENTER mode."); + + hbox_container->set_alignment(BoxContainer::ALIGNMENT_END); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 0)), + "Child control is aligned to the right in RTL ALIGNMENT_END mode."); + + memdelete(child_control); + } + + SUBCASE("Child Separation") { + Control *child_control_1 = memnew(Control); + Control *child_control_2 = memnew(Control); + hbox_container->add_child(child_control_1); + hbox_container->add_child(child_control_2); + + hbox_container->add_theme_constant_override("separation", 10); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(10, 0)), + "Child controls are separated by the specified separation value."); + + memdelete(child_control_1); + memdelete(child_control_2); + } + + SUBCASE("Spacers") { + Control *child_control_1 = memnew(Control); + Control *child_control_2 = memnew(Control); + hbox_container->add_child(child_control_1); + hbox_container->add_spacer(); + hbox_container->add_child(child_control_2); + hbox_container->add_theme_constant_override("separation", 0); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(100, 0)), + "Spacer pushes child controls apart."); + + memdelete(child_control_1); + memdelete(child_control_2); + } + + SUBCASE("Expanding children") { + Control *child_control_1 = memnew(Control); + Control *child_control_2 = memnew(Control); + hbox_container->add_child(child_control_1); + hbox_container->add_child(child_control_2); + hbox_container->add_theme_constant_override("separation", 0); + + child_control_1->set_h_size_flags(Control::SIZE_EXPAND_FILL); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_1->get_size().is_equal_approx(Size2(100, 100)), + "One child control expands to fill available space."); + + CHECK_MESSAGE( + child_control_2->get_size().is_equal_approx(Size2(0, 100)), + "The non-expanding child control remains at minimum size."); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(0, 0)), + "Expanding child control is aligned to the left."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(100, 0)), + "Non-expanding child control is positioned after the expanding child control."); + + child_control_2->set_custom_minimum_size(Size2(20, 0)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_1->get_size().is_equal_approx(Size2(80, 100)), + "Expanding child control shrinks to accommodate the custom minimum size of the other child."); + + CHECK_MESSAGE( + child_control_2->get_size().is_equal_approx(Size2(20, 100)), + "The non-expanding child control respects its custom minimum size."); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(0, 0)), + "Expanding child control is aligned to the left."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(80, 0)), + "Non-expanding child control is positioned after the expanding child control."); + + child_control_2->set_h_size_flags(Control::SIZE_EXPAND_FILL); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_1->get_size().is_equal_approx(Size2(50, 100)), + "Both child controls expand equally to fill available space."); + + CHECK_MESSAGE( + child_control_2->get_size().is_equal_approx(Size2(50, 100)), + "Both child controls expand equally to fill available space."); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(0, 0)), + "First expanding child control is aligned to the left."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(50, 0)), + "Second expanding child control is positioned after the first expanding child control."); + + child_control_2->set_stretch_ratio(3); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_1->get_size().is_equal_approx(Size2(25, 100)), + "Child control with lower stretch ratio takes less space."); + + CHECK_MESSAGE( + child_control_2->get_size().is_equal_approx(Size2(75, 100)), + "Child control with higher stretch ratio takes more space."); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(0, 0)), + "First expanding child control is aligned to the left."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(25, 0)), + "Second expanding child control is positioned after the first expanding child control."); + + memdelete(child_control_1); + memdelete(child_control_2); + } + + memdelete(hbox_container); +} + +TEST_CASE("[SceneTree][BoxContainer] VBoxContainer") { + VBoxContainer *vbox_container = memnew(VBoxContainer); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(vbox_container); + + vbox_container->set_size(Size2(100, 100)); + SceneTree::get_singleton()->process(0); + + SUBCASE("Default behavior") { + Control *child_control = memnew(Control); + vbox_container->add_child(child_control); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + vbox_container->get_alignment() == BoxContainer::ALIGNMENT_BEGIN, + "HBoxContainer alignment is set to ALIGNMENT_BEGIN by default."); + + CHECK_MESSAGE( + vbox_container->is_vertical(), + "VBoxContainer orientation is vertical."); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 0)), + "Child control is aligned to the left in ALIGNMENT_BEGIN mode."); + + CHECK_MESSAGE( + child_control->get_h_size_flags() == Control::SIZE_FILL, + "Child control horizontal size flags are set to SIZE_FILL by default."); + + CHECK_MESSAGE( + child_control->get_v_size_flags() == Control::SIZE_FILL, + "Child control vertical size flags are set to SIZE_FILL by default."); + + CHECK_MESSAGE( + child_control->get_size().is_equal_approx(Size2(100, 0)), + "Child control takes up minimum vertical space and matches container width by default."); + + memdelete(child_control); + } + + SUBCASE("Child Size Flags") { + Control *child_control = memnew(Control); + vbox_container->add_child(child_control); + child_control->set_custom_minimum_size(Size2(20, 20)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 0)), + "Child control is aligned to the top by default with SIZE_FILL."); + + child_control->set_v_size_flags(Control::SIZE_SHRINK_BEGIN); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 0)), + "Child control is aligned to the top of in SIZE_SHRINK_BEGIN vertical size flag mode."); + + child_control->set_v_size_flags(Control::SIZE_SHRINK_CENTER); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 0)), + "Child control is aligned to the top of in SIZE_SHRINK_CENTER vertical size flag mode."); + + child_control->set_v_size_flags(Control::SIZE_SHRINK_END); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 0)), + "Child control is aligned to the top of in SIZE_SHRINK_END vertical size flag mode."); + + child_control->set_v_size_flags(Control::SIZE_EXPAND | Control::SIZE_SHRINK_BEGIN); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 0)), + "Child control is aligned to the top in SIZE_EXPAND | SIZE_SHRINK_BEGIN vertical size flag mode."); + + child_control->set_v_size_flags(Control::SIZE_EXPAND | Control::SIZE_SHRINK_CENTER); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 40)), + "Child control is aligned to the center in SIZE_EXPAND | SIZE_SHRINK_CENTER vertical size flag mode."); + + child_control->set_v_size_flags(Control::SIZE_EXPAND | Control::SIZE_SHRINK_END); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 80)), + "Child control is aligned to the bottom in SIZE_EXPAND | SIZE_SHRINK_END vertical size flag mode."); + + memdelete(child_control); + } + + SUBCASE("Child Horizontal Size Flags") { + Control *child_control = memnew(Control); + vbox_container->add_child(child_control); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 0)), + "Child control is aligned to the left by default with SIZE_FILL."); + + child_control->set_h_size_flags(Control::SIZE_SHRINK_BEGIN); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 0)), + "Child control is aligned to the left of in SIZE_SHRINK_BEGIN horizontal size flag mode."); + + child_control->set_h_size_flags(Control::SIZE_SHRINK_CENTER); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(50, 0)), + "Child control is aligned to the center of in SIZE_SHRINK_CENTER horizontal size flag mode."); + + child_control->set_h_size_flags(Control::SIZE_SHRINK_END); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(100, 0)), + "Child control is aligned to the right of in SIZE_SHRINK_END horizontal size flag mode."); + + memdelete(child_control); + } + + SUBCASE("Child Alignment") { + Control *child_control = memnew(Control); + vbox_container->add_child(child_control); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 0)), + "Child control is aligned to the top in ALIGNMENT_BEGIN mode by default."); + + vbox_container->set_alignment(BoxContainer::ALIGNMENT_CENTER); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 50)), + "Child control is aligned to the center in ALIGNMENT_CENTER mode."); + + vbox_container->set_alignment(BoxContainer::ALIGNMENT_END); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 100)), + "Child control is aligned to the bottom in ALIGNMENT_END mode."); + + memdelete(child_control); + } + + SUBCASE("Child Separation") { + Control *child_control_1 = memnew(Control); + Control *child_control_2 = memnew(Control); + vbox_container->add_child(child_control_1); + vbox_container->add_child(child_control_2); + + vbox_container->add_theme_constant_override("separation", 10); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(0, 10)), + "Child controls are separated by the specified separation value."); + + memdelete(child_control_1); + memdelete(child_control_2); + } + + SUBCASE("Spacers") { + Control *child_control_1 = memnew(Control); + Control *child_control_2 = memnew(Control); + vbox_container->add_child(child_control_1); + vbox_container->add_spacer(); + vbox_container->add_child(child_control_2); + vbox_container->add_theme_constant_override("separation", 0); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(0, 100)), + "Spacer pushes child controls apart."); + + memdelete(child_control_1); + memdelete(child_control_2); + } + + SUBCASE("Expanding children") { + Control *child_control_1 = memnew(Control); + Control *child_control_2 = memnew(Control); + vbox_container->add_child(child_control_1); + vbox_container->add_child(child_control_2); + vbox_container->add_theme_constant_override("separation", 0); + + child_control_1->set_v_size_flags(Control::SIZE_EXPAND_FILL); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_1->get_size().is_equal_approx(Size2(100, 100)), + "One child control expands to fill available space."); + + CHECK_MESSAGE( + child_control_2->get_size().is_equal_approx(Size2(100, 0)), + "The non-expanding child control remains at minimum size."); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(0, 0)), + "Expanding child control is aligned to the left."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(0, 100)), + "Non-expanding child control is positioned after the expanding child control."); + + child_control_2->set_custom_minimum_size(Size2(0, 20)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_1->get_size().is_equal_approx(Size2(100, 80)), + "Expanding child control shrinks to accommodate the custom minimum size of the other child."); + + CHECK_MESSAGE( + child_control_2->get_size().is_equal_approx(Size2(100, 20)), + "The non-expanding child control respects its custom minimum size."); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(0, 0)), + "Expanding child control is aligned to the left."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(0, 80)), + "Non-expanding child control is positioned after the expanding child control."); + + child_control_2->set_v_size_flags(Control::SIZE_EXPAND_FILL); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_1->get_size().is_equal_approx(Size2(100, 50)), + "Both child controls expand equally to fill available space."); + + CHECK_MESSAGE( + child_control_2->get_size().is_equal_approx(Size2(100, 50)), + "Both child controls expand equally to fill available space."); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(0, 0)), + "First expanding child control is aligned to the left."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(0, 50)), + "Second expanding child control is positioned after the first expanding child control."); + + child_control_2->set_stretch_ratio(3); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_1->get_size().is_equal_approx(Size2(100, 25)), + "Child control with lower stretch ratio takes less space."); + + CHECK_MESSAGE( + child_control_2->get_size().is_equal_approx(Size2(100, 75)), + "Child control with higher stretch ratio takes more space."); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(0, 0)), + "First expanding child control is aligned to the left."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(0, 25)), + "Second expanding child control is positioned after the first expanding child control."); + + memdelete(child_control_1); + memdelete(child_control_2); + } + + memdelete(vbox_container); +} + +} // namespace TestBoxContainer diff --git a/tests/scene/test_center_container.cpp b/tests/scene/test_center_container.cpp new file mode 100644 index 0000000000..04c7d4e74c --- /dev/null +++ b/tests/scene/test_center_container.cpp @@ -0,0 +1,170 @@ +/**************************************************************************/ +/* test_center_container.cpp */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#include "tests/test_macros.h" + +TEST_FORCE_LINK(test_center_container) + +#include "scene/gui/center_container.h" +#include "scene/gui/control.h" +#include "scene/main/scene_tree.h" +#include "scene/main/window.h" + +namespace TestCenterContainer { + +TEST_CASE("[SceneTree][CenterContainer] Standard behavior") { + CenterContainer *center_container = memnew(CenterContainer); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(center_container); + Control *child_control = memnew(Control); + center_container->add_child(child_control); + + center_container->set_size(Size2(100, 100)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(50, 50)), + "Child control is centered within the CenterContainer by default."); + + child_control->set_custom_minimum_size(Size2(20, 20)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(40, 40)), + "Child control remains centered when custom minimum size is set."); + + child_control->set_custom_minimum_size(Size2(200, 200)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + center_container->get_size().is_equal_approx(Size2(200, 200)), + "CenterContainer expands to accommodate the minimum size of its child control."); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 0)), + "Child control is positioned at the top-left corner when its minimum size equals the container size."); + + child_control->set_custom_minimum_size(Size2()); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(50, 50)), + "Child control re-centers when custom minimum size is reset."); + + CHECK_MESSAGE( + center_container->get_size().is_equal_approx(Size2(100, 100)), + "CenterContainer returns to original size when child control's custom minimum size is reset."); + + center_container->set_use_top_left(true); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 0)), + "Child control is aligned to the top-left corner when use_top_left is enabled."); + + center_container->set_layout_direction(Control::LAYOUT_DIRECTION_RTL); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(100, 0)), + "Child control is aligned to the top-right corner in RTL layout direction when use_top_left is enabled."); + + memdelete(child_control); + memdelete(center_container); +} + +TEST_CASE("[SceneTree][CenterContainer] Multiple children") { + CenterContainer *center_container = memnew(CenterContainer); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(center_container); + + center_container->set_size(Size2(100, 100)); + + Control *child_control_1 = memnew(Control); + Control *child_control_2 = memnew(Control); + center_container->add_child(child_control_1); + center_container->add_child(child_control_2); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(50, 50)), + "First child control is centered within the CenterContainer."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(50, 50)), + "Second child control is also centered and overlaps the first child control by default."); + + child_control_1->set_custom_minimum_size(Size2(20, 20)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(40, 40)), + "First child control remains centered when custom minimum size is set."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(50, 50)), + "Second child control remains centered and overlaps the first child control even when the first child has a custom minimum size."); + + child_control_1->set_custom_minimum_size(Size2(200, 0)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + center_container->get_size().is_equal_approx(Size2(200, 100)), + "CenterContainer expands to accommodate the minimum size of the first child control."); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(0, 50)), + "First child control is aligned to the left edge of the CenterContainer when its minimum width equals the container width."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(100, 50)), + "Second child control is centered on the new container center."); + + child_control_2->set_custom_minimum_size(Size2(0, 200)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + center_container->get_size().is_equal_approx(Size2(200, 200)), + "CenterContainer expands to accommodate the minimum size of the second child control."); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(0, 100)), + "First child control is aligned to the left edge and centered vertically when its minimum width equals the container width but its minimum height is smaller than the container height."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(100, 0)), + "Second child control is aligned to the top edge and centered horizontally when its minimum height equals the container height but its minimum width is smaller than the container width."); + + memdelete(child_control_2); + memdelete(child_control_1); + memdelete(center_container); +} + +} // namespace TestCenterContainer diff --git a/tests/scene/test_flow_container.cpp b/tests/scene/test_flow_container.cpp new file mode 100644 index 0000000000..3131b3b61a --- /dev/null +++ b/tests/scene/test_flow_container.cpp @@ -0,0 +1,576 @@ +/**************************************************************************/ +/* test_flow_container.cpp */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#include "tests/test_macros.h" + +TEST_FORCE_LINK(test_flow_container) + +#include "scene/gui/control.h" +#include "scene/gui/flow_container.h" +#include "scene/main/scene_tree.h" +#include "scene/main/window.h" + +namespace TestFlowContainer { + +TEST_CASE("[SceneTree][FlowContainer] HFlowContainer") { + HFlowContainer *hflow_container = memnew(HFlowContainer); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(hflow_container); + + hflow_container->add_theme_constant_override("h_separation", 0); + hflow_container->add_theme_constant_override("v_separation", 0); + hflow_container->set_size(Size2(100, 100)); + SceneTree::get_singleton()->process(0); + + SUBCASE("Default behavior") { + Control *child_control_1 = memnew(Control); + Control *child_control_2 = memnew(Control); + Control *child_control_3 = memnew(Control); + child_control_1->set_custom_minimum_size(Size2(20, 10)); + child_control_2->set_custom_minimum_size(Size2(20, 10)); + child_control_3->set_custom_minimum_size(Size2(20, 10)); + hflow_container->add_child(child_control_1); + hflow_container->add_child(child_control_2); + hflow_container->add_child(child_control_3); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + hflow_container->get_alignment() == FlowContainer::ALIGNMENT_BEGIN, + "HFlowContainer alignment is set to ALIGNMENT_BEGIN by default."); + + CHECK_MESSAGE( + hflow_container->get_last_wrap_alignment() == FlowContainer::LAST_WRAP_ALIGNMENT_INHERIT, + "HFlowContainer last wrap alignment is set to LAST_WRAP_ALIGNMENT_INHERIT by default."); + + CHECK_MESSAGE( + !hflow_container->is_vertical(), + "HFlowContainer is horizontal."); + + CHECK_MESSAGE( + !hflow_container->is_reverse_fill(), + "HFlowContainer reverse fill is disabled by default."); + + CHECK_MESSAGE( + hflow_container->get_line_count() == 1, + "HFlowContainer keeps all children in one line when there is enough horizontal space."); + + CHECK_MESSAGE( + hflow_container->get_line_max_child_count() == 3, + "HFlowContainer reports the child count of the first line."); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(0, 0)), + "First child control is positioned at the beginning of the line."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(20, 0)), + "Second child control is placed after the first child control."); + + CHECK_MESSAGE( + child_control_3->get_position().is_equal_approx(Point2(40, 0)), + "Third child control is placed after the second child control."); + + memdelete(child_control_3); + memdelete(child_control_2); + memdelete(child_control_1); + } + + SUBCASE("Wrapping and last wrap alignment") { + Control *child_control_1 = memnew(Control); + Control *child_control_2 = memnew(Control); + Control *child_control_3 = memnew(Control); + child_control_1->set_custom_minimum_size(Size2(20, 10)); + child_control_2->set_custom_minimum_size(Size2(20, 10)); + child_control_3->set_custom_minimum_size(Size2(20, 10)); + hflow_container->set_size(Size2(50, 100)); + hflow_container->add_child(child_control_1); + hflow_container->add_child(child_control_2); + hflow_container->add_child(child_control_3); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + hflow_container->get_line_count() == 2, + "HFlowContainer wraps children to a second line when they no longer fit."); + + CHECK_MESSAGE( + hflow_container->get_line_max_child_count() == 2, + "HFlowContainer reports the first line child count as the line max child count."); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(0, 0)), + "First child control starts at the first line origin."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(20, 0)), + "Second child control remains on the first line."); + + CHECK_MESSAGE( + child_control_3->get_position().is_equal_approx(Point2(0, 10)), + "Third child control wraps to the second line."); + + hflow_container->set_last_wrap_alignment(FlowContainer::LAST_WRAP_ALIGNMENT_CENTER); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_3->get_position().is_equal_approx(Point2(10, 10)), + "Last wrapped line is centered relative to the previous line when LAST_WRAP_ALIGNMENT_CENTER is used."); + + hflow_container->set_last_wrap_alignment(FlowContainer::LAST_WRAP_ALIGNMENT_END); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_3->get_position().is_equal_approx(Point2(20, 10)), + "Last wrapped line is aligned to the end relative to the previous line when LAST_WRAP_ALIGNMENT_END is used."); + + memdelete(child_control_3); + memdelete(child_control_2); + memdelete(child_control_1); + } + + SUBCASE("Alignment and RTL") { + Control *child_control_1 = memnew(Control); + Control *child_control_2 = memnew(Control); + child_control_1->set_custom_minimum_size(Size2(20, 10)); + child_control_2->set_custom_minimum_size(Size2(20, 10)); + hflow_container->set_size(Size2(100, 100)); + hflow_container->add_child(child_control_1); + hflow_container->add_child(child_control_2); + SceneTree::get_singleton()->process(0); + + hflow_container->set_alignment(FlowContainer::ALIGNMENT_CENTER); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(30, 0)), + "First child control is centered when ALIGNMENT_CENTER is used."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(50, 0)), + "Second child control follows centered alignment when ALIGNMENT_CENTER is used."); + + hflow_container->set_alignment(FlowContainer::ALIGNMENT_END); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(60, 0)), + "First child control is aligned to the end when ALIGNMENT_END is used."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(80, 0)), + "Second child control is aligned to the end when ALIGNMENT_END is used."); + + hflow_container->set_alignment(FlowContainer::ALIGNMENT_BEGIN); + hflow_container->set_layout_direction(Control::LAYOUT_DIRECTION_RTL); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(80, 0)), + "First child control starts at the right edge in RTL mode with ALIGNMENT_BEGIN."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(60, 0)), + "Second child control follows right-to-left ordering in RTL mode with ALIGNMENT_BEGIN."); + + memdelete(child_control_2); + memdelete(child_control_1); + } + + SUBCASE("Expanding children and stretch ratio") { + Control *child_control_1 = memnew(Control); + Control *child_control_2 = memnew(Control); + child_control_1->set_custom_minimum_size(Size2(20, 10)); + child_control_2->set_custom_minimum_size(Size2(20, 10)); + child_control_1->set_h_size_flags(Control::SIZE_EXPAND_FILL); + child_control_2->set_h_size_flags(Control::SIZE_EXPAND_FILL); + hflow_container->set_size(Size2(100, 100)); + hflow_container->add_child(child_control_1); + hflow_container->add_child(child_control_2); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_1->get_size().is_equal_approx(Size2(50, 10)), + "First child control expands to half of the available width when both children have equal stretch ratio."); + + CHECK_MESSAGE( + child_control_2->get_size().is_equal_approx(Size2(50, 10)), + "Second child control expands to half of the available width when both children have equal stretch ratio."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(50, 0)), + "Second child control is placed immediately after the first expanded child control."); + + child_control_2->set_stretch_ratio(3.0f); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_1->get_size().is_equal_approx(Size2(35, 10)), + "First child control receives less width when its stretch ratio is lower."); + + CHECK_MESSAGE( + child_control_2->get_size().is_equal_approx(Size2(65, 10)), + "Second child control receives more width when its stretch ratio is higher."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(35, 0)), + "Second child control starts after the first child control's stretched width."); + + memdelete(child_control_2); + memdelete(child_control_1); + } + + SUBCASE("Reverse fill") { + Control *child_control_1 = memnew(Control); + Control *child_control_2 = memnew(Control); + Control *child_control_3 = memnew(Control); + child_control_1->set_custom_minimum_size(Size2(20, 10)); + child_control_2->set_custom_minimum_size(Size2(20, 10)); + child_control_3->set_custom_minimum_size(Size2(20, 10)); + hflow_container->set_size(Size2(50, 100)); + hflow_container->add_child(child_control_1); + hflow_container->add_child(child_control_2); + hflow_container->add_child(child_control_3); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(0, 0)), + "First child control starts at the top row by default."); + + CHECK_MESSAGE( + child_control_3->get_position().is_equal_approx(Point2(0, 10)), + "Third child control wraps to the next row by default."); + + hflow_container->set_reverse_fill(true); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(0, 90)), + "First child control moves to the bottom row when reverse fill is enabled."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(20, 90)), + "Second child control remains in the same row as the first child control when reverse fill is enabled."); + + CHECK_MESSAGE( + child_control_3->get_position().is_equal_approx(Point2(0, 80)), + "Wrapped row order is inverted from bottom to top when reverse fill is enabled."); + + hflow_container->set_layout_direction(Control::LAYOUT_DIRECTION_RTL); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(30, 90)), + "RTL mirrors horizontal placement while reverse fill keeps rows bottom to top."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(10, 90)), + "Second child control follows mirrored RTL order while reverse fill remains enabled."); + + CHECK_MESSAGE( + child_control_3->get_position().is_equal_approx(Point2(30, 80)), + "Wrapped child control is mirrored in RTL while keeping reverse-filled row order."); + + memdelete(child_control_3); + memdelete(child_control_2); + memdelete(child_control_1); + } + + memdelete(hflow_container); +} + +TEST_CASE("[SceneTree][FlowContainer] VFlowContainer") { + VFlowContainer *vflow_container = memnew(VFlowContainer); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(vflow_container); + + vflow_container->add_theme_constant_override("h_separation", 0); + vflow_container->add_theme_constant_override("v_separation", 0); + vflow_container->set_size(Size2(100, 100)); + SceneTree::get_singleton()->process(0); + + SUBCASE("Default behavior") { + Control *child_control_1 = memnew(Control); + Control *child_control_2 = memnew(Control); + Control *child_control_3 = memnew(Control); + child_control_1->set_custom_minimum_size(Size2(20, 10)); + child_control_2->set_custom_minimum_size(Size2(20, 10)); + child_control_3->set_custom_minimum_size(Size2(20, 10)); + vflow_container->add_child(child_control_1); + vflow_container->add_child(child_control_2); + vflow_container->add_child(child_control_3); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + vflow_container->get_alignment() == FlowContainer::ALIGNMENT_BEGIN, + "VFlowContainer alignment is set to ALIGNMENT_BEGIN by default."); + + CHECK_MESSAGE( + vflow_container->get_last_wrap_alignment() == FlowContainer::LAST_WRAP_ALIGNMENT_INHERIT, + "VFlowContainer last wrap alignment is set to LAST_WRAP_ALIGNMENT_INHERIT by default."); + + CHECK_MESSAGE( + vflow_container->is_vertical(), + "VFlowContainer is vertical."); + + CHECK_MESSAGE( + !vflow_container->is_reverse_fill(), + "VFlowContainer reverse fill is disabled by default."); + + CHECK_MESSAGE( + vflow_container->get_line_count() == 1, + "VFlowContainer keeps all children in one column when there is enough vertical space."); + + CHECK_MESSAGE( + vflow_container->get_line_max_child_count() == 3, + "VFlowContainer reports the child count of the first column."); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(0, 0)), + "First child control is positioned at the top of the first column."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(0, 10)), + "Second child control is placed below the first child control."); + + CHECK_MESSAGE( + child_control_3->get_position().is_equal_approx(Point2(0, 20)), + "Third child control is placed below the second child control."); + + memdelete(child_control_3); + memdelete(child_control_2); + memdelete(child_control_1); + } + + SUBCASE("Wrapping and last wrap alignment") { + Control *child_control_1 = memnew(Control); + Control *child_control_2 = memnew(Control); + Control *child_control_3 = memnew(Control); + child_control_1->set_custom_minimum_size(Size2(20, 20)); + child_control_2->set_custom_minimum_size(Size2(20, 20)); + child_control_3->set_custom_minimum_size(Size2(20, 20)); + vflow_container->set_size(Size2(100, 50)); + vflow_container->add_child(child_control_1); + vflow_container->add_child(child_control_2); + vflow_container->add_child(child_control_3); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + vflow_container->get_line_count() == 2, + "VFlowContainer wraps children to a second column when they no longer fit vertically."); + + CHECK_MESSAGE( + vflow_container->get_line_max_child_count() == 2, + "VFlowContainer reports the first column child count as the line max child count."); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(0, 0)), + "First child control starts at the first column origin."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(0, 20)), + "Second child control remains in the first column."); + + CHECK_MESSAGE( + child_control_3->get_position().is_equal_approx(Point2(20, 0)), + "Third child control wraps to the second column."); + + vflow_container->set_last_wrap_alignment(FlowContainer::LAST_WRAP_ALIGNMENT_CENTER); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_3->get_position().is_equal_approx(Point2(20, 10)), + "Last wrapped column is centered relative to the previous column when LAST_WRAP_ALIGNMENT_CENTER is used."); + + vflow_container->set_last_wrap_alignment(FlowContainer::LAST_WRAP_ALIGNMENT_END); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_3->get_position().is_equal_approx(Point2(20, 20)), + "Last wrapped column is aligned to the end relative to the previous column when LAST_WRAP_ALIGNMENT_END is used."); + + memdelete(child_control_3); + memdelete(child_control_2); + memdelete(child_control_1); + } + + SUBCASE("Alignment and RTL") { + Control *child_control_1 = memnew(Control); + Control *child_control_2 = memnew(Control); + child_control_1->set_custom_minimum_size(Size2(20, 20)); + child_control_2->set_custom_minimum_size(Size2(20, 20)); + vflow_container->set_size(Size2(100, 100)); + vflow_container->add_child(child_control_1); + vflow_container->add_child(child_control_2); + SceneTree::get_singleton()->process(0); + + vflow_container->set_alignment(FlowContainer::ALIGNMENT_CENTER); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(0, 30)), + "First child control is vertically centered when ALIGNMENT_CENTER is used."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(0, 50)), + "Second child control follows centered vertical alignment when ALIGNMENT_CENTER is used."); + + vflow_container->set_alignment(FlowContainer::ALIGNMENT_END); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(0, 60)), + "First child control is aligned to the bottom when ALIGNMENT_END is used."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(0, 80)), + "Second child control is aligned to the bottom when ALIGNMENT_END is used."); + + vflow_container->set_alignment(FlowContainer::ALIGNMENT_BEGIN); + vflow_container->set_layout_direction(Control::LAYOUT_DIRECTION_RTL); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(80, 0)), + "First child control moves to the right edge in RTL mode with vertical flow."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(80, 20)), + "Second child control follows RTL horizontal mirroring in vertical flow."); + + memdelete(child_control_2); + memdelete(child_control_1); + } + + SUBCASE("Expanding children and stretch ratio") { + Control *child_control_1 = memnew(Control); + Control *child_control_2 = memnew(Control); + child_control_1->set_custom_minimum_size(Size2(20, 20)); + child_control_2->set_custom_minimum_size(Size2(20, 20)); + child_control_1->set_v_size_flags(Control::SIZE_EXPAND_FILL); + child_control_2->set_v_size_flags(Control::SIZE_EXPAND_FILL); + vflow_container->set_size(Size2(100, 100)); + vflow_container->add_child(child_control_1); + vflow_container->add_child(child_control_2); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_1->get_size().is_equal_approx(Size2(20, 50)), + "First child control expands to half of the available height when both children have equal stretch ratio."); + + CHECK_MESSAGE( + child_control_2->get_size().is_equal_approx(Size2(20, 50)), + "Second child control expands to half of the available height when both children have equal stretch ratio."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(0, 50)), + "Second child control is placed immediately after the first expanded child control."); + + child_control_2->set_stretch_ratio(3.0f); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_1->get_size().is_equal_approx(Size2(20, 35)), + "First child control receives less height when its stretch ratio is lower."); + + CHECK_MESSAGE( + child_control_2->get_size().is_equal_approx(Size2(20, 65)), + "Second child control receives more height when its stretch ratio is higher."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(0, 35)), + "Second child control starts after the first child control's stretched height."); + + memdelete(child_control_2); + memdelete(child_control_1); + } + + SUBCASE("Reverse fill") { + Control *child_control_1 = memnew(Control); + Control *child_control_2 = memnew(Control); + Control *child_control_3 = memnew(Control); + child_control_1->set_custom_minimum_size(Size2(20, 20)); + child_control_2->set_custom_minimum_size(Size2(20, 20)); + child_control_3->set_custom_minimum_size(Size2(20, 20)); + vflow_container->set_size(Size2(100, 30)); + vflow_container->add_child(child_control_1); + vflow_container->add_child(child_control_2); + vflow_container->add_child(child_control_3); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(0, 0)), + "First child control starts in the first column by default."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(20, 0)), + "Second child control starts in the second column by default."); + + CHECK_MESSAGE( + child_control_3->get_position().is_equal_approx(Point2(40, 0)), + "Third child control starts in the third column by default."); + + vflow_container->set_reverse_fill(true); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(80, 0)), + "First child control starts from the right when reverse fill is enabled in vertical mode."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(60, 0)), + "Second child control follows right-to-left column filling when reverse fill is enabled."); + + CHECK_MESSAGE( + child_control_3->get_position().is_equal_approx(Point2(40, 0)), + "Third child control continues right-to-left column filling when reverse fill is enabled."); + + vflow_container->set_layout_direction(Control::LAYOUT_DIRECTION_RTL); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(0, 0)), + "Vertical flow with RTL and reverse fill fills columns from left to right."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(20, 0)), + "Second child control follows left-to-right order in vertical mode with RTL and reverse fill."); + + CHECK_MESSAGE( + child_control_3->get_position().is_equal_approx(Point2(40, 0)), + "Third child control follows left-to-right order in vertical mode with RTL and reverse fill."); + + memdelete(child_control_3); + memdelete(child_control_2); + memdelete(child_control_1); + } + + memdelete(vflow_container); +} + +} // namespace TestFlowContainer diff --git a/tests/scene/test_foldable_container.cpp b/tests/scene/test_foldable_container.cpp new file mode 100644 index 0000000000..26f9070b9d --- /dev/null +++ b/tests/scene/test_foldable_container.cpp @@ -0,0 +1,270 @@ +/**************************************************************************/ +/* test_foldable_container.cpp */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#include "tests/test_macros.h" + +TEST_FORCE_LINK(test_foldable_container) + +#ifndef ADVANCED_GUI_DISABLED + +#include "scene/gui/control.h" +#include "scene/gui/foldable_container.h" +#include "scene/main/scene_tree.h" +#include "scene/main/window.h" + +namespace TestFoldableContainer { + +TEST_CASE("[SceneTree][FoldableContainer] Default properties") { + FoldableContainer *foldable_container = memnew(FoldableContainer); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(foldable_container); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + !foldable_container->is_folded(), + "FoldableContainer is expanded by default."); + + CHECK_MESSAGE( + foldable_container->get_title().is_empty(), + "FoldableContainer title is empty by default."); + + CHECK_MESSAGE( + foldable_container->get_title_alignment() == HORIZONTAL_ALIGNMENT_LEFT, + "FoldableContainer title alignment is set to left by default."); + + CHECK_MESSAGE( + foldable_container->get_title_position() == FoldableContainer::POSITION_TOP, + "FoldableContainer title position is set to top by default."); + + CHECK_MESSAGE( + foldable_container->get_title_text_direction() == Control::TEXT_DIRECTION_AUTO, + "FoldableContainer title text direction is set to auto by default."); + + CHECK_MESSAGE( + foldable_container->get_title_text_overrun_behavior() == TextServer::OVERRUN_NO_TRIMMING, + "FoldableContainer title overrun behavior is set to no trimming by default."); + + CHECK_MESSAGE( + foldable_container->get_language().is_empty(), + "FoldableContainer language is empty by default."); + + CHECK_MESSAGE( + foldable_container->get_focus_mode() == Control::FOCUS_ALL, + "FoldableContainer focus mode is set to FOCUS_ALL by default."); + + CHECK_MESSAGE( + foldable_container->get_mouse_filter() == Control::MOUSE_FILTER_STOP, + "FoldableContainer mouse filter is set to MOUSE_FILTER_STOP by default."); + + CHECK_MESSAGE( + foldable_container->get_foldable_group().is_null(), + "FoldableContainer has no foldable group by default."); + + memdelete(foldable_container); +} + +TEST_CASE("[SceneTree][FoldableContainer] Fold and expand behavior") { + FoldableContainer *foldable_container = memnew(FoldableContainer("Section")); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(foldable_container); + + Control *child_control = memnew(Control); + child_control->set_custom_minimum_size(Size2(120, 80)); + foldable_container->add_child(child_control); + foldable_container->set_size(Size2(200, 200)); + SceneTree::get_singleton()->process(0); + + Size2 expanded_minimum_size = foldable_container->get_minimum_size(); + + CHECK_MESSAGE( + child_control->is_visible(), + "Child control is visible when FoldableContainer is expanded."); + + foldable_container->fold(); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + foldable_container->is_folded(), + "FoldableContainer is folded after calling fold()."); + + CHECK_MESSAGE( + !child_control->is_visible(), + "Child control is hidden when FoldableContainer is folded."); + + CHECK_MESSAGE( + foldable_container->get_minimum_size().y < expanded_minimum_size.y, + "Folded minimum size is smaller than expanded minimum size when content is present."); + + foldable_container->expand(); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + !foldable_container->is_folded(), + "FoldableContainer is expanded after calling expand()."); + + CHECK_MESSAGE( + child_control->is_visible(), + "Child control is visible again after FoldableContainer is expanded."); + + foldable_container->set_folded(true); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + foldable_container->is_folded(), + "set_folded(true) folds the FoldableContainer."); + + foldable_container->set_folded(false); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + !foldable_container->is_folded(), + "set_folded(false) expands the FoldableContainer."); + + memdelete(child_control); + memdelete(foldable_container); +} + +TEST_CASE("[SceneTree][FoldableContainer] Title position and title bar controls") { + FoldableContainer *foldable_container = memnew(FoldableContainer("Section")); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(foldable_container); + + Control *content_control = memnew(Control); + content_control->set_custom_minimum_size(Size2(40, 40)); + foldable_container->add_child(content_control); + + Control *title_control = memnew(Control); + title_control->set_custom_minimum_size(Size2(30, 20)); + foldable_container->add_title_bar_control(title_control); + + foldable_container->set_size(Size2(200, 140)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + title_control->get_parent() == foldable_container, + "Title bar control is reparented to the FoldableContainer when added."); + + real_t top_title_control_y = title_control->get_position().y; + real_t top_content_y = content_control->get_position().y; + + foldable_container->set_title_position(FoldableContainer::POSITION_BOTTOM); + SceneTree::get_singleton()->process(0); + + real_t bottom_title_control_y = title_control->get_position().y; + real_t bottom_content_y = content_control->get_position().y; + + CHECK_MESSAGE( + bottom_title_control_y > top_title_control_y, + "Title bar controls move to the bottom when title position is set to POSITION_BOTTOM."); + + CHECK_MESSAGE( + bottom_content_y < top_content_y, + "Content area shifts upward when title position is changed from top to bottom."); + + foldable_container->remove_title_bar_control(title_control); + + CHECK_MESSAGE( + title_control->get_parent() == nullptr, + "Title bar control is detached from FoldableContainer when removed."); + + memdelete(title_control); + memdelete(content_control); + memdelete(foldable_container); +} + +TEST_CASE("[SceneTree][FoldableContainer] FoldableGroup behavior") { + Ref foldable_group; + foldable_group.instantiate(); + + FoldableContainer *container_a = memnew(FoldableContainer("A")); + FoldableContainer *container_b = memnew(FoldableContainer("B")); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(container_a); + root->add_child(container_b); + + container_a->set_folded(true); + container_b->set_folded(true); + container_a->set_foldable_group(foldable_group); + container_b->set_foldable_group(foldable_group); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + !container_a->is_folded(), + "First grouped FoldableContainer auto-expands when allow_folding_all is false and no container is expanded."); + + CHECK_MESSAGE( + container_b->is_folded(), + "Second grouped FoldableContainer remains folded when another container in the group is expanded."); + + CHECK_MESSAGE( + foldable_group->get_expanded_container() == container_a, + "FoldableGroup tracks the currently expanded container."); + + container_b->set_folded(false); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + container_a->is_folded(), + "Expanding one grouped FoldableContainer folds the previously expanded container."); + + CHECK_MESSAGE( + !container_b->is_folded(), + "Expanded grouped FoldableContainer remains unfolded."); + + CHECK_MESSAGE( + foldable_group->get_expanded_container() == container_b, + "FoldableGroup updates expanded container after a different container is expanded."); + + container_b->set_folded(true); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + !container_b->is_folded(), + "Expanded grouped FoldableContainer cannot be folded when allow_folding_all is false."); + + foldable_group->set_allow_folding_all(true); + container_b->set_folded(true); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + container_b->is_folded(), + "Grouped FoldableContainer can be folded when allow_folding_all is true."); + + CHECK_MESSAGE( + foldable_group->get_expanded_container() == nullptr, + "FoldableGroup has no expanded container when all grouped containers are folded."); + + memdelete(container_b); + memdelete(container_a); +} + +} // namespace TestFoldableContainer + +#endif diff --git a/tests/scene/test_grid_container.cpp b/tests/scene/test_grid_container.cpp new file mode 100644 index 0000000000..a37481fceb --- /dev/null +++ b/tests/scene/test_grid_container.cpp @@ -0,0 +1,265 @@ +/**************************************************************************/ +/* test_grid_container.cpp */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#include "tests/test_macros.h" + +TEST_FORCE_LINK(test_grid_container) + +#include "scene/gui/control.h" +#include "scene/gui/grid_container.h" +#include "scene/main/scene_tree.h" +#include "scene/main/window.h" + +namespace TestGridContainer { + +TEST_CASE("[SceneTree][GridContainer] Default properties") { + GridContainer *grid_container = memnew(GridContainer); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(grid_container); + + grid_container->set_size(Size2(200, 200)); + grid_container->add_theme_constant_override("h_separation", 0); + grid_container->add_theme_constant_override("v_separation", 0); + + Control *child_control = memnew(Control); + child_control->set_custom_minimum_size(Size2(20, 10)); + grid_container->add_child(child_control); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + grid_container->get_columns() == 1, + "GridContainer columns are set to 1 by default."); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 0)), + "Single child control starts at the top-left corner."); + + CHECK_MESSAGE( + child_control->get_size().is_equal_approx(Size2(20, 10)), + "Single child control keeps its minimum size when no expansion is requested."); + + memdelete(child_control); + memdelete(grid_container); +} + +TEST_CASE("[SceneTree][GridContainer] Columns and placement order") { + GridContainer *grid_container = memnew(GridContainer); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(grid_container); + + Control *child_control_1 = memnew(Control); + Control *child_control_2 = memnew(Control); + Control *child_control_3 = memnew(Control); + Control *child_control_4 = memnew(Control); + child_control_1->set_custom_minimum_size(Size2(10, 10)); + child_control_2->set_custom_minimum_size(Size2(10, 10)); + child_control_3->set_custom_minimum_size(Size2(10, 10)); + child_control_4->set_custom_minimum_size(Size2(10, 10)); + + grid_container->set_columns(2); + grid_container->set_size(Size2(200, 200)); + grid_container->add_theme_constant_override("h_separation", 0); + grid_container->add_theme_constant_override("v_separation", 0); + grid_container->add_child(child_control_1); + grid_container->add_child(child_control_2); + grid_container->add_child(child_control_3); + grid_container->add_child(child_control_4); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + grid_container->get_columns() == 2, + "GridContainer uses the configured number of columns."); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(0, 0)), + "First child control is placed at row 0, column 0."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(10, 0)), + "Second child control is placed at row 0, column 1."); + + CHECK_MESSAGE( + child_control_3->get_position().is_equal_approx(Point2(0, 10)), + "Third child control is placed at row 1, column 0."); + + CHECK_MESSAGE( + child_control_4->get_position().is_equal_approx(Point2(10, 10)), + "Fourth child control is placed at row 1, column 1."); + + memdelete(child_control_4); + memdelete(child_control_3); + memdelete(child_control_2); + memdelete(child_control_1); + memdelete(grid_container); +} + +TEST_CASE("[SceneTree][GridContainer] Separation and minimum size") { + GridContainer *grid_container = memnew(GridContainer); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(grid_container); + + Control *child_control_1 = memnew(Control); + Control *child_control_2 = memnew(Control); + Control *child_control_3 = memnew(Control); + child_control_1->set_custom_minimum_size(Size2(10, 20)); + child_control_2->set_custom_minimum_size(Size2(30, 10)); + child_control_3->set_custom_minimum_size(Size2(15, 40)); + + grid_container->set_columns(2); + grid_container->add_theme_constant_override("h_separation", 5); + grid_container->add_theme_constant_override("v_separation", 7); + grid_container->add_child(child_control_1); + grid_container->add_child(child_control_2); + grid_container->add_child(child_control_3); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + grid_container->get_h_separation() == 5, + "GridContainer reports overridden horizontal separation."); + + CHECK_MESSAGE( + grid_container->get_minimum_size().is_equal_approx(Size2(50, 67)), + "GridContainer minimum size matches per-column and per-row maxima plus separations."); + + grid_container->set_size(Size2(50, 67)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(0, 0)), + "First child control is placed at row 0, column 0 with configured separations."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(20, 0)), + "Second child control is offset by column width and horizontal separation."); + + CHECK_MESSAGE( + child_control_3->get_position().is_equal_approx(Point2(0, 27)), + "Third child control is offset by row height and vertical separation."); + + CHECK_MESSAGE( + child_control_1->get_size().is_equal_approx(Size2(15, 20)), + "First child control fills the first grid cell dimensions."); + + CHECK_MESSAGE( + child_control_2->get_size().is_equal_approx(Size2(30, 20)), + "Second child control fills the second grid cell dimensions."); + + CHECK_MESSAGE( + child_control_3->get_size().is_equal_approx(Size2(15, 40)), + "Third child control fills the second-row first-column cell dimensions."); + + memdelete(child_control_3); + memdelete(child_control_2); + memdelete(child_control_1); + memdelete(grid_container); +} + +TEST_CASE("[SceneTree][GridContainer] Expanding rows and columns") { + GridContainer *grid_container = memnew(GridContainer); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(grid_container); + + Control *child_control_1 = memnew(Control); + Control *child_control_2 = memnew(Control); + Control *child_control_3 = memnew(Control); + child_control_1->set_custom_minimum_size(Size2(20, 20)); + child_control_2->set_custom_minimum_size(Size2(20, 20)); + child_control_3->set_custom_minimum_size(Size2(20, 20)); + child_control_1->set_h_size_flags(Control::SIZE_EXPAND_FILL); + child_control_1->set_v_size_flags(Control::SIZE_EXPAND_FILL); + + grid_container->set_columns(2); + grid_container->add_theme_constant_override("h_separation", 0); + grid_container->add_theme_constant_override("v_separation", 0); + grid_container->set_size(Size2(100, 100)); + grid_container->add_child(child_control_1); + grid_container->add_child(child_control_2); + grid_container->add_child(child_control_3); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_1->get_size().is_equal_approx(Size2(80, 80)), + "Expanded child control grows with its expanded column and row."); + + CHECK_MESSAGE( + child_control_2->get_size().is_equal_approx(Size2(20, 80)), + "Non-expanded column still receives expanded row height."); + + CHECK_MESSAGE( + child_control_3->get_size().is_equal_approx(Size2(80, 20)), + "Non-expanded row still receives expanded column width."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(80, 0)), + "Second child control is positioned after expanded first column width."); + + CHECK_MESSAGE( + child_control_3->get_position().is_equal_approx(Point2(0, 80)), + "Third child control is positioned after expanded first row height."); + + memdelete(child_control_3); + memdelete(child_control_2); + memdelete(child_control_1); + memdelete(grid_container); +} + +TEST_CASE("[SceneTree][GridContainer] RTL placement") { + GridContainer *grid_container = memnew(GridContainer); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(grid_container); + + Control *child_control_1 = memnew(Control); + Control *child_control_2 = memnew(Control); + child_control_1->set_custom_minimum_size(Size2(20, 20)); + child_control_2->set_custom_minimum_size(Size2(20, 20)); + + grid_container->set_columns(2); + grid_container->add_theme_constant_override("h_separation", 0); + grid_container->add_theme_constant_override("v_separation", 0); + grid_container->set_size(Size2(40, 20)); + grid_container->add_child(child_control_1); + grid_container->add_child(child_control_2); + grid_container->set_layout_direction(Control::LAYOUT_DIRECTION_RTL); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control_1->get_position().is_equal_approx(Point2(20, 0)), + "First child control starts in the right column in RTL mode."); + + CHECK_MESSAGE( + child_control_2->get_position().is_equal_approx(Point2(0, 0)), + "Second child control is placed in the left column in RTL mode."); + + memdelete(child_control_2); + memdelete(child_control_1); + memdelete(grid_container); +} + +} // namespace TestGridContainer diff --git a/tests/scene/test_margin_container.cpp b/tests/scene/test_margin_container.cpp new file mode 100644 index 0000000000..4f7d70fe55 --- /dev/null +++ b/tests/scene/test_margin_container.cpp @@ -0,0 +1,146 @@ +/**************************************************************************/ +/* test_margin_container.cpp */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#include "tests/test_macros.h" + +TEST_FORCE_LINK(test_margin_container) + +#include "scene/gui/control.h" +#include "scene/gui/margin_container.h" +#include "scene/main/scene_tree.h" +#include "scene/main/window.h" + +namespace TestMarginContainer { + +TEST_CASE("[SceneTree][MarginContainer] Default margins and layout") { + MarginContainer *margin_container = memnew(MarginContainer); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(margin_container); + + Control *child_control = memnew(Control); + child_control->set_custom_minimum_size(Size2(20, 10)); + margin_container->add_child(child_control); + + margin_container->set_size(Size2(100, 80)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + margin_container->get_margin_size(SIDE_LEFT) == 0, + "Left margin is 0 by default."); + + CHECK_MESSAGE( + margin_container->get_margin_size(SIDE_TOP) == 0, + "Top margin is 0 by default."); + + CHECK_MESSAGE( + margin_container->get_margin_size(SIDE_RIGHT) == 0, + "Right margin is 0 by default."); + + CHECK_MESSAGE( + margin_container->get_margin_size(SIDE_BOTTOM) == 0, + "Bottom margin is 0 by default."); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(0, 0)), + "Child control starts at the top-left corner when all margins are 0."); + + CHECK_MESSAGE( + child_control->get_size().is_equal_approx(Size2(100, 80)), + "Child control fills the container area when all margins are 0."); + + CHECK_MESSAGE( + margin_container->get_minimum_size().is_equal_approx(Size2(20, 10)), + "Minimum size equals the child minimum size when all margins are 0."); + + memdelete(child_control); + memdelete(margin_container); +} + +TEST_CASE("[SceneTree][MarginContainer] Custom margins affect layout and minimum size") { + MarginContainer *margin_container = memnew(MarginContainer); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(margin_container); + + margin_container->add_theme_constant_override("margin_left", 10); + margin_container->add_theme_constant_override("margin_top", 20); + margin_container->add_theme_constant_override("margin_right", 30); + margin_container->add_theme_constant_override("margin_bottom", 40); + + Control *child_control = memnew(Control); + child_control->set_custom_minimum_size(Size2(20, 10)); + margin_container->add_child(child_control); + + margin_container->set_size(Size2(200, 150)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(10, 20)), + "Child control is offset by left and top margins."); + + CHECK_MESSAGE( + child_control->get_size().is_equal_approx(Size2(160, 90)), + "Child control size is reduced by left/right and top/bottom margins."); + + CHECK_MESSAGE( + margin_container->get_minimum_size().is_equal_approx(Size2(60, 70)), + "Minimum size equals child minimum size plus all configured margins."); + + memdelete(child_control); + memdelete(margin_container); +} + +TEST_CASE("[SceneTree][MarginContainer] Multiple children use maximum minimum size") { + MarginContainer *margin_container = memnew(MarginContainer); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(margin_container); + + margin_container->add_theme_constant_override("margin_left", 1); + margin_container->add_theme_constant_override("margin_top", 2); + margin_container->add_theme_constant_override("margin_right", 3); + margin_container->add_theme_constant_override("margin_bottom", 4); + + Control *child_control_1 = memnew(Control); + Control *child_control_2 = memnew(Control); + child_control_1->set_custom_minimum_size(Size2(40, 10)); + child_control_2->set_custom_minimum_size(Size2(20, 50)); + margin_container->add_child(child_control_1); + margin_container->add_child(child_control_2); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + margin_container->get_minimum_size().is_equal_approx(Size2(44, 56)), + "Minimum size uses maximum child minimum width/height plus margins."); + + memdelete(child_control_2); + memdelete(child_control_1); + memdelete(margin_container); +} + +} // namespace TestMarginContainer diff --git a/tests/scene/test_panel_container.cpp b/tests/scene/test_panel_container.cpp new file mode 100644 index 0000000000..47a509b226 --- /dev/null +++ b/tests/scene/test_panel_container.cpp @@ -0,0 +1,114 @@ +/**************************************************************************/ +/* test_panel_container.cpp */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#include "tests/test_macros.h" + +TEST_FORCE_LINK(test_panel_container) + +#include "scene/gui/control.h" +#include "scene/gui/panel_container.h" +#include "scene/main/scene_tree.h" +#include "scene/main/window.h" +#include "scene/resources/style_box.h" + +namespace TestPanelContainer { + +TEST_CASE("[SceneTree][PanelContainer] Default properties") { + PanelContainer *panel_container = memnew(PanelContainer); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(panel_container); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + panel_container->get_mouse_filter() == Control::MOUSE_FILTER_STOP, + "PanelContainer mouse filter is set to MOUSE_FILTER_STOP by default."); + + memdelete(panel_container); +} + +TEST_CASE("[SceneTree][PanelContainer] StyleBox affects layout and minimum size") { + PanelContainer *panel_container = memnew(PanelContainer); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(panel_container); + + Ref panel_style = memnew(StyleBoxEmpty); + panel_style->set_content_margin_individual(5, 6, 7, 8); + panel_container->add_theme_style_override("panel", panel_style); + + Control *child_control = memnew(Control); + child_control->set_custom_minimum_size(Size2(20, 10)); + panel_container->add_child(child_control); + + panel_container->set_size(Size2(100, 80)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + child_control->get_position().is_equal_approx(Point2(5, 6)), + "Child control is offset by the panel StyleBox margins."); + + CHECK_MESSAGE( + child_control->get_size().is_equal_approx(Size2(88, 66)), + "Child control size is reduced by the panel StyleBox minimum size."); + + CHECK_MESSAGE( + panel_container->get_minimum_size().is_equal_approx(Size2(32, 24)), + "Minimum size equals child minimum size plus panel StyleBox minimum size."); + + memdelete(child_control); + memdelete(panel_container); +} + +TEST_CASE("[SceneTree][PanelContainer] Multiple children use maximum minimum size") { + PanelContainer *panel_container = memnew(PanelContainer); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(panel_container); + + Ref panel_style = memnew(StyleBoxEmpty); + panel_style->set_content_margin_individual(2, 3, 4, 5); + panel_container->add_theme_style_override("panel", panel_style); + + Control *child_control_1 = memnew(Control); + Control *child_control_2 = memnew(Control); + child_control_1->set_custom_minimum_size(Size2(40, 10)); + child_control_2->set_custom_minimum_size(Size2(20, 50)); + panel_container->add_child(child_control_1); + panel_container->add_child(child_control_2); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + panel_container->get_minimum_size().is_equal_approx(Size2(46, 58)), + "Minimum size uses maximum child minimum width/height plus panel StyleBox minimum size."); + + memdelete(child_control_2); + memdelete(child_control_1); + memdelete(panel_container); +} + +} // namespace TestPanelContainer diff --git a/tests/scene/test_scroll_container.cpp b/tests/scene/test_scroll_container.cpp new file mode 100644 index 0000000000..c643bf55d3 --- /dev/null +++ b/tests/scene/test_scroll_container.cpp @@ -0,0 +1,475 @@ +/**************************************************************************/ +/* test_scroll_container.cpp */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#include "tests/test_macros.h" + +TEST_FORCE_LINK(test_scroll_container) + +#include "scene/gui/control.h" +#include "scene/gui/scroll_container.h" +#include "scene/main/scene_tree.h" +#include "scene/main/window.h" + +namespace TestScrollContainer { + +TEST_CASE("[SceneTree][ScrollContainer] Default behavior") { + ScrollContainer *test_container = memnew(ScrollContainer); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(test_container); + + Control *test_control = memnew(Control); + test_control->set_custom_minimum_size(Size2(20, 20)); + test_container->add_child(test_control); + + test_container->set_size(Size2(100, 100)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + test_container->get_horizontal_scroll_mode() == ScrollContainer::SCROLL_MODE_AUTO, + "Horizontal scroll mode is SCROLL_MODE_AUTO by default."); + + CHECK_MESSAGE( + test_container->get_vertical_scroll_mode() == ScrollContainer::SCROLL_MODE_AUTO, + "Vertical scroll mode is SCROLL_MODE_AUTO by default."); + + CHECK_MESSAGE( + test_container->get_scroll_hint_mode() == ScrollContainer::SCROLL_HINT_MODE_DISABLED, + "Scroll hint mode is SCROLL_HINT_MODE_DISABLED by default."); + + CHECK_MESSAGE( + !test_container->is_scroll_hint_tiled(), + "tile_scroll_hint is disabled by default."); + + CHECK_MESSAGE( + !test_container->is_following_focus(), + "follow_focus is disabled by default."); + + CHECK_MESSAGE( + !test_container->is_scroll_horizontal_by_default(), + "scroll_horizontal_by_default is disabled by default."); + + CHECK_MESSAGE( + !test_container->get_draw_focus_border(), + "draw_focus_border is disabled by default."); + + CHECK_MESSAGE( + test_container->is_clipping_contents(), + "clip_contents is enabled by default."); + + CHECK_MESSAGE( + test_container->get_h_scroll() == 0, + "Horizontal scroll starts at 0 by default."); + + CHECK_MESSAGE( + test_container->get_v_scroll() == 0, + "Vertical scroll starts at 0 by default."); + + CHECK_MESSAGE( + test_container->get_h_scroll_bar() != nullptr, + "Horizontal scrollbar is created by default."); + + CHECK_MESSAGE( + test_container->get_v_scroll_bar() != nullptr, + "Vertical scrollbar is created by default."); + + CHECK_MESSAGE( + !test_container->get_h_scroll_bar()->is_visible(), + "Horizontal scrollbar is hidden in auto mode when content fits."); + + CHECK_MESSAGE( + !test_container->get_v_scroll_bar()->is_visible(), + "Vertical scrollbar is hidden in auto mode when content fits."); + + memdelete(test_control); + memdelete(test_container); +} + +TEST_CASE("[SceneTree][ScrollContainer] Scroll modes") { + ScrollContainer *test_container = memnew(ScrollContainer); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(test_container); + Control *test_control = memnew(Control); + test_container->add_child(test_control); + test_control->set_custom_minimum_size(Size2(100, 100)); + Control *target_control = memnew(Control); + target_control->set_custom_minimum_size(Size2(20, 20)); + target_control->set_position(Point2(80, 80)); + target_control->set_size(Size2(20, 20)); + test_control->add_child(target_control); + + test_container->set_size(Size2(50, 50)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + test_container->get_h_scroll_bar()->is_visible(), + "H Scrollbar displays when ScrollContainer width is smaller than contents width with SCROLL_MODE_AUTO."); + + CHECK_MESSAGE( + test_container->get_v_scroll_bar()->is_visible(), + "V Scrollbar displays when ScrollContainer height is smaller than contents height with SCROLL_MODE_AUTO."); + + test_container->set_size(Size2(150, 150)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + !test_container->get_h_scroll_bar()->is_visible(), + "H Scrollbar does not display when ScrollContainer width is larger than contents width with SCROLL_MODE_AUTO."); + + CHECK_MESSAGE( + !test_container->get_v_scroll_bar()->is_visible(), + "V Scrollbar does not display when ScrollContainer height is larger than contents height with SCROLL_MODE_AUTO."); + + test_container->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); + test_container->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); + + test_container->set_size(Size2(50, 50)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + !test_container->get_h_scroll_bar()->is_visible(), + "H Scrollbar does not display when ScrollContainer width is smaller than contents width with SCROLL_MODE_DISABLED."); + + CHECK_MESSAGE( + !test_container->get_v_scroll_bar()->is_visible(), + "V Scrollbar does not display when ScrollContainer height is smaller than contents height with SCROLL_MODE_DISABLED."); + + test_container->set_h_scroll(0); + test_container->set_v_scroll(0); + int disabled_h_scroll_before = test_container->get_h_scroll(); + int disabled_v_scroll_before = test_container->get_v_scroll(); + test_container->ensure_control_visible(target_control); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + test_container->get_h_scroll() == disabled_h_scroll_before, + "SCROLL_MODE_DISABLED does not change horizontal scroll when ensure_control_visible() targets an out-of-view descendant."); + + CHECK_MESSAGE( + test_container->get_v_scroll() == disabled_v_scroll_before, + "SCROLL_MODE_DISABLED does not change vertical scroll when ensure_control_visible() targets an out-of-view descendant."); + + test_container->set_h_scroll(0); + test_container->set_v_scroll(0); + + test_container->set_size(Size2(150, 150)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + !test_container->get_h_scroll_bar()->is_visible(), + "H Scrollbar does not display when ScrollContainer width is larger than contents width with SCROLL_MODE_DISABLED."); + + CHECK_MESSAGE( + !test_container->get_v_scroll_bar()->is_visible(), + "V Scrollbar does not display when ScrollContainer height is larger than contents height with SCROLL_MODE_DISABLED."); + + test_container->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_SHOW_ALWAYS); + test_container->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_SHOW_ALWAYS); + + test_container->set_size(Size2(50, 50)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + test_container->get_h_scroll_bar()->is_visible(), + "H Scrollbar displays when ScrollContainer width is smaller than contents width with SCROLL_MODE_SHOW_ALWAYS."); + + CHECK_MESSAGE( + test_container->get_v_scroll_bar()->is_visible(), + "V Scrollbar displays when ScrollContainer height is smaller than contents height with SCROLL_MODE_SHOW_ALWAYS."); + + test_container->set_size(Size2(150, 150)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + test_container->get_h_scroll_bar()->is_visible(), + "H Scrollbar displays when ScrollContainer width is larger than contents width with SCROLL_MODE_SHOW_ALWAYS."); + + CHECK_MESSAGE( + test_container->get_v_scroll_bar()->is_visible(), + "V Scrollbar displays when ScrollContainer height is larger than contents height with SCROLL_MODE_SHOW_ALWAYS."); + + test_container->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_SHOW_NEVER); + test_container->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_SHOW_NEVER); + + test_container->set_size(Size2(50, 50)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + !test_container->get_h_scroll_bar()->is_visible(), + "H Scrollbar does not display when ScrollContainer width is smaller than contents width with SCROLL_MODE_SHOW_NEVER."); + + CHECK_MESSAGE( + !test_container->get_v_scroll_bar()->is_visible(), + "V Scrollbar does not display when ScrollContainer height is smaller than contents height with SCROLL_MODE_SHOW_NEVER."); + + test_container->set_h_scroll(0); + test_container->set_v_scroll(0); + int show_never_h_scroll_before = test_container->get_h_scroll(); + int show_never_v_scroll_before = test_container->get_v_scroll(); + test_container->ensure_control_visible(target_control); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + test_container->get_h_scroll() > show_never_h_scroll_before, + "SCROLL_MODE_SHOW_NEVER keeps horizontal scrollbar hidden while still allowing programmatic scrolling."); + + CHECK_MESSAGE( + test_container->get_v_scroll() > show_never_v_scroll_before, + "SCROLL_MODE_SHOW_NEVER keeps vertical scrollbar hidden while still allowing programmatic scrolling."); + + test_container->set_h_scroll(0); + test_container->set_v_scroll(0); + + test_container->set_size(Size2(150, 150)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + !test_container->get_h_scroll_bar()->is_visible(), + "H Scrollbar does not display when ScrollContainer width is larger than contents width with SCROLL_MODE_SHOW_NEVER."); + + CHECK_MESSAGE( + !test_container->get_v_scroll_bar()->is_visible(), + "V Scrollbar does not display when ScrollContainer height is larger than contents height with SCROLL_MODE_SHOW_NEVER."); + + test_container->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_RESERVE); + test_container->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_RESERVE); + + test_container->set_size(Size2(50, 50)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + test_container->get_h_scroll_bar()->is_visible(), + "H Scrollbar displays when ScrollContainer width is smaller than contents width with SCROLL_MODE_RESERVE."); + + CHECK_MESSAGE( + test_container->get_v_scroll_bar()->is_visible(), + "V Scrollbar displays when ScrollContainer height is smaller than contents height with SCROLL_MODE_RESERVE."); + + test_container->set_size(Size2(150, 150)); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + !test_container->get_h_scroll_bar()->is_visible(), + "H Scrollbar does not display when ScrollContainer width is larger than contents width with SCROLL_MODE_RESERVE."); + + CHECK_MESSAGE( + !test_container->get_v_scroll_bar()->is_visible(), + "V Scrollbar does not display when ScrollContainer height is larger than contents height with SCROLL_MODE_RESERVE."); + + memdelete(test_control); + memdelete(test_container); +} + +TEST_CASE("[SceneTree][ScrollContainer] Scroll values and property setters") { + ScrollContainer *test_container = memnew(ScrollContainer); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(test_container); + + Control *test_control = memnew(Control); + test_control->set_custom_minimum_size(Size2(300, 300)); + test_container->add_child(test_control); + + test_container->set_size(Size2(100, 100)); + SceneTree::get_singleton()->process(0); + + test_container->set_h_scroll(25); + test_container->set_v_scroll(35); + + CHECK_MESSAGE( + test_container->get_h_scroll() == 25, + "Horizontal scroll value is updated by set_h_scroll()."); + + CHECK_MESSAGE( + test_container->get_v_scroll() == 35, + "Vertical scroll value is updated by set_v_scroll()."); + + test_container->set_horizontal_custom_step(7.0f); + test_container->set_vertical_custom_step(9.0f); + + CHECK_MESSAGE( + Math::is_equal_approx(test_container->get_horizontal_custom_step(), 7.0f), + "Horizontal custom step is updated correctly."); + + CHECK_MESSAGE( + Math::is_equal_approx(test_container->get_vertical_custom_step(), 9.0f), + "Vertical custom step is updated correctly."); + + test_container->set_deadzone(13); + CHECK_MESSAGE( + test_container->get_deadzone() == 13, + "Deadzone value is updated correctly."); + + test_container->set_scroll_horizontal_by_default(true); + CHECK_MESSAGE( + test_container->is_scroll_horizontal_by_default(), + "scroll_horizontal_by_default is enabled correctly."); + + test_container->set_follow_focus(true); + CHECK_MESSAGE( + test_container->is_following_focus(), + "follow_focus is enabled correctly."); + + test_container->set_scroll_hint_mode(ScrollContainer::SCROLL_HINT_MODE_ALL); + CHECK_MESSAGE( + test_container->get_scroll_hint_mode() == ScrollContainer::SCROLL_HINT_MODE_ALL, + "Scroll hint mode is updated correctly."); + + test_container->set_tile_scroll_hint(true); + CHECK_MESSAGE( + test_container->is_scroll_hint_tiled(), + "Tiled scroll hint mode is enabled correctly."); + + test_container->set_draw_focus_border(true); + CHECK_MESSAGE( + test_container->get_draw_focus_border(), + "draw_focus_border is enabled correctly."); + + memdelete(test_control); + memdelete(test_container); +} + +TEST_CASE("[SceneTree][ScrollContainer] ensure_control_visible and follow_focus") { + ScrollContainer *test_container = memnew(ScrollContainer); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(test_container); + + Control *content_control = memnew(Control); + content_control->set_custom_minimum_size(Size2(300, 300)); + test_container->add_child(content_control); + + Control *target_control = memnew(Control); + target_control->set_focus_mode(Control::FOCUS_ALL); + target_control->set_custom_minimum_size(Size2(20, 20)); + target_control->set_position(Point2(260, 250)); + target_control->set_size(Size2(20, 20)); + content_control->add_child(target_control); + + test_container->set_size(Size2(100, 100)); + test_container->set_h_scroll(0); + test_container->set_v_scroll(0); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + test_container->get_h_scroll() == 0, + "Horizontal scroll starts at 0 for this setup."); + + CHECK_MESSAGE( + test_container->get_v_scroll() == 0, + "Vertical scroll starts at 0 for this setup."); + + test_container->ensure_control_visible(target_control); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + test_container->get_h_scroll() > 0, + "ensure_control_visible() scrolls horizontally when target control is outside the visible area."); + + CHECK_MESSAGE( + test_container->get_v_scroll() > 0, + "ensure_control_visible() scrolls vertically when target control is outside the visible area."); + + Rect2 target_rect(target_control->get_position(), target_control->get_size()); + real_t side_margin = test_container->get_v_scroll_bar()->is_visible() ? test_container->get_v_scroll_bar()->get_size().x : 0.0f; + real_t bottom_margin = test_container->get_h_scroll_bar()->is_visible() ? test_container->get_h_scroll_bar()->get_size().y : 0.0f; + real_t visible_left = test_container->get_h_scroll(); + real_t visible_top = test_container->get_v_scroll(); + real_t visible_right = visible_left + test_container->get_size().x - side_margin; + real_t visible_bottom = visible_top + test_container->get_size().y - bottom_margin; + + CHECK_MESSAGE( + (target_rect.position.x >= visible_left && (target_rect.position.x + target_rect.size.x) <= visible_right), + "ensure_control_visible() fully reveals target control horizontally."); + + CHECK_MESSAGE( + (target_rect.position.y >= visible_top && (target_rect.position.y + target_rect.size.y) <= visible_bottom), + "ensure_control_visible() fully reveals target control vertically."); + + test_container->set_h_scroll(0); + test_container->set_v_scroll(0); + test_container->set_follow_focus(true); + SceneTree::get_singleton()->process(0); + + target_control->grab_focus(); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + test_container->get_h_scroll() > 0, + "follow_focus scrolls horizontally to focused descendants."); + + CHECK_MESSAGE( + test_container->get_v_scroll() > 0, + "follow_focus scrolls vertically to focused descendants."); + + visible_left = test_container->get_h_scroll(); + visible_top = test_container->get_v_scroll(); + visible_right = visible_left + test_container->get_size().x - side_margin; + visible_bottom = visible_top + test_container->get_size().y - bottom_margin; + + CHECK_MESSAGE( + (target_rect.position.x >= visible_left && (target_rect.position.x + target_rect.size.x) <= visible_right), + "follow_focus fully reveals focused target control horizontally."); + + CHECK_MESSAGE( + (target_rect.position.y >= visible_top && (target_rect.position.y + target_rect.size.y) <= visible_bottom), + "follow_focus fully reveals focused target control vertically."); + + memdelete(target_control); + memdelete(content_control); + memdelete(test_container); +} + +TEST_CASE("[SceneTree][ScrollContainer] Configuration warnings") { + ScrollContainer *test_container = memnew(ScrollContainer); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(test_container); + + CHECK_MESSAGE( + test_container->get_configuration_warnings().size() == 1, + "ScrollContainer reports one configuration warning when it has no content child."); + + Control *child_control_1 = memnew(Control); + Control *child_control_2 = memnew(Control); + test_container->add_child(child_control_1); + + CHECK_MESSAGE( + test_container->get_configuration_warnings().is_empty(), + "ScrollContainer reports no configuration warning when it has exactly one content child."); + + test_container->add_child(child_control_2); + + CHECK_MESSAGE( + test_container->get_configuration_warnings().size() == 1, + "ScrollContainer reports one configuration warning when it has more than one content child."); + + memdelete(child_control_2); + memdelete(child_control_1); + memdelete(test_container); +} + +} // namespace TestScrollContainer