| // Copyright 2025 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/views/infobars/infobar_view.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/memory/raw_ptr.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "chrome/browser/ui/ui_features.h" |
| #include "chrome/browser/ui/views/chrome_layout_provider.h" |
| #include "chrome/browser/ui/views/infobars/confirm_infobar.h" |
| #include "components/infobars/core/confirm_infobar_delegate.h" |
| #include "components/infobars/core/infobar_delegate.h" |
| #include "components/vector_icons/vector_icons.h" |
| #include "ui/views/controls/button/md_text_button.h" |
| #include "ui/views/controls/image_view.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/layout/flex_layout.h" |
| #include "ui/views/layout/layout_provider.h" |
| #include "ui/views/test/views_test_base.h" |
| #include "ui/views/view_class_properties.h" |
| #include "ui/views/widget/widget.h" |
| |
| namespace { |
| |
| class TestInfoBarDelegateWithIcon : public infobars::InfoBarDelegate { |
| public: |
| TestInfoBarDelegateWithIcon() = default; |
| ~TestInfoBarDelegateWithIcon() override = default; |
| |
| infobars::InfoBarDelegate::InfoBarIdentifier GetIdentifier() const override { |
| return TEST_INFOBAR; |
| } |
| |
| const gfx::VectorIcon& GetVectorIcon() const override { |
| return vector_icons::kWarningIcon; |
| } |
| }; |
| |
| class TestInfoBarViewWithLabelAndIcon : public InfoBarView { |
| public: |
| explicit TestInfoBarViewWithLabelAndIcon( |
| std::unique_ptr<infobars::InfoBarDelegate> delegate) |
| : InfoBarView(std::move(delegate)) { |
| test_label_ = AddContentChildView(CreateLabel(u"Test Label")); |
| test_label_->SetProperty( |
| views::kFlexBehaviorKey, |
| views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero, |
| views::MaximumFlexSizeRule::kPreferred)); |
| } |
| |
| views::Label* test_label() { return test_label_; } |
| int GetPublicEndX() const { return GetEndX(); } |
| int GetTotalHeight() const { |
| return ChromeLayoutProvider::Get()->GetDistanceMetric( |
| DISTANCE_INFOBAR_HEIGHT); |
| } |
| |
| protected: |
| int GetContentMinimumWidth() const override { return 0; } |
| |
| int GetContentPreferredWidth() const override { |
| return test_label_->GetPreferredSize().width(); |
| } |
| |
| private: |
| raw_ptr<views::Label> test_label_ = nullptr; |
| }; |
| |
| class TestConfirmInfoBarDelegate : public ConfirmInfoBarDelegate { |
| public: |
| TestConfirmInfoBarDelegate() = default; |
| ~TestConfirmInfoBarDelegate() override = default; |
| |
| infobars::InfoBarDelegate::InfoBarIdentifier GetIdentifier() const override { |
| return TEST_INFOBAR; |
| } |
| |
| std::u16string GetMessageText() const override { return u"Test message"; } |
| |
| int GetButtons() const override { return BUTTON_OK; } |
| }; |
| |
| } // namespace |
| |
| class InfoBarViewUnitTest : public views::ViewsTestBase { |
| public: |
| InfoBarViewUnitTest() { |
| feature_list_.InitAndEnableFeature(features::kInfobarRefresh); |
| } |
| |
| void SetUp() override { |
| views::ViewsTestBase::SetUp(); |
| layout_provider_ = std::make_unique<ChromeLayoutProvider>(); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| std::unique_ptr<ChromeLayoutProvider> layout_provider_; |
| }; |
| |
| TEST_F(InfoBarViewUnitTest, CenteredLayout) { |
| auto delegate = std::make_unique<TestInfoBarDelegateWithIcon>(); |
| auto infobar_view = |
| std::make_unique<TestInfoBarViewWithLabelAndIcon>(std::move(delegate)); |
| |
| auto widget = std::make_unique<views::Widget>(); |
| views::Widget::InitParams params = |
| CreateParams(views::Widget::InitParams::CLIENT_OWNS_WIDGET, |
| views::Widget::InitParams::TYPE_WINDOW_FRAMELESS); |
| widget->Init(std::move(params)); |
| |
| widget->SetContentsView(infobar_view.get()); |
| widget->SetBounds(gfx::Rect(0, 0, 500, 50)); |
| widget->Show(); |
| widget->LayoutRootViewIfNecessary(); |
| |
| // Verify the FlexLayout properties for centering. |
| views::FlexLayout* layout = |
| static_cast<views::FlexLayout*>(infobar_view->GetLayoutManager()); |
| ASSERT_NE(nullptr, layout); |
| EXPECT_EQ(views::LayoutAlignment::kCenter, layout->cross_axis_alignment()); |
| |
| // Find the balancer and spacer views used for centering. |
| views::View* left_balancer = nullptr; |
| views::View* right_spacer = nullptr; |
| std::vector<views::View*> spacers; |
| for (views::View* child : infobar_view->children()) { |
| if (child->GetProperty(views::kElementIdentifierKey) == |
| InfoBarView::kLeftBalancerElementId) { |
| left_balancer = child; |
| } else if (child->GetProperty(views::kElementIdentifierKey) == |
| InfoBarView::kRightSpacerElementId) { |
| right_spacer = child; |
| } |
| const views::FlexSpecification* flex_spec = |
| child->GetProperty(views::kFlexBehaviorKey); |
| if (flex_spec && flex_spec->weight() == 1) { |
| spacers.push_back(child); |
| } |
| } |
| |
| // Verify the balancer and spacer are present. |
| ASSERT_NE(nullptr, left_balancer); |
| ASSERT_NE(nullptr, right_spacer); |
| |
| // Verify there are at least two spacers with the correct flex properties. |
| ASSERT_GE(spacers.size(), 2u); |
| for (views::View* spacer : spacers) { |
| const views::FlexSpecification* spec = |
| spacer->GetProperty(views::kFlexBehaviorKey); |
| ASSERT_NE(nullptr, spec); |
| EXPECT_EQ(1, spec->weight()); |
| } |
| |
| widget->CloseNow(); |
| } |
| |
| TEST_F(InfoBarViewUnitTest, CloseButtonIsVisibleAndCorrectlyPositioned) { |
| auto delegate = std::make_unique<TestInfoBarDelegateWithIcon>(); |
| auto infobar_view = |
| std::make_unique<TestInfoBarViewWithLabelAndIcon>(std::move(delegate)); |
| |
| auto widget = std::make_unique<views::Widget>(); |
| views::Widget::InitParams params = |
| CreateParams(views::Widget::InitParams::CLIENT_OWNS_WIDGET, |
| views::Widget::InitParams::TYPE_WINDOW_FRAMELESS); |
| widget->Init(std::move(params)); |
| |
| widget->SetContentsView(infobar_view.get()); |
| widget->SetBounds(gfx::Rect(0, 0, 500, 50)); |
| widget->Show(); |
| widget->LayoutRootViewIfNecessary(); |
| |
| // Verify that the close button is visible. |
| views::View* close_button = infobar_view->close_button(); |
| ASSERT_NE(nullptr, close_button); |
| EXPECT_TRUE(close_button->GetVisible()); |
| |
| // Verify that the close button is positioned at the end of the infobar. |
| EXPECT_GT(close_button->x(), infobar_view->test_label()->x()); |
| |
| widget->CloseNow(); |
| } |
| |
| TEST_F(InfoBarViewUnitTest, ConfirmInfoBarButtonPadding) { |
| auto delegate = std::make_unique<TestConfirmInfoBarDelegate>(); |
| auto infobar_view = std::make_unique<ConfirmInfoBar>(std::move(delegate)); |
| |
| auto widget = std::make_unique<views::Widget>(); |
| views::Widget::InitParams params = |
| CreateParams(views::Widget::InitParams::CLIENT_OWNS_WIDGET, |
| views::Widget::InitParams::TYPE_WINDOW_FRAMELESS); |
| widget->Init(std::move(params)); |
| |
| widget->SetContentsView(infobar_view.get()); |
| widget->SetBounds(gfx::Rect(0, 0, 500, 50)); |
| widget->Show(); |
| widget->LayoutRootViewIfNecessary(); |
| |
| views::MdTextButton* ok_button = infobar_view->ok_button_for_testing(); |
| ASSERT_NE(nullptr, ok_button); |
| |
| const gfx::Insets expected_padding = |
| gfx::Insets::VH(ChromeLayoutProvider::Get()->GetDistanceMetric( |
| DISTANCE_INFOBAR_BUTTON_VERTICAL_PADDING), |
| ChromeLayoutProvider::Get()->GetDistanceMetric( |
| DISTANCE_INFOBAR_BUTTON_HORIZONTAL_PADDING)); |
| |
| EXPECT_EQ(expected_padding, ok_button->GetInsets()); |
| widget->CloseNow(); |
| } |
| |
| TEST_F(InfoBarViewUnitTest, IconSizeForInfobarRefresh) { |
| auto delegate = std::make_unique<TestInfoBarDelegateWithIcon>(); |
| auto infobar_view = |
| std::make_unique<TestInfoBarViewWithLabelAndIcon>(std::move(delegate)); |
| |
| auto widget = std::make_unique<views::Widget>(); |
| views::Widget::InitParams params = |
| CreateParams(views::Widget::InitParams::CLIENT_OWNS_WIDGET, |
| views::Widget::InitParams::TYPE_WINDOW_FRAMELESS); |
| widget->Init(std::move(params)); |
| |
| widget->SetContentsView(infobar_view.get()); |
| widget->SetBounds(gfx::Rect(0, 0, 500, 50)); |
| widget->Show(); |
| widget->LayoutRootViewIfNecessary(); |
| |
| views::ImageView* icon = infobar_view->icon(); |
| ASSERT_NE(nullptr, icon); |
| |
| EXPECT_EQ(gfx::Size(24, 24), icon->GetPreferredSize()); |
| widget->CloseNow(); |
| } |
| |
| TEST_F(InfoBarViewUnitTest, InfobarContainerPadding) { |
| auto delegate = std::make_unique<TestInfoBarDelegateWithIcon>(); |
| auto infobar_view = |
| std::make_unique<TestInfoBarViewWithLabelAndIcon>(std::move(delegate)); |
| |
| auto widget = std::make_unique<views::Widget>(); |
| views::Widget::InitParams params = |
| CreateParams(views::Widget::InitParams::CLIENT_OWNS_WIDGET, |
| views::Widget::InitParams::TYPE_WINDOW_FRAMELESS); |
| widget->Init(std::move(params)); |
| |
| widget->SetContentsView(infobar_view.get()); |
| widget->SetBounds(gfx::Rect(0, 0, 500, 50)); |
| widget->Show(); |
| widget->LayoutRootViewIfNecessary(); |
| |
| EXPECT_EQ(infobar_view->GetTotalHeight(), infobar_view->target_height()); |
| |
| widget->CloseNow(); |
| } |
| |
| TEST_F(InfoBarViewUnitTest, InfobarEliding) { |
| auto delegate = std::make_unique<TestInfoBarDelegateWithIcon>(); |
| auto infobar_view = |
| std::make_unique<TestInfoBarViewWithLabelAndIcon>(std::move(delegate)); |
| views::Label* label = infobar_view->test_label(); |
| label->SetText( |
| u"This is a long label that should be elided when the window is narrow."); |
| |
| auto widget = std::make_unique<views::Widget>(); |
| views::Widget::InitParams params = |
| CreateParams(views::Widget::InitParams::CLIENT_OWNS_WIDGET, |
| views::Widget::InitParams::TYPE_WINDOW_FRAMELESS); |
| widget->Init(std::move(params)); |
| |
| widget->SetContentsView(infobar_view.get()); |
| widget->SetBounds(gfx::Rect(0, 0, 200, 50)); |
| widget->Show(); |
| widget->LayoutRootViewIfNecessary(); |
| |
| // Verify that the label is elided in a narrow window. |
| EXPECT_TRUE(label->IsDisplayTextTruncated()); |
| |
| // Resize the window to be wider and verify that the label is no longer |
| // elided. |
| widget->SetBounds(gfx::Rect(0, 0, 800, 50)); |
| widget->LayoutRootViewIfNecessary(); |
| EXPECT_FALSE(label->IsDisplayTextTruncated()); |
| |
| widget->CloseNow(); |
| } |