blob: 7ee645335805c9f1160f7b8a243af170f5f7b9a5 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/views/controls/button/label_button.h"
#include <algorithm>
#include <string>
#include <utility>
#include "base/command_line.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/ui_base_switches.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/font_list.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/gfx/text_utils.h"
#include "ui/native_theme/native_theme.h"
#include "ui/native_theme/native_theme_base.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/animation/test/ink_drop_host_view_test_api.h"
#include "ui/views/animation/test/test_ink_drop.h"
#include "ui/views/buildflags.h"
#include "ui/views/layout/layout_provider.h"
#include "ui/views/style/platform_style.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/view_test_api.h"
#include "ui/views/widget/widget_utils.h"
using base::ASCIIToUTF16;
namespace {
gfx::ImageSkia CreateTestImage(int width, int height) {
SkBitmap bitmap;
bitmap.allocN32Pixels(width, height);
return gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
}
// A test theme that always returns a fixed color.
class TestNativeTheme : public ui::NativeThemeBase {
public:
static constexpr SkColor kSystemColor = SK_ColorRED;
TestNativeTheme() = default;
TestNativeTheme(const TestNativeTheme&) = delete;
TestNativeTheme& operator=(const TestNativeTheme&) = delete;
// NativeThemeBase:
SkColor GetSystemColorDeprecated(ColorId color_id,
ColorScheme color_scheme,
bool apply_processing) const override {
return kSystemColor;
}
};
constexpr SkColor TestNativeTheme::kSystemColor;
} // namespace
namespace views {
// Testing button that exposes protected methods.
class TestLabelButton : public LabelButton {
public:
explicit TestLabelButton(const std::u16string& text = std::u16string(),
int button_context = style::CONTEXT_BUTTON)
: LabelButton(Button::PressedCallback(), text, button_context) {}
using LabelButton::GetVisualState;
using LabelButton::image;
using LabelButton::label;
using LabelButton::OnThemeChanged;
private:
DISALLOW_COPY_AND_ASSIGN(TestLabelButton);
};
class LabelButtonTest : public test::WidgetTest {
public:
LabelButtonTest() = default;
// testing::Test:
void SetUp() override {
WidgetTest::SetUp();
// Make a Widget to host the button. This ensures appropriate borders are
// used (which could be derived from the Widget's NativeTheme).
test_widget_ = CreateTopLevelPlatformWidget();
// Ensure the Widget is active, since LabelButton appearance in inactive
// Windows is platform-dependent.
test_widget_->Show();
// The test code below is not prepared to handle dark mode.
test_widget_->GetNativeTheme()->set_use_dark_colors(false);
button_ = test_widget_->GetContentsView()->AddChildView(
std::make_unique<TestLabelButton>());
// Establish the expected text colors for testing changes due to state.
themed_normal_text_color_ = button_->GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_LabelEnabledColor);
// For styled buttons only, platforms other than Desktop Linux either ignore
// NativeTheme and use a hardcoded black or (on Mac) have a NativeTheme that
// reliably returns black.
styled_normal_text_color_ = SK_ColorBLACK;
#if (defined(OS_LINUX) || defined(OS_CHROMEOS)) && \
BUILDFLAG(ENABLE_DESKTOP_AURA)
// The Linux theme provides a non-black highlight text color, but it's not
// used for styled buttons.
styled_highlight_text_color_ = styled_normal_text_color_ =
button_->GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_ButtonEnabledColor);
#else
styled_highlight_text_color_ = styled_normal_text_color_;
#endif
}
void TearDown() override {
test_widget_->CloseNow();
WidgetTest::TearDown();
}
protected:
TestLabelButton* button_ = nullptr;
SkColor themed_normal_text_color_ = 0;
SkColor styled_normal_text_color_ = 0;
SkColor styled_highlight_text_color_ = 0;
private:
Widget* test_widget_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(LabelButtonTest);
};
TEST_F(LabelButtonTest, FocusBehavior) {
EXPECT_EQ(PlatformStyle::kDefaultFocusBehavior, button_->GetFocusBehavior());
}
TEST_F(LabelButtonTest, Init) {
const std::u16string text(u"abc");
TestLabelButton button(text);
EXPECT_TRUE(button.GetImage(Button::STATE_NORMAL).isNull());
EXPECT_TRUE(button.GetImage(Button::STATE_HOVERED).isNull());
EXPECT_TRUE(button.GetImage(Button::STATE_PRESSED).isNull());
EXPECT_TRUE(button.GetImage(Button::STATE_DISABLED).isNull());
EXPECT_EQ(text, button.GetText());
ui::AXNodeData accessible_node_data;
button.GetAccessibleNodeData(&accessible_node_data);
EXPECT_EQ(ax::mojom::Role::kButton, accessible_node_data.role);
EXPECT_EQ(text, accessible_node_data.GetString16Attribute(
ax::mojom::StringAttribute::kName));
EXPECT_FALSE(button.GetIsDefault());
EXPECT_EQ(Button::STATE_NORMAL, button.GetState());
EXPECT_EQ(button.image()->parent(), &button);
EXPECT_EQ(button.label()->parent(), &button);
}
TEST_F(LabelButtonTest, Label) {
EXPECT_TRUE(button_->GetText().empty());
const gfx::FontList font_list = button_->label()->font_list();
const std::u16string short_text(u"abcdefghijklm");
const std::u16string long_text(u"abcdefghijklmnopqrstuvwxyz");
const int short_text_width = gfx::GetStringWidth(short_text, font_list);
const int long_text_width = gfx::GetStringWidth(long_text, font_list);
EXPECT_LT(button_->GetPreferredSize().width(), short_text_width);
button_->SetText(short_text);
EXPECT_GT(button_->GetPreferredSize().height(), font_list.GetHeight());
EXPECT_GT(button_->GetPreferredSize().width(), short_text_width);
EXPECT_LT(button_->GetPreferredSize().width(), long_text_width);
button_->SetText(long_text);
EXPECT_GT(button_->GetPreferredSize().width(), long_text_width);
button_->SetText(short_text);
EXPECT_GT(button_->GetPreferredSize().width(), short_text_width);
EXPECT_LT(button_->GetPreferredSize().width(), long_text_width);
// Clamp the size to a maximum value.
button_->SetText(long_text);
button_->SetMaxSize(gfx::Size(short_text_width, 1));
const gfx::Size preferred_size = button_->GetPreferredSize();
EXPECT_LE(preferred_size.width(), short_text_width);
EXPECT_EQ(1, preferred_size.height());
// Clamp the size to a minimum value.
button_->SetText(short_text);
button_->SetMaxSize(gfx::Size());
button_->SetMinSize(gfx::Size(long_text_width, font_list.GetHeight() * 2));
EXPECT_EQ(button_->GetPreferredSize(),
gfx::Size(long_text_width, font_list.GetHeight() * 2));
}
// Tests LabelButton's usage of SetMaximumWidthSingleLine.
TEST_F(LabelButtonTest, LabelPreferredSizeWithMaxWidth) {
const std::string text_cases[] = {
{"The"},
{"The quick"},
{"The quick brown"},
{"The quick brown fox"},
{"The quick brown fox jumps"},
{"The quick brown fox jumps over"},
{"The quick brown fox jumps over the"},
{"The quick brown fox jumps over the lazy"},
{"The quick brown fox jumps over the lazy dog"},
};
const int width_cases[] = {
10, 30, 50, 70, 90, 110, 130, 170, 200, 500,
};
for (bool set_image = false; button_->GetImage(Button::STATE_NORMAL).isNull();
set_image = true) {
if (set_image)
button_->SetImage(Button::STATE_NORMAL, CreateTestImage(16, 16));
bool preferred_size_is_sometimes_narrower_than_max = false;
for (size_t i = 0; i < base::size(text_cases); ++i) {
for (size_t j = 0; j < base::size(width_cases); ++j) {
button_->SetText(ASCIIToUTF16(text_cases[i]));
button_->SetMaxSize(gfx::Size(width_cases[j], 30));
const gfx::Size preferred_size = button_->GetPreferredSize();
EXPECT_LE(preferred_size.width(), width_cases[j]);
if (preferred_size.width() < width_cases[j])
preferred_size_is_sometimes_narrower_than_max = true;
}
}
EXPECT_TRUE(preferred_size_is_sometimes_narrower_than_max);
}
}
TEST_F(LabelButtonTest, LabelShrinkDown) {
ASSERT_TRUE(button_->GetText().empty());
const gfx::FontList font_list = button_->label()->font_list();
const std::u16string text(u"abcdefghijklm");
const int text_width = gfx::GetStringWidth(text, font_list);
ASSERT_LT(button_->GetPreferredSize().width(), text_width);
button_->SetText(text);
EXPECT_GT(button_->GetPreferredSize().width(), text_width);
button_->SetSize(button_->GetPreferredSize());
// When shrinking, the button should report again the size with no label
// (while keeping the label).
button_->ShrinkDownThenClearText();
EXPECT_EQ(button_->GetText(), text);
EXPECT_LT(button_->GetPreferredSize().width(), text_width);
// After the layout manager resizes the button to it's desired size, it's text
// should be empty again.
button_->SetSize(button_->GetPreferredSize());
EXPECT_TRUE(button_->GetText().empty());
}
TEST_F(LabelButtonTest, LabelShrinksDownOnManualSetBounds) {
ASSERT_TRUE(button_->GetText().empty());
ASSERT_GT(button_->GetPreferredSize().width(), 1);
const std::u16string text(u"abcdefghijklm");
button_->SetText(text);
EXPECT_EQ(button_->GetText(), text);
button_->SetSize(button_->GetPreferredSize());
button_->SetBoundsRect(gfx::Rect(button_->GetPreferredSize()));
button_->ShrinkDownThenClearText();
// Manually setting a smaller size should also clear text.
button_->SetBoundsRect(gfx::Rect(1, 1));
EXPECT_TRUE(button_->GetText().empty());
}
TEST_F(LabelButtonTest, LabelShrinksDownCanceledBySettingText) {
ASSERT_TRUE(button_->GetText().empty());
const gfx::FontList font_list = button_->label()->font_list();
const std::u16string text(u"abcdefghijklm");
const int text_width = gfx::GetStringWidth(text, font_list);
ASSERT_LT(button_->GetPreferredSize().width(), text_width);
button_->SetText(text);
EXPECT_GT(button_->GetPreferredSize().width(), text_width);
button_->SetBoundsRect(gfx::Rect(button_->GetPreferredSize()));
// When shrinking, the button should report again the size with no label
// (while keeping the label).
button_->ShrinkDownThenClearText();
EXPECT_EQ(button_->GetText(), text);
gfx::Size shrinking_size = button_->GetPreferredSize();
EXPECT_LT(shrinking_size.width(), text_width);
// When we SetText() again, the shrinking gets canceled.
button_->SetText(text);
EXPECT_GT(button_->GetPreferredSize().width(), text_width);
// Even if the layout manager resizes the button to it's size desired for
// shrinking, it's text does not get cleared and it still prefers having space
// for its label.
button_->SetSize(shrinking_size);
EXPECT_FALSE(button_->GetText().empty());
EXPECT_GT(button_->GetPreferredSize().width(), text_width);
}
TEST_F(
LabelButtonTest,
LabelShrinksDownImmediatelyIfAlreadySmallerThanPreferredSizeWithoutLabel) {
button_->SetBoundsRect(gfx::Rect(1, 1));
button_->SetText(u"abcdefghijklm");
// Shrinking the text down when it's already shrunk down (its size is smaller
// than preferred without label) should clear the text immediately.
EXPECT_FALSE(button_->GetText().empty());
button_->ShrinkDownThenClearText();
EXPECT_TRUE(button_->GetText().empty());
}
// Test behavior of View::GetAccessibleNodeData() for buttons when setting a
// label.
TEST_F(LabelButtonTest, AccessibleState) {
ui::AXNodeData accessible_node_data;
button_->GetAccessibleNodeData(&accessible_node_data);
EXPECT_EQ(ax::mojom::Role::kButton, accessible_node_data.role);
EXPECT_EQ(std::u16string(), accessible_node_data.GetString16Attribute(
ax::mojom::StringAttribute::kName));
// Without a label (e.g. image-only), the accessible name should automatically
// be set from the tooltip.
const std::u16string tooltip_text = u"abc";
button_->SetTooltipText(tooltip_text);
button_->GetAccessibleNodeData(&accessible_node_data);
EXPECT_EQ(tooltip_text, accessible_node_data.GetString16Attribute(
ax::mojom::StringAttribute::kName));
EXPECT_EQ(std::u16string(), button_->GetText());
// Setting a label overrides the tooltip text.
const std::u16string label_text = u"def";
button_->SetText(label_text);
button_->GetAccessibleNodeData(&accessible_node_data);
EXPECT_EQ(label_text, accessible_node_data.GetString16Attribute(
ax::mojom::StringAttribute::kName));
EXPECT_EQ(label_text, button_->GetText());
EXPECT_EQ(tooltip_text, button_->GetTooltipText(gfx::Point()));
}
// Test View::GetAccessibleNodeData() for default buttons.
TEST_F(LabelButtonTest, AccessibleDefaultState) {
{
// If SetIsDefault() is not called, the ax default state should not be set.
ui::AXNodeData ax_data;
button_->GetViewAccessibility().GetAccessibleNodeData(&ax_data);
EXPECT_FALSE(ax_data.HasState(ax::mojom::State::kDefault));
}
{
button_->SetIsDefault(true);
ui::AXNodeData ax_data;
button_->GetViewAccessibility().GetAccessibleNodeData(&ax_data);
EXPECT_TRUE(ax_data.HasState(ax::mojom::State::kDefault));
}
{
button_->SetIsDefault(false);
ui::AXNodeData ax_data;
button_->GetViewAccessibility().GetAccessibleNodeData(&ax_data);
EXPECT_FALSE(ax_data.HasState(ax::mojom::State::kDefault));
}
}
TEST_F(LabelButtonTest, Image) {
const int small_size = 50, large_size = 100;
const gfx::ImageSkia small_image = CreateTestImage(small_size, small_size);
const gfx::ImageSkia large_image = CreateTestImage(large_size, large_size);
EXPECT_LT(button_->GetPreferredSize().width(), small_size);
EXPECT_LT(button_->GetPreferredSize().height(), small_size);
button_->SetImage(Button::STATE_NORMAL, small_image);
EXPECT_GT(button_->GetPreferredSize().width(), small_size);
EXPECT_GT(button_->GetPreferredSize().height(), small_size);
EXPECT_LT(button_->GetPreferredSize().width(), large_size);
EXPECT_LT(button_->GetPreferredSize().height(), large_size);
button_->SetImage(Button::STATE_NORMAL, large_image);
EXPECT_GT(button_->GetPreferredSize().width(), large_size);
EXPECT_GT(button_->GetPreferredSize().height(), large_size);
button_->SetImage(Button::STATE_NORMAL, small_image);
EXPECT_GT(button_->GetPreferredSize().width(), small_size);
EXPECT_GT(button_->GetPreferredSize().height(), small_size);
EXPECT_LT(button_->GetPreferredSize().width(), large_size);
EXPECT_LT(button_->GetPreferredSize().height(), large_size);
// Clamp the size to a maximum value.
button_->SetImage(Button::STATE_NORMAL, large_image);
button_->SetMaxSize(gfx::Size(large_size, 1));
EXPECT_EQ(button_->GetPreferredSize(), gfx::Size(large_size, 1));
// Clamp the size to a minimum value.
button_->SetImage(Button::STATE_NORMAL, small_image);
button_->SetMaxSize(gfx::Size());
button_->SetMinSize(gfx::Size(large_size, large_size));
EXPECT_EQ(button_->GetPreferredSize(), gfx::Size(large_size, large_size));
}
TEST_F(LabelButtonTest, ImageAlignmentWithMultilineLabel) {
const std::u16string text(
u"Some long text that would result in multiline label");
button_->SetText(text);
const int max_label_width = 40;
button_->label()->SetMultiLine(true);
button_->label()->SetMaximumWidth(max_label_width);
const int image_size = 16;
const gfx::ImageSkia image = CreateTestImage(image_size, image_size);
button_->SetImage(Button::STATE_NORMAL, image);
button_->SetBoundsRect(gfx::Rect(button_->GetPreferredSize()));
button_->Layout();
int y_origin_centered = button_->image()->origin().y();
button_->SetBoundsRect(gfx::Rect(button_->GetPreferredSize()));
button_->SetImageCentered(false);
button_->Layout();
int y_origin_not_centered = button_->image()->origin().y();
EXPECT_LT(y_origin_not_centered, y_origin_centered);
}
TEST_F(LabelButtonTest, LabelAndImage) {
const gfx::FontList font_list = button_->label()->font_list();
const std::u16string text(u"abcdefghijklm");
const int text_width = gfx::GetStringWidth(text, font_list);
const int image_size = 50;
const gfx::ImageSkia image = CreateTestImage(image_size, image_size);
ASSERT_LT(font_list.GetHeight(), image_size);
EXPECT_LT(button_->GetPreferredSize().width(), text_width);
EXPECT_LT(button_->GetPreferredSize().width(), image_size);
EXPECT_LT(button_->GetPreferredSize().height(), image_size);
button_->SetText(text);
EXPECT_GT(button_->GetPreferredSize().width(), text_width);
EXPECT_GT(button_->GetPreferredSize().height(), font_list.GetHeight());
EXPECT_LT(button_->GetPreferredSize().width(), text_width + image_size);
EXPECT_LT(button_->GetPreferredSize().height(), image_size);
button_->SetImage(Button::STATE_NORMAL, image);
EXPECT_GT(button_->GetPreferredSize().width(), text_width + image_size);
EXPECT_GT(button_->GetPreferredSize().height(), image_size);
// Layout and ensure the image is left of the label except for ALIGN_RIGHT.
// (A proper parent view or layout manager would Layout on its invalidations).
// Also make sure CENTER alignment moves the label compared to LEFT alignment.
gfx::Size button_size = button_->GetPreferredSize();
button_size.Enlarge(50, 0);
button_->SetSize(button_size);
button_->Layout();
EXPECT_LT(button_->image()->bounds().right(), button_->label()->bounds().x());
int left_align_label_midpoint = button_->label()->bounds().CenterPoint().x();
button_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
button_->Layout();
EXPECT_LT(button_->image()->bounds().right(), button_->label()->bounds().x());
int center_align_label_midpoint =
button_->label()->bounds().CenterPoint().x();
EXPECT_LT(left_align_label_midpoint, center_align_label_midpoint);
button_->SetHorizontalAlignment(gfx::ALIGN_RIGHT);
button_->Layout();
EXPECT_LT(button_->label()->bounds().right(), button_->image()->bounds().x());
button_->SetText(std::u16string());
EXPECT_LT(button_->GetPreferredSize().width(), text_width + image_size);
EXPECT_GT(button_->GetPreferredSize().width(), image_size);
EXPECT_GT(button_->GetPreferredSize().height(), image_size);
button_->SetImage(Button::STATE_NORMAL, gfx::ImageSkia());
EXPECT_LT(button_->GetPreferredSize().width(), image_size);
EXPECT_LT(button_->GetPreferredSize().height(), image_size);
// Clamp the size to a minimum value.
button_->SetText(text);
button_->SetImage(Button::STATE_NORMAL, image);
button_->SetMinSize(gfx::Size((text_width + image_size) * 2, image_size * 2));
EXPECT_EQ(button_->GetPreferredSize().width(), (text_width + image_size) * 2);
EXPECT_EQ(button_->GetPreferredSize().height(), image_size * 2);
// Clamp the size to a maximum value.
button_->SetMinSize(gfx::Size());
button_->SetMaxSize(gfx::Size(1, 1));
EXPECT_EQ(button_->GetPreferredSize(), gfx::Size(1, 1));
}
TEST_F(LabelButtonTest, LabelWrapAndImageAlignment) {
LayoutProvider* provider = LayoutProvider::Get();
const gfx::FontList font_list = button_->label()->font_list();
const std::u16string text(u"abcdefghijklm abcdefghijklm");
const int text_wrap_width = gfx::GetStringWidth(text, font_list) / 2;
const int image_spacing =
provider->GetDistanceMetric(DISTANCE_RELATED_LABEL_HORIZONTAL);
button_->SetText(text);
button_->label()->SetMultiLine(true);
const int image_size = font_list.GetHeight();
const gfx::ImageSkia image = CreateTestImage(image_size, image_size);
ASSERT_EQ(font_list.GetHeight(), image.width());
button_->SetImage(Button::STATE_NORMAL, image);
button_->SetImageCentered(false);
button_->SetMaxSize(
gfx::Size(image.width() + image_spacing + text_wrap_width, 0));
gfx::Insets button_insets = button_->GetInsets();
gfx::Size preferred_size = button_->GetPreferredSize();
preferred_size.set_height(button_->GetHeightForWidth(preferred_size.width()));
button_->SetSize(preferred_size);
button_->Layout();
EXPECT_EQ(preferred_size.width(),
image.width() + image_spacing + text_wrap_width);
EXPECT_EQ(preferred_size.height(),
font_list.GetHeight() * 2 + button_insets.height());
// The image should be centered on the first line of the multi-line label
EXPECT_EQ(button_->image()->y(),
(font_list.GetHeight() - button_->image()->height()) / 2 +
button_insets.top());
}
// This test was added because GetHeightForWidth and GetPreferredSize were
// inconsistent. GetPreferredSize would account for image size + insets whereas
// GetHeightForWidth wouldn't. As of writing they share a large chunk of
// logic, but this remains in place so they don't diverge as easily.
TEST_F(LabelButtonTest, GetHeightForWidthConsistentWithGetPreferredSize) {
const std::u16string text(u"abcdefghijklm");
constexpr int kTinyImageSize = 2;
constexpr int kLargeImageSize = 50;
const int font_height = button_->label()->font_list().GetHeight();
// Parts of this test (accounting for label height) doesn't make sense if the
// font is smaller than the tiny test image and insets.
ASSERT_GT(font_height, button_->GetInsets().height() + kTinyImageSize);
// Parts of this test (accounting for image insets) doesn't make sense if the
// font is larger than the large test image.
ASSERT_LT(font_height, kLargeImageSize);
button_->SetText(text);
for (int image_size : {kTinyImageSize, kLargeImageSize}) {
SCOPED_TRACE(testing::Message() << "Image Size: " << image_size);
// Set image and reset monotonic min size for every test iteration.
const gfx::ImageSkia image = CreateTestImage(image_size, image_size);
button_->SetImage(Button::STATE_NORMAL, image);
const gfx::Size preferred_button_size = button_->GetPreferredSize();
// The preferred button height should be the larger of image / label
// heights + inset height.
EXPECT_EQ(std::max(image_size, font_height) + button_->GetInsets().height(),
preferred_button_size.height());
// Make sure this preferred height is consistent with GetHeightForWidth().
EXPECT_EQ(preferred_button_size.height(),
button_->GetHeightForWidth(preferred_button_size.width()));
}
}
// Ensure that the text used for button labels correctly adjusts in response
// to provided style::TextContext values.
TEST_F(LabelButtonTest, TextSizeFromContext) {
constexpr style::TextContext kDefaultContext = style::CONTEXT_BUTTON;
// Although CONTEXT_DIALOG_TITLE isn't used for buttons, picking a style with
// a small delta risks finding a font with a different point-size but with the
// same maximum glyph height.
constexpr style::TextContext kAlternateContext = style::CONTEXT_DIALOG_TITLE;
// First sanity that the TextConstants used in the test give different sizes.
const auto get_delta = [](auto context) {
return TypographyProvider()
.GetFont(context, style::STYLE_PRIMARY)
.GetFontSize() -
gfx::FontList().GetFontSize();
};
TypographyProvider typography_provider;
int default_delta = get_delta(kDefaultContext);
int alternate_delta = get_delta(kAlternateContext);
EXPECT_LT(default_delta, alternate_delta);
const std::u16string text(u"abcdefghijklm");
button_->SetText(text);
EXPECT_EQ(default_delta, button_->label()->font_list().GetFontSize() -
gfx::FontList().GetFontSize());
TestLabelButton* alternate_button =
new TestLabelButton(text, kAlternateContext);
button_->parent()->AddChildView(alternate_button);
EXPECT_EQ(alternate_delta,
alternate_button->label()->font_list().GetFontSize() -
gfx::FontList().GetFontSize());
// The button size increases when the font size is increased.
EXPECT_LT(button_->GetPreferredSize().width(),
alternate_button->GetPreferredSize().width());
EXPECT_LT(button_->GetPreferredSize().height(),
alternate_button->GetPreferredSize().height());
}
TEST_F(LabelButtonTest, ChangeTextSize) {
const std::u16string text(u"abc");
const std::u16string longer_text(u"abcdefghijklm");
button_->SetText(text);
button_->SizeToPreferredSize();
gfx::Rect bounds(button_->bounds());
const int original_width = button_->GetPreferredSize().width();
EXPECT_EQ(original_width, bounds.width());
// Reserve more space in the button.
bounds.set_width(bounds.width() * 10);
button_->SetBoundsRect(bounds);
// Label view in the button is sized to short text.
const int original_label_width = button_->label()->bounds().width();
// The button preferred size and the label size increase when the text size
// is increased.
button_->SetText(longer_text);
EXPECT_TRUE(ViewTestApi(button_).needs_layout());
button_->Layout();
EXPECT_GT(button_->label()->bounds().width(), original_label_width * 2);
EXPECT_GT(button_->GetPreferredSize().width(), original_width * 2);
// The button and the label view return to its original size when the original
// text is restored.
button_->SetText(text);
EXPECT_TRUE(ViewTestApi(button_).needs_layout());
button_->Layout();
EXPECT_EQ(original_label_width, button_->label()->bounds().width());
EXPECT_EQ(original_width, button_->GetPreferredSize().width());
}
TEST_F(LabelButtonTest, ChangeLabelImageSpacing) {
button_->SetText(u"abc");
button_->SetImage(Button::STATE_NORMAL, CreateTestImage(50, 50));
const int kOriginalSpacing = 5;
button_->SetImageLabelSpacing(kOriginalSpacing);
const int original_width = button_->GetPreferredSize().width();
// Increasing the spacing between the text and label should increase the size.
button_->SetImageLabelSpacing(2 * kOriginalSpacing);
EXPECT_GT(button_->GetPreferredSize().width(), original_width);
// The button shrinks if the original spacing is restored.
button_->SetImageLabelSpacing(kOriginalSpacing);
EXPECT_EQ(original_width, button_->GetPreferredSize().width());
}
// Ensure the label gets the correct style when pressed or becoming default.
TEST_F(LabelButtonTest, HighlightedButtonStyle) {
// The NativeTheme might not provide SK_ColorBLACK, but it should be the same
// for normal and pressed states.
EXPECT_EQ(themed_normal_text_color_, button_->label()->GetEnabledColor());
button_->SetState(Button::STATE_PRESSED);
EXPECT_EQ(themed_normal_text_color_, button_->label()->GetEnabledColor());
}
// Ensure the label resets the enabled color after LabelButton::OnThemeChanged()
// is invoked.
TEST_F(LabelButtonTest, OnThemeChanged) {
ASSERT_NE(button_->GetNativeTheme()->GetPlatformHighContrastColorScheme(),
ui::NativeTheme::PlatformHighContrastColorScheme::kDark);
ASSERT_NE(button_->label()->GetBackgroundColor(), SK_ColorBLACK);
EXPECT_EQ(themed_normal_text_color_, button_->label()->GetEnabledColor());
button_->label()->SetBackgroundColor(SK_ColorBLACK);
button_->label()->SetAutoColorReadabilityEnabled(true);
EXPECT_NE(themed_normal_text_color_, button_->label()->GetEnabledColor());
button_->OnThemeChanged();
EXPECT_EQ(themed_normal_text_color_, button_->label()->GetEnabledColor());
}
TEST_F(LabelButtonTest, SetEnabledTextColorsResetsToThemeColors) {
constexpr SkColor kReplacementColor = SK_ColorCYAN;
// This test doesn't make sense if any used colors are equal.
EXPECT_NE(themed_normal_text_color_, kReplacementColor);
EXPECT_NE(themed_normal_text_color_, TestNativeTheme::kSystemColor);
EXPECT_NE(kReplacementColor, TestNativeTheme::kSystemColor);
// Initially the test should have the normal colors.
EXPECT_EQ(themed_normal_text_color_, button_->label()->GetEnabledColor());
// Setting the enabled text colors should replace the label's enabled color.
button_->SetEnabledTextColors(kReplacementColor);
EXPECT_EQ(kReplacementColor, button_->label()->GetEnabledColor());
// Replace the theme. This should not replace the enabled text color as it's
// been manually overridden above.
TestNativeTheme test_theme;
button_->SetNativeThemeForTesting(&test_theme);
EXPECT_EQ(kReplacementColor, button_->label()->GetEnabledColor());
// Removing the enabled text color restore colors from the new theme, not
// the original colors used before the theme changed.
button_->SetEnabledTextColors(base::nullopt);
EXPECT_EQ(TestNativeTheme::kSystemColor, button_->label()->GetEnabledColor());
}
TEST_F(LabelButtonTest, ImageOrLabelGetClipped) {
const std::u16string text(u"abc");
button_->SetText(text);
const gfx::FontList font_list = button_->label()->font_list();
const int image_size = font_list.GetHeight();
button_->SetImage(Button::STATE_NORMAL,
CreateTestImage(image_size, image_size));
button_->SetBoundsRect(gfx::Rect(button_->GetPreferredSize()));
// The border size + the content height is more than button's preferred size.
button_->SetBorder(CreateEmptyBorder(image_size / 2, 0, image_size / 2, 0));
button_->Layout();
// Ensure that content (image and label) doesn't get clipped by the border.
EXPECT_GE(button_->image()->height(), image_size);
EXPECT_GE(button_->label()->height(), image_size);
}
TEST_F(LabelButtonTest, UpdateImageAfterSettingImageModel) {
auto is_showing_image = [&](const gfx::ImageSkia& image) {
return button_->image()->GetImage().BackedBySameObjectAs(image);
};
auto normal_image = CreateTestImage(16, 16);
button_->SetImageModel(Button::STATE_NORMAL,
ui::ImageModel::FromImageSkia(normal_image));
EXPECT_TRUE(is_showing_image(normal_image));
// When the button has no specific disabled image, changing the normal image
// while the button is disabled should update the currently-visible image.
normal_image = CreateTestImage(16, 16);
button_->SetState(Button::STATE_DISABLED);
button_->SetImageModel(Button::STATE_NORMAL,
ui::ImageModel::FromImageSkia(normal_image));
EXPECT_TRUE(is_showing_image(normal_image));
// Any specific disabled image should take precedence over the normal image.
auto disabled_image = CreateTestImage(16, 16);
button_->SetImageModel(Button::STATE_DISABLED,
ui::ImageModel::FromImageSkia(disabled_image));
EXPECT_TRUE(is_showing_image(disabled_image));
// Removing the disabled image should result in falling back to the normal
// image again.
button_->SetImageModel(Button::STATE_DISABLED, ui::ImageModel());
EXPECT_TRUE(is_showing_image(normal_image));
}
// Test fixture for a LabelButton that has an ink drop configured.
class InkDropLabelButtonTest : public ViewsTestBase {
public:
InkDropLabelButtonTest() = default;
// ViewsTestBase:
void SetUp() override {
ViewsTestBase::SetUp();
// Create a widget so that the Button can query the hover state
// correctly.
widget_ = std::make_unique<Widget>();
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.bounds = gfx::Rect(0, 0, 20, 20);
widget_->Init(std::move(params));
widget_->Show();
button_ = widget_->SetContentsView(std::make_unique<LabelButton>(
Button::PressedCallback(), std::u16string()));
test_ink_drop_ = new test::TestInkDrop();
test::InkDropHostViewTestApi(button_).SetInkDrop(
base::WrapUnique(test_ink_drop_));
}
void TearDown() override {
widget_.reset();
ViewsTestBase::TearDown();
}
protected:
// Required to host the test target.
std::unique_ptr<Widget> widget_;
// The test target.
LabelButton* button_ = nullptr;
// Weak ptr, |button_| owns the instance.
test::TestInkDrop* test_ink_drop_ = nullptr;
private:
DISALLOW_COPY_AND_ASSIGN(InkDropLabelButtonTest);
};
TEST_F(InkDropLabelButtonTest, HoverStateAfterMouseEnterAndExitEvents) {
ui::test::EventGenerator event_generator(GetRootWindow(widget_.get()));
const gfx::Point out_of_bounds_point(button_->bounds().bottom_right() +
gfx::Vector2d(1, 1));
const gfx::Point in_bounds_point(button_->bounds().CenterPoint());
event_generator.MoveMouseTo(out_of_bounds_point);
EXPECT_FALSE(test_ink_drop_->is_hovered());
event_generator.MoveMouseTo(in_bounds_point);
EXPECT_TRUE(test_ink_drop_->is_hovered());
event_generator.MoveMouseTo(out_of_bounds_point);
EXPECT_FALSE(test_ink_drop_->is_hovered());
}
// Verifies the target event handler View is the |LabelButton| and not any of
// the child Views.
TEST_F(InkDropLabelButtonTest, TargetEventHandler) {
View* target_view = widget_->GetRootView()->GetEventHandlerForPoint(
button_->bounds().CenterPoint());
EXPECT_EQ(button_, target_view);
}
class LabelButtonVisualStateTest : public test::WidgetTest {
public:
LabelButtonVisualStateTest() = default;
LabelButtonVisualStateTest(const LabelButtonVisualStateTest&) = delete;
LabelButtonVisualStateTest& operator=(const LabelButtonVisualStateTest&) =
delete;
// testing::Test:
void SetUp() override {
WidgetTest::SetUp();
test_widget_ = CreateTopLevelPlatformWidget();
dummy_widget_ = CreateTopLevelPlatformWidget();
button_ = MakeButtonAsContent(test_widget_);
style_of_inactive_widget_ =
PlatformStyle::kInactiveWidgetControlsAppearDisabled
? Button::STATE_DISABLED
: Button::STATE_NORMAL;
}
void TearDown() override {
test_widget_->CloseNow();
dummy_widget_->CloseNow();
WidgetTest::TearDown();
}
protected:
std::unique_ptr<Widget> CreateActivatableChildWidget(Widget* parent) {
auto child = std::make_unique<Widget>();
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
params.parent = parent->GetNativeView();
params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.activatable = Widget::InitParams::ACTIVATABLE_YES;
child->Init(std::move(params));
child->SetContentsView(std::make_unique<View>());
return child;
}
TestLabelButton* MakeButtonAsContent(Widget* widget) {
return widget->GetContentsView()->AddChildView(
std::make_unique<TestLabelButton>());
}
TestLabelButton* button_ = nullptr;
Widget* test_widget_ = nullptr;
Widget* dummy_widget_ = nullptr;
Button::ButtonState style_of_inactive_widget_;
};
TEST_F(LabelButtonVisualStateTest, IndependentWidget) {
test_widget_->ShowInactive();
EXPECT_EQ(button_->GetVisualState(), style_of_inactive_widget_);
test_widget_->Activate();
EXPECT_EQ(button_->GetVisualState(), Button::STATE_NORMAL);
auto paint_as_active_lock = test_widget_->LockPaintAsActive();
dummy_widget_->Show();
EXPECT_EQ(button_->GetVisualState(), Button::STATE_NORMAL);
}
TEST_F(LabelButtonVisualStateTest, ChildWidget) {
std::unique_ptr<Widget> child_widget =
CreateActivatableChildWidget(test_widget_);
TestLabelButton* child_button = MakeButtonAsContent(child_widget.get());
test_widget_->Show();
EXPECT_EQ(button_->GetVisualState(), Button::STATE_NORMAL);
EXPECT_EQ(child_button->GetVisualState(), Button::STATE_NORMAL);
dummy_widget_->Show();
EXPECT_EQ(button_->GetVisualState(), style_of_inactive_widget_);
EXPECT_EQ(child_button->GetVisualState(), style_of_inactive_widget_);
child_widget->Show();
#if defined(OS_MAC)
// Child widget is in a key window and it will lock its parent.
// See crrev.com/c/2048144.
EXPECT_EQ(button_->GetVisualState(), Button::STATE_NORMAL);
#else
EXPECT_EQ(button_->GetVisualState(), style_of_inactive_widget_);
#endif
EXPECT_EQ(child_button->GetVisualState(), Button::STATE_NORMAL);
}
} // namespace views