| // Copyright 2016 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 <map> |
| #include <memory> |
| #include <utility> |
| |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/message_center/fake_message_center.h" |
| #include "ui/message_center/notification.h" |
| #include "ui/message_center/notification_list.h" |
| #include "ui/message_center/views/message_center_controller.h" |
| #include "ui/message_center/views/message_list_view.h" |
| #include "ui/message_center/views/notification_view.h" |
| #include "ui/views/test/views_test_base.h" |
| |
| using ::testing::ElementsAre; |
| |
| namespace message_center { |
| |
| static const char* kNotificationId1 = "notification id 1"; |
| |
| namespace { |
| |
| /* Types **********************************************************************/ |
| |
| enum CallType { GET_PREFERRED_SIZE, GET_HEIGHT_FOR_WIDTH, LAYOUT }; |
| |
| /* Instrumented/Mock NotificationView subclass ********************************/ |
| |
| class MockNotificationView : public NotificationView { |
| public: |
| class Test { |
| public: |
| virtual void RegisterCall(CallType type) = 0; |
| }; |
| |
| MockNotificationView(MessageCenterController* controller, |
| const Notification& notification, |
| Test* test); |
| ~MockNotificationView() override; |
| |
| gfx::Size GetPreferredSize() const override; |
| int GetHeightForWidth(int w) const override; |
| void Layout() override; |
| |
| private: |
| Test* test_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MockNotificationView); |
| }; |
| |
| MockNotificationView::MockNotificationView(MessageCenterController* controller, |
| const Notification& notification, |
| Test* test) |
| : NotificationView(controller, notification), test_(test) {} |
| |
| MockNotificationView::~MockNotificationView() {} |
| |
| gfx::Size MockNotificationView::GetPreferredSize() const { |
| test_->RegisterCall(GET_PREFERRED_SIZE); |
| DCHECK(child_count() > 0); |
| return NotificationView::GetPreferredSize(); |
| } |
| |
| int MockNotificationView::GetHeightForWidth(int width) const { |
| test_->RegisterCall(GET_HEIGHT_FOR_WIDTH); |
| DCHECK(child_count() > 0); |
| return NotificationView::GetHeightForWidth(width); |
| } |
| |
| void MockNotificationView::Layout() { |
| test_->RegisterCall(LAYOUT); |
| DCHECK(child_count() > 0); |
| NotificationView::Layout(); |
| } |
| |
| } // anonymous namespace |
| |
| /* Test fixture ***************************************************************/ |
| |
| class MessageListViewTest : public views::ViewsTestBase, |
| public MockNotificationView::Test, |
| public MessageListView::Observer, |
| public MessageCenterController { |
| public: |
| MessageListViewTest() {} |
| |
| ~MessageListViewTest() override {} |
| |
| void SetUp() override { |
| views::ViewsTestBase::SetUp(); |
| |
| message_list_view_.reset(new MessageListView()); |
| message_list_view_->AddObserver(this); |
| message_list_view_->set_owned_by_client(); |
| |
| widget_.reset(new views::Widget()); |
| views::Widget::InitParams params = |
| CreateParams(views::Widget::InitParams::TYPE_POPUP); |
| params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; |
| params.bounds = gfx::Rect(50, 50, 650, 650); |
| widget_->Init(params); |
| views::View* root = widget_->GetRootView(); |
| root->AddChildView(message_list_view_.get()); |
| widget_->Show(); |
| widget_->Activate(); |
| } |
| |
| void TearDown() override { |
| widget_->CloseNow(); |
| widget_.reset(); |
| |
| message_list_view_->RemoveObserver(this); |
| message_list_view_.reset(); |
| |
| views::ViewsTestBase::TearDown(); |
| } |
| |
| protected: |
| MessageListView* message_list_view() const { |
| return message_list_view_.get(); |
| } |
| |
| int& reposition_top() { return message_list_view_->reposition_top_; } |
| |
| int& fixed_height() { return message_list_view_->fixed_height_; } |
| |
| std::vector<int> ComputeRepositionOffsets(const std::vector<int>& heights, |
| const std::vector<bool>& deleting, |
| int target_index, |
| int padding) { |
| return message_list_view_->ComputeRepositionOffsets(heights, deleting, |
| target_index, padding); |
| } |
| |
| MockNotificationView* CreateNotificationView( |
| const Notification& notification) { |
| return new MockNotificationView(this, notification, this); |
| } |
| |
| private: |
| // MockNotificationView::Test override |
| void RegisterCall(CallType type) override {} |
| |
| // MessageListView::Observer override |
| void OnAllNotificationsCleared() override {} |
| |
| // MessageCenterController override: |
| void ClickOnNotification(const std::string& notification_id) override {} |
| void RemoveNotification(const std::string& notification_id, |
| bool by_user) override {} |
| std::unique_ptr<ui::MenuModel> CreateMenuModel( |
| const NotifierId& notifier_id, |
| const base::string16& display_source) override { |
| NOTREACHED(); |
| return nullptr; |
| } |
| bool HasClickedListener(const std::string& notification_id) override { |
| return false; |
| } |
| void ClickOnNotificationButton(const std::string& notification_id, |
| int button_index) override {} |
| void ClickOnSettingsButton(const std::string& notification_id) override {} |
| void UpdateNotificationSize(const std::string& notification_id) override; |
| |
| // Widget to host a MessageListView. |
| std::unique_ptr<views::Widget> widget_; |
| // MessageListView to be tested. |
| std::unique_ptr<MessageListView> message_list_view_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MessageListViewTest); |
| }; |
| |
| void MessageListViewTest::UpdateNotificationSize( |
| const std::string& notification_id) { |
| // For this test, this method should not be invoked. |
| NOTREACHED(); |
| } |
| |
| /* Unit tests *****************************************************************/ |
| |
| TEST_F(MessageListViewTest, AddNotification) { |
| // Create a dummy notification. |
| auto* notification_view = CreateNotificationView( |
| Notification(NOTIFICATION_TYPE_SIMPLE, std::string(kNotificationId1), |
| base::UTF8ToUTF16("title"), base::UTF8ToUTF16("message1"), |
| gfx::Image(), base::UTF8ToUTF16("display source"), GURL(), |
| NotifierId(NotifierId::APPLICATION, "extension_id"), |
| message_center::RichNotificationData(), nullptr)); |
| |
| EXPECT_EQ(0, message_list_view()->child_count()); |
| EXPECT_FALSE(message_list_view()->Contains(notification_view)); |
| |
| // Add a notification. |
| message_list_view()->AddNotificationAt(notification_view, 0); |
| |
| EXPECT_EQ(1, message_list_view()->child_count()); |
| EXPECT_TRUE(message_list_view()->Contains(notification_view)); |
| } |
| |
| TEST_F(MessageListViewTest, RepositionOffsets) { |
| const auto insets = message_list_view()->GetInsets(); |
| const int top = insets.top(); |
| std::vector<int> positions; |
| |
| // Notification above grows. |reposition_top| should remain at the same |
| // offset from the bottom. |
| fixed_height() = 4 + insets.height(); |
| reposition_top() = 1 + top; |
| positions = |
| ComputeRepositionOffsets({2, 1, 1, 1}, {false, false, false, false}, |
| 1 /* target_index */, 0 /* padding */); |
| EXPECT_THAT(positions, ElementsAre(top, top + 2, top + 3, top + 4)); |
| EXPECT_EQ(4 + insets.height() + 1, fixed_height()); |
| EXPECT_EQ(1 + top + 1, reposition_top()); |
| |
| // Notification above shrinks. The message center keeps its height. |
| // All notifications should remain at the same position from the top. |
| fixed_height() = 5 + insets.height(); |
| reposition_top() = 2 + top; |
| positions = |
| ComputeRepositionOffsets({1, 1, 1, 1}, {false, false, false, false}, |
| 1 /* target_index */, 0 /* padding */); |
| EXPECT_THAT(positions, ElementsAre(top, top + 2, top + 3, top + 4)); |
| EXPECT_EQ(5 + insets.height(), fixed_height()); |
| EXPECT_EQ(2 + top, reposition_top()); |
| |
| // Notification above is being deleted. |reposition_top| should remain at the |
| // same place. |
| fixed_height() = 5 + insets.height(); |
| reposition_top() = 2 + top; |
| positions = |
| ComputeRepositionOffsets({1, 1, 1, 1}, {true, false, false, false}, |
| 1 /* target_index */, 0 /* padding */); |
| EXPECT_THAT(positions, ElementsAre(top, top + 2, top + 3, top + 4)); |
| EXPECT_EQ(5 + insets.height(), fixed_height()); |
| EXPECT_EQ(2 + top, reposition_top()); |
| |
| // Notification above is inserted. |reposition_top| should remain at the |
| // same offset from the bottom. |
| fixed_height() = 5 + insets.height(); |
| reposition_top() = 2 + top; |
| positions = |
| ComputeRepositionOffsets({1, 1, 1, 1}, {false, false, false, false}, |
| 1 /* target_index */, 0 /* padding */); |
| EXPECT_THAT(positions, ElementsAre(top, top + 2, top + 3, top + 4)); |
| EXPECT_EQ(5 + insets.height(), fixed_height()); |
| EXPECT_EQ(top + 2, reposition_top()); |
| |
| // Target notification grows with no free space. |reposition_top| is forced to |
| // change its offset from the bottom. |
| fixed_height() = 5 + insets.height(); |
| reposition_top() = 2 + top; |
| positions = |
| ComputeRepositionOffsets({1, 2, 1, 1}, {false, false, false, false}, |
| 1 /* target_index */, 0 /* padding */); |
| EXPECT_THAT(positions, ElementsAre(top, top + 2, top + 4, top + 5)); |
| EXPECT_EQ(5 + insets.height() + 1, fixed_height()); |
| EXPECT_EQ(2 + top, reposition_top()); |
| |
| // Target notification grows with free space. |reposition_top| should remain |
| // at the same offset from the bottom. |
| fixed_height() = 6 + insets.height(); |
| reposition_top() = 2 + top; |
| positions = |
| ComputeRepositionOffsets({1, 2, 1, 1}, {false, false, false, false}, |
| 1 /* target_index */, 0 /* padding */); |
| EXPECT_THAT(positions, ElementsAre(top, top + 2, top + 4, top + 5)); |
| EXPECT_EQ(6 + insets.height(), fixed_height()); |
| EXPECT_EQ(2 + top, reposition_top()); |
| |
| // Target notification grows with not enough free space. |reposition_top| |
| // should change its offset as little as possible, and consume the free space. |
| fixed_height() = 6 + insets.height(); |
| reposition_top() = 2 + top; |
| positions = |
| ComputeRepositionOffsets({1, 3, 1, 1}, {false, false, false, false}, |
| 1 /* target_index */, 0 /* padding */); |
| EXPECT_THAT(positions, ElementsAre(top, top + 2, top + 5, top + 6)); |
| EXPECT_EQ(6 + insets.height() + 1, fixed_height()); |
| EXPECT_EQ(2 + top, reposition_top()); |
| |
| // Target notification shrinks. |reposition_top| should remain at the |
| // same offset from the bottom. |
| fixed_height() = 7 + insets.height(); |
| reposition_top() = 2 + top; |
| positions = |
| ComputeRepositionOffsets({1, 1, 1, 1}, {false, false, false, false}, |
| 1 /* target_index */, 0 /* padding */); |
| EXPECT_THAT(positions, ElementsAre(top, top + 2, top + 3, top + 4)); |
| EXPECT_EQ(7 + insets.height(), fixed_height()); |
| EXPECT_EQ(2 + top, reposition_top()); |
| |
| // Notification below grows with no free space. |reposition_top| is forced to |
| // change its offset from the bottom. |
| fixed_height() = 7 + insets.height(); |
| reposition_top() = 2 + top; |
| positions = |
| ComputeRepositionOffsets({1, 1, 4, 1}, {false, false, false, false}, |
| 1 /* target_index */, 0 /* padding */); |
| EXPECT_THAT(positions, ElementsAre(top, top + 2, top + 3, top + 7)); |
| EXPECT_EQ(7 + insets.height() + 1, fixed_height()); |
| EXPECT_EQ(2 + top, reposition_top()); |
| |
| // Notification below grows with free space. |reposition_top| should remain |
| // at the same offset from the bottom. |
| fixed_height() = 8 + insets.height(); |
| reposition_top() = 2 + top; |
| positions = |
| ComputeRepositionOffsets({1, 1, 2, 1}, {false, false, false, false}, |
| 1 /* target_index */, 0 /* padding */); |
| EXPECT_THAT(positions, ElementsAre(top, top + 2, top + 3, top + 5)); |
| EXPECT_EQ(8 + insets.height(), fixed_height()); |
| EXPECT_EQ(2 + top, reposition_top()); |
| |
| // Notification below shrinks. |reposition_top| should remain at the same |
| // offset from the bottom. |
| fixed_height() = 8 + insets.height(); |
| reposition_top() = 2 + top; |
| positions = |
| ComputeRepositionOffsets({1, 1, 1, 1}, {false, false, false, false}, |
| 1 /* target_index */, 0 /* padding */); |
| EXPECT_THAT(positions, ElementsAre(top, top + 2, top + 3, top + 4)); |
| EXPECT_EQ(8 + insets.height(), fixed_height()); |
| EXPECT_EQ(2 + top, reposition_top()); |
| |
| // Notification below is being deleted. |reposition_top| should remain at the |
| // same offset from the bottom. |
| fixed_height() = 8 + insets.height(); |
| reposition_top() = 2 + top; |
| positions = |
| ComputeRepositionOffsets({1, 1, 1, 1}, {false, false, true, false}, |
| 1 /* target_index */, 0 /* padding */); |
| EXPECT_THAT(positions, ElementsAre(top, top + 2, top + 3, top + 3)); |
| EXPECT_EQ(8 + insets.height(), fixed_height()); |
| EXPECT_EQ(2 + top, reposition_top()); |
| |
| // Notification below is inserted with free space. |reposition_top| should |
| // remain at the same offset from the bottom. |
| fixed_height() = 8 + insets.height(); |
| reposition_top() = 2 + top; |
| positions = |
| ComputeRepositionOffsets({1, 1, 1, 1}, {false, false, false, false}, |
| 1 /* target_index */, 0 /* padding */); |
| EXPECT_THAT(positions, ElementsAre(top, top + 2, top + 3, top + 4)); |
| EXPECT_EQ(8 + insets.height(), fixed_height()); |
| EXPECT_EQ(2 + top, reposition_top()); |
| |
| // Notification below is inserted with no free space. |reposition_top| is |
| // forced to change its offset from the bottom. |
| fixed_height() = 8 + insets.height(); |
| reposition_top() = 2 + top; |
| positions = ComputeRepositionOffsets({1, 1, 1, 4, 1}, |
| {false, false, false, false, false}, |
| 1 /* target_index */, 0 /* padding */); |
| EXPECT_THAT(positions, ElementsAre(top, top + 2, top + 3, top + 4, top + 8)); |
| EXPECT_EQ(8 + insets.height() + 1, fixed_height()); |
| EXPECT_EQ(2 + top, reposition_top()); |
| |
| // Test padding. |
| fixed_height() = 20 + insets.height(); |
| reposition_top() = 5 + top; |
| positions = |
| ComputeRepositionOffsets({5, 3, 3, 3}, {false, false, false, false}, |
| 1 /* target_index */, 2 /* padding */); |
| EXPECT_THAT(positions, |
| ElementsAre(top, top + 7, top + 7 + 5, top + 7 + 5 + 5)); |
| EXPECT_EQ(20 + insets.height() + 2, fixed_height()); |
| EXPECT_EQ(5 + top + 2, reposition_top()); |
| } |
| |
| } // namespace |