1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/views/controls/styled_label.h"
6
7 #include <stddef.h>
8
9 #include <memory>
10 #include <string>
11 #include <utility>
12
13 #include "base/command_line.h"
14 #include "base/i18n/base_i18n_switches.h"
15 #include "base/macros.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "base/test/icu_test_util.h"
18 #include "build/build_config.h"
19 #include "testing/gtest/include/gtest/gtest.h"
20 #include "third_party/skia/include/core/SkColor.h"
21 #include "ui/base/ui_base_switches.h"
22 #include "ui/gfx/font_list.h"
23 #include "ui/views/border.h"
24 #include "ui/views/controls/link.h"
25 #include "ui/views/style/typography.h"
26 #include "ui/views/test/test_layout_provider.h"
27 #include "ui/views/test/test_views.h"
28 #include "ui/views/test/views_test_base.h"
29 #include "ui/views/widget/widget.h"
30
31 using base::ASCIIToUTF16;
32
33 namespace views {
34
35 namespace {
36
LabelAt(StyledLabel * styled,size_t index,std::string expected_classname=Label::kViewClassName)37 Label* LabelAt(StyledLabel* styled,
38 size_t index,
39 std::string expected_classname = Label::kViewClassName) {
40 View* const child = styled->children()[index];
41 EXPECT_EQ(expected_classname, child->GetClassName());
42 return static_cast<Label*>(child);
43 }
44
StyledLabelContentHeightForWidth(StyledLabel * styled,int w)45 int StyledLabelContentHeightForWidth(StyledLabel* styled, int w) {
46 return styled->GetHeightForWidth(w) - styled->GetInsets().height();
47 }
48
49 } // namespace
50
51 class StyledLabelTest : public ViewsTestBase {
52 public:
53 StyledLabelTest() = default;
54 StyledLabelTest(const StyledLabelTest&) = delete;
55 StyledLabelTest& operator=(const StyledLabelTest&) = delete;
56 ~StyledLabelTest() override = default;
57
58 protected:
styled() const59 StyledLabel* styled() const { return styled_.get(); }
60
InitStyledLabel(const std::string & ascii_text)61 void InitStyledLabel(const std::string& ascii_text) {
62 styled_ = std::make_unique<StyledLabel>();
63 styled_->SetText(ASCIIToUTF16(ascii_text));
64 }
65
66 private:
67 std::unique_ptr<StyledLabel> styled_;
68 };
69
70 class StyledLabelInWidgetTest : public ViewsTestBase {
71 public:
72 StyledLabelInWidgetTest() = default;
73 StyledLabelInWidgetTest(const StyledLabelInWidgetTest&) = delete;
74 StyledLabelInWidgetTest& operator=(const StyledLabelInWidgetTest&) = delete;
75 ~StyledLabelInWidgetTest() override = default;
76
77 protected:
SetUp()78 void SetUp() override {
79 ViewsTestBase::SetUp();
80
81 widget_ = CreateTestWidget();
82 }
83
TearDown()84 void TearDown() override {
85 widget_.reset();
86
87 ViewsTestBase::TearDown();
88 }
89
styled() const90 StyledLabel* styled() const { return styled_; }
widget() const91 Widget* widget() const { return widget_.get(); }
92
InitStyledLabel(const std::string & ascii_text)93 void InitStyledLabel(const std::string& ascii_text) {
94 View* container = widget_->SetContentsView(std::make_unique<View>());
95 styled_ = container->AddChildView(std::make_unique<StyledLabel>());
96 styled_->SetText(ASCIIToUTF16(ascii_text));
97 }
98
99 private:
100 StyledLabel* styled_;
101 std::unique_ptr<Widget> widget_;
102 };
103
TEST_F(StyledLabelTest,NoWrapping)104 TEST_F(StyledLabelTest, NoWrapping) {
105 const std::string text("This is a test block of text");
106 InitStyledLabel(text);
107 Label label(ASCIIToUTF16(text));
108 const gfx::Size label_preferred_size = label.GetPreferredSize();
109 EXPECT_EQ(label_preferred_size.height(),
110 StyledLabelContentHeightForWidth(styled(),
111 label_preferred_size.width() * 2));
112 }
113
TEST_F(StyledLabelTest,TrailingWhitespaceiIgnored)114 TEST_F(StyledLabelTest, TrailingWhitespaceiIgnored) {
115 const std::string text("This is a test block of text ");
116 InitStyledLabel(text);
117
118 styled()->SetBounds(0, 0, 1000, 1000);
119 styled()->Layout();
120
121 ASSERT_EQ(1u, styled()->children().size());
122 EXPECT_EQ(ASCIIToUTF16("This is a test block of text"),
123 LabelAt(styled(), 0)->GetText());
124 }
125
TEST_F(StyledLabelTest,RespectLeadingWhitespace)126 TEST_F(StyledLabelTest, RespectLeadingWhitespace) {
127 const std::string text(" This is a test block of text");
128 InitStyledLabel(text);
129
130 styled()->SetBounds(0, 0, 1000, 1000);
131 styled()->Layout();
132
133 ASSERT_EQ(1u, styled()->children().size());
134 EXPECT_EQ(ASCIIToUTF16(" This is a test block of text"),
135 LabelAt(styled(), 0)->GetText());
136 }
137
TEST_F(StyledLabelTest,RespectLeadingSpacesInNonFirstLine)138 TEST_F(StyledLabelTest, RespectLeadingSpacesInNonFirstLine) {
139 const std::string indented_line = " indented line";
140 const std::string text(std::string("First line\n") + indented_line);
141 InitStyledLabel(text);
142 styled()->SetBounds(0, 0, 1000, 1000);
143 styled()->Layout();
144 ASSERT_EQ(2u, styled()->children().size());
145 EXPECT_EQ(ASCIIToUTF16(indented_line), LabelAt(styled(), 1)->GetText());
146 }
147
TEST_F(StyledLabelTest,CorrectWrapAtNewline)148 TEST_F(StyledLabelTest, CorrectWrapAtNewline) {
149 const std::string first_line = "Line one";
150 const std::string second_line = " two";
151 const std::string multiline_text(first_line + "\n" + second_line);
152 InitStyledLabel(multiline_text);
153 Label label(ASCIIToUTF16(first_line));
154 gfx::Size label_preferred_size = label.GetPreferredSize();
155 // Correct handling of \n and label width limit encountered at the same place
156 styled()->SetBounds(0, 0, label_preferred_size.width(), 1000);
157 styled()->Layout();
158 ASSERT_EQ(2u, styled()->children().size());
159 EXPECT_EQ(ASCIIToUTF16(first_line), LabelAt(styled(), 0)->GetText());
160 const auto* label_1 = LabelAt(styled(), 1);
161 EXPECT_EQ(ASCIIToUTF16(second_line), label_1->GetText());
162 EXPECT_EQ(styled()->GetHeightForWidth(1000), label_1->bounds().bottom());
163 }
164
TEST_F(StyledLabelTest,FirstLineNotEmptyWhenLeadingWhitespaceTooLong)165 TEST_F(StyledLabelTest, FirstLineNotEmptyWhenLeadingWhitespaceTooLong) {
166 const std::string text(" a");
167 InitStyledLabel(text);
168
169 Label label(ASCIIToUTF16(text));
170 gfx::Size label_preferred_size = label.GetPreferredSize();
171
172 styled()->SetBounds(0, 0, label_preferred_size.width() / 2, 1000);
173 styled()->Layout();
174
175 ASSERT_EQ(1u, styled()->children().size());
176 EXPECT_EQ(ASCIIToUTF16("a"), LabelAt(styled(), 0)->GetText());
177 EXPECT_EQ(label_preferred_size.height(),
178 styled()->GetHeightForWidth(label_preferred_size.width() / 2));
179 }
180
TEST_F(StyledLabelTest,BasicWrapping)181 TEST_F(StyledLabelTest, BasicWrapping) {
182 const std::string text("This is a test block of text");
183 InitStyledLabel(text);
184 Label label(ASCIIToUTF16(text.substr(0, text.size() * 2 / 3)));
185 gfx::Size label_preferred_size = label.GetPreferredSize();
186 EXPECT_EQ(
187 label_preferred_size.height() * 2,
188 StyledLabelContentHeightForWidth(styled(), label_preferred_size.width()));
189
190 // Also respect the border.
191 styled()->SetBorder(CreateEmptyBorder(3, 3, 3, 3));
192 styled()->SetBounds(
193 0, 0, styled()->GetInsets().width() + label_preferred_size.width(),
194 styled()->GetInsets().height() + 2 * label_preferred_size.height());
195 styled()->Layout();
196 ASSERT_EQ(2u, styled()->children().size());
197 EXPECT_EQ(3, styled()->children()[0]->x());
198 EXPECT_EQ(3, styled()->children()[0]->y());
199 EXPECT_EQ(styled()->height() - 3, styled()->children()[1]->bounds().bottom());
200 }
201
TEST_F(StyledLabelTest,AllowEmptyLines)202 TEST_F(StyledLabelTest, AllowEmptyLines) {
203 const std::string text("one");
204 InitStyledLabel(text);
205 int default_height = styled()->GetHeightForWidth(1000);
206 const std::string multiline_text("one\n\nthree");
207 InitStyledLabel(multiline_text);
208 styled()->SetBounds(0, 0, 1000, 1000);
209 styled()->Layout();
210 EXPECT_EQ(3 * default_height, styled()->GetHeightForWidth(1000));
211 ASSERT_EQ(2u, styled()->children().size());
212 EXPECT_EQ(styled()->GetHeightForWidth(1000),
213 styled()->children()[1]->bounds().bottom());
214 }
215
TEST_F(StyledLabelTest,WrapLongWords)216 TEST_F(StyledLabelTest, WrapLongWords) {
217 const std::string text("ThisIsTextAsASingleWord");
218 InitStyledLabel(text);
219 Label label(ASCIIToUTF16(text.substr(0, text.size() * 2 / 3)));
220 gfx::Size label_preferred_size = label.GetPreferredSize();
221 EXPECT_EQ(
222 label_preferred_size.height() * 2,
223 StyledLabelContentHeightForWidth(styled(), label_preferred_size.width()));
224
225 styled()->SetBounds(
226 0, 0, styled()->GetInsets().width() + label_preferred_size.width(),
227 styled()->GetInsets().height() + 2 * label_preferred_size.height());
228 styled()->Layout();
229
230 ASSERT_EQ(2u, styled()->children().size());
231 ASSERT_EQ(gfx::Point(), styled()->origin());
232 const auto* label_0 = LabelAt(styled(), 0);
233 const auto* label_1 = LabelAt(styled(), 1);
234 EXPECT_EQ(gfx::Point(), label_0->origin());
235 EXPECT_EQ(gfx::Point(0, styled()->height() / 2), label_1->origin());
236
237 EXPECT_FALSE(label_0->GetText().empty());
238 EXPECT_FALSE(label_1->GetText().empty());
239 EXPECT_EQ(ASCIIToUTF16(text), label_0->GetText() + label_1->GetText());
240 }
241
TEST_F(StyledLabelTest,CreateLinks)242 TEST_F(StyledLabelTest, CreateLinks) {
243 const std::string text("This is a test block of text.");
244 InitStyledLabel(text);
245
246 // Without links, there should be no focus border.
247 EXPECT_TRUE(styled()->GetInsets().IsEmpty());
248
249 // Now let's add some links.
250 styled()->AddStyleRange(
251 gfx::Range(0, 1),
252 StyledLabel::RangeStyleInfo::CreateForLink(base::RepeatingClosure()));
253 styled()->AddStyleRange(
254 gfx::Range(1, 2),
255 StyledLabel::RangeStyleInfo::CreateForLink(base::RepeatingClosure()));
256 styled()->AddStyleRange(
257 gfx::Range(10, 11),
258 StyledLabel::RangeStyleInfo::CreateForLink(base::RepeatingClosure()));
259 styled()->AddStyleRange(
260 gfx::Range(12, 13),
261 StyledLabel::RangeStyleInfo::CreateForLink(base::RepeatingClosure()));
262
263 // Insets shouldn't change when links are added, since the links indicate
264 // focus by adding an underline instead.
265 EXPECT_TRUE(styled()->GetInsets().IsEmpty());
266
267 // Verify layout creates the right number of children.
268 styled()->SetBounds(0, 0, 1000, 1000);
269 styled()->Layout();
270 EXPECT_EQ(7u, styled()->children().size());
271 }
272
TEST_F(StyledLabelTest,DontBreakLinks)273 TEST_F(StyledLabelTest, DontBreakLinks) {
274 const std::string text("This is a test block of text, ");
275 const std::string link_text("and this should be a link");
276 InitStyledLabel(text + link_text);
277 styled()->AddStyleRange(
278 gfx::Range(text.size(), text.size() + link_text.size()),
279 StyledLabel::RangeStyleInfo::CreateForLink(base::RepeatingClosure()));
280
281 Label label(ASCIIToUTF16(text + link_text.substr(0, link_text.size() / 2)));
282 gfx::Size label_preferred_size = label.GetPreferredSize();
283 int pref_height = styled()->GetHeightForWidth(label_preferred_size.width());
284 EXPECT_EQ(label_preferred_size.height() * 2,
285 pref_height - styled()->GetInsets().height());
286
287 styled()->SetBounds(0, 0, label_preferred_size.width(), pref_height);
288 styled()->Layout();
289 ASSERT_EQ(2u, styled()->children().size());
290
291 // No additional insets should be added.
292 EXPECT_EQ(0, styled()->children()[0]->x());
293 // The Link shouldn't be offset.
294 EXPECT_EQ(0, styled()->children()[1]->x());
295 }
296
TEST_F(StyledLabelTest,StyledRangeWithDisabledLineWrapping)297 TEST_F(StyledLabelTest, StyledRangeWithDisabledLineWrapping) {
298 const std::string text("This is a test block of text, ");
299 const std::string unbreakable_text("and this should not be broken");
300 InitStyledLabel(text + unbreakable_text);
301 StyledLabel::RangeStyleInfo style_info;
302 style_info.disable_line_wrapping = true;
303 styled()->AddStyleRange(
304 gfx::Range(text.size(), text.size() + unbreakable_text.size()),
305 style_info);
306
307 Label label(ASCIIToUTF16(
308 text + unbreakable_text.substr(0, unbreakable_text.size() / 2)));
309 gfx::Size label_preferred_size = label.GetPreferredSize();
310 int pref_height = styled()->GetHeightForWidth(label_preferred_size.width());
311 EXPECT_EQ(label_preferred_size.height() * 2,
312 pref_height - styled()->GetInsets().height());
313
314 styled()->SetBounds(0, 0, label_preferred_size.width(), pref_height);
315 styled()->Layout();
316 ASSERT_EQ(2u, styled()->children().size());
317 EXPECT_EQ(0, styled()->children()[0]->x());
318 EXPECT_EQ(0, styled()->children()[1]->x());
319 }
320
TEST_F(StyledLabelTest,StyledRangeCustomFontUnderlined)321 TEST_F(StyledLabelTest, StyledRangeCustomFontUnderlined) {
322 const std::string text("This is a test block of text, ");
323 const std::string underlined_text("and this should be undelined");
324 InitStyledLabel(text + underlined_text);
325 StyledLabel::RangeStyleInfo style_info;
326 style_info.tooltip = ASCIIToUTF16("tooltip");
327 style_info.custom_font =
328 styled()->GetFontList().DeriveWithStyle(gfx::Font::UNDERLINE);
329 styled()->AddStyleRange(
330 gfx::Range(text.size(), text.size() + underlined_text.size()),
331 style_info);
332
333 styled()->SetBounds(0, 0, 1000, 1000);
334 styled()->Layout();
335
336 ASSERT_EQ(2u, styled()->children().size());
337 EXPECT_EQ(gfx::Font::UNDERLINE,
338 LabelAt(styled(), 1)->font_list().GetFontStyle());
339 }
340
TEST_F(StyledLabelTest,StyledRangeTextStyleBold)341 TEST_F(StyledLabelTest, StyledRangeTextStyleBold) {
342 test::TestLayoutProvider bold_provider;
343 const std::string bold_text(
344 "This is a block of text whose style will be set to BOLD in the test");
345 const std::string text(" normal text");
346 InitStyledLabel(bold_text + text);
347
348 // Pretend disabled text becomes bold for testing.
349 bold_provider.SetFont(
350 style::CONTEXT_LABEL, style::STYLE_DISABLED,
351 styled()->GetFontList().DeriveWithWeight(gfx::Font::Weight::BOLD));
352
353 StyledLabel::RangeStyleInfo style_info;
354 style_info.text_style = style::STYLE_DISABLED;
355 styled()->AddStyleRange(gfx::Range(0u, bold_text.size()), style_info);
356
357 // Calculate the bold text width if it were a pure label view, both with bold
358 // and normal style.
359 Label label(ASCIIToUTF16(bold_text));
360 const gfx::Size normal_label_size = label.GetPreferredSize();
361 label.SetFontList(
362 label.font_list().DeriveWithWeight(gfx::Font::Weight::BOLD));
363 const gfx::Size bold_label_size = label.GetPreferredSize();
364
365 ASSERT_GE(bold_label_size.width(), normal_label_size.width());
366
367 // Set the width so |bold_text| doesn't fit on a single line with bold style,
368 // but does with normal font style.
369 int styled_width = (normal_label_size.width() + bold_label_size.width()) / 2;
370 int pref_height = styled()->GetHeightForWidth(styled_width);
371
372 // Sanity check that |bold_text| with normal font style would fit on a single
373 // line in a styled label with width |styled_width|.
374 StyledLabel unstyled;
375 unstyled.SetText(ASCIIToUTF16(bold_text));
376 unstyled.SetBounds(0, 0, styled_width, pref_height);
377 unstyled.Layout();
378 EXPECT_EQ(1u, unstyled.children().size());
379
380 styled()->SetBounds(0, 0, styled_width, pref_height);
381 styled()->Layout();
382
383 ASSERT_EQ(3u, styled()->children().size());
384
385 // The bold text should be broken up into two parts.
386 const auto* label_0 = LabelAt(styled(), 0);
387 const auto* label_1 = LabelAt(styled(), 1);
388 const auto* label_2 = LabelAt(styled(), 2);
389 EXPECT_EQ(gfx::Font::Weight::BOLD, label_0->font_list().GetFontWeight());
390 EXPECT_EQ(gfx::Font::Weight::BOLD, label_1->font_list().GetFontWeight());
391 EXPECT_EQ(gfx::Font::NORMAL, label_2->font_list().GetFontStyle());
392
393 // The second bold part should start on a new line.
394 EXPECT_EQ(0, label_0->x());
395 EXPECT_EQ(0, label_1->x());
396 EXPECT_EQ(label_1->bounds().right(), label_2->x());
397 }
398
TEST_F(StyledLabelInWidgetTest,Color)399 TEST_F(StyledLabelInWidgetTest, Color) {
400 const std::string text_blue("BLUE");
401 const std::string text_link("link");
402 const std::string text("word");
403 InitStyledLabel(text_blue + text_link + text);
404
405 StyledLabel::RangeStyleInfo style_info_blue;
406 style_info_blue.override_color = SK_ColorBLUE;
407 styled()->AddStyleRange(gfx::Range(0u, text_blue.size()), style_info_blue);
408
409 StyledLabel::RangeStyleInfo style_info_link =
410 StyledLabel::RangeStyleInfo::CreateForLink(base::RepeatingClosure());
411 styled()->AddStyleRange(
412 gfx::Range(text_blue.size(), text_blue.size() + text_link.size()),
413 style_info_link);
414
415 styled()->SetBounds(0, 0, 1000, 1000);
416 styled()->Layout();
417
418 // The code below is not prepared to deal with dark mode.
419 widget()->GetNativeTheme()->set_use_dark_colors(false);
420
421 auto* container = widget()->GetContentsView();
422 // Obtain the default text color for a label.
423 Label* label =
424 container->AddChildView(std::make_unique<Label>(ASCIIToUTF16(text)));
425 const SkColor kDefaultTextColor = label->GetEnabledColor();
426
427 // Obtain the default text color for a link.
428 Link* link =
429 container->AddChildView(std::make_unique<Link>(ASCIIToUTF16(text_link)));
430 const SkColor kDefaultLinkColor = link->GetEnabledColor();
431
432 ASSERT_EQ(3u, styled()->children().size());
433 EXPECT_EQ(SK_ColorBLUE, LabelAt(styled(), 0)->GetEnabledColor());
434 EXPECT_EQ(kDefaultLinkColor,
435 LabelAt(styled(), 1, Link::kViewClassName)->GetEnabledColor());
436 EXPECT_EQ(kDefaultTextColor, LabelAt(styled(), 2)->GetEnabledColor());
437
438 // Test adjusted color readability.
439 styled()->SetDisplayedOnBackgroundColor(SK_ColorBLACK);
440 styled()->Layout();
441 label->SetBackgroundColor(SK_ColorBLACK);
442
443 const SkColor kAdjustedTextColor = label->GetEnabledColor();
444 EXPECT_NE(kAdjustedTextColor, kDefaultTextColor);
445 EXPECT_EQ(kAdjustedTextColor, LabelAt(styled(), 2)->GetEnabledColor());
446 }
447
TEST_F(StyledLabelTest,StyledRangeWithTooltip)448 TEST_F(StyledLabelTest, StyledRangeWithTooltip) {
449 const std::string text("This is a test block of text, ");
450 const std::string tooltip_text("this should have a tooltip,");
451 const std::string normal_text(" this should not have a tooltip, ");
452 const std::string link_text("and this should be a link");
453
454 const size_t tooltip_start = text.size();
455 const size_t link_start =
456 text.size() + tooltip_text.size() + normal_text.size();
457
458 InitStyledLabel(text + tooltip_text + normal_text + link_text);
459 StyledLabel::RangeStyleInfo tooltip_style;
460 tooltip_style.tooltip = ASCIIToUTF16("tooltip");
461 styled()->AddStyleRange(
462 gfx::Range(tooltip_start, tooltip_start + tooltip_text.size()),
463 tooltip_style);
464 styled()->AddStyleRange(
465 gfx::Range(link_start, link_start + link_text.size()),
466 StyledLabel::RangeStyleInfo::CreateForLink(base::RepeatingClosure()));
467
468 // Break line inside the range with the tooltip.
469 Label label(
470 ASCIIToUTF16(text + tooltip_text.substr(0, tooltip_text.size() - 3)));
471 gfx::Size label_preferred_size = label.GetPreferredSize();
472 int pref_height = styled()->GetHeightForWidth(label_preferred_size.width());
473 EXPECT_EQ(label_preferred_size.height() * 3,
474 pref_height - styled()->GetInsets().height());
475
476 styled()->SetBounds(0, 0, label_preferred_size.width(), pref_height);
477 styled()->Layout();
478
479 EXPECT_EQ(label_preferred_size.width(), styled()->width());
480
481 ASSERT_EQ(5u, styled()->children().size());
482
483 // The labels shouldn't be offset to cater for focus rings.
484 EXPECT_EQ(0, styled()->children()[0]->x());
485 EXPECT_EQ(0, styled()->children()[2]->x());
486
487 EXPECT_EQ(styled()->children()[0]->bounds().right(),
488 styled()->children()[1]->x());
489 EXPECT_EQ(styled()->children()[2]->bounds().right(),
490 styled()->children()[3]->x());
491 EXPECT_EQ(0, styled()->children()[4]->x());
492
493 base::string16 tooltip =
494 styled()->children()[1]->GetTooltipText(gfx::Point(1, 1));
495 EXPECT_EQ(ASCIIToUTF16("tooltip"), tooltip);
496 tooltip = styled()->children()[2]->GetTooltipText(gfx::Point(1, 1));
497 EXPECT_EQ(ASCIIToUTF16("tooltip"), tooltip);
498 }
499
TEST_F(StyledLabelTest,SetTextContextAndDefaultStyle)500 TEST_F(StyledLabelTest, SetTextContextAndDefaultStyle) {
501 const std::string text("This is a test block of text.");
502 InitStyledLabel(text);
503 styled()->SetTextContext(style::CONTEXT_DIALOG_TITLE);
504 styled()->SetDefaultTextStyle(style::STYLE_DISABLED);
505 Label label(ASCIIToUTF16(text), style::CONTEXT_DIALOG_TITLE,
506 style::STYLE_DISABLED);
507
508 styled()->SetBounds(0, 0, label.GetPreferredSize().width(),
509 label.GetPreferredSize().height());
510
511 // Make sure we have the same sizing as a label with the same style.
512 EXPECT_EQ(label.GetPreferredSize().height(), styled()->height());
513 EXPECT_EQ(label.GetPreferredSize().width(), styled()->width());
514
515 styled()->Layout();
516 ASSERT_EQ(1u, styled()->children().size());
517 Label* sublabel = LabelAt(styled(), 0);
518 EXPECT_EQ(style::CONTEXT_DIALOG_TITLE, sublabel->GetTextContext());
519
520 EXPECT_NE(SK_ColorBLACK, label.GetEnabledColor()); // Sanity check,
521 EXPECT_EQ(label.GetEnabledColor(), sublabel->GetEnabledColor());
522 }
523
TEST_F(StyledLabelTest,LineHeight)524 TEST_F(StyledLabelTest, LineHeight) {
525 const std::string text("one\ntwo\nthree");
526 InitStyledLabel(text);
527 styled()->SetLineHeight(18);
528 EXPECT_EQ(18 * 3, styled()->GetHeightForWidth(100));
529 }
530
TEST_F(StyledLabelTest,LineHeightWithBorder)531 TEST_F(StyledLabelTest, LineHeightWithBorder) {
532 const std::string text("one\ntwo\nthree");
533 InitStyledLabel(text);
534 styled()->SetLineHeight(18);
535 styled()->SetBorder(views::CreateSolidBorder(1, SK_ColorGRAY));
536 EXPECT_EQ(18 * 3 + 2, styled()->GetHeightForWidth(100));
537 }
538
TEST_F(StyledLabelTest,LineHeightWithLink)539 TEST_F(StyledLabelTest, LineHeightWithLink) {
540 const std::string text("one\ntwo\nthree");
541 InitStyledLabel(text);
542 styled()->SetLineHeight(18);
543
544 styled()->AddStyleRange(
545 gfx::Range(0, 3),
546 StyledLabel::RangeStyleInfo::CreateForLink(base::RepeatingClosure()));
547 styled()->AddStyleRange(
548 gfx::Range(4, 7),
549 StyledLabel::RangeStyleInfo::CreateForLink(base::RepeatingClosure()));
550 styled()->AddStyleRange(
551 gfx::Range(8, 13),
552 StyledLabel::RangeStyleInfo::CreateForLink(base::RepeatingClosure()));
553 EXPECT_EQ(18 * 3, styled()->GetHeightForWidth(100));
554 }
555
TEST_F(StyledLabelTest,HandleEmptyLayout)556 TEST_F(StyledLabelTest, HandleEmptyLayout) {
557 const std::string text("This is a test block of text.");
558 InitStyledLabel(text);
559 styled()->Layout();
560 EXPECT_EQ(0u, styled()->children().size());
561 }
562
TEST_F(StyledLabelTest,CacheSize)563 TEST_F(StyledLabelTest, CacheSize) {
564 const int preferred_height = 50;
565 const int preferred_width = 100;
566 const std::string text("This is a test block of text.");
567 const base::string16 another_text(base::ASCIIToUTF16(
568 "This is a test block of text. This text is much longer than previous"));
569
570 InitStyledLabel(text);
571
572 // we should be able to calculate height without any problem
573 // no controls should be created
574 int precalculated_height = styled()->GetHeightForWidth(preferred_width);
575 EXPECT_LT(0, precalculated_height);
576 EXPECT_EQ(0u, styled()->children().size());
577
578 styled()->SetBounds(0, 0, preferred_width, preferred_height);
579 styled()->Layout();
580
581 // controls should be created after layout
582 // height should be the same as precalculated
583 int real_height = styled()->GetHeightForWidth(styled()->width());
584 View* first_child_after_layout =
585 styled()->children().empty() ? nullptr : styled()->children().front();
586 EXPECT_LT(0u, styled()->children().size());
587 EXPECT_LT(0, real_height);
588 EXPECT_EQ(real_height, precalculated_height);
589
590 // another call to Layout should not kill and recreate all controls
591 styled()->Layout();
592 View* first_child_after_second_layout =
593 styled()->children().empty() ? nullptr : styled()->children().front();
594 EXPECT_EQ(first_child_after_layout, first_child_after_second_layout);
595
596 // if text is changed:
597 // layout should be recalculated
598 // all controls should be recreated
599 styled()->SetText(another_text);
600 int updated_height = styled()->GetHeightForWidth(styled()->width());
601 EXPECT_NE(updated_height, real_height);
602 View* first_child_after_text_update =
603 styled()->children().empty() ? nullptr : styled()->children().front();
604 EXPECT_NE(first_child_after_text_update, first_child_after_layout);
605 }
606
TEST_F(StyledLabelTest,Border)607 TEST_F(StyledLabelTest, Border) {
608 const std::string text("One line");
609 InitStyledLabel(text);
610 Label label(ASCIIToUTF16(text));
611 gfx::Size label_preferred_size = label.GetPreferredSize();
612 styled()->SetBorder(
613 CreateEmptyBorder(5 /*top*/, 10 /*left*/, 6 /*bottom*/, 20 /*right*/));
614 styled()->SetBounds(0, 0, 1000, 0);
615 styled()->Layout();
616 EXPECT_EQ(
617 label_preferred_size.height() + 5 /*top border*/ + 6 /*bottom border*/,
618 styled()->GetPreferredSize().height());
619 EXPECT_EQ(
620 label_preferred_size.width() + 10 /*left border*/ + 20 /*right border*/,
621 styled()->GetPreferredSize().width());
622 }
623
TEST_F(StyledLabelTest,LineHeightWithShorterCustomView)624 TEST_F(StyledLabelTest, LineHeightWithShorterCustomView) {
625 const std::string text("one ");
626 InitStyledLabel(text);
627 int default_height = styled()->GetHeightForWidth(1000);
628
629 const std::string custom_view_text("with custom view");
630 const int less_height = 10;
631 std::unique_ptr<View> custom_view = std::make_unique<StaticSizedView>(
632 gfx::Size(20, default_height - less_height));
633 StyledLabel::RangeStyleInfo style_info;
634 style_info.custom_view = custom_view.get();
635 InitStyledLabel(text + custom_view_text);
636 styled()->AddStyleRange(
637 gfx::Range(text.size(), text.size() + custom_view_text.size()),
638 style_info);
639 styled()->AddCustomView(std::move(custom_view));
640 EXPECT_EQ(default_height, styled()->GetHeightForWidth(100));
641 }
642
TEST_F(StyledLabelTest,LineHeightWithTallerCustomView)643 TEST_F(StyledLabelTest, LineHeightWithTallerCustomView) {
644 const std::string text("one ");
645 InitStyledLabel(text);
646 int default_height = styled()->GetHeightForWidth(100);
647
648 const std::string custom_view_text("with custom view");
649 const int more_height = 10;
650 std::unique_ptr<View> custom_view = std::make_unique<StaticSizedView>(
651 gfx::Size(20, default_height + more_height));
652 StyledLabel::RangeStyleInfo style_info;
653 style_info.custom_view = custom_view.get();
654 InitStyledLabel(text + custom_view_text);
655 styled()->AddStyleRange(
656 gfx::Range(text.size(), text.size() + custom_view_text.size()),
657 style_info);
658 styled()->AddCustomView(std::move(custom_view));
659 EXPECT_EQ(default_height + more_height, styled()->GetHeightForWidth(100));
660 }
661
TEST_F(StyledLabelTest,LineWrapperWithCustomView)662 TEST_F(StyledLabelTest, LineWrapperWithCustomView) {
663 const std::string text_before("one ");
664 InitStyledLabel(text_before);
665 int default_height = styled()->GetHeightForWidth(100);
666 const std::string custom_view_text("two with custom view ");
667 const std::string text_after("three");
668
669 int custom_view_height = 25;
670 std::unique_ptr<View> custom_view =
671 std::make_unique<StaticSizedView>(gfx::Size(200, custom_view_height));
672 StyledLabel::RangeStyleInfo style_info;
673 style_info.custom_view = custom_view.get();
674 InitStyledLabel(text_before + custom_view_text + text_after);
675 styled()->AddStyleRange(
676 gfx::Range(text_before.size(),
677 text_before.size() + custom_view_text.size()),
678 style_info);
679 styled()->AddCustomView(std::move(custom_view));
680 EXPECT_EQ(default_height * 2 + custom_view_height,
681 styled()->GetHeightForWidth(100));
682 }
683
TEST_F(StyledLabelTest,AlignmentInLTR)684 TEST_F(StyledLabelTest, AlignmentInLTR) {
685 const std::string text("text");
686 InitStyledLabel(text);
687 styled()->SetBounds(0, 0, 1000, 1000);
688 styled()->Layout();
689 const auto& children = styled()->children();
690 ASSERT_EQ(1u, children.size());
691
692 // Test the default alignment puts the text on the leading side (left).
693 EXPECT_EQ(0, children.front()->bounds().x());
694
695 styled()->SetHorizontalAlignment(gfx::ALIGN_RIGHT);
696 styled()->Layout();
697 EXPECT_EQ(1000, children.front()->bounds().right());
698
699 styled()->SetHorizontalAlignment(gfx::ALIGN_LEFT);
700 styled()->Layout();
701 EXPECT_EQ(0, children.front()->bounds().x());
702
703 styled()->SetHorizontalAlignment(gfx::ALIGN_CENTER);
704 styled()->Layout();
705 Label label(ASCIIToUTF16(text));
706 EXPECT_EQ((1000 - label.GetPreferredSize().width()) / 2,
707 children.front()->bounds().x());
708 }
709
TEST_F(StyledLabelTest,AlignmentInRTL)710 TEST_F(StyledLabelTest, AlignmentInRTL) {
711 // |g_icu_text_direction| is cached to prevent reading new commandline switch.
712 // Set |g_icu_text_direction| to |UNKNOWN_DIRECTION| in order to read the new
713 // commandline switch.
714 base::test::ScopedRestoreICUDefaultLocale scoped_locale("en_US");
715 base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
716 switches::kForceUIDirection, switches::kForceDirectionRTL);
717
718 const std::string text("text");
719 InitStyledLabel(text);
720 styled()->SetBounds(0, 0, 1000, 1000);
721 styled()->Layout();
722 const auto& children = styled()->children();
723 ASSERT_EQ(1u, children.size());
724
725 // Test the default alignment puts the text on the leading side (right).
726 // Note that x-coordinates in RTL place the origin (0) on the right.
727 EXPECT_EQ(0, children.front()->bounds().x());
728
729 // Setting |ALIGN_LEFT| should be flipped to |ALIGN_RIGHT|.
730 styled()->SetHorizontalAlignment(gfx::ALIGN_LEFT);
731 styled()->Layout();
732 EXPECT_EQ(1000, children.front()->bounds().right());
733
734 // Setting |ALIGN_RIGHT| should be flipped to |ALIGN_LEFT|.
735 styled()->SetHorizontalAlignment(gfx::ALIGN_RIGHT);
736 styled()->Layout();
737 EXPECT_EQ(0, children.front()->bounds().x());
738
739 styled()->SetHorizontalAlignment(gfx::ALIGN_CENTER);
740 styled()->Layout();
741 Label label(ASCIIToUTF16(text));
742 EXPECT_EQ((1000 - label.GetPreferredSize().width()) / 2,
743 children.front()->bounds().x());
744 }
745
TEST_F(StyledLabelTest,ViewsCenteredWithLinkAndCustomView)746 TEST_F(StyledLabelTest, ViewsCenteredWithLinkAndCustomView) {
747 const std::string text("This is a test block of text, ");
748 const std::string link_text("and this should be a link");
749 const std::string custom_view_text("And this is a custom view");
750 InitStyledLabel(text + link_text + custom_view_text);
751 styled()->AddStyleRange(
752 gfx::Range(text.size(), text.size() + link_text.size()),
753 StyledLabel::RangeStyleInfo::CreateForLink(base::RepeatingClosure()));
754
755 int custom_view_height = 25;
756 std::unique_ptr<View> custom_view =
757 std::make_unique<StaticSizedView>(gfx::Size(20, custom_view_height));
758 StyledLabel::RangeStyleInfo style_info;
759 style_info.custom_view = custom_view.get();
760 styled()->AddStyleRange(
761 gfx::Range(text.size() + link_text.size(),
762 text.size() + link_text.size() + custom_view_text.size()),
763 style_info);
764 styled()->AddCustomView(std::move(custom_view));
765
766 styled()->SetBounds(0, 0, 1000, 500);
767 styled()->Layout();
768 const int height = styled()->GetPreferredSize().height();
769 for (const auto* child : styled()->children())
770 EXPECT_EQ(height / 2, child->bounds().CenterPoint().y());
771 }
772
TEST_F(StyledLabelTest,ViewsCenteredForEvenAndOddSizes)773 TEST_F(StyledLabelTest, ViewsCenteredForEvenAndOddSizes) {
774 constexpr int kViewWidth = 30;
775 for (int height : {60, 61}) {
776 InitStyledLabel("abc");
777
778 const int view_heights[] = {height, height / 2, height / 2 + 1};
779 for (uint32_t i = 0; i < 3; ++i) {
780 auto view = std::make_unique<StaticSizedView>(
781 gfx::Size(kViewWidth, view_heights[i]));
782 StyledLabel::RangeStyleInfo style_info;
783 style_info.custom_view = view.get();
784 styled()->AddStyleRange(gfx::Range(i, i + 1), style_info);
785 styled()->AddCustomView(std::move(view));
786 }
787
788 styled()->SetBounds(0, 0, kViewWidth * 3, height);
789 styled()->Layout();
790
791 for (const auto* child : styled()->children())
792 EXPECT_EQ(height / 2, child->bounds().CenterPoint().y());
793 }
794 }
795
TEST_F(StyledLabelTest,CacheSizeWithAlignment)796 TEST_F(StyledLabelTest, CacheSizeWithAlignment) {
797 const std::string text("text");
798 InitStyledLabel(text);
799 styled()->SetHorizontalAlignment(gfx::ALIGN_RIGHT);
800 styled()->SetBounds(0, 0, 1000, 1000);
801 styled()->Layout();
802 ASSERT_EQ(1u, styled()->children().size());
803 const View* child = styled()->children().front();
804 EXPECT_EQ(1000, child->bounds().right());
805
806 styled()->SetSize({800, 1000});
807 styled()->Layout();
808 ASSERT_EQ(1u, styled()->children().size());
809 const View* new_child = styled()->children().front();
810 EXPECT_EQ(child, new_child);
811 EXPECT_EQ(800, new_child->bounds().right());
812 }
813
814 // Verifies that calling SizeToFit() on a label which requires less width still
815 // causes it to take the whole requested width.
TEST_F(StyledLabelTest,SizeToFit)816 TEST_F(StyledLabelTest, SizeToFit) {
817 const std::string text("text");
818 InitStyledLabel(text);
819 styled()->SetHorizontalAlignment(gfx::ALIGN_RIGHT);
820 styled()->SizeToFit(1000);
821 styled()->Layout();
822 ASSERT_EQ(1u, styled()->children().size());
823 EXPECT_EQ(1000, styled()->children().front()->bounds().right());
824 }
825
826 // Verifies that a non-empty label has a preferred size by default.
TEST_F(StyledLabelTest,PreferredSizeNonEmpty)827 TEST_F(StyledLabelTest, PreferredSizeNonEmpty) {
828 const std::string text("text");
829 InitStyledLabel(text);
830 EXPECT_FALSE(styled()->GetPreferredSize().IsEmpty());
831 }
832
833 // Verifies that GetPreferredSize() respects the existing wrapping.
TEST_F(StyledLabelTest,PreferredSizeRespectsWrapping)834 TEST_F(StyledLabelTest, PreferredSizeRespectsWrapping) {
835 const std::string text("Long text that can be split across lines");
836 InitStyledLabel(text);
837 gfx::Size size = styled()->GetPreferredSize();
838 size.set_width(size.width() / 2);
839 size.set_height(styled()->GetHeightForWidth(size.width()));
840 styled()->SetSize(size);
841 styled()->Layout();
842 const gfx::Size new_size = styled()->GetPreferredSize();
843 EXPECT_LE(new_size.width(), size.width());
844 EXPECT_EQ(new_size.height(), size.height());
845 }
846
847 // Verifies that calling a const method does not change the preferred size.
TEST_F(StyledLabelTest,PreferredSizeAcrossConstCall)848 TEST_F(StyledLabelTest, PreferredSizeAcrossConstCall) {
849 const std::string text("Long text that can be split across lines");
850 InitStyledLabel(text);
851 const gfx::Size size = styled()->GetPreferredSize();
852 styled()->GetHeightForWidth(size.width() / 2);
853 EXPECT_EQ(size, styled()->GetPreferredSize());
854 }
855
856 } // namespace views
857