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