| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/views/controls/styled_label.h" |
| |
| #include <stddef.h> |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/command_line.h" |
| #include "base/functional/callback.h" |
| #include "base/i18n/base_i18n_switches.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/icu_test_util.h" |
| #include "build/build_config.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "ui/base/ui_base_switches.h" |
| #include "ui/gfx/font_list.h" |
| #include "ui/gfx/text_constants.h" |
| #include "ui/views/border.h" |
| #include "ui/views/controls/link.h" |
| #include "ui/views/controls/link_fragment.h" |
| #include "ui/views/style/typography.h" |
| #include "ui/views/test/test_layout_provider.h" |
| #include "ui/views/test/test_views.h" |
| #include "ui/views/test/views_test_base.h" |
| #include "ui/views/test/views_test_utils.h" |
| #include "ui/views/widget/widget.h" |
| |
| using base::ASCIIToUTF16; |
| |
| namespace views { |
| |
| namespace { |
| |
| using ::testing::SizeIs; |
| |
| Label* LabelAt(StyledLabel* styled, |
| size_t index, |
| std::string expected_classname = Label::kViewClassName) { |
| View* const child = styled->children()[index]; |
| EXPECT_EQ(expected_classname, child->GetClassName()); |
| return static_cast<Label*>(child); |
| } |
| |
| int StyledLabelContentHeightForWidth(StyledLabel* styled, int w) { |
| return styled->GetHeightForWidth(w) - styled->GetInsets().height(); |
| } |
| |
| } // namespace |
| |
| class StyledLabelTest : public ViewsTestBase { |
| public: |
| StyledLabelTest() = default; |
| StyledLabelTest(const StyledLabelTest&) = delete; |
| StyledLabelTest& operator=(const StyledLabelTest&) = delete; |
| ~StyledLabelTest() override = default; |
| |
| protected: |
| StyledLabel* styled() const { return styled_.get(); } |
| |
| void InitStyledLabel(const std::string& ascii_text) { |
| styled_ = std::make_unique<StyledLabel>(); |
| styled_->SetText(ASCIIToUTF16(ascii_text)); |
| } |
| |
| private: |
| std::unique_ptr<StyledLabel> styled_; |
| }; |
| |
| class StyledLabelInWidgetTest : public ViewsTestBase { |
| public: |
| StyledLabelInWidgetTest() = default; |
| StyledLabelInWidgetTest(const StyledLabelInWidgetTest&) = delete; |
| StyledLabelInWidgetTest& operator=(const StyledLabelInWidgetTest&) = delete; |
| ~StyledLabelInWidgetTest() override = default; |
| |
| protected: |
| void SetUp() override { |
| ViewsTestBase::SetUp(); |
| |
| widget_ = CreateTestWidget(); |
| } |
| |
| void TearDown() override { |
| widget_.reset(); |
| |
| ViewsTestBase::TearDown(); |
| } |
| |
| StyledLabel* styled() const { return styled_; } |
| Widget* widget() const { return widget_.get(); } |
| |
| void InitStyledLabel(const std::string& ascii_text) { |
| View* container = widget_->SetContentsView(std::make_unique<View>()); |
| styled_ = container->AddChildView(std::make_unique<StyledLabel>()); |
| styled_->SetText(ASCIIToUTF16(ascii_text)); |
| } |
| |
| private: |
| raw_ptr<StyledLabel> styled_; |
| std::unique_ptr<Widget> widget_; |
| }; |
| |
| TEST_F(StyledLabelTest, NoWrapping) { |
| const std::string text("This is a test block of text"); |
| InitStyledLabel(text); |
| Label label(ASCIIToUTF16(text)); |
| const gfx::Size label_preferred_size = label.GetPreferredSize(); |
| EXPECT_EQ(label_preferred_size.height(), |
| StyledLabelContentHeightForWidth(styled(), |
| label_preferred_size.width() * 2)); |
| } |
| |
| TEST_F(StyledLabelTest, TrailingWhitespaceiIgnored) { |
| const std::string text("This is a test block of text "); |
| InitStyledLabel(text); |
| |
| styled()->SetBounds(0, 0, 1000, 1000); |
| test::RunScheduledLayout(styled()); |
| |
| ASSERT_EQ(1u, styled()->children().size()); |
| EXPECT_EQ(u"This is a test block of text", LabelAt(styled(), 0)->GetText()); |
| } |
| |
| TEST_F(StyledLabelTest, RespectLeadingWhitespace) { |
| const std::string text(" This is a test block of text"); |
| InitStyledLabel(text); |
| |
| styled()->SetBounds(0, 0, 1000, 1000); |
| test::RunScheduledLayout(styled()); |
| |
| ASSERT_EQ(1u, styled()->children().size()); |
| EXPECT_EQ(u" This is a test block of text", |
| LabelAt(styled(), 0)->GetText()); |
| } |
| |
| TEST_F(StyledLabelTest, RespectLeadingSpacesInNonFirstLine) { |
| const std::string indented_line = " indented line"; |
| const std::string text(std::string("First line\n") + indented_line); |
| InitStyledLabel(text); |
| styled()->SetBounds(0, 0, 1000, 1000); |
| test::RunScheduledLayout(styled()); |
| ASSERT_EQ(2u, styled()->children().size()); |
| EXPECT_EQ(ASCIIToUTF16(indented_line), LabelAt(styled(), 1)->GetText()); |
| } |
| |
| TEST_F(StyledLabelTest, CorrectWrapAtNewline) { |
| const std::string first_line = "Line one"; |
| const std::string second_line = " two"; |
| const std::string multiline_text(first_line + "\n" + second_line); |
| InitStyledLabel(multiline_text); |
| Label label(ASCIIToUTF16(first_line)); |
| gfx::Size label_preferred_size = label.GetPreferredSize(); |
| // Correct handling of \n and label width limit encountered at the same place |
| styled()->SetBounds(0, 0, label_preferred_size.width(), 1000); |
| test::RunScheduledLayout(styled()); |
| ASSERT_EQ(2u, styled()->children().size()); |
| EXPECT_EQ(ASCIIToUTF16(first_line), LabelAt(styled(), 0)->GetText()); |
| const auto* label_1 = LabelAt(styled(), 1); |
| EXPECT_EQ(ASCIIToUTF16(second_line), label_1->GetText()); |
| EXPECT_EQ(styled()->GetHeightForWidth(1000), label_1->bounds().bottom()); |
| } |
| |
| TEST_F(StyledLabelTest, FirstLineNotEmptyWhenLeadingWhitespaceTooLong) { |
| const std::string text(" a"); |
| InitStyledLabel(text); |
| |
| Label label(ASCIIToUTF16(text)); |
| gfx::Size label_preferred_size = label.GetPreferredSize(); |
| |
| styled()->SetBounds(0, 0, label_preferred_size.width() / 2, 1000); |
| test::RunScheduledLayout(styled()); |
| |
| ASSERT_EQ(1u, styled()->children().size()); |
| EXPECT_EQ(u"a", LabelAt(styled(), 0)->GetText()); |
| EXPECT_EQ(label_preferred_size.height(), |
| styled()->GetHeightForWidth(label_preferred_size.width() / 2)); |
| } |
| |
| TEST_F(StyledLabelTest, BasicWrapping) { |
| const std::string text("This is a test block of text"); |
| InitStyledLabel(text); |
| Label label(ASCIIToUTF16(text.substr(0, text.size() * 2 / 3))); |
| gfx::Size label_preferred_size = label.GetPreferredSize(); |
| EXPECT_EQ( |
| label_preferred_size.height() * 2, |
| StyledLabelContentHeightForWidth(styled(), label_preferred_size.width())); |
| |
| // Also respect the border. |
| styled()->SetBorder(CreateEmptyBorder(3)); |
| styled()->SetBounds( |
| 0, 0, styled()->GetInsets().width() + label_preferred_size.width(), |
| styled()->GetInsets().height() + 2 * label_preferred_size.height()); |
| test::RunScheduledLayout(styled()); |
| ASSERT_EQ(2u, styled()->children().size()); |
| EXPECT_EQ(3, styled()->children()[0]->x()); |
| EXPECT_EQ(3, styled()->children()[0]->y()); |
| EXPECT_EQ(styled()->height() - 3, styled()->children()[1]->bounds().bottom()); |
| } |
| |
| TEST_F(StyledLabelTest, AllowEmptyLines) { |
| const std::string text("one"); |
| InitStyledLabel(text); |
| int default_height = styled()->GetHeightForWidth(1000); |
| const std::string multiline_text("one\n\nthree"); |
| InitStyledLabel(multiline_text); |
| styled()->SetBounds(0, 0, 1000, 1000); |
| test::RunScheduledLayout(styled()); |
| EXPECT_EQ(3 * default_height, styled()->GetHeightForWidth(1000)); |
| ASSERT_EQ(2u, styled()->children().size()); |
| EXPECT_EQ(styled()->GetHeightForWidth(1000), |
| styled()->children()[1]->bounds().bottom()); |
| } |
| |
| TEST_F(StyledLabelTest, WrapLongWords) { |
| const std::string text("ThisIsTextAsASingleWord"); |
| InitStyledLabel(text); |
| Label label(ASCIIToUTF16(text.substr(0, text.size() * 2 / 3))); |
| gfx::Size label_preferred_size = label.GetPreferredSize(); |
| EXPECT_EQ( |
| label_preferred_size.height() * 2, |
| StyledLabelContentHeightForWidth(styled(), label_preferred_size.width())); |
| |
| styled()->SetBounds( |
| 0, 0, styled()->GetInsets().width() + label_preferred_size.width(), |
| styled()->GetInsets().height() + 2 * label_preferred_size.height()); |
| test::RunScheduledLayout(styled()); |
| |
| ASSERT_EQ(2u, styled()->children().size()); |
| ASSERT_EQ(gfx::Point(), styled()->origin()); |
| const auto* label_0 = LabelAt(styled(), 0); |
| const auto* label_1 = LabelAt(styled(), 1); |
| EXPECT_EQ(gfx::Point(), label_0->origin()); |
| EXPECT_EQ(gfx::Point(0, styled()->height() / 2), label_1->origin()); |
| |
| EXPECT_FALSE(label_0->GetText().empty()); |
| EXPECT_FALSE(label_1->GetText().empty()); |
| EXPECT_EQ(ASCIIToUTF16(text), label_0->GetText() + label_1->GetText()); |
| } |
| |
| TEST_F(StyledLabelTest, CreateLinks) { |
| const std::string text("This is a test block of text."); |
| InitStyledLabel(text); |
| |
| // Without links, there should be no focus border. |
| EXPECT_TRUE(styled()->GetInsets().IsEmpty()); |
| |
| // Now let's add some links. |
| styled()->AddStyleRange( |
| gfx::Range(0, 1), |
| StyledLabel::RangeStyleInfo::CreateForLink(base::RepeatingClosure())); |
| styled()->AddStyleRange( |
| gfx::Range(1, 2), |
| StyledLabel::RangeStyleInfo::CreateForLink(base::RepeatingClosure())); |
| styled()->AddStyleRange( |
| gfx::Range(10, 11), |
| StyledLabel::RangeStyleInfo::CreateForLink(base::RepeatingClosure())); |
| styled()->AddStyleRange( |
| gfx::Range(12, 13), |
| StyledLabel::RangeStyleInfo::CreateForLink(base::RepeatingClosure())); |
| |
| // Insets shouldn't change when links are added, since the links indicate |
| // focus by adding an underline instead. |
| EXPECT_TRUE(styled()->GetInsets().IsEmpty()); |
| |
| // Verify layout creates the right number of children. |
| styled()->SetBounds(0, 0, 1000, 1000); |
| test::RunScheduledLayout(styled()); |
| EXPECT_EQ(7u, styled()->children().size()); |
| } |
| |
| TEST_F(StyledLabelTest, StyledRangeCustomFontUnderlined) { |
| const std::string text("This is a test block of text, "); |
| const std::string underlined_text("and this should be undelined"); |
| InitStyledLabel(text + underlined_text); |
| StyledLabel::RangeStyleInfo style_info; |
| style_info.tooltip = u"tooltip"; |
| style_info.custom_font = |
| styled()->GetFontList().DeriveWithStyle(gfx::Font::UNDERLINE); |
| styled()->AddStyleRange( |
| gfx::Range(text.size(), text.size() + underlined_text.size()), |
| style_info); |
| |
| styled()->SetBounds(0, 0, 1000, 1000); |
| test::RunScheduledLayout(styled()); |
| |
| ASSERT_EQ(2u, styled()->children().size()); |
| EXPECT_EQ(gfx::Font::UNDERLINE, |
| LabelAt(styled(), 1)->font_list().GetFontStyle()); |
| } |
| |
| TEST_F(StyledLabelTest, StyledRangeTextStyleBold) { |
| test::TestLayoutProvider bold_provider; |
| const std::string bold_text( |
| "This is a block of text whose style will be set to BOLD in the test"); |
| const std::string text(" normal text"); |
| InitStyledLabel(bold_text + text); |
| |
| // Pretend disabled text becomes bold for testing. |
| auto details = |
| bold_provider.GetFontDetails(style::CONTEXT_LABEL, style::STYLE_DISABLED); |
| details.weight = gfx::Font::Weight::BOLD; |
| bold_provider.SetFontDetails(style::CONTEXT_LABEL, style::STYLE_DISABLED, |
| details); |
| |
| StyledLabel::RangeStyleInfo style_info; |
| style_info.text_style = style::STYLE_DISABLED; |
| styled()->AddStyleRange(gfx::Range(0u, bold_text.size()), style_info); |
| |
| // Calculate the bold text width if it were a pure label view, both with bold |
| // and normal style. |
| Label label(ASCIIToUTF16(bold_text)); |
| const gfx::Size normal_label_size = label.GetPreferredSize(); |
| label.SetFontList( |
| label.font_list().DeriveWithWeight(gfx::Font::Weight::BOLD)); |
| const gfx::Size bold_label_size = label.GetPreferredSize(); |
| |
| ASSERT_GE(bold_label_size.width(), normal_label_size.width()); |
| |
| // Set the width so |bold_text| doesn't fit on a single line with bold style, |
| // but does with normal font style. |
| int styled_width = (normal_label_size.width() + bold_label_size.width()) / 2; |
| int pref_height = styled()->GetHeightForWidth(styled_width); |
| |
| // Sanity check that |bold_text| with normal font style would fit on a single |
| // line in a styled label with width |styled_width|. |
| StyledLabel unstyled; |
| unstyled.SetText(ASCIIToUTF16(bold_text)); |
| unstyled.SetBounds(0, 0, styled_width, pref_height); |
| test::RunScheduledLayout(&unstyled); |
| EXPECT_EQ(1u, unstyled.children().size()); |
| |
| styled()->SetBounds(0, 0, styled_width, pref_height); |
| test::RunScheduledLayout(styled()); |
| |
| ASSERT_EQ(3u, styled()->children().size()); |
| |
| // The bold text should be broken up into two parts. |
| const auto* label_0 = LabelAt(styled(), 0); |
| const auto* label_1 = LabelAt(styled(), 1); |
| const auto* label_2 = LabelAt(styled(), 2); |
| EXPECT_EQ(gfx::Font::Weight::BOLD, label_0->font_list().GetFontWeight()); |
| EXPECT_EQ(gfx::Font::Weight::BOLD, label_1->font_list().GetFontWeight()); |
| EXPECT_EQ(gfx::Font::NORMAL, label_2->font_list().GetFontStyle()); |
| |
| // The second bold part should start on a new line. |
| EXPECT_EQ(0, label_0->x()); |
| EXPECT_EQ(0, label_1->x()); |
| EXPECT_EQ(label_1->bounds().right(), label_2->x()); |
| } |
| |
| TEST_F(StyledLabelInWidgetTest, Color) { |
| const std::string text_blue("BLUE"); |
| const std::string text_link("link"); |
| const std::string text("word"); |
| InitStyledLabel(text_blue + text_link + text); |
| |
| StyledLabel::RangeStyleInfo style_info_blue; |
| style_info_blue.override_color = SK_ColorBLUE; |
| styled()->AddStyleRange(gfx::Range(0u, text_blue.size()), style_info_blue); |
| |
| StyledLabel::RangeStyleInfo style_info_link = |
| StyledLabel::RangeStyleInfo::CreateForLink(base::RepeatingClosure()); |
| styled()->AddStyleRange( |
| gfx::Range(text_blue.size(), text_blue.size() + text_link.size()), |
| style_info_link); |
| |
| styled()->SetBounds(0, 0, 1000, 1000); |
| test::RunScheduledLayout(styled()); |
| |
| // The code below is not prepared to deal with dark mode. |
| auto* const native_theme = widget()->GetNativeTheme(); |
| native_theme->set_use_dark_colors(false); |
| native_theme->NotifyOnNativeThemeUpdated(); |
| |
| auto* container = widget()->GetContentsView(); |
| // Obtain the default text color for a label. |
| Label* label = |
| container->AddChildView(std::make_unique<Label>(ASCIIToUTF16(text))); |
| const SkColor kDefaultTextColor = label->GetEnabledColor(); |
| |
| // Obtain the default text color for a link. |
| Link* link = |
| container->AddChildView(std::make_unique<Link>(ASCIIToUTF16(text_link))); |
| const SkColor kDefaultLinkColor = link->GetEnabledColor(); |
| |
| ASSERT_EQ(3u, styled()->children().size()); |
| EXPECT_EQ(SK_ColorBLUE, LabelAt(styled(), 0)->GetEnabledColor()); |
| EXPECT_EQ( |
| kDefaultLinkColor, |
| LabelAt(styled(), 1, LinkFragment::kViewClassName)->GetEnabledColor()); |
| EXPECT_EQ(kDefaultTextColor, LabelAt(styled(), 2)->GetEnabledColor()); |
| |
| // Test adjusted color readability. |
| styled()->SetDisplayedOnBackgroundColor(SK_ColorBLACK); |
| test::RunScheduledLayout(styled()); |
| label->SetBackgroundColor(SK_ColorBLACK); |
| |
| const SkColor kAdjustedTextColor = label->GetEnabledColor(); |
| EXPECT_NE(kAdjustedTextColor, kDefaultTextColor); |
| EXPECT_EQ(kAdjustedTextColor, LabelAt(styled(), 2)->GetEnabledColor()); |
| } |
| |
| TEST_F(StyledLabelInWidgetTest, SetBackgroundColor) { |
| InitStyledLabel("test label"); |
| |
| styled()->SetBounds(0, 0, 1000, 1000); |
| test::RunScheduledLayout(styled()); |
| |
| ASSERT_THAT(styled()->children(), SizeIs(1u)); |
| // The default background color is `ui::kColorDialogBackground`. |
| EXPECT_EQ(widget()->GetColorProvider()->GetColor(ui::kColorDialogBackground), |
| LabelAt(styled(), 0)->GetBackgroundColor()); |
| |
| styled()->SetDisplayedOnBackgroundColor(SK_ColorBLUE); |
| EXPECT_EQ(SK_ColorBLUE, LabelAt(styled(), 0)->GetBackgroundColor()); |
| |
| styled()->SetDisplayedOnBackgroundColor(ui::kColorAlertHighSeverity); |
| EXPECT_EQ(widget()->GetColorProvider()->GetColor(ui::kColorAlertHighSeverity), |
| LabelAt(styled(), 0)->GetBackgroundColor()); |
| |
| // Setting a color overwrites the color id. |
| styled()->SetDisplayedOnBackgroundColor(SK_ColorCYAN); |
| EXPECT_EQ(SK_ColorCYAN, LabelAt(styled(), 0)->GetBackgroundColor()); |
| } |
| |
| TEST_F(StyledLabelInWidgetTest, SetBackgroundColorIdReactsToThemeChange) { |
| InitStyledLabel("test label"); |
| |
| styled()->SetBounds(0, 0, 1000, 1000); |
| test::RunScheduledLayout(styled()); |
| |
| ASSERT_THAT(styled()->children(), SizeIs(1u)); |
| auto* const native_theme = widget()->GetNativeTheme(); |
| native_theme->set_use_dark_colors(true); |
| native_theme->NotifyOnNativeThemeUpdated(); |
| EXPECT_EQ(widget()->GetColorProvider()->GetColor(ui::kColorDialogBackground), |
| LabelAt(styled(), 0)->GetBackgroundColor()); |
| |
| native_theme->set_use_dark_colors(false); |
| native_theme->NotifyOnNativeThemeUpdated(); |
| EXPECT_EQ(widget()->GetColorProvider()->GetColor(ui::kColorDialogBackground), |
| LabelAt(styled(), 0)->GetBackgroundColor()); |
| |
| styled()->SetDisplayedOnBackgroundColor(ui::kColorAlertHighSeverity); |
| EXPECT_EQ(widget()->GetColorProvider()->GetColor(ui::kColorAlertHighSeverity), |
| LabelAt(styled(), 0)->GetBackgroundColor()); |
| |
| native_theme->set_use_dark_colors(true); |
| native_theme->NotifyOnNativeThemeUpdated(); |
| EXPECT_EQ(widget()->GetColorProvider()->GetColor(ui::kColorAlertHighSeverity), |
| LabelAt(styled(), 0)->GetBackgroundColor()); |
| } |
| |
| TEST_F(StyledLabelTest, StyledRangeWithTooltip) { |
| const std::string text("This is a test block of text, "); |
| const std::string tooltip_text("this should have a tooltip,"); |
| const std::string normal_text(" this should not have a tooltip, "); |
| const std::string link_text("and this should be a link"); |
| |
| const size_t tooltip_start = text.size(); |
| const size_t link_start = |
| text.size() + tooltip_text.size() + normal_text.size(); |
| |
| InitStyledLabel(text + tooltip_text + normal_text + link_text); |
| StyledLabel::RangeStyleInfo tooltip_style; |
| tooltip_style.tooltip = u"tooltip"; |
| styled()->AddStyleRange( |
| gfx::Range(tooltip_start, tooltip_start + tooltip_text.size()), |
| tooltip_style); |
| styled()->AddStyleRange( |
| gfx::Range(link_start, link_start + link_text.size()), |
| StyledLabel::RangeStyleInfo::CreateForLink(base::RepeatingClosure())); |
| |
| // Break line inside the range with the tooltip. |
| Label label( |
| ASCIIToUTF16(text + tooltip_text.substr(0, tooltip_text.size() - 3))); |
| gfx::Size label_preferred_size = label.GetPreferredSize(); |
| int pref_height = styled()->GetHeightForWidth(label_preferred_size.width()); |
| EXPECT_EQ(label_preferred_size.height() * 3, |
| pref_height - styled()->GetInsets().height()); |
| |
| styled()->SetBounds(0, 0, label_preferred_size.width(), pref_height); |
| test::RunScheduledLayout(styled()); |
| |
| EXPECT_EQ(label_preferred_size.width(), styled()->width()); |
| |
| ASSERT_EQ(6u, styled()->children().size()); |
| |
| // The labels shouldn't be offset to cater for focus rings. |
| EXPECT_EQ(0, styled()->children()[0]->x()); |
| EXPECT_EQ(0, styled()->children()[2]->x()); |
| |
| EXPECT_EQ(styled()->children()[0]->bounds().right(), |
| styled()->children()[1]->x()); |
| EXPECT_EQ(styled()->children()[2]->bounds().right(), |
| styled()->children()[3]->x()); |
| |
| std::u16string tooltip = |
| styled()->children()[1]->GetTooltipText(gfx::Point(1, 1)); |
| EXPECT_EQ(u"tooltip", tooltip); |
| tooltip = styled()->children()[2]->GetTooltipText(gfx::Point(1, 1)); |
| EXPECT_EQ(u"tooltip", tooltip); |
| } |
| |
| TEST_F(StyledLabelTest, SetTextContextAndDefaultStyle) { |
| const std::string text("This is a test block of text."); |
| InitStyledLabel(text); |
| styled()->SetTextContext(style::CONTEXT_DIALOG_TITLE); |
| styled()->SetDefaultTextStyle(style::STYLE_DISABLED); |
| Label label(ASCIIToUTF16(text), style::CONTEXT_DIALOG_TITLE, |
| style::STYLE_DISABLED); |
| |
| styled()->SetBounds(0, 0, label.GetPreferredSize().width(), |
| label.GetPreferredSize().height()); |
| |
| // Make sure we have the same sizing as a label with the same style. |
| EXPECT_EQ(label.GetPreferredSize().height(), styled()->height()); |
| EXPECT_EQ(label.GetPreferredSize().width(), styled()->width()); |
| |
| test::RunScheduledLayout(styled()); |
| ASSERT_EQ(1u, styled()->children().size()); |
| Label* sublabel = LabelAt(styled(), 0); |
| EXPECT_EQ(style::CONTEXT_DIALOG_TITLE, sublabel->GetTextContext()); |
| |
| EXPECT_NE(SK_ColorBLACK, label.GetEnabledColor()); // Sanity check, |
| EXPECT_EQ(label.GetEnabledColor(), sublabel->GetEnabledColor()); |
| } |
| |
| TEST_F(StyledLabelTest, LineHeight) { |
| const std::string text("one\ntwo\nthree"); |
| InitStyledLabel(text); |
| styled()->SetLineHeight(18); |
| EXPECT_EQ(18 * 3, styled()->GetHeightForWidth(100)); |
| } |
| |
| TEST_F(StyledLabelTest, LineHeightWithBorder) { |
| const std::string text("one\ntwo\nthree"); |
| InitStyledLabel(text); |
| styled()->SetLineHeight(18); |
| styled()->SetBorder(views::CreateSolidBorder(1, SK_ColorGRAY)); |
| EXPECT_EQ(18 * 3 + 2, styled()->GetHeightForWidth(100)); |
| } |
| |
| TEST_F(StyledLabelTest, LineHeightWithLink) { |
| const std::string text("one\ntwo\nthree"); |
| InitStyledLabel(text); |
| styled()->SetLineHeight(18); |
| |
| styled()->AddStyleRange( |
| gfx::Range(0, 3), |
| StyledLabel::RangeStyleInfo::CreateForLink(base::RepeatingClosure())); |
| styled()->AddStyleRange( |
| gfx::Range(4, 7), |
| StyledLabel::RangeStyleInfo::CreateForLink(base::RepeatingClosure())); |
| styled()->AddStyleRange( |
| gfx::Range(8, 13), |
| StyledLabel::RangeStyleInfo::CreateForLink(base::RepeatingClosure())); |
| EXPECT_EQ(18 * 3, styled()->GetHeightForWidth(100)); |
| } |
| |
| TEST_F(StyledLabelTest, HandleEmptyLayout) { |
| const std::string text("This is a test block of text."); |
| InitStyledLabel(text); |
| test::RunScheduledLayout(styled()); |
| EXPECT_EQ(0u, styled()->children().size()); |
| } |
| |
| TEST_F(StyledLabelTest, CacheSize) { |
| const int preferred_height = 50; |
| const int preferred_width = 100; |
| const std::string text("This is a test block of text."); |
| const std::u16string another_text( |
| u"This is a test block of text. This text is much longer than previous"); |
| |
| InitStyledLabel(text); |
| |
| // we should be able to calculate height without any problem |
| // no controls should be created |
| int precalculated_height = styled()->GetHeightForWidth(preferred_width); |
| EXPECT_LT(0, precalculated_height); |
| EXPECT_EQ(0u, styled()->children().size()); |
| |
| styled()->SetBounds(0, 0, preferred_width, preferred_height); |
| test::RunScheduledLayout(styled()); |
| |
| // controls should be created after layout |
| // height should be the same as precalculated |
| int real_height = styled()->GetHeightForWidth(styled()->width()); |
| View* first_child_after_layout = |
| styled()->children().empty() ? nullptr : styled()->children().front(); |
| EXPECT_LT(0u, styled()->children().size()); |
| EXPECT_LT(0, real_height); |
| EXPECT_EQ(real_height, precalculated_height); |
| |
| // another call to Layout should not kill and recreate all controls |
| test::RunScheduledLayout(styled()); |
| View* first_child_after_second_layout = |
| styled()->children().empty() ? nullptr : styled()->children().front(); |
| EXPECT_EQ(first_child_after_layout, first_child_after_second_layout); |
| |
| // if text is changed: |
| // layout should be recalculated |
| // all controls should be recreated |
| styled()->SetText(another_text); |
| int updated_height = styled()->GetHeightForWidth(styled()->width()); |
| EXPECT_NE(updated_height, real_height); |
| View* first_child_after_text_update = |
| styled()->children().empty() ? nullptr : styled()->children().front(); |
| EXPECT_NE(first_child_after_text_update, first_child_after_layout); |
| } |
| |
| TEST_F(StyledLabelTest, Border) { |
| const std::string text("One line"); |
| InitStyledLabel(text); |
| Label label(ASCIIToUTF16(text)); |
| gfx::Size label_preferred_size = label.GetPreferredSize(); |
| styled()->SetBorder(CreateEmptyBorder(gfx::Insets::TLBR(5, 10, 6, 20))); |
| styled()->SetBounds(0, 0, 1000, 0); |
| test::RunScheduledLayout(styled()); |
| EXPECT_EQ( |
| label_preferred_size.height() + 5 /*top border*/ + 6 /*bottom border*/, |
| styled()->GetPreferredSize().height()); |
| EXPECT_EQ( |
| label_preferred_size.width() + 10 /*left border*/ + 20 /*right border*/, |
| styled()->GetPreferredSize().width()); |
| } |
| |
| TEST_F(StyledLabelTest, LineHeightWithShorterCustomView) { |
| const std::string text("one "); |
| InitStyledLabel(text); |
| int default_height = styled()->GetHeightForWidth(1000); |
| |
| const std::string custom_view_text("with custom view"); |
| const int less_height = 10; |
| std::unique_ptr<View> custom_view = std::make_unique<StaticSizedView>( |
| gfx::Size(20, default_height - less_height)); |
| StyledLabel::RangeStyleInfo style_info; |
| style_info.custom_view = custom_view.get(); |
| InitStyledLabel(text + custom_view_text); |
| styled()->AddStyleRange( |
| gfx::Range(text.size(), text.size() + custom_view_text.size()), |
| style_info); |
| styled()->AddCustomView(std::move(custom_view)); |
| EXPECT_EQ(default_height, styled()->GetHeightForWidth(100)); |
| } |
| |
| TEST_F(StyledLabelTest, LineHeightWithTallerCustomView) { |
| const std::string text("one "); |
| InitStyledLabel(text); |
| int default_height = styled()->GetHeightForWidth(100); |
| |
| const std::string custom_view_text("with custom view"); |
| const int more_height = 10; |
| std::unique_ptr<View> custom_view = std::make_unique<StaticSizedView>( |
| gfx::Size(20, default_height + more_height)); |
| StyledLabel::RangeStyleInfo style_info; |
| style_info.custom_view = custom_view.get(); |
| InitStyledLabel(text + custom_view_text); |
| styled()->AddStyleRange( |
| gfx::Range(text.size(), text.size() + custom_view_text.size()), |
| style_info); |
| styled()->AddCustomView(std::move(custom_view)); |
| EXPECT_EQ(default_height + more_height, styled()->GetHeightForWidth(100)); |
| } |
| |
| TEST_F(StyledLabelTest, LineWrapperWithCustomView) { |
| const std::string text_before("one "); |
| InitStyledLabel(text_before); |
| int default_height = styled()->GetHeightForWidth(100); |
| const std::string custom_view_text("two with custom view "); |
| const std::string text_after("three"); |
| |
| int custom_view_height = 25; |
| std::unique_ptr<View> custom_view = |
| std::make_unique<StaticSizedView>(gfx::Size(200, custom_view_height)); |
| StyledLabel::RangeStyleInfo style_info; |
| style_info.custom_view = custom_view.get(); |
| InitStyledLabel(text_before + custom_view_text + text_after); |
| styled()->AddStyleRange( |
| gfx::Range(text_before.size(), |
| text_before.size() + custom_view_text.size()), |
| style_info); |
| styled()->AddCustomView(std::move(custom_view)); |
| EXPECT_EQ(default_height * 2 + custom_view_height, |
| styled()->GetHeightForWidth(100)); |
| } |
| |
| TEST_F(StyledLabelTest, AlignmentInLTR) { |
| const std::string text("text"); |
| InitStyledLabel(text); |
| styled()->SetBounds(0, 0, 1000, 1000); |
| test::RunScheduledLayout(styled()); |
| const auto& children = styled()->children(); |
| ASSERT_EQ(1u, children.size()); |
| |
| // Test the default alignment puts the text on the leading side (left). |
| EXPECT_EQ(0, children.front()->bounds().x()); |
| |
| // Setting |ALIGN_RIGHT| indicates the text should be aligned to the trailing |
| // side, and hence its trailing side coordinates (i.e. right) should align |
| // with the trailing side coordinate of the label (right). |
| styled()->SetHorizontalAlignment(gfx::ALIGN_RIGHT); |
| test::RunScheduledLayout(styled()); |
| EXPECT_EQ(1000, children.front()->bounds().right()); |
| |
| // Setting |ALIGN_LEFT| indicates the text should be aligned to the leading |
| // side, and hence its leading side coordinates (i.e. x) should align with the |
| // leading side coordinate of the label (x). |
| styled()->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| test::RunScheduledLayout(styled()); |
| EXPECT_EQ(0, children.front()->bounds().x()); |
| |
| styled()->SetHorizontalAlignment(gfx::ALIGN_CENTER); |
| test::RunScheduledLayout(styled()); |
| Label label(ASCIIToUTF16(text)); |
| EXPECT_EQ((1000 - label.GetPreferredSize().width()) / 2, |
| children.front()->bounds().x()); |
| } |
| |
| TEST_F(StyledLabelTest, AlignmentInRTL) { |
| // |g_icu_text_direction| is cached to prevent reading new commandline switch. |
| // Set |g_icu_text_direction| to |UNKNOWN_DIRECTION| in order to read the new |
| // commandline switch. |
| base::test::ScopedRestoreICUDefaultLocale scoped_locale("en_US"); |
| base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( |
| switches::kForceUIDirection, switches::kForceDirectionRTL); |
| |
| const std::string text("text"); |
| InitStyledLabel(text); |
| styled()->SetBounds(0, 0, 1000, 1000); |
| test::RunScheduledLayout(styled()); |
| const auto& children = styled()->children(); |
| ASSERT_EQ(1u, children.size()); |
| |
| // Test the default alignment puts the text on the leading side (right). |
| // Note that x-coordinates in RTL place the origin (0) on the right. |
| EXPECT_EQ(0, children.front()->bounds().x()); |
| |
| // Setting |ALIGN_RIGHT| indicates the text should be aligned to the trailing |
| // side, and hence its trailing side coordinates (i.e. right) should align |
| // with the trailing side coordinate of the label (right). |
| styled()->SetHorizontalAlignment(gfx::ALIGN_RIGHT); |
| test::RunScheduledLayout(styled()); |
| EXPECT_EQ(1000, children.front()->bounds().right()); |
| |
| // Setting |ALIGN_LEFT| indicates the text should be aligned to the leading |
| // side, and hence its leading side coordinates (i.e. x) should align with the |
| // leading side coordinate of the label (x). |
| styled()->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| test::RunScheduledLayout(styled()); |
| EXPECT_EQ(0, children.front()->bounds().x()); |
| |
| styled()->SetHorizontalAlignment(gfx::ALIGN_CENTER); |
| test::RunScheduledLayout(styled()); |
| Label label(ASCIIToUTF16(text)); |
| EXPECT_EQ((1000 - label.GetPreferredSize().width()) / 2, |
| children.front()->bounds().x()); |
| } |
| |
| TEST_F(StyledLabelTest, ViewsCenteredWithLinkAndCustomView) { |
| const std::string text("This is a test block of text, "); |
| const std::string link_text("and this should be a link"); |
| const std::string custom_view_text("And this is a custom view"); |
| InitStyledLabel(text + link_text + custom_view_text); |
| styled()->AddStyleRange( |
| gfx::Range(text.size(), text.size() + link_text.size()), |
| StyledLabel::RangeStyleInfo::CreateForLink(base::RepeatingClosure())); |
| |
| int custom_view_height = 25; |
| std::unique_ptr<View> custom_view = |
| std::make_unique<StaticSizedView>(gfx::Size(20, custom_view_height)); |
| StyledLabel::RangeStyleInfo style_info; |
| style_info.custom_view = custom_view.get(); |
| styled()->AddStyleRange( |
| gfx::Range(text.size() + link_text.size(), |
| text.size() + link_text.size() + custom_view_text.size()), |
| style_info); |
| styled()->AddCustomView(std::move(custom_view)); |
| |
| styled()->SetBounds(0, 0, 1000, 500); |
| test::RunScheduledLayout(styled()); |
| const int height = styled()->GetPreferredSize().height(); |
| for (const auto* child : styled()->children()) |
| EXPECT_EQ(height / 2, child->bounds().CenterPoint().y()); |
| } |
| |
| TEST_F(StyledLabelTest, ViewsCenteredForEvenAndOddSizes) { |
| constexpr int kViewWidth = 30; |
| for (int height : {60, 61}) { |
| InitStyledLabel("abc"); |
| |
| const int view_heights[] = {height, height / 2, height / 2 + 1}; |
| for (uint32_t i = 0; i < 3; ++i) { |
| auto view = std::make_unique<StaticSizedView>( |
| gfx::Size(kViewWidth, view_heights[i])); |
| StyledLabel::RangeStyleInfo style_info; |
| style_info.custom_view = view.get(); |
| styled()->AddStyleRange(gfx::Range(i, i + 1), style_info); |
| styled()->AddCustomView(std::move(view)); |
| } |
| |
| styled()->SetBounds(0, 0, kViewWidth * 3, height); |
| test::RunScheduledLayout(styled()); |
| |
| for (const auto* child : styled()->children()) |
| EXPECT_EQ(height / 2, child->bounds().CenterPoint().y()); |
| } |
| } |
| |
| TEST_F(StyledLabelTest, CacheSizeWithAlignment) { |
| const std::string text("text"); |
| InitStyledLabel(text); |
| styled()->SetHorizontalAlignment(gfx::ALIGN_RIGHT); |
| styled()->SetBounds(0, 0, 1000, 1000); |
| test::RunScheduledLayout(styled()); |
| ASSERT_EQ(1u, styled()->children().size()); |
| const View* child = styled()->children().front(); |
| EXPECT_EQ(1000, child->bounds().right()); |
| |
| styled()->SetSize({800, 1000}); |
| test::RunScheduledLayout(styled()); |
| ASSERT_EQ(1u, styled()->children().size()); |
| const View* new_child = styled()->children().front(); |
| EXPECT_EQ(child, new_child); |
| EXPECT_EQ(800, new_child->bounds().right()); |
| } |
| |
| // Verifies that calling SizeToFit() on a label which requires less width still |
| // causes it to take the whole requested width. |
| TEST_F(StyledLabelTest, SizeToFit) { |
| const std::string text("text"); |
| InitStyledLabel(text); |
| styled()->SetHorizontalAlignment(gfx::ALIGN_RIGHT); |
| styled()->SizeToFit(1000); |
| test::RunScheduledLayout(styled()); |
| ASSERT_EQ(1u, styled()->children().size()); |
| EXPECT_EQ(1000, styled()->children().front()->bounds().right()); |
| } |
| |
| // Verifies that a non-empty label has a preferred size by default. |
| TEST_F(StyledLabelTest, PreferredSizeNonEmpty) { |
| const std::string text("text"); |
| InitStyledLabel(text); |
| EXPECT_FALSE(styled()->GetPreferredSize().IsEmpty()); |
| } |
| |
| // Verifies that GetPreferredSize() respects the existing wrapping. |
| TEST_F(StyledLabelTest, PreferredSizeRespectsWrapping) { |
| const std::string text("Long text that can be split across lines"); |
| InitStyledLabel(text); |
| gfx::Size size = styled()->GetPreferredSize(); |
| size.set_width(size.width() / 2); |
| size.set_height(styled()->GetHeightForWidth(size.width())); |
| styled()->SetSize(size); |
| test::RunScheduledLayout(styled()); |
| const gfx::Size new_size = styled()->GetPreferredSize(); |
| EXPECT_LE(new_size.width(), size.width()); |
| EXPECT_EQ(new_size.height(), size.height()); |
| } |
| |
| // Verifies that calling a const method does not change the preferred size. |
| TEST_F(StyledLabelTest, PreferredSizeAcrossConstCall) { |
| const std::string text("Long text that can be split across lines"); |
| InitStyledLabel(text); |
| const gfx::Size size = styled()->GetPreferredSize(); |
| styled()->GetHeightForWidth(size.width() / 2); |
| EXPECT_EQ(size, styled()->GetPreferredSize()); |
| } |
| |
| } // namespace views |