Skip to content

Commit 4b0199b

Browse files
committed
tests: add several unit tests for text layout
1 parent 5e58a5b commit 4b0199b

1 file changed

Lines changed: 171 additions & 40 deletions

File tree

tests/unit/elements/Text.cpp

Lines changed: 171 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@
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/FontTypes.hpp"
17+
#include "hyprtoolkit/types/SizeType.hpp"
1118

1219
using namespace Hyprtoolkit;
1320
using namespace Hyprutils::Math;
@@ -25,60 +32,184 @@ TEST(Element, text) {
2532
text.reset();
2633
}
2734

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) {
35+
TEST(Element, textPreferredSize) {
3536
Tests::Tricks::createBackendSupport();
3637

37-
auto text = CTextBuilder::begin()->text("Hello World Foo Bar Baz")->commence();
38+
auto text = CTextBuilder::begin()->text("Hello World Foo Bar Baz")->noEllipsize(true)->commence();
3839

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);
40+
const auto NATURAL_SIZE = text->preferredSize({0, 0});
41+
EXPECT_TRUE(NATURAL_SIZE.has_value());
4342

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);
43+
const auto CLAMPED_WIDTH = std::floor(NATURAL_SIZE->x * 3 / 4);
4744

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);
45+
const auto WRAPPED_SIZE = text->preferredSize({CLAMPED_WIDTH, NATURAL_SIZE->y * 3});
46+
EXPECT_TRUE(WRAPPED_SIZE.has_value());
47+
EXPECT_LE(WRAPPED_SIZE->x, CLAMPED_WIDTH);
48+
EXPECT_GT(WRAPPED_SIZE->y, NATURAL_SIZE->y);
49+
50+
text->rebuild()->noEllipsize(false)->commence();
51+
const auto ELLIPSIZED_SIZE = text->preferredSize({CLAMPED_WIDTH, NATURAL_SIZE->y * 3});
52+
EXPECT_TRUE(ELLIPSIZED_SIZE.has_value());
53+
EXPECT_LE(ELLIPSIZED_SIZE->x, CLAMPED_WIDTH);
54+
EXPECT_EQ(ELLIPSIZED_SIZE->y, NATURAL_SIZE->y);
55+
56+
const auto FINAL_NATURAL_SIZE = text->preferredSize({0, 0});
57+
EXPECT_TRUE(FINAL_NATURAL_SIZE.has_value());
58+
EXPECT_EQ(FINAL_NATURAL_SIZE->x, NATURAL_SIZE->x);
59+
EXPECT_EQ(FINAL_NATURAL_SIZE->y, NATURAL_SIZE->y);
5560

5661
text.reset();
5762
}
5863

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) {
64+
TEST(Element, textGrowsAgainstNeighbor) {
6265
Tests::Tricks::createBackendSupport();
6366

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);
67+
const CBox POSITION = {0, 0, 1000, 100};
68+
69+
auto layout = CRowLayoutBuilder::begin()->commence();
70+
auto text = CTextBuilder::begin()->text("Hello World Foo Bar Baz")->commence();
71+
auto rect = CRectangleBuilder::begin()->size({CDynamicSize::HT_SIZE_AUTO, CDynamicSize::HT_SIZE_ABSOLUTE, {1, 10}})->commence();
72+
rect->setGrow(true);
73+
74+
layout->addChild(text);
75+
layout->addChild(rect);
6776

68-
const CBox widebox = {{}, {NATURAL, 20.F}};
69-
const Vector2D tight = {NATURAL * 0.4F, 20.F};
77+
g_positioner->position(layout, POSITION);
7078

71-
g_positioner->position(text, widebox, tight);
72-
const float CLAMPED = text->preferredSize({}).value_or(Vector2D{}).x;
73-
EXPECT_LT(CLAMPED, NATURAL);
79+
EXPECT_EQ(rect->impl->position.width + text->impl->position.width, POSITION.width);
80+
const auto PREVIOUS_TEXT_WIDTH = text->impl->position.width;
7481

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);
82+
text->rebuild()->text("Hello World Foo Bar Baz but longer")->commence();
83+
g_positioner->position(layout, POSITION);
7884

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);
85+
EXPECT_EQ(rect->impl->position.width + text->impl->position.width, POSITION.width);
86+
EXPECT_GT(text->impl->position.width, PREVIOUS_TEXT_WIDTH);
8287

8388
text.reset();
84-
}
89+
rect.reset();
90+
layout.reset();
91+
}
92+
93+
TEST(Element, textSideBySide) {
94+
Tests::Tricks::createBackendSupport();
95+
96+
const CBox POSITION = {0, 0, 300, 100};
97+
98+
auto text1 = CTextBuilder::begin()
99+
->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {0.5, 1.0}})
100+
->text("First longish paragraph goes here. I love Hyprland it is the best.")
101+
->noEllipsize(true)
102+
->commence();
103+
auto text2 = CTextBuilder::begin()
104+
->size({CDynamicSize::HT_SIZE_PERCENT, CDynamicSize::HT_SIZE_AUTO, {0.5, 1.0}})
105+
->text("Second longish paragraph goes here. Who needs GTK and Qt when you have Hyprtoolkit?")
106+
->noEllipsize(true)
107+
->commence();
108+
auto layout = CRowLayoutBuilder::begin()->commence();
109+
110+
const auto NATURAL_HEIGHT = text1->preferredSize({0, 0})->y;
111+
112+
layout->addChild(text1);
113+
layout->addChild(text2);
114+
115+
g_positioner->position(layout, POSITION);
116+
117+
ASSERT_EQ(layout->impl->position.width, POSITION.width);
118+
ASSERT_EQ(text1->impl->position.width, POSITION.width / 2);
119+
ASSERT_EQ(text2->impl->position.width, POSITION.width / 2);
120+
ASSERT_EQ(text1->impl->position.width, text2->impl->position.x);
121+
ASSERT_GT(text1->impl->position.height, NATURAL_HEIGHT);
122+
ASSERT_GT(text2->impl->position.height, NATURAL_HEIGHT);
123+
124+
text1.reset();
125+
text2.reset();
126+
layout.reset();
127+
}
128+
129+
TEST(Element, textShrinkingThenGrowing) {
130+
Tests::Tricks::createBackendSupport();
131+
132+
// This container is necessary because we need the text to auto size itself
133+
// if we position it directly, the text sizing code is never run
134+
auto container = CNullBuilder::begin()->commence();
135+
auto text = CTextBuilder::begin()->text("First longish paragraph goes here. I love Hyprland it is the best.")->commence();
136+
137+
container->addChild(text);
138+
139+
const auto NATURAL_WIDTH = text->preferredSize({0, 0})->x;
140+
const auto CLAMPED_WIDTH = std::floor(NATURAL_WIDTH * 3 / 4);
141+
142+
g_positioner->position(container, {0, 0, CLAMPED_WIDTH, 100});
143+
ASSERT_LE(text->impl->position.width, CLAMPED_WIDTH);
144+
145+
g_positioner->position(container, {0, 0, NATURAL_WIDTH * 2, 100});
146+
ASSERT_EQ(text->impl->position.width, NATURAL_WIDTH);
147+
148+
container.reset();
149+
text.reset();
150+
}
151+
152+
TEST(Element, textChangingSize) {
153+
Tests::Tricks::createBackendSupport();
154+
155+
auto text = CTextBuilder::begin()->text("First longish paragraph goes here. I love Hyprland it is the best.")->commence();
156+
157+
const auto FIRST_SIZE = text->preferredSize({0, 0});
158+
159+
text->rebuild()->fontSize(CFontSize::HT_FONT_H1)->commence();
160+
161+
const auto SECOND_SIZE = text->preferredSize({0, 0});
162+
163+
ASSERT_GT(SECOND_SIZE->x, FIRST_SIZE->x);
164+
ASSERT_GT(SECOND_SIZE->y, FIRST_SIZE->y);
165+
166+
text.reset();
167+
}
168+
169+
TEST(Element, textChangingContent) {
170+
Tests::Tricks::createBackendSupport();
171+
172+
auto text = CTextBuilder::begin()->text("First longish paragraph goes here.")->commence();
173+
174+
const auto FIRST_SIZE = text->preferredSize({0, 0});
175+
176+
text->rebuild()->text("First longish paragraph goes here but event longer")->commence();
177+
178+
const auto SECOND_SIZE = text->preferredSize({0, 0});
179+
180+
ASSERT_GT(SECOND_SIZE->x, FIRST_SIZE->x);
181+
182+
text.reset();
183+
}
184+
185+
TEST(Element, textClampSize) {
186+
Tests::Tricks::createBackendSupport();
187+
188+
auto text = CTextBuilder::begin()->text("First longish paragraph goes here.")->commence();
189+
190+
const auto NATURAL_SIZE = text->preferredSize({0, 0});
191+
const auto CLAMPED_WIDTH = std::floor(NATURAL_SIZE->x * 3 / 4);
192+
193+
text->rebuild()->clampSize({CLAMPED_WIDTH, -1.0})->commence();
194+
195+
ASSERT_LE(text->preferredSize({0, 0})->x, CLAMPED_WIDTH);
196+
197+
text.reset();
198+
}
199+
200+
TEST(Element, textCharPosition) {
201+
Tests::Tricks::createBackendSupport();
202+
203+
auto text = CTextBuilder::begin()->text("First longish paragraph goes here.")->commence();
204+
205+
ASSERT_EQ(text->m_impl->getCursorPos(0), 0);
206+
ASSERT_GT(text->m_impl->getCursorPos(1), 0);
207+
ASSERT_GT(text->m_impl->getCursorPos(1000), 0);
208+
209+
text->rebuild()->text("")->commence();
210+
211+
ASSERT_EQ(text->m_impl->getCursorPos(0), 0);
212+
ASSERT_EQ(text->m_impl->getCursorPos(1), 0);
213+
214+
text.reset();
215+
}

0 commit comments

Comments
 (0)