Skip to content

Commit c83afff

Browse files
committed
tests: add several unit tests for text layout
1 parent 265831b commit c83afff

1 file changed

Lines changed: 121 additions & 39 deletions

File tree

tests/unit/elements/Text.cpp

Lines changed: 121 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88
#include <layout/Positioner.hpp>
99

1010
#include "../tricks/Tricks.hpp"
11+
#include "element/Element.hpp"
12+
#include "hyprtoolkit/element/Element.hpp"
13+
#include "hyprtoolkit/element/Null.hpp"
14+
#include "hyprtoolkit/element/Rectangle.hpp"
15+
#include "hyprtoolkit/element/RowLayout.hpp"
16+
#include "hyprtoolkit/types/SizeType.hpp"
1117

1218
using namespace Hyprtoolkit;
1319
using namespace Hyprutils::Math;
@@ -25,60 +31,136 @@ TEST(Element, text) {
2531
text.reset();
2632
}
2733

28-
// an auto label gets no room hint and its box tracks its own clamped preferred, so the box feeds
29-
// back into the next layout. a transient narrow box on first layout (e.g. before an async icon
30-
// sibling reflows the row) must not lock the text ellipsized: once the box equals the clamped
31-
// preferred the clamp has to release and the text recover. #94 compared the box against a cached
32-
// natural size, so the box-feedback condition stayed true and the ellipsis locked forever. this is
33-
// the hyprlauncher regression.
34-
TEST(Element, textEllipsizeRecoversFromTransientClamp) {
34+
TEST(Element, textPreferredSize) {
3535
Tests::Tricks::createBackendSupport();
3636

37+
auto text = CTextBuilder::begin()->text("Hello World Foo Bar Baz")->noEllipsize(true)->commence();
38+
39+
const auto NATURAL_SIZE = text->preferredSize({0, 0});
40+
EXPECT_TRUE(NATURAL_SIZE.has_value());
41+
42+
const auto WRAPPED_SIZE = text->preferredSize({ NATURAL_SIZE->x * 3 / 4, NATURAL_SIZE->y * 3 });
43+
EXPECT_TRUE(WRAPPED_SIZE.has_value());
44+
EXPECT_LE(WRAPPED_SIZE->x, NATURAL_SIZE->x * 3 / 4);
45+
EXPECT_GT(WRAPPED_SIZE->y, NATURAL_SIZE->y);
46+
47+
text->rebuild()->noEllipsize(false)->commence();
48+
const auto ELLIPSIZED_SIZE = text->preferredSize({ NATURAL_SIZE->x * 3 / 4, NATURAL_SIZE->y * 3 });
49+
EXPECT_TRUE(ELLIPSIZED_SIZE.has_value());
50+
EXPECT_LE(ELLIPSIZED_SIZE->x, NATURAL_SIZE->x * 3 / 4);
51+
EXPECT_EQ(ELLIPSIZED_SIZE->y, NATURAL_SIZE->y);
52+
53+
const auto FINAL_NATURAL_SIZE = text->preferredSize({0, 0});
54+
EXPECT_TRUE(FINAL_NATURAL_SIZE.has_value());
55+
EXPECT_EQ(FINAL_NATURAL_SIZE->x, FINAL_NATURAL_SIZE->x);
56+
EXPECT_EQ(FINAL_NATURAL_SIZE->y, FINAL_NATURAL_SIZE->y);
57+
58+
text.reset();
59+
}
60+
61+
TEST(Element, textGrowsAgainstNeighbor) {
62+
Tests::Tricks::createBackendSupport();
63+
64+
const CBox POSITION = { 0, 0, 1000, 100 };
65+
66+
auto layout = CRowLayoutBuilder::begin()->commence();
67+
auto text = CTextBuilder::begin()->text("Hello World Foo Bar Baz")->commence();
68+
auto rect = CRectangleBuilder::begin()->size({CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_ABSOLUTE, {1, 10}})->commence();
69+
rect->setGrow(true);
70+
71+
layout->addChild(text);
72+
layout->addChild(rect);
73+
74+
g_positioner->position(layout, POSITION);
75+
76+
EXPECT_EQ(rect->impl->position.width + text->impl->position.width, POSITION.width);
77+
const auto PREVIOUS_TEXT_WIDTH = text->impl->position.width;
78+
79+
text->rebuild()->text("Hello World Foo Bar Baz but longer");
80+
EXPECT_EQ(rect->impl->position.width + text->impl->position.width, POSITION.width);
81+
EXPECT_GE(text->impl->position.width, PREVIOUS_TEXT_WIDTH);
82+
83+
text.reset();
84+
rect.reset();
85+
layout.reset();
86+
}
87+
88+
TEST(Element, textGrowsAfterShrinking) {
89+
Tests::Tricks::createBackendSupport();
90+
91+
const CBox POSITION = { 0, 0, 1000, 100 };
92+
3793
auto text = CTextBuilder::begin()->text("Hello World Foo Bar Baz")->commence();
3894

39-
// natural width: a generous box, no room hint
40-
g_positioner->position(text, {{}, {1000.F, 20.F}}, {-1.F, -1.F});
41-
const float NATURAL = text->preferredSize({}).value_or(Vector2D{}).x;
42-
EXPECT_GT(NATURAL, 40.F);
95+
g_positioner->position(text, POSITION);
96+
const auto NATURAL_TEXT_WIDTH = text->impl->position.width;
4397

44-
// a transient narrow box on first layout clamps it
45-
g_positioner->position(text, {{}, {NATURAL * 0.3F, 20.F}}, {-1.F, -1.F});
46-
EXPECT_LT(text->preferredSize({}).value_or(Vector2D{}).x, NATURAL);
98+
g_positioner->position(text, CBox{0, 0, NATURAL_TEXT_WIDTH * 3 / 4, 100 });
99+
EXPECT_LT(text->impl->position.width, NATURAL_TEXT_WIDTH);
47100

48-
// from here the layout hands the auto label a box equal to its own preferred. feed that back a
49-
// few frames: it must converge back to NATURAL, not stay locked at the clamped width.
50-
for (int i = 0; i < 4; ++i) {
51-
const auto PREF = text->preferredSize({}).value_or(Vector2D{});
52-
g_positioner->position(text, {{}, PREF}, {-1.F, -1.F});
53-
}
54-
EXPECT_FLOAT_EQ(text->preferredSize({}).value_or(Vector2D{}).x, NATURAL);
101+
g_positioner->position(text, POSITION);
102+
EXPECT_EQ(text->impl->position.width, NATURAL_TEXT_WIDTH);
55103

56104
text.reset();
57105
}
58106

59-
// a stable room hint (e.g. the combobox row layout) clamps to the room and stays put, no per-frame
60-
// flicker at the fit/elide boundary.
61-
TEST(Element, textEllipsizeStableUnderRoomHint) {
107+
TEST(Element, textSideBySide) {
108+
Tests::Tricks::createBackendSupport();
109+
110+
const CBox POSITION = { 0, 0, 300, 100 };
111+
112+
auto text1 = CTextBuilder::begin()
113+
->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {0.5, 1.0}})
114+
->text("First longish paragraph goes here. I love Hyprland it is the best.")
115+
->noEllipsize(true)
116+
->commence();
117+
auto text2 = CTextBuilder::begin()
118+
->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {0.5, 1.0}})
119+
->text("Second longish paragraph goes here. Who needs GTK and Qt when you have Hyprtoolkit?")
120+
->noEllipsize(true)
121+
->commence();
122+
auto layout = CRowLayoutBuilder::begin()
123+
->commence();
124+
125+
const auto NATURAL_HEIGHT = text1->preferredSize({0, 0})->y;
126+
127+
layout->addChild(text1);
128+
layout->addChild(text2);
129+
130+
g_positioner->position(layout, POSITION);
131+
132+
ASSERT_EQ(layout->impl->position.width, POSITION.width);
133+
ASSERT_EQ(text1->impl->position.width, POSITION.width / 2);
134+
ASSERT_EQ(text2->impl->position.width, POSITION.width / 2);
135+
ASSERT_EQ(text1->impl->position.width, text2->impl->position.x);
136+
ASSERT_GT(text1->impl->position.height, NATURAL_HEIGHT);
137+
ASSERT_GT(text2->impl->position.height, NATURAL_HEIGHT);
138+
139+
text1.reset();
140+
text2.reset();
141+
layout.reset();
142+
}
143+
144+
TEST(Element, textShrinkingThenGrowing) {
62145
Tests::Tricks::createBackendSupport();
63146

64-
auto text = CTextBuilder::begin()->text("Hello World Foo Bar Baz")->commence();
65-
const float NATURAL = text->preferredSize({}).value_or(Vector2D{}).x;
66-
EXPECT_GT(NATURAL, 20.F);
147+
// This container is necessary because we need the text to auto size itself
148+
// if we position it directly, the text sizing code is never run
149+
auto container = CNullBuilder::begin()->commence();
150+
auto text = CTextBuilder::begin()
151+
->text("First longish paragraph goes here. I love Hyprland it is the best.")
152+
->commence();
67153

68-
const CBox widebox = {{}, {NATURAL, 20.F}};
69-
const Vector2D tight = {NATURAL * 0.4F, 20.F};
154+
container->addChild(text);
70155

71-
g_positioner->position(text, widebox, tight);
72-
const float CLAMPED = text->preferredSize({}).value_or(Vector2D{}).x;
73-
EXPECT_LT(CLAMPED, NATURAL);
156+
const auto NATURAL_WIDTH = text->preferredSize({0, 0})->x;
74157

75-
// same room again does not flip the size
76-
g_positioner->position(text, widebox, tight);
77-
EXPECT_FLOAT_EQ(text->preferredSize({}).value_or(Vector2D{}).x, CLAMPED);
158+
g_positioner->position(container, { 0, 0, NATURAL_WIDTH * 3 / 4, 100 });
159+
ASSERT_LE(text->impl->position.width, NATURAL_WIDTH * 3 / 4);
78160

79-
// room grows back: recover to full
80-
g_positioner->position(text, widebox, {NATURAL + 80.F, 20.F});
81-
EXPECT_FLOAT_EQ(text->preferredSize({}).value_or(Vector2D{}).x, NATURAL);
161+
g_positioner->position(container, { 0, 0, NATURAL_WIDTH * 2, 100 });
162+
ASSERT_EQ(text->impl->position.width, NATURAL_WIDTH);
82163

164+
container.reset();
83165
text.reset();
84-
}
166+
}

0 commit comments

Comments
 (0)