| // Copyright (c) 2013 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/message_center/views/message_popup_collection.h" |
| |
| #include <list> |
| |
| #include "base/message_loop/message_loop.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/events/event.h" |
| #include "ui/events/event_constants.h" |
| #include "ui/events/event_utils.h" |
| #include "ui/gfx/display.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/message_center/fake_message_center.h" |
| #include "ui/message_center/views/desktop_popup_alignment_delegate.h" |
| #include "ui/message_center/views/toast_contents_view.h" |
| #include "ui/views/test/views_test_base.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/views/widget/widget_delegate.h" |
| |
| namespace message_center { |
| namespace test { |
| |
| class MessagePopupCollectionTest : public views::ViewsTestBase { |
| public: |
| void SetUp() override { |
| views::ViewsTestBase::SetUp(); |
| MessageCenter::Initialize(); |
| MessageCenter::Get()->DisableTimersForTest(); |
| alignment_delegate_.reset(new DesktopPopupAlignmentDelegate); |
| collection_.reset(new MessagePopupCollection( |
| GetContext(), MessageCenter::Get(), NULL, alignment_delegate_.get())); |
| // This size fits test machines resolution and also can keep a few toasts |
| // w/o ill effects of hitting the screen overflow. This allows us to assume |
| // and verify normal layout of the toast stack. |
| SetDisplayInfo(gfx::Rect(0, 0, 600, 390), // taskbar at the bottom. |
| gfx::Rect(0, 0, 600, 400)); |
| id_ = 0; |
| PrepareForWait(); |
| } |
| |
| void TearDown() override { |
| collection_.reset(); |
| MessageCenter::Shutdown(); |
| views::ViewsTestBase::TearDown(); |
| } |
| |
| protected: |
| MessagePopupCollection* collection() { return collection_.get(); } |
| |
| size_t GetToastCounts() { |
| return collection_->toasts_.size(); |
| } |
| |
| bool MouseInCollection() { |
| return collection_->latest_toast_entered_ != NULL; |
| } |
| |
| bool IsToastShown(const std::string& id) { |
| views::Widget* widget = collection_->GetWidgetForTest(id); |
| return widget && widget->IsVisible(); |
| } |
| |
| views::Widget* GetWidget(const std::string& id) { |
| return collection_->GetWidgetForTest(id); |
| } |
| |
| void SetDisplayInfo(const gfx::Rect& work_area, |
| const gfx::Rect& display_bounds) { |
| gfx::Display dummy_display; |
| dummy_display.set_bounds(display_bounds); |
| dummy_display.set_work_area(work_area); |
| alignment_delegate_->RecomputeAlignment(dummy_display); |
| PrepareForWait(); |
| } |
| |
| gfx::Rect GetWorkArea() { |
| return alignment_delegate_->work_area_; |
| } |
| |
| ToastContentsView* GetToast(const std::string& id) { |
| for (MessagePopupCollection::Toasts::iterator iter = |
| collection_->toasts_.begin(); |
| iter != collection_->toasts_.end(); ++iter) { |
| if ((*iter)->id() == id) |
| return *iter; |
| } |
| return NULL; |
| } |
| |
| std::string AddNotification() { |
| std::string id = base::IntToString(id_++); |
| scoped_ptr<Notification> notification( |
| new Notification(NOTIFICATION_TYPE_BASE_FORMAT, |
| id, |
| base::UTF8ToUTF16("test title"), |
| base::UTF8ToUTF16("test message"), |
| gfx::Image(), |
| base::string16() /* display_source */, |
| NotifierId(), |
| message_center::RichNotificationData(), |
| NULL /* delegate */)); |
| MessageCenter::Get()->AddNotification(notification.Pass()); |
| return id; |
| } |
| |
| void PrepareForWait() { collection_->CreateRunLoopForTest(); } |
| |
| // Assumes there is non-zero pending work. |
| void WaitForTransitionsDone() { |
| collection_->WaitForTest(); |
| collection_->CreateRunLoopForTest(); |
| } |
| |
| void CloseAllToasts() { |
| // Assumes there is at least one toast to close. |
| EXPECT_TRUE(GetToastCounts() > 0); |
| MessageCenter::Get()->RemoveAllNotifications(false); |
| } |
| |
| gfx::Rect GetToastRectAt(size_t index) { |
| return collection_->GetToastRectAt(index); |
| } |
| |
| private: |
| scoped_ptr<MessagePopupCollection> collection_; |
| scoped_ptr<DesktopPopupAlignmentDelegate> alignment_delegate_; |
| int id_; |
| }; |
| |
| TEST_F(MessagePopupCollectionTest, DismissOnClick) { |
| |
| std::string id1 = AddNotification(); |
| std::string id2 = AddNotification(); |
| WaitForTransitionsDone(); |
| |
| EXPECT_EQ(2u, GetToastCounts()); |
| EXPECT_TRUE(IsToastShown(id1)); |
| EXPECT_TRUE(IsToastShown(id2)); |
| |
| MessageCenter::Get()->ClickOnNotification(id2); |
| WaitForTransitionsDone(); |
| |
| EXPECT_EQ(1u, GetToastCounts()); |
| EXPECT_TRUE(IsToastShown(id1)); |
| EXPECT_FALSE(IsToastShown(id2)); |
| |
| MessageCenter::Get()->ClickOnNotificationButton(id1, 0); |
| WaitForTransitionsDone(); |
| EXPECT_EQ(0u, GetToastCounts()); |
| EXPECT_FALSE(IsToastShown(id1)); |
| EXPECT_FALSE(IsToastShown(id2)); |
| } |
| |
| TEST_F(MessagePopupCollectionTest, ShutdownDuringShowing) { |
| std::string id1 = AddNotification(); |
| std::string id2 = AddNotification(); |
| WaitForTransitionsDone(); |
| EXPECT_EQ(2u, GetToastCounts()); |
| EXPECT_TRUE(IsToastShown(id1)); |
| EXPECT_TRUE(IsToastShown(id2)); |
| |
| // Finish without cleanup of notifications, which may cause use-after-free. |
| // See crbug.com/236448 |
| GetWidget(id1)->CloseNow(); |
| collection()->OnMouseExited(GetToast(id2)); |
| } |
| |
| TEST_F(MessagePopupCollectionTest, DefaultPositioning) { |
| std::string id0 = AddNotification(); |
| std::string id1 = AddNotification(); |
| std::string id2 = AddNotification(); |
| std::string id3 = AddNotification(); |
| WaitForTransitionsDone(); |
| |
| gfx::Rect r0 = GetToastRectAt(0); |
| gfx::Rect r1 = GetToastRectAt(1); |
| gfx::Rect r2 = GetToastRectAt(2); |
| gfx::Rect r3 = GetToastRectAt(3); |
| |
| // 3 toasts are shown, equal size, vertical stack. |
| EXPECT_TRUE(IsToastShown(id0)); |
| EXPECT_TRUE(IsToastShown(id1)); |
| EXPECT_TRUE(IsToastShown(id2)); |
| |
| EXPECT_EQ(r0.width(), r1.width()); |
| EXPECT_EQ(r1.width(), r2.width()); |
| |
| EXPECT_EQ(r0.height(), r1.height()); |
| EXPECT_EQ(r1.height(), r2.height()); |
| |
| EXPECT_GT(r0.y(), r1.y()); |
| EXPECT_GT(r1.y(), r2.y()); |
| |
| EXPECT_EQ(r0.x(), r1.x()); |
| EXPECT_EQ(r1.x(), r2.x()); |
| |
| // The 4th toast is not shown yet. |
| EXPECT_FALSE(IsToastShown(id3)); |
| EXPECT_EQ(0, r3.width()); |
| EXPECT_EQ(0, r3.height()); |
| |
| CloseAllToasts(); |
| EXPECT_EQ(0u, GetToastCounts()); |
| } |
| |
| TEST_F(MessagePopupCollectionTest, DefaultPositioningWithRightTaskbar) { |
| // If taskbar is on the right we show the toasts bottom to top as usual. |
| |
| // Simulate a taskbar at the right. |
| SetDisplayInfo(gfx::Rect(0, 0, 590, 400), // Work-area. |
| gfx::Rect(0, 0, 600, 400)); // Display-bounds. |
| std::string id0 = AddNotification(); |
| std::string id1 = AddNotification(); |
| WaitForTransitionsDone(); |
| |
| gfx::Rect r0 = GetToastRectAt(0); |
| gfx::Rect r1 = GetToastRectAt(1); |
| |
| // 2 toasts are shown, equal size, vertical stack. |
| EXPECT_TRUE(IsToastShown(id0)); |
| EXPECT_TRUE(IsToastShown(id1)); |
| |
| EXPECT_EQ(r0.width(), r1.width()); |
| EXPECT_EQ(r0.height(), r1.height()); |
| EXPECT_GT(r0.y(), r1.y()); |
| EXPECT_EQ(r0.x(), r1.x()); |
| |
| CloseAllToasts(); |
| EXPECT_EQ(0u, GetToastCounts()); |
| |
| // Restore simulated taskbar position to bottom. |
| SetDisplayInfo(gfx::Rect(0, 0, 600, 390), // Work-area. |
| gfx::Rect(0, 0, 600, 400)); // Display-bounds. |
| } |
| |
| TEST_F(MessagePopupCollectionTest, TopDownPositioningWithTopTaskbar) { |
| // Simulate a taskbar at the top. |
| SetDisplayInfo(gfx::Rect(0, 10, 600, 390), // Work-area. |
| gfx::Rect(0, 0, 600, 400)); // Display-bounds. |
| std::string id0 = AddNotification(); |
| std::string id1 = AddNotification(); |
| WaitForTransitionsDone(); |
| |
| gfx::Rect r0 = GetToastRectAt(0); |
| gfx::Rect r1 = GetToastRectAt(1); |
| |
| // 2 toasts are shown, equal size, vertical stack. |
| EXPECT_TRUE(IsToastShown(id0)); |
| EXPECT_TRUE(IsToastShown(id1)); |
| |
| EXPECT_EQ(r0.width(), r1.width()); |
| EXPECT_EQ(r0.height(), r1.height()); |
| EXPECT_LT(r0.y(), r1.y()); |
| EXPECT_EQ(r0.x(), r1.x()); |
| |
| CloseAllToasts(); |
| EXPECT_EQ(0u, GetToastCounts()); |
| |
| // Restore simulated taskbar position to bottom. |
| SetDisplayInfo(gfx::Rect(0, 0, 600, 390), // Work-area. |
| gfx::Rect(0, 0, 600, 400)); // Display-bounds. |
| } |
| |
| TEST_F(MessagePopupCollectionTest, TopDownPositioningWithLeftAndTopTaskbar) { |
| // If there "seems" to be a taskbar on left and top (like in Unity), it is |
| // assumed that the actual taskbar is the top one. |
| |
| // Simulate a taskbar at the top and left. |
| SetDisplayInfo(gfx::Rect(10, 10, 590, 390), // Work-area. |
| gfx::Rect(0, 0, 600, 400)); // Display-bounds. |
| std::string id0 = AddNotification(); |
| std::string id1 = AddNotification(); |
| WaitForTransitionsDone(); |
| |
| gfx::Rect r0 = GetToastRectAt(0); |
| gfx::Rect r1 = GetToastRectAt(1); |
| |
| // 2 toasts are shown, equal size, vertical stack. |
| EXPECT_TRUE(IsToastShown(id0)); |
| EXPECT_TRUE(IsToastShown(id1)); |
| |
| EXPECT_EQ(r0.width(), r1.width()); |
| EXPECT_EQ(r0.height(), r1.height()); |
| EXPECT_LT(r0.y(), r1.y()); |
| EXPECT_EQ(r0.x(), r1.x()); |
| |
| CloseAllToasts(); |
| EXPECT_EQ(0u, GetToastCounts()); |
| |
| // Restore simulated taskbar position to bottom. |
| SetDisplayInfo(gfx::Rect(0, 0, 600, 390), // Work-area. |
| gfx::Rect(0, 0, 600, 400)); // Display-bounds. |
| } |
| |
| TEST_F(MessagePopupCollectionTest, TopDownPositioningWithBottomAndTopTaskbar) { |
| // If there "seems" to be a taskbar on bottom and top (like in Gnome), it is |
| // assumed that the actual taskbar is the top one. |
| |
| // Simulate a taskbar at the top and bottom. |
| SetDisplayInfo(gfx::Rect(0, 10, 580, 400), // Work-area. |
| gfx::Rect(0, 0, 600, 400)); // Display-bounds. |
| std::string id0 = AddNotification(); |
| std::string id1 = AddNotification(); |
| WaitForTransitionsDone(); |
| |
| gfx::Rect r0 = GetToastRectAt(0); |
| gfx::Rect r1 = GetToastRectAt(1); |
| |
| // 2 toasts are shown, equal size, vertical stack. |
| EXPECT_TRUE(IsToastShown(id0)); |
| EXPECT_TRUE(IsToastShown(id1)); |
| |
| EXPECT_EQ(r0.width(), r1.width()); |
| EXPECT_EQ(r0.height(), r1.height()); |
| EXPECT_LT(r0.y(), r1.y()); |
| EXPECT_EQ(r0.x(), r1.x()); |
| |
| CloseAllToasts(); |
| EXPECT_EQ(0u, GetToastCounts()); |
| |
| // Restore simulated taskbar position to bottom. |
| SetDisplayInfo(gfx::Rect(0, 0, 600, 390), // Work-area. |
| gfx::Rect(0, 0, 600, 400)); // Display-bounds. |
| } |
| |
| TEST_F(MessagePopupCollectionTest, LeftPositioningWithLeftTaskbar) { |
| // Simulate a taskbar at the left. |
| SetDisplayInfo(gfx::Rect(10, 0, 590, 400), // Work-area. |
| gfx::Rect(0, 0, 600, 400)); // Display-bounds. |
| std::string id0 = AddNotification(); |
| std::string id1 = AddNotification(); |
| WaitForTransitionsDone(); |
| |
| gfx::Rect r0 = GetToastRectAt(0); |
| gfx::Rect r1 = GetToastRectAt(1); |
| |
| // 2 toasts are shown, equal size, vertical stack. |
| EXPECT_TRUE(IsToastShown(id0)); |
| EXPECT_TRUE(IsToastShown(id1)); |
| |
| EXPECT_EQ(r0.width(), r1.width()); |
| EXPECT_EQ(r0.height(), r1.height()); |
| EXPECT_GT(r0.y(), r1.y()); |
| EXPECT_EQ(r0.x(), r1.x()); |
| |
| // Ensure that toasts are on the left. |
| EXPECT_LT(r1.x(), GetWorkArea().CenterPoint().x()); |
| |
| CloseAllToasts(); |
| EXPECT_EQ(0u, GetToastCounts()); |
| |
| // Restore simulated taskbar position to bottom. |
| SetDisplayInfo(gfx::Rect(0, 0, 600, 390), // Work-area. |
| gfx::Rect(0, 0, 600, 400)); // Display-bounds. |
| } |
| |
| TEST_F(MessagePopupCollectionTest, DetectMouseHover) { |
| std::string id0 = AddNotification(); |
| std::string id1 = AddNotification(); |
| WaitForTransitionsDone(); |
| |
| views::WidgetDelegateView* toast0 = GetToast(id0); |
| EXPECT_TRUE(toast0 != NULL); |
| views::WidgetDelegateView* toast1 = GetToast(id1); |
| EXPECT_TRUE(toast1 != NULL); |
| |
| ui::MouseEvent event(ui::ET_MOUSE_MOVED, gfx::Point(), gfx::Point(), |
| ui::EventTimeForNow(), 0, 0); |
| |
| // Test that mouse detection logic works in presence of out-of-order events. |
| toast0->OnMouseEntered(event); |
| EXPECT_TRUE(MouseInCollection()); |
| toast1->OnMouseEntered(event); |
| EXPECT_TRUE(MouseInCollection()); |
| toast0->OnMouseExited(event); |
| EXPECT_TRUE(MouseInCollection()); |
| toast1->OnMouseExited(event); |
| EXPECT_FALSE(MouseInCollection()); |
| |
| // Test that mouse detection logic works in presence of WindowClosing events. |
| toast0->OnMouseEntered(event); |
| EXPECT_TRUE(MouseInCollection()); |
| toast1->OnMouseEntered(event); |
| EXPECT_TRUE(MouseInCollection()); |
| toast0->WindowClosing(); |
| EXPECT_TRUE(MouseInCollection()); |
| toast1->WindowClosing(); |
| EXPECT_FALSE(MouseInCollection()); |
| } |
| |
| // TODO(dimich): Test repositioning - both normal one and when user is closing |
| // the toasts. |
| TEST_F(MessagePopupCollectionTest, DetectMouseHoverWithUserClose) { |
| std::string id0 = AddNotification(); |
| std::string id1 = AddNotification(); |
| WaitForTransitionsDone(); |
| |
| views::WidgetDelegateView* toast0 = GetToast(id0); |
| EXPECT_TRUE(toast0 != NULL); |
| views::WidgetDelegateView* toast1 = GetToast(id1); |
| ASSERT_TRUE(toast1 != NULL); |
| |
| ui::MouseEvent event(ui::ET_MOUSE_MOVED, gfx::Point(), gfx::Point(), |
| ui::EventTimeForNow(), 0, 0); |
| toast1->OnMouseEntered(event); |
| static_cast<MessageCenterObserver*>(collection())->OnNotificationRemoved( |
| id1, true); |
| |
| EXPECT_FALSE(MouseInCollection()); |
| std::string id2 = AddNotification(); |
| |
| WaitForTransitionsDone(); |
| views::WidgetDelegateView* toast2 = GetToast(id2); |
| EXPECT_TRUE(toast2 != NULL); |
| } |
| |
| |
| } // namespace test |
| } // namespace message_center |