Home | History | Annotate | Download | only in cocoa
      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 #import "ui/message_center/cocoa/popup_collection.h"
      6 
      7 #include "base/mac/scoped_nsobject.h"
      8 #include "base/memory/scoped_ptr.h"
      9 #include "base/message_loop/message_loop.h"
     10 #include "base/run_loop.h"
     11 #include "base/strings/sys_string_conversions.h"
     12 #include "base/strings/utf_string_conversions.h"
     13 #import "ui/base/test/ui_cocoa_test_helper.h"
     14 #import "ui/message_center/cocoa/notification_controller.h"
     15 #import "ui/message_center/cocoa/popup_controller.h"
     16 #include "ui/message_center/message_center.h"
     17 #include "ui/message_center/message_center_style.h"
     18 #include "ui/message_center/notification.h"
     19 
     20 class PopupCollectionTest : public ui::CocoaTest {
     21  public:
     22   PopupCollectionTest()
     23     : message_loop_(base::MessageLoop::TYPE_UI) {
     24     message_center::MessageCenter::Initialize();
     25     center_ = message_center::MessageCenter::Get();
     26     collection_.reset(
     27         [[MCPopupCollection alloc] initWithMessageCenter:center_]);
     28     [collection_ setAnimationDuration:0.001];
     29     [collection_ setAnimationEndedCallback:^{
     30         if (nested_run_loop_.get())
     31           nested_run_loop_->Quit();
     32     }];
     33   }
     34 
     35   virtual void TearDown() OVERRIDE {
     36     collection_.reset();  // Close all popups.
     37     ui::CocoaTest::TearDown();
     38   }
     39 
     40   virtual ~PopupCollectionTest() {
     41     message_center::MessageCenter::Shutdown();
     42   }
     43 
     44   void AddThreeNotifications() {
     45     scoped_ptr<message_center::Notification> notification;
     46     notification.reset(new message_center::Notification(
     47         message_center::NOTIFICATION_TYPE_SIMPLE,
     48         "1",
     49         ASCIIToUTF16("One"),
     50         ASCIIToUTF16("This is the first notification to"
     51                      " be displayed"),
     52         gfx::Image(),
     53         string16(),
     54         std::string(),
     55         message_center::RichNotificationData(),
     56         NULL));
     57     center_->AddNotification(notification.Pass());
     58 
     59     notification.reset(new message_center::Notification(
     60         message_center::NOTIFICATION_TYPE_SIMPLE,
     61         "2",
     62         ASCIIToUTF16("Two"),
     63         ASCIIToUTF16("This is the second notification."),
     64         gfx::Image(),
     65         string16(),
     66         std::string(),
     67         message_center::RichNotificationData(),
     68         NULL));
     69     center_->AddNotification(notification.Pass());
     70 
     71     notification.reset(new message_center::Notification(
     72         message_center::NOTIFICATION_TYPE_SIMPLE,
     73         "3",
     74         ASCIIToUTF16("Three"),
     75         ASCIIToUTF16("This is the third notification "
     76                      "that has a much longer body "
     77                      "than the other notifications. It "
     78                      "may not fit on the screen if we "
     79                      "set the screen size too small."),
     80         gfx::Image(),
     81         string16(),
     82         std::string(),
     83         message_center::RichNotificationData(),
     84         NULL));
     85     center_->AddNotification(notification.Pass());
     86     WaitForAnimationEnded();
     87   }
     88 
     89   bool CheckSpacingBetween(MCPopupController* upper, MCPopupController* lower) {
     90     CGFloat minY = NSMinY([[upper window] frame]);
     91     CGFloat maxY = NSMaxY([[lower window] frame]);
     92     CGFloat delta = minY - maxY;
     93     EXPECT_EQ(message_center::kMarginBetweenItems, delta);
     94     return delta == message_center::kMarginBetweenItems;
     95   }
     96 
     97   void WaitForAnimationEnded() {
     98     if (![collection_ isAnimating])
     99       return;
    100     nested_run_loop_.reset(new base::RunLoop());
    101     nested_run_loop_->Run();
    102     nested_run_loop_.reset();
    103   }
    104 
    105   base::MessageLoop message_loop_;
    106   scoped_ptr<base::RunLoop> nested_run_loop_;
    107   message_center::MessageCenter* center_;
    108   base::scoped_nsobject<MCPopupCollection> collection_;
    109 };
    110 
    111 TEST_F(PopupCollectionTest, AddThreeCloseOne) {
    112   EXPECT_EQ(0u, [[collection_ popups] count]);
    113   AddThreeNotifications();
    114   EXPECT_EQ(3u, [[collection_ popups] count]);
    115 
    116   center_->RemoveNotification("2", true);
    117   WaitForAnimationEnded();
    118   EXPECT_EQ(2u, [[collection_ popups] count]);
    119 }
    120 
    121 TEST_F(PopupCollectionTest, AttemptFourOneOffscreen) {
    122   [collection_ setScreenFrame:NSMakeRect(0, 0, 800, 300)];
    123 
    124   EXPECT_EQ(0u, [[collection_ popups] count]);
    125   AddThreeNotifications();
    126   EXPECT_EQ(2u, [[collection_ popups] count]);  // "3" does not fit on screen.
    127 
    128   scoped_ptr<message_center::Notification> notification;
    129 
    130   notification.reset(new message_center::Notification(
    131       message_center::NOTIFICATION_TYPE_SIMPLE,
    132       "4",
    133       ASCIIToUTF16("Four"),
    134       ASCIIToUTF16("This is the fourth notification."),
    135       gfx::Image(),
    136       string16(),
    137       std::string(),
    138       message_center::RichNotificationData(),
    139       NULL));
    140   center_->AddNotification(notification.Pass());
    141   WaitForAnimationEnded();
    142 
    143   // Remove "1" and "3" should fit on screen.
    144   center_->RemoveNotification("1", true);
    145   WaitForAnimationEnded();
    146   ASSERT_EQ(2u, [[collection_ popups] count]);
    147 
    148   EXPECT_EQ("2", [[[collection_ popups] objectAtIndex:0] notificationID]);
    149   EXPECT_EQ("3", [[[collection_ popups] objectAtIndex:1] notificationID]);
    150 
    151   // Remove "2" and "4" should fit on screen.
    152   center_->RemoveNotification("2", true);
    153   WaitForAnimationEnded();
    154   ASSERT_EQ(2u, [[collection_ popups] count]);
    155 
    156   EXPECT_EQ("3", [[[collection_ popups] objectAtIndex:0] notificationID]);
    157   EXPECT_EQ("4", [[[collection_ popups] objectAtIndex:1] notificationID]);
    158 }
    159 
    160 TEST_F(PopupCollectionTest, LayoutSpacing) {
    161   const CGFloat kScreenSize = 500;
    162   [collection_ setScreenFrame:NSMakeRect(0, 0, kScreenSize, kScreenSize)];
    163 
    164   AddThreeNotifications();
    165   NSArray* popups = [collection_ popups];
    166 
    167   EXPECT_EQ(message_center::kMarginBetweenItems,
    168             kScreenSize - NSMaxY([[[popups objectAtIndex:0] window] frame]));
    169 
    170   EXPECT_TRUE(CheckSpacingBetween([popups objectAtIndex:0],
    171                                   [popups objectAtIndex:1]));
    172   EXPECT_TRUE(CheckSpacingBetween([popups objectAtIndex:1],
    173                                   [popups objectAtIndex:2]));
    174 
    175   // Set priority so that kMaxVisiblePopupNotifications does not hide it.
    176   message_center::RichNotificationData optional;
    177   optional.priority = message_center::HIGH_PRIORITY;
    178   scoped_ptr<message_center::Notification> notification;
    179   notification.reset(new message_center::Notification(
    180       message_center::NOTIFICATION_TYPE_SIMPLE,
    181       "4",
    182       ASCIIToUTF16("Four"),
    183       ASCIIToUTF16("This is the fourth notification."),
    184       gfx::Image(),
    185       string16(),
    186       std::string(),
    187       optional,
    188       NULL));
    189   center_->AddNotification(notification.Pass());
    190   WaitForAnimationEnded();
    191   EXPECT_TRUE(CheckSpacingBetween([popups objectAtIndex:2],
    192                                   [popups objectAtIndex:3]));
    193 
    194   // Remove "2".
    195   center_->RemoveNotification("2", true);
    196   WaitForAnimationEnded();
    197   EXPECT_TRUE(CheckSpacingBetween([popups objectAtIndex:0],
    198                                   [popups objectAtIndex:1]));
    199   EXPECT_TRUE(CheckSpacingBetween([popups objectAtIndex:1],
    200                                   [popups objectAtIndex:2]));
    201 
    202   // Remove "1".
    203   center_->RemoveNotification("2", true);
    204   WaitForAnimationEnded();
    205   EXPECT_EQ(message_center::kMarginBetweenItems,
    206             kScreenSize - NSMaxY([[[popups objectAtIndex:0] window] frame]));
    207   EXPECT_TRUE(CheckSpacingBetween([popups objectAtIndex:0],
    208                                   [popups objectAtIndex:1]));
    209 }
    210 
    211 TEST_F(PopupCollectionTest, TinyScreen) {
    212   [collection_ setScreenFrame:NSMakeRect(0, 0, 800, 100)];
    213 
    214   EXPECT_EQ(0u, [[collection_ popups] count]);
    215   scoped_ptr<message_center::Notification> notification;
    216   notification.reset(new message_center::Notification(
    217       message_center::NOTIFICATION_TYPE_SIMPLE,
    218       "1",
    219       ASCIIToUTF16("One"),
    220       ASCIIToUTF16("This is the first notification to"
    221               " be displayed"),
    222       gfx::Image(),
    223       string16(),
    224       std::string(),
    225       message_center::RichNotificationData(),
    226       NULL));
    227   center_->AddNotification(notification.Pass());
    228   WaitForAnimationEnded();
    229   EXPECT_EQ(1u, [[collection_ popups] count]);
    230 
    231   // Now give the notification a longer message so that it no longer fits.
    232   notification.reset(new message_center::Notification(
    233       message_center::NOTIFICATION_TYPE_SIMPLE,
    234       "1",
    235       ASCIIToUTF16("One"),
    236       ASCIIToUTF16("This is now a very very very very "
    237               "very very very very very very very "
    238               "very very very very very very very "
    239               "very very very very very very very "
    240               "very very very very very very very "
    241               "very very very very very very very "
    242               "very very very very very very very "
    243               "long notification."),
    244       gfx::Image(),
    245       string16(),
    246       std::string(),
    247       message_center::RichNotificationData(),
    248       NULL));
    249   center_->UpdateNotification("1", notification.Pass());
    250   WaitForAnimationEnded();
    251   EXPECT_EQ(0u, [[collection_ popups] count]);
    252 }
    253 
    254 TEST_F(PopupCollectionTest, UpdateIconAndBody) {
    255   AddThreeNotifications();
    256   NSArray* popups = [collection_ popups];
    257 
    258   EXPECT_EQ(3u, [popups count]);
    259 
    260   // Update "2" icon.
    261   MCNotificationController* controller =
    262       [[popups objectAtIndex:1] notificationController];
    263   EXPECT_FALSE([[controller iconView] image]);
    264   center_->SetNotificationIcon("2",
    265       gfx::Image([[NSImage imageNamed:NSImageNameUser] retain]));
    266   WaitForAnimationEnded();
    267   EXPECT_TRUE([[controller iconView] image]);
    268 
    269   EXPECT_EQ(3u, [popups count]);
    270   EXPECT_TRUE(CheckSpacingBetween([popups objectAtIndex:0],
    271                                   [popups objectAtIndex:1]));
    272   EXPECT_TRUE(CheckSpacingBetween([popups objectAtIndex:1],
    273                                   [popups objectAtIndex:2]));
    274 
    275   // Replace "1".
    276   controller = [[popups objectAtIndex:0] notificationController];
    277   NSRect old_frame = [[controller view] frame];
    278   scoped_ptr<message_center::Notification> notification;
    279   notification.reset(new message_center::Notification(
    280       message_center::NOTIFICATION_TYPE_SIMPLE,
    281       "1",
    282       ASCIIToUTF16("One is going to get a much longer "
    283               "title than it previously had."),
    284       ASCIIToUTF16("This is the first notification to "
    285               "be displayed, but it will also be "
    286               "updated to have a significantly "
    287               "longer body"),
    288       gfx::Image(),
    289       string16(),
    290       std::string(),
    291       message_center::RichNotificationData(),
    292       NULL));
    293   center_->AddNotification(notification.Pass());
    294   WaitForAnimationEnded();
    295   EXPECT_GT(NSHeight([[controller view] frame]), NSHeight(old_frame));
    296 
    297   // Test updated spacing.
    298   EXPECT_EQ(3u, [popups count]);
    299   EXPECT_TRUE(CheckSpacingBetween([popups objectAtIndex:0],
    300                                   [popups objectAtIndex:1]));
    301   EXPECT_TRUE(CheckSpacingBetween([popups objectAtIndex:1],
    302                                   [popups objectAtIndex:2]));
    303   EXPECT_EQ("1", [[popups objectAtIndex:0] notificationID]);
    304   EXPECT_EQ("2", [[popups objectAtIndex:1] notificationID]);
    305   EXPECT_EQ("3", [[popups objectAtIndex:2] notificationID]);
    306 }
    307 
    308 TEST_F(PopupCollectionTest, CloseCollectionBeforeNewPopupAnimationEnds) {
    309   // Add a notification and don't wait for the animation to finish.
    310   scoped_ptr<message_center::Notification> notification;
    311   notification.reset(new message_center::Notification(
    312       message_center::NOTIFICATION_TYPE_SIMPLE,
    313       "1",
    314       ASCIIToUTF16("One"),
    315       ASCIIToUTF16("This is the first notification to"
    316                    " be displayed"),
    317       gfx::Image(),
    318       string16(),
    319       std::string(),
    320       message_center::RichNotificationData(),
    321       NULL));
    322   center_->AddNotification(notification.Pass());
    323 
    324   // Release the popup collection before the animation ends. No crash should
    325   // be expected.
    326   collection_.reset();
    327 }
    328 
    329 TEST_F(PopupCollectionTest, CloseCollectionBeforeClosePopupAnimationEnds) {
    330   AddThreeNotifications();
    331 
    332   // Remove a notification and don't wait for the animation to finish.
    333   center_->RemoveNotification("1", true);
    334 
    335   // Release the popup collection before the animation ends. No crash should
    336   // be expected.
    337   collection_.reset();
    338 }
    339 
    340 TEST_F(PopupCollectionTest, CloseCollectionBeforeUpdatePopupAnimationEnds) {
    341   AddThreeNotifications();
    342 
    343   // Update a notification and don't wait for the animation to finish.
    344   scoped_ptr<message_center::Notification> notification;
    345   notification.reset(new message_center::Notification(
    346       message_center::NOTIFICATION_TYPE_SIMPLE,
    347       "1",
    348       ASCIIToUTF16("One"),
    349       ASCIIToUTF16("New message."),
    350       gfx::Image(),
    351       string16(),
    352       std::string(),
    353       message_center::RichNotificationData(),
    354       NULL));
    355   center_->UpdateNotification("1", notification.Pass());
    356 
    357   // Release the popup collection before the animation ends. No crash should
    358   // be expected.
    359   collection_.reset();
    360 }
    361