Modules/Widgets/tests/CollapsibleTests.cpp
2026-10-15 18:11:50 +03:00

201 lines
6.4 KiB
C++

#include "Collapsible.hpp"
#include "Slider.hpp"
#include "UiDriver.hpp"
#include "BoxLayout.hpp"
#include "UnitTest++/UnitTest++.h"
using namespace tp;
using namespace tp::ui;
using namespace tp::ui::testing;
namespace {
struct CollapsibleScene {
Widget root;
Collapsible section{ "Shape" };
LabeledSlider roundness{ "Roundness" };
LabeledSlider angle{ "Angle", 0, 360 };
Label after{ "After" };
CollapsibleScene() {
root.emplaceLayout<ColumnLayout>(4).setPadding(Insets::uniform(8));
root.setSizing(AxisSizing::fixed(300), AxisSizing::fit());
roundness.setSizing(AxisSizing::fill(), AxisSizing::fit());
angle.setSizing(AxisSizing::fill(), AxisSizing::fit());
section.body().addChild(roundness);
section.body().addChild(angle);
root.addChild(section);
root.addChild(after);
}
};
}
SUITE(CollapsibleWidget) {
TEST(StartsCollapsedWithBodyHidden) {
CollapsibleScene scene;
UiDriver driver(scene.root, { 400, 400 });
CHECK(!scene.section.isExpanded());
CHECK(!scene.section.isBodyShown());
CHECK_CLOSE(scene.section.header().size().y, scene.section.size().y, 0.5f);
}
TEST(ClickingTheHeaderExpandsAndCollapses) {
CollapsibleScene scene;
UiDriver driver(scene.root, { 400, 400 });
std::vector<bool> toggles;
scene.section.setOnToggled([&toggles](bool expanded) { toggles.push_back(expanded); });
const halnf collapsedAfter = scene.after.windowRect().pos.y;
driver.click(scene.section.header());
CHECK(scene.section.isExpanded());
CHECK(scene.section.isBodyShown());
CHECK(scene.after.windowRect().pos.y > collapsedAfter + 40);
driver.settle();
CHECK_CLOSE(1, scene.section.header().chevronTurn(), 0.01f);
driver.click(scene.section.header());
CHECK(!scene.section.isExpanded());
CHECK_CLOSE(collapsedAfter, scene.after.windowRect().pos.y, 0.5f);
REQUIRE CHECK_EQUAL(2u, toggles.size());
CHECK(toggles[0] && !toggles[1]);
}
TEST(ExpandedSlidersFillTheWidth) {
CollapsibleScene scene;
UiDriver driver(scene.root, { 400, 400 });
scene.section.setExpanded(true, false);
driver.layout();
const RectF body = scene.section.body().windowRect();
CHECK_CLOSE(scene.section.windowRect().size.x, body.size.x, 0.5f);
CHECK(scene.roundness.windowRect().size.x > 230);
CHECK_CLOSE(
body.pos.x + body.size.x, scene.roundness.windowRect().pos.x + scene.roundness.windowRect().size.x, 0.5f
);
}
TEST(OpeningRevealsTheBodyWithoutOverlappingTheNextWidget) {
CollapsibleScene scene;
UiDriver driver(scene.root, { 400, 400 });
driver.setAutoSettle(false);
driver.click(scene.section.header());
CHECK(scene.section.clipsChildren());
bool grew = false;
halnf lastBottom = 0;
for (int frame = 0; frame < 40; frame++) {
driver.layout();
const RectF section = scene.section.windowRect();
const halnf bottom = section.pos.y + section.size.y;
CHECK(scene.after.windowRect().pos.y >= bottom - 0.5f);
grew = grew || (frame > 0 && bottom > lastBottom + 0.5f);
lastBottom = bottom;
driver.advance(16);
}
CHECK(grew);
driver.settle();
CHECK(!scene.section.isGeometryMoving());
const RectF angle = scene.angle.windowRect();
CHECK(angle.pos.y + angle.size.y <= scene.section.windowRect().pos.y + scene.section.windowRect().size.y + 0.5f);
}
TEST(OpeningAndClosingAnimateTheHeightOverSeveralFrames) {
CollapsibleScene scene;
UiDriver driver(scene.root, { 400, 400 });
const halnf closed = scene.section.size().y;
driver.setAutoSettle(false);
driver.click(scene.section.header());
std::vector<halnf> opening;
for (int frame = 0; frame < 30 && scene.section.isTransitioning(); frame++) {
driver.advance(16);
driver.layout();
opening.push_back(scene.section.size().y);
}
REQUIRE CHECK(opening.size() >= 5);
CHECK(opening[1] > closed);
CHECK(opening[1] < opening.back() - 10);
for (size_t index = 1; index < opening.size(); index++)
CHECK(opening[index] >= opening[index - 1] - 0.5f);
const halnf open = opening.back();
CHECK(scene.section.isBodyShown());
driver.click(scene.section.header());
CHECK(scene.section.isBodyShown());
std::vector<halnf> closing;
for (int frame = 0; frame < 30 && scene.section.isTransitioning(); frame++) {
driver.advance(16);
driver.layout();
closing.push_back(scene.section.size().y);
}
driver.layout();
REQUIRE CHECK(closing.size() >= 5);
CHECK(closing[1] < open);
CHECK(closing[1] > closed + 10);
CHECK_CLOSE(closed, scene.section.size().y, 0.5f);
CHECK(!scene.section.isBodyShown());
}
TEST(SlidersInsideStayUsable) {
CollapsibleScene scene;
UiDriver driver(scene.root, { 400, 400 });
driver.click(scene.section.header());
driver.setSlider(scene.angle.slider(), 90);
CHECK_CLOSE(90, scene.angle.value(), 3);
}
TEST(SummaryShowsOnTheHeader) {
CollapsibleScene scene;
scene.section.header().setSummary("45°");
UiDriver driver(scene.root, { 400, 400 });
CHECK(driver.paint().hasText("Shape"));
CHECK(driver.paint().hasText("45°"));
}
TEST(KeyboardActivationToggles) {
CollapsibleScene scene;
UiDriver driver(scene.root, { 400, 400 });
driver.ui().setKeyboardFocus(&scene.section.header());
driver.key(InputID::SPACE);
CHECK(scene.section.isExpanded());
}
}
SUITE(CollapsibleTransitions) {
TEST(BodyNeverPaintsOverTheHeaderWhileOpening) {
CollapsibleScene scene;
UiDriver driver(scene.root, { 400, 400 });
driver.setAutoSettle(false);
driver.click(scene.section.header());
for (int frame = 0; frame < 30; frame++) {
driver.advance(16);
driver.layout();
const RectF header = scene.section.header().windowRect();
const halnf headerBottom = header.pos.y + header.size.y;
CHECK(driver.ui().widgetAt(header.center()) == &scene.section.header());
const RectF visibleRoundness = scene.roundness.windowRect();
if (visibleRoundness.pos.y < headerBottom) {
const Widget* hit = driver.ui().widgetAt({ visibleRoundness.center().x, headerBottom - 2 });
CHECK(hit == &scene.section.header());
}
}
}
TEST(TappingTheHeaderMidTransitionNeverReachesTheItems) {
CollapsibleScene scene;
UiDriver driver(scene.root, { 400, 400 });
int changes = 0;
scene.roundness.setOnChanged([&changes](halnf) { changes++; });
driver.setAutoSettle(false);
driver.click(scene.section.header());
driver.advance(48);
driver.layout();
driver.click(scene.section.header());
driver.advance(48);
driver.layout();
driver.click(scene.section.header());
CHECK_EQUAL(0, changes);
CHECK(scene.section.isExpanded());
}
}