| // 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 <stddef.h> |
| |
| #include <algorithm> |
| #include <list> |
| #include <numeric> |
| #include <utility> |
| |
| #include "base/memory/ptr_util.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/optional.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "build/build_config.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/display/display.h" |
| #include "ui/events/event.h" |
| #include "ui/events/event_constants.h" |
| #include "ui/events/event_utils.h" |
| #include "ui/gfx/animation/animation_delegate.h" |
| #include "ui/gfx/animation/slide_animation.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/message_center/fake_message_center.h" |
| #include "ui/message_center/message_center_style.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 { |
| |
| std::unique_ptr<message_center::Notification> CreateTestNotification( |
| std::string id, |
| std::string text) { |
| return base::MakeUnique<message_center::Notification>( |
| message_center::NOTIFICATION_TYPE_BASE_FORMAT, id, |
| base::UTF8ToUTF16("test title"), base::ASCIIToUTF16(text), gfx::Image(), |
| base::string16() /* display_source */, GURL(), |
| message_center::NotifierId(message_center::NotifierId::APPLICATION, id), |
| message_center::RichNotificationData(), |
| new message_center::NotificationDelegate()); |
| } |
| |
| // Provides an aura window context for widget creation. |
| class TestPopupAlignmentDelegate |
| : public message_center::DesktopPopupAlignmentDelegate { |
| public: |
| explicit TestPopupAlignmentDelegate(gfx::NativeWindow context) |
| : context_(context) {} |
| ~TestPopupAlignmentDelegate() override {} |
| |
| // PopupAlignmentDelegate: |
| void ConfigureWidgetInitParamsForContainer( |
| views::Widget* widget, |
| views::Widget::InitParams* init_params) override { |
| init_params->context = context_; |
| } |
| |
| private: |
| gfx::NativeWindow context_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestPopupAlignmentDelegate); |
| }; |
| |
| } // namespace |
| |
| 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 TestPopupAlignmentDelegate(GetContext())); |
| collection_.reset(new MessagePopupCollection(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) { |
| display::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_++); |
| std::unique_ptr<Notification> notification(new Notification( |
| NOTIFICATION_TYPE_BASE_FORMAT, id, base::UTF8ToUTF16("test title"), |
| base::UTF8ToUTF16("test message"), gfx::Image(), |
| base::string16() /* display_source */, GURL(), NotifierId(), |
| message_center::RichNotificationData(), new NotificationDelegate())); |
| MessageCenter::Get()->AddNotification(std::move(notification)); |
| 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 /* by_user */, MessageCenter::RemoveType::ALL); |
| } |
| |
| gfx::Rect GetToastRectAt(size_t index) { |
| return collection_->GetToastRectAt(index); |
| } |
| |
| // Checks: |
| // 1) sizes of toast and corresponding widget are equal; |
| // 2) widgets do not owerlap; |
| // 3) after animation is done, aligment is propper; |
| class CheckedAnimationDelegate : public gfx::AnimationDelegate { |
| public: |
| explicit CheckedAnimationDelegate(MessagePopupCollectionTest* test); |
| |
| // returns first encountered error |
| const base::Optional<std::string>& error_msg() const { return error_msg_; } |
| |
| // gfx::AnimationDelegate overrides |
| void AnimationEnded(const gfx::Animation* animation) override; |
| void AnimationProgressed(const gfx::Animation* animation) override; |
| void AnimationCanceled(const gfx::Animation* animation) override; |
| |
| private: |
| // we attach ourselves to the last toast, because we accept |
| // invalidation of invariants during intermidiate |
| // notification updates. |
| ToastContentsView& animation_delegate() { return *toasts_->back(); } |
| const ToastContentsView& animation_delegate() const { |
| return *toasts_->back(); |
| } |
| |
| void CheckWidgetLEView(const std::string& calling_func); |
| void CheckToastsAreAligned(const std::string& calling_func); |
| void CheckToastsDontOverlap(const std::string& calling_func); |
| |
| static int ComputeYDistance(const ToastContentsView& top, |
| const ToastContentsView& bottom); |
| |
| MessagePopupCollection::Toasts* toasts_; |
| |
| // first encountered error |
| base::Optional<std::string> error_msg_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CheckedAnimationDelegate); |
| }; |
| |
| static std::string YPositionsToString( |
| const MessagePopupCollection::Toasts& toasts); |
| |
| private: |
| std::unique_ptr<MessagePopupCollection> collection_; |
| std::unique_ptr<DesktopPopupAlignmentDelegate> alignment_delegate_; |
| int id_; |
| }; |
| |
| MessagePopupCollectionTest::CheckedAnimationDelegate::CheckedAnimationDelegate( |
| MessagePopupCollectionTest* test) |
| : toasts_(&test->collection_->toasts_) { |
| DCHECK(!toasts_->empty()); |
| animation_delegate().bounds_animation_->set_delegate(this); |
| } |
| |
| void MessagePopupCollectionTest::CheckedAnimationDelegate::AnimationEnded( |
| const gfx::Animation* animation) { |
| animation_delegate().AnimationEnded(animation); |
| CheckWidgetLEView("AnimationEnded"); |
| CheckToastsAreAligned("AnimationEnded"); |
| } |
| |
| void MessagePopupCollectionTest::CheckedAnimationDelegate::AnimationProgressed( |
| const gfx::Animation* animation) { |
| animation_delegate().AnimationProgressed(animation); |
| CheckWidgetLEView("AnimationProgressed"); |
| CheckToastsDontOverlap("AnimationProgressed"); |
| } |
| |
| void MessagePopupCollectionTest::CheckedAnimationDelegate::AnimationCanceled( |
| const gfx::Animation* animation) { |
| animation_delegate().AnimationCanceled(animation); |
| CheckWidgetLEView("AnimationCanceled"); |
| CheckToastsDontOverlap("AnimationCanceled"); |
| } |
| |
| void MessagePopupCollectionTest::CheckedAnimationDelegate::CheckWidgetLEView( |
| const std::string& calling_func) { |
| if (error_msg_) |
| return; |
| for (ToastContentsView* toast : *toasts_) { |
| auto* widget = toast->GetWidget(); |
| DCHECK(widget) << "no widget for: " << toast->id(); |
| if (toast->bounds().height() < widget->GetWindowBoundsInScreen().height()) { |
| error_msg_ = calling_func + " CheckWidgetSizeLEView: id: " + toast->id() + |
| "\ntoast size: " + toast->bounds().size().ToString() + |
| "\nwidget size: " + |
| widget->GetWindowBoundsInScreen().size().ToString(); |
| return; |
| } |
| } |
| }; |
| |
| void MessagePopupCollectionTest::CheckedAnimationDelegate:: |
| CheckToastsAreAligned(const std::string& calling_func) { |
| if (error_msg_) |
| return; |
| auto poorly_aligned = std::adjacent_find( |
| toasts_->begin(), toasts_->end(), |
| [](ToastContentsView* top, ToastContentsView* bottom) { |
| return ComputeYDistance(*top, *bottom) != kMarginBetweenItems; |
| }); |
| if (poorly_aligned != toasts_->end()) |
| error_msg_ = calling_func + " CheckToastsAreAligned: distance between: " + |
| (*poorly_aligned)->id() + ' ' + |
| (*std::next(poorly_aligned))->id() + ": " + |
| std::to_string(ComputeYDistance(**poorly_aligned, |
| **std::next(poorly_aligned))) + |
| " expected: " + std::to_string(kMarginBetweenItems) + |
| "\nLayout:\n" + YPositionsToString(*toasts_); |
| } |
| |
| void MessagePopupCollectionTest::CheckedAnimationDelegate:: |
| CheckToastsDontOverlap(const std::string& calling_func) { |
| if (error_msg_) |
| return; |
| auto poorly_aligned = std::adjacent_find( |
| toasts_->begin(), toasts_->end(), |
| [](ToastContentsView* top, ToastContentsView* bottom) { |
| return ComputeYDistance(*top, *bottom) < 0; |
| }); |
| if (poorly_aligned != toasts_->end()) |
| error_msg_ = calling_func + " CheckToastsDontOverlap: distance between: " + |
| (*poorly_aligned)->id() + ' ' + |
| (*std::next(poorly_aligned))->id() + ": " + |
| std::to_string(ComputeYDistance(**poorly_aligned, |
| **std::next(poorly_aligned))) + |
| "\nLayout:\n" + YPositionsToString(*toasts_); |
| } |
| |
| // static |
| std::string MessagePopupCollectionTest::YPositionsToString( |
| const MessagePopupCollection::Toasts& toasts) { |
| return std::accumulate(toasts.begin(), toasts.end(), std::string(), |
| [](std::string res, const ToastContentsView* toast) { |
| const auto& bounds = |
| toast->GetWidget()->GetWindowBoundsInScreen(); |
| res += toast->id(); |
| res += ' '; |
| res += std::to_string(bounds.y()); |
| res += ", "; |
| res += std::to_string(bounds.y() + bounds.height()); |
| res += '\n'; |
| return res; |
| }); |
| } |
| |
| // static |
| int MessagePopupCollectionTest::CheckedAnimationDelegate::ComputeYDistance( |
| const ToastContentsView& top, |
| const ToastContentsView& bottom) { |
| const auto* top_widget = top.GetWidget(); |
| const auto* bottom_widget = bottom.GetWidget(); |
| const auto& top_bounds = top_widget->GetWindowBoundsInScreen(); |
| const auto& bottom_bounds = bottom_widget->GetWindowBoundsInScreen(); |
| return bottom_bounds.y() - (top_bounds.y() + top_bounds.height()); |
| } |
| |
| #if defined(OS_CHROMEOS) |
| 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)); |
| } |
| |
| #else |
| |
| TEST_F(MessagePopupCollectionTest, NotDismissedOnClick) { |
| 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); |
| collection()->DoUpdateIfPossible(); |
| |
| EXPECT_EQ(2u, GetToastCounts()); |
| EXPECT_TRUE(IsToastShown(id1)); |
| EXPECT_TRUE(IsToastShown(id2)); |
| |
| MessageCenter::Get()->ClickOnNotificationButton(id1, 0); |
| collection()->DoUpdateIfPossible(); |
| EXPECT_EQ(2u, GetToastCounts()); |
| EXPECT_TRUE(IsToastShown(id1)); |
| EXPECT_TRUE(IsToastShown(id2)); |
| |
| GetWidget(id1)->CloseNow(); |
| GetWidget(id2)->CloseNow(); |
| } |
| |
| #endif // OS_CHROMEOS |
| |
| 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)); |
| |
| GetWidget(id2)->CloseNow(); |
| } |
| |
| 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()); |
| WaitForTransitionsDone(); |
| } |
| |
| 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()); |
| WaitForTransitionsDone(); |
| } |
| |
| 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()); |
| WaitForTransitionsDone(); |
| } |
| |
| 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()); |
| WaitForTransitionsDone(); |
| } |
| |
| 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()); |
| WaitForTransitionsDone(); |
| } |
| |
| 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()); |
| WaitForTransitionsDone(); |
| } |
| |
| 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->GetWidget()->CloseNow(); |
| EXPECT_TRUE(MouseInCollection()); |
| toast1->GetWidget()->CloseNow(); |
| 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); |
| |
| CloseAllToasts(); |
| WaitForTransitionsDone(); |
| } |
| |
| TEST_F(MessagePopupCollectionTest, ManyPopupNotifications) { |
| // Add the max visible popup notifications +1, ensure the correct num visible. |
| size_t notifications_to_add = 3 + 1; |
| std::vector<std::string> ids(notifications_to_add); |
| for (size_t i = 0; i < notifications_to_add; ++i) { |
| ids[i] = AddNotification(); |
| } |
| |
| WaitForTransitionsDone(); |
| |
| for (size_t i = 0; i < notifications_to_add - 1; ++i) { |
| EXPECT_TRUE(IsToastShown(ids[i])) << "Should show the " << i << "th ID"; |
| } |
| EXPECT_FALSE(IsToastShown(ids[notifications_to_add - 1])); |
| |
| CloseAllToasts(); |
| WaitForTransitionsDone(); |
| } |
| |
| #if defined(OS_CHROMEOS) |
| |
| TEST_F(MessagePopupCollectionTest, CloseNonClosableNotifications) { |
| const char* kNotificationId = "NOTIFICATION1"; |
| |
| std::unique_ptr<Notification> notification(new Notification( |
| NOTIFICATION_TYPE_BASE_FORMAT, kNotificationId, |
| base::UTF8ToUTF16("test title"), base::UTF8ToUTF16("test message"), |
| gfx::Image(), base::string16() /* display_source */, GURL(), |
| NotifierId(NotifierId::APPLICATION, kNotificationId), |
| message_center::RichNotificationData(), new NotificationDelegate())); |
| notification->set_pinned(true); |
| |
| // Add a pinned notification. |
| MessageCenter::Get()->AddNotification(std::move(notification)); |
| WaitForTransitionsDone(); |
| |
| // Confirms that there is a toast. |
| EXPECT_EQ(1u, GetToastCounts()); |
| EXPECT_EQ(1u, MessageCenter::Get()->NotificationCount()); |
| |
| // Close the toast. |
| views::WidgetDelegateView* toast1 = GetToast(kNotificationId); |
| 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(kNotificationId, true); |
| WaitForTransitionsDone(); |
| |
| // Confirms that there is no toast. |
| EXPECT_EQ(0u, GetToastCounts()); |
| // But the notification still exists. |
| EXPECT_EQ(1u, MessageCenter::Get()->NotificationCount()); |
| } |
| |
| #endif // defined(OS_CHROMEOS) |
| |
| // When notifications were displayed on top, change of notification |
| // size didn't affect corresponding widget. |
| TEST_F(MessagePopupCollectionTest, ChangingNotificationSize) { |
| // Simulate a taskbar at the top. |
| SetDisplayInfo(gfx::Rect(0, 10, 600, 390), // Work-area. |
| gfx::Rect(0, 0, 600, 400)); // Display-bounds. |
| |
| struct TestCase { |
| std::string name; |
| std::string text; |
| }; |
| std::vector<TestCase> updates = { |
| {"shrinking", ""}, |
| {"enlarging", "abc\ndef\nghk\n"}, |
| {"restoring", "abc\ndef\n"}, |
| }; |
| |
| std::vector<std::string> notification_ids; |
| // adding notifications |
| { |
| constexpr int max_visible_popup_notifications = 3; |
| notification_ids.reserve(max_visible_popup_notifications); |
| for (int i = 0; i < max_visible_popup_notifications; ++i) { |
| notification_ids.push_back(std::to_string(i)); |
| auto notification = |
| CreateTestNotification(notification_ids.back(), updates.back().text); |
| MessageCenter::Get()->AddNotification(std::move(notification)); |
| } |
| } |
| |
| WaitForTransitionsDone(); |
| |
| // updating notifications one by one |
| for (const std::string& id : notification_ids) { |
| for (const auto& update : updates) { |
| MessageCenter::Get()->UpdateNotification( |
| id, CreateTestNotification(id, update.text)); |
| |
| CheckedAnimationDelegate checked_animation(this); |
| |
| WaitForTransitionsDone(); |
| |
| EXPECT_FALSE(checked_animation.error_msg()) |
| << "Animation error, test case: " << id << ' ' << update.name << ":\n" |
| << *checked_animation.error_msg(); |
| } |
| } |
| |
| CloseAllToasts(); |
| WaitForTransitionsDone(); |
| } |
| |
| } // namespace test |
| } // namespace message_center |