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