Home | History | Annotate | Download | only in views
      1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "ui/message_center/views/message_popup_collection.h"
      6 
      7 #include <list>
      8 
      9 #include "base/message_loop/message_loop.h"
     10 #include "base/strings/string_number_conversions.h"
     11 #include "base/strings/utf_string_conversions.h"
     12 #include "testing/gtest/include/gtest/gtest.h"
     13 #include "ui/events/event.h"
     14 #include "ui/events/event_constants.h"
     15 #include "ui/gfx/display.h"
     16 #include "ui/gfx/rect.h"
     17 #include "ui/message_center/fake_message_center.h"
     18 #include "ui/message_center/views/toast_contents_view.h"
     19 #include "ui/views/test/views_test_base.h"
     20 #include "ui/views/widget/widget.h"
     21 #include "ui/views/widget/widget_delegate.h"
     22 
     23 namespace message_center {
     24 namespace test {
     25 
     26 class MessagePopupCollectionTest : public views::ViewsTestBase {
     27  public:
     28   virtual void SetUp() OVERRIDE {
     29     views::ViewsTestBase::SetUp();
     30     MessageCenter::Initialize();
     31     MessageCenter::Get()->DisableTimersForTest();
     32     collection_.reset(new MessagePopupCollection(
     33         GetContext(), MessageCenter::Get(), NULL, false));
     34     // This size fits test machines resolution and also can keep a few toasts
     35     // w/o ill effects of hitting the screen overflow. This allows us to assume
     36     // and verify normal layout of the toast stack.
     37     collection_->SetDisplayInfo(gfx::Rect(0, 0, 600, 390),
     38                                 gfx::Rect(0, 0, 600, 400));  // Simulate a
     39                                                              // taskbar at the
     40                                                              // bottom.
     41     id_ = 0;
     42     PrepareForWait();
     43   }
     44 
     45   virtual void TearDown() OVERRIDE {
     46     collection_.reset();
     47     MessageCenter::Shutdown();
     48     views::ViewsTestBase::TearDown();
     49   }
     50 
     51  protected:
     52   MessagePopupCollection* collection() { return collection_.get(); }
     53 
     54   size_t GetToastCounts() {
     55     return collection_->toasts_.size();
     56   }
     57 
     58   bool MouseInCollection() {
     59     return collection_->latest_toast_entered_ != NULL;
     60   }
     61 
     62   bool IsToastShown(const std::string& id) {
     63     views::Widget* widget = collection_->GetWidgetForTest(id);
     64     return widget && widget->IsVisible();
     65   }
     66 
     67   views::Widget* GetWidget(const std::string& id) {
     68     return collection_->GetWidgetForTest(id);
     69   }
     70 
     71   gfx::Rect GetWorkArea() {
     72     return collection_->work_area_;
     73   }
     74 
     75   ToastContentsView* GetToast(const std::string& id) {
     76     for (MessagePopupCollection::Toasts::iterator iter =
     77              collection_->toasts_.begin();
     78          iter != collection_->toasts_.end(); ++iter) {
     79       if ((*iter)->id() == id)
     80         return *iter;
     81     }
     82     return NULL;
     83   }
     84 
     85   std::string AddNotification() {
     86     std::string id = base::IntToString(id_++);
     87     scoped_ptr<Notification> notification(
     88         new Notification(NOTIFICATION_TYPE_BASE_FORMAT,
     89                          id,
     90                          base::UTF8ToUTF16("test title"),
     91                          base::UTF8ToUTF16("test message"),
     92                          gfx::Image(),
     93                          base::string16() /* display_source */,
     94                          NotifierId(),
     95                          message_center::RichNotificationData(),
     96                          NULL /* delegate */));
     97     MessageCenter::Get()->AddNotification(notification.Pass());
     98     return id;
     99   }
    100 
    101   void PrepareForWait() { collection_->CreateRunLoopForTest(); }
    102 
    103   // Assumes there is non-zero pending work.
    104   void WaitForTransitionsDone() {
    105     collection_->WaitForTest();
    106     collection_->CreateRunLoopForTest();
    107   }
    108 
    109   void CloseAllToasts() {
    110     // Assumes there is at least one toast to close.
    111     EXPECT_TRUE(GetToastCounts() > 0);
    112     MessageCenter::Get()->RemoveAllNotifications(false);
    113   }
    114 
    115   gfx::Rect GetToastRectAt(size_t index) {
    116     return collection_->GetToastRectAt(index);
    117   }
    118 
    119  private:
    120   scoped_ptr<MessagePopupCollection> collection_;
    121   int id_;
    122 };
    123 
    124 TEST_F(MessagePopupCollectionTest, DismissOnClick) {
    125 
    126   std::string id1 = AddNotification();
    127   std::string id2 = AddNotification();
    128   WaitForTransitionsDone();
    129 
    130   EXPECT_EQ(2u, GetToastCounts());
    131   EXPECT_TRUE(IsToastShown(id1));
    132   EXPECT_TRUE(IsToastShown(id2));
    133 
    134   MessageCenter::Get()->ClickOnNotification(id2);
    135   WaitForTransitionsDone();
    136 
    137   EXPECT_EQ(1u, GetToastCounts());
    138   EXPECT_TRUE(IsToastShown(id1));
    139   EXPECT_FALSE(IsToastShown(id2));
    140 
    141   MessageCenter::Get()->ClickOnNotificationButton(id1, 0);
    142   WaitForTransitionsDone();
    143   EXPECT_EQ(0u, GetToastCounts());
    144   EXPECT_FALSE(IsToastShown(id1));
    145   EXPECT_FALSE(IsToastShown(id2));
    146 }
    147 
    148 TEST_F(MessagePopupCollectionTest, ShutdownDuringShowing) {
    149   std::string id1 = AddNotification();
    150   std::string id2 = AddNotification();
    151   WaitForTransitionsDone();
    152   EXPECT_EQ(2u, GetToastCounts());
    153   EXPECT_TRUE(IsToastShown(id1));
    154   EXPECT_TRUE(IsToastShown(id2));
    155 
    156   // Finish without cleanup of notifications, which may cause use-after-free.
    157   // See crbug.com/236448
    158   GetWidget(id1)->CloseNow();
    159   collection()->OnMouseExited(GetToast(id2));
    160 }
    161 
    162 TEST_F(MessagePopupCollectionTest, DefaultPositioning) {
    163   std::string id0 = AddNotification();
    164   std::string id1 = AddNotification();
    165   std::string id2 = AddNotification();
    166   std::string id3 = AddNotification();
    167   WaitForTransitionsDone();
    168 
    169   gfx::Rect r0 = GetToastRectAt(0);
    170   gfx::Rect r1 = GetToastRectAt(1);
    171   gfx::Rect r2 = GetToastRectAt(2);
    172   gfx::Rect r3 = GetToastRectAt(3);
    173 
    174   // 3 toasts are shown, equal size, vertical stack.
    175   EXPECT_TRUE(IsToastShown(id0));
    176   EXPECT_TRUE(IsToastShown(id1));
    177   EXPECT_TRUE(IsToastShown(id2));
    178 
    179   EXPECT_EQ(r0.width(), r1.width());
    180   EXPECT_EQ(r1.width(), r2.width());
    181 
    182   EXPECT_EQ(r0.height(), r1.height());
    183   EXPECT_EQ(r1.height(), r2.height());
    184 
    185   EXPECT_GT(r0.y(), r1.y());
    186   EXPECT_GT(r1.y(), r2.y());
    187 
    188   EXPECT_EQ(r0.x(), r1.x());
    189   EXPECT_EQ(r1.x(), r2.x());
    190 
    191   // The 4th toast is not shown yet.
    192   EXPECT_FALSE(IsToastShown(id3));
    193   EXPECT_EQ(0, r3.width());
    194   EXPECT_EQ(0, r3.height());
    195 
    196   CloseAllToasts();
    197   EXPECT_EQ(0u, GetToastCounts());
    198 }
    199 
    200 TEST_F(MessagePopupCollectionTest, DefaultPositioningWithRightTaskbar) {
    201   // If taskbar is on the right we show the toasts bottom to top as usual.
    202 
    203   // Simulate a taskbar at the right.
    204   collection()->SetDisplayInfo(gfx::Rect(0, 0, 590, 400),   // Work-area.
    205                                gfx::Rect(0, 0, 600, 400));  // Display-bounds.
    206   std::string id0 = AddNotification();
    207   std::string id1 = AddNotification();
    208   WaitForTransitionsDone();
    209 
    210   gfx::Rect r0 = GetToastRectAt(0);
    211   gfx::Rect r1 = GetToastRectAt(1);
    212 
    213   // 2 toasts are shown, equal size, vertical stack.
    214   EXPECT_TRUE(IsToastShown(id0));
    215   EXPECT_TRUE(IsToastShown(id1));
    216 
    217   EXPECT_EQ(r0.width(), r1.width());
    218   EXPECT_EQ(r0.height(), r1.height());
    219   EXPECT_GT(r0.y(), r1.y());
    220   EXPECT_EQ(r0.x(), r1.x());
    221 
    222   CloseAllToasts();
    223   EXPECT_EQ(0u, GetToastCounts());
    224 
    225   // Restore simulated taskbar position to bottom.
    226   collection()->SetDisplayInfo(gfx::Rect(0, 0, 600, 390),  // Work-area.
    227                                gfx::Rect(0, 0, 600, 400)); // Display-bounds.
    228 }
    229 
    230 TEST_F(MessagePopupCollectionTest, TopDownPositioningWithTopTaskbar) {
    231   // Simulate a taskbar at the top.
    232   collection()->SetDisplayInfo(gfx::Rect(0, 10, 600, 390),  // Work-area.
    233                                gfx::Rect(0, 0, 600, 400));  // Display-bounds.
    234   std::string id0 = AddNotification();
    235   std::string id1 = AddNotification();
    236   WaitForTransitionsDone();
    237 
    238   gfx::Rect r0 = GetToastRectAt(0);
    239   gfx::Rect r1 = GetToastRectAt(1);
    240 
    241   // 2 toasts are shown, equal size, vertical stack.
    242   EXPECT_TRUE(IsToastShown(id0));
    243   EXPECT_TRUE(IsToastShown(id1));
    244 
    245   EXPECT_EQ(r0.width(), r1.width());
    246   EXPECT_EQ(r0.height(), r1.height());
    247   EXPECT_LT(r0.y(), r1.y());
    248   EXPECT_EQ(r0.x(), r1.x());
    249 
    250   CloseAllToasts();
    251   EXPECT_EQ(0u, GetToastCounts());
    252 
    253   // Restore simulated taskbar position to bottom.
    254   collection()->SetDisplayInfo(gfx::Rect(0, 0, 600, 390),   // Work-area.
    255                                gfx::Rect(0, 0, 600, 400));  // Display-bounds.
    256 }
    257 
    258 TEST_F(MessagePopupCollectionTest, TopDownPositioningWithLeftAndTopTaskbar) {
    259   // If there "seems" to be a taskbar on left and top (like in Unity), it is
    260   // assumed that the actual taskbar is the top one.
    261 
    262   // Simulate a taskbar at the top and left.
    263   collection()->SetDisplayInfo(gfx::Rect(10, 10, 590, 390),  // Work-area.
    264                                gfx::Rect(0, 0, 600, 400));   // Display-bounds.
    265   std::string id0 = AddNotification();
    266   std::string id1 = AddNotification();
    267   WaitForTransitionsDone();
    268 
    269   gfx::Rect r0 = GetToastRectAt(0);
    270   gfx::Rect r1 = GetToastRectAt(1);
    271 
    272   // 2 toasts are shown, equal size, vertical stack.
    273   EXPECT_TRUE(IsToastShown(id0));
    274   EXPECT_TRUE(IsToastShown(id1));
    275 
    276   EXPECT_EQ(r0.width(), r1.width());
    277   EXPECT_EQ(r0.height(), r1.height());
    278   EXPECT_LT(r0.y(), r1.y());
    279   EXPECT_EQ(r0.x(), r1.x());
    280 
    281   CloseAllToasts();
    282   EXPECT_EQ(0u, GetToastCounts());
    283 
    284   // Restore simulated taskbar position to bottom.
    285   collection()->SetDisplayInfo(gfx::Rect(0, 0, 600, 390),   // Work-area.
    286                                gfx::Rect(0, 0, 600, 400));  // Display-bounds.
    287 }
    288 
    289 TEST_F(MessagePopupCollectionTest, TopDownPositioningWithBottomAndTopTaskbar) {
    290   // If there "seems" to be a taskbar on bottom and top (like in Gnome), it is
    291   // assumed that the actual taskbar is the top one.
    292 
    293   // Simulate a taskbar at the top and bottom.
    294   collection()->SetDisplayInfo(gfx::Rect(0, 10, 580, 400),  // Work-area.
    295                                gfx::Rect(0, 0, 600, 400));  // Display-bounds.
    296   std::string id0 = AddNotification();
    297   std::string id1 = AddNotification();
    298   WaitForTransitionsDone();
    299 
    300   gfx::Rect r0 = GetToastRectAt(0);
    301   gfx::Rect r1 = GetToastRectAt(1);
    302 
    303   // 2 toasts are shown, equal size, vertical stack.
    304   EXPECT_TRUE(IsToastShown(id0));
    305   EXPECT_TRUE(IsToastShown(id1));
    306 
    307   EXPECT_EQ(r0.width(), r1.width());
    308   EXPECT_EQ(r0.height(), r1.height());
    309   EXPECT_LT(r0.y(), r1.y());
    310   EXPECT_EQ(r0.x(), r1.x());
    311 
    312   CloseAllToasts();
    313   EXPECT_EQ(0u, GetToastCounts());
    314 
    315   // Restore simulated taskbar position to bottom.
    316   collection()->SetDisplayInfo(gfx::Rect(0, 0, 600, 390),   // Work-area.
    317                                gfx::Rect(0, 0, 600, 400));  // Display-bounds.
    318 }
    319 
    320 TEST_F(MessagePopupCollectionTest, LeftPositioningWithLeftTaskbar) {
    321   // Simulate a taskbar at the left.
    322   collection()->SetDisplayInfo(gfx::Rect(10, 0, 590, 400),  // Work-area.
    323                                gfx::Rect(0, 0, 600, 400));  // Display-bounds.
    324   std::string id0 = AddNotification();
    325   std::string id1 = AddNotification();
    326   WaitForTransitionsDone();
    327 
    328   gfx::Rect r0 = GetToastRectAt(0);
    329   gfx::Rect r1 = GetToastRectAt(1);
    330 
    331   // 2 toasts are shown, equal size, vertical stack.
    332   EXPECT_TRUE(IsToastShown(id0));
    333   EXPECT_TRUE(IsToastShown(id1));
    334 
    335   EXPECT_EQ(r0.width(), r1.width());
    336   EXPECT_EQ(r0.height(), r1.height());
    337   EXPECT_GT(r0.y(), r1.y());
    338   EXPECT_EQ(r0.x(), r1.x());
    339 
    340   // Ensure that toasts are on the left.
    341   EXPECT_LT(r1.x(), GetWorkArea().CenterPoint().x());
    342 
    343   CloseAllToasts();
    344   EXPECT_EQ(0u, GetToastCounts());
    345 
    346   // Restore simulated taskbar position to bottom.
    347   collection()->SetDisplayInfo(gfx::Rect(0, 0, 600, 390),   // Work-area.
    348                                gfx::Rect(0, 0, 600, 400));  // Display-bounds.
    349 }
    350 
    351 TEST_F(MessagePopupCollectionTest, DetectMouseHover) {
    352   std::string id0 = AddNotification();
    353   std::string id1 = AddNotification();
    354   WaitForTransitionsDone();
    355 
    356   views::WidgetDelegateView* toast0 = GetToast(id0);
    357   EXPECT_TRUE(toast0 != NULL);
    358   views::WidgetDelegateView* toast1 = GetToast(id1);
    359   EXPECT_TRUE(toast1 != NULL);
    360 
    361   ui::MouseEvent event(ui::ET_MOUSE_MOVED, gfx::Point(), gfx::Point(), 0, 0);
    362 
    363   // Test that mouse detection logic works in presence of out-of-order events.
    364   toast0->OnMouseEntered(event);
    365   EXPECT_TRUE(MouseInCollection());
    366   toast1->OnMouseEntered(event);
    367   EXPECT_TRUE(MouseInCollection());
    368   toast0->OnMouseExited(event);
    369   EXPECT_TRUE(MouseInCollection());
    370   toast1->OnMouseExited(event);
    371   EXPECT_FALSE(MouseInCollection());
    372 
    373   // Test that mouse detection logic works in presence of WindowClosing events.
    374   toast0->OnMouseEntered(event);
    375   EXPECT_TRUE(MouseInCollection());
    376   toast1->OnMouseEntered(event);
    377   EXPECT_TRUE(MouseInCollection());
    378   toast0->WindowClosing();
    379   EXPECT_TRUE(MouseInCollection());
    380   toast1->WindowClosing();
    381   EXPECT_FALSE(MouseInCollection());
    382 }
    383 
    384 // TODO(dimich): Test repositioning - both normal one and when user is closing
    385 // the toasts.
    386 TEST_F(MessagePopupCollectionTest, DetectMouseHoverWithUserClose) {
    387   std::string id0 = AddNotification();
    388   std::string id1 = AddNotification();
    389   WaitForTransitionsDone();
    390 
    391   views::WidgetDelegateView* toast0 = GetToast(id0);
    392   EXPECT_TRUE(toast0 != NULL);
    393   views::WidgetDelegateView* toast1 = GetToast(id1);
    394   ASSERT_TRUE(toast1 != NULL);
    395 
    396   ui::MouseEvent event(ui::ET_MOUSE_MOVED, gfx::Point(), gfx::Point(), 0, 0);
    397   toast1->OnMouseEntered(event);
    398   static_cast<MessageCenterObserver*>(collection())->OnNotificationRemoved(
    399       id1, true);
    400 
    401   EXPECT_FALSE(MouseInCollection());
    402   std::string id2 = AddNotification();
    403 
    404   WaitForTransitionsDone();
    405   views::WidgetDelegateView* toast2 = GetToast(id2);
    406   EXPECT_TRUE(toast2 != NULL);
    407 }
    408 
    409 
    410 }  // namespace test
    411 }  // namespace message_center
    412