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/notification_controller.h"
      6 
      7 #include "base/mac/foundation_util.h"
      8 #include "base/mac/scoped_nsobject.h"
      9 #include "base/memory/scoped_ptr.h"
     10 #include "base/strings/sys_string_conversions.h"
     11 #include "base/strings/utf_string_conversions.h"
     12 #include "third_party/skia/include/core/SkBitmap.h"
     13 #import "ui/base/cocoa/hover_image_button.h"
     14 #import "ui/gfx/test/ui_cocoa_test_helper.h"
     15 #include "ui/message_center/fake_message_center.h"
     16 #include "ui/message_center/message_center_style.h"
     17 #include "ui/message_center/notification.h"
     18 #include "ui/message_center/notification_types.h"
     19 
     20 using base::ASCIIToUTF16;
     21 using base::UTF8ToUTF16;
     22 
     23 namespace {
     24 
     25 class MockMessageCenter : public message_center::FakeMessageCenter {
     26  public:
     27   MockMessageCenter()
     28       : last_removed_by_user_(false),
     29         remove_count_(0),
     30         last_clicked_index_(-1) {}
     31 
     32   virtual void RemoveNotification(const std::string& id,
     33                                   bool by_user) OVERRIDE {
     34     last_removed_id_ = id;
     35     last_removed_by_user_ = by_user;
     36     ++remove_count_;
     37   }
     38 
     39   virtual void ClickOnNotificationButton(const std::string& id,
     40                                          int button_index) OVERRIDE {
     41     last_clicked_id_ = id;
     42     last_clicked_index_ = button_index;
     43   }
     44 
     45   const std::string& last_removed_id() const { return last_removed_id_; }
     46   bool last_removed_by_user() const { return last_removed_by_user_; }
     47   int remove_count() const { return remove_count_; }
     48   const std::string& last_clicked_id() const { return last_clicked_id_; }
     49   int last_clicked_index() const { return last_clicked_index_; }
     50 
     51  private:
     52   std::string last_removed_id_;
     53   bool last_removed_by_user_;
     54   int remove_count_;
     55 
     56   std::string last_clicked_id_;
     57   int last_clicked_index_;
     58 
     59   DISALLOW_COPY_AND_ASSIGN(MockMessageCenter);
     60 };
     61 
     62 }  // namespace
     63 
     64 @implementation MCNotificationController (TestingInterface)
     65 - (NSButton*)closeButton {
     66   return closeButton_.get();
     67 }
     68 
     69 - (NSImageView*)smallImageView {
     70   return smallImage_.get();
     71 }
     72 
     73 - (NSButton*)secondButton {
     74   // The buttons are in Cocoa-y-order, so the 2nd button is first.
     75   NSView* view = [[bottomView_ subviews] objectAtIndex:0];
     76   return base::mac::ObjCCastStrict<NSButton>(view);
     77 }
     78 
     79 - (NSArray*)bottomSubviews {
     80   return [bottomView_ subviews];
     81 }
     82 
     83 - (NSImageView*)iconView {
     84   return icon_.get();
     85 }
     86 
     87 - (NSTextView*)titleView {
     88   return title_.get();
     89 }
     90 
     91 - (NSTextView*)messageView {
     92   return message_.get();
     93 }
     94 
     95 - (NSTextView*)contextMessageView {
     96   return contextMessage_.get();
     97 }
     98 
     99 - (NSView*)listView {
    100   return listView_.get();
    101 }
    102 @end
    103 
    104 namespace message_center {
    105 
    106 class NotificationControllerTest : public ui::CocoaTest {
    107  public:
    108   NSImage* TestIcon() {
    109     return [NSImage imageNamed:NSImageNameUser];
    110   }
    111 
    112  protected:
    113   message_center::NotifierId DummyNotifierId() {
    114     return message_center::NotifierId();
    115   }
    116 };
    117 
    118 TEST_F(NotificationControllerTest, BasicLayout) {
    119   scoped_ptr<message_center::Notification> notification(
    120       new message_center::Notification(
    121           message_center::NOTIFICATION_TYPE_SIMPLE,
    122           "",
    123           ASCIIToUTF16("Added to circles"),
    124           ASCIIToUTF16("Jonathan and 5 others"),
    125           gfx::Image(),
    126           base::string16(),
    127           DummyNotifierId(),
    128           message_center::RichNotificationData(),
    129           NULL));
    130   gfx::Image testIcon([TestIcon() retain]);
    131   notification->set_icon(testIcon);
    132   notification->set_small_image(testIcon);
    133 
    134   base::scoped_nsobject<MCNotificationController> controller(
    135       [[MCNotificationController alloc] initWithNotification:notification.get()
    136                                                messageCenter:NULL]);
    137   [controller view];
    138 
    139   EXPECT_EQ(TestIcon(), [[controller iconView] image]);
    140   EXPECT_EQ(TestIcon(), [[controller smallImageView] image]);
    141   EXPECT_EQ(base::SysNSStringToUTF16([[controller titleView] string]),
    142             notification->title());
    143   EXPECT_EQ(base::SysNSStringToUTF16([[controller messageView] string]),
    144             notification->message());
    145   EXPECT_EQ(controller.get(), [[controller closeButton] target]);
    146 }
    147 
    148 TEST_F(NotificationControllerTest, OverflowText) {
    149   scoped_ptr<message_center::Notification> notification(
    150       new message_center::Notification(
    151           message_center::NOTIFICATION_TYPE_SIMPLE,
    152           "",
    153           ASCIIToUTF16("This is a much longer title that should wrap "
    154                        "multiple lines."),
    155           ASCIIToUTF16("And even the message is long. This sure is a wordy "
    156                        "notification. Are you really going to read this "
    157                        "entire thing?"),
    158           gfx::Image(),
    159           base::string16(),
    160           DummyNotifierId(),
    161           message_center::RichNotificationData(),
    162           NULL));
    163   base::scoped_nsobject<MCNotificationController> controller(
    164       [[MCNotificationController alloc] initWithNotification:notification.get()
    165                                                messageCenter:NULL]);
    166   [controller view];
    167 
    168   EXPECT_GT(NSHeight([[controller view] frame]),
    169             message_center::kNotificationIconSize);
    170 }
    171 
    172 TEST_F(NotificationControllerTest, Close) {
    173   scoped_ptr<message_center::Notification> notification(
    174       new message_center::Notification(
    175           message_center::NOTIFICATION_TYPE_SIMPLE,
    176           "an_id",
    177           base::string16(),
    178           base::string16(),
    179           gfx::Image(),
    180           base::string16(),
    181           DummyNotifierId(),
    182           message_center::RichNotificationData(),
    183           NULL));
    184   MockMessageCenter message_center;
    185 
    186   base::scoped_nsobject<MCNotificationController> controller(
    187       [[MCNotificationController alloc] initWithNotification:notification.get()
    188                                                messageCenter:&message_center]);
    189   [controller view];
    190 
    191   [[controller closeButton] performClick:nil];
    192 
    193   EXPECT_EQ(1, message_center.remove_count());
    194   EXPECT_EQ("an_id", message_center.last_removed_id());
    195   EXPECT_TRUE(message_center.last_removed_by_user());
    196 }
    197 
    198 TEST_F(NotificationControllerTest, Update) {
    199   scoped_ptr<message_center::Notification> notification(
    200       new message_center::Notification(
    201           message_center::NOTIFICATION_TYPE_SIMPLE,
    202           "",
    203           ASCIIToUTF16("A simple title"),
    204           ASCIIToUTF16("This message isn't too long and should fit in the"
    205                        "default bounds."),
    206           gfx::Image(),
    207           base::string16(),
    208           DummyNotifierId(),
    209           message_center::RichNotificationData(),
    210           NULL));
    211   base::scoped_nsobject<MCNotificationController> controller(
    212       [[MCNotificationController alloc] initWithNotification:notification.get()
    213                                                messageCenter:NULL]);
    214 
    215   // Set up the default layout.
    216   [controller view];
    217   EXPECT_EQ(NSHeight([[controller view] frame]),
    218             message_center::kNotificationIconSize);
    219   EXPECT_FALSE([[controller iconView] image]);
    220   EXPECT_FALSE([[controller smallImageView] image]);
    221 
    222   // Update the icon.
    223   gfx::Image testIcon([TestIcon() retain]);
    224   notification->set_icon(testIcon);
    225   notification->set_small_image(testIcon);
    226   [controller updateNotification:notification.get()];
    227   EXPECT_EQ(TestIcon(), [[controller iconView] image]);
    228   EXPECT_EQ(TestIcon(), [[controller smallImageView] image]);
    229   EXPECT_EQ(NSHeight([[controller view] frame]),
    230             message_center::kNotificationIconSize);
    231 }
    232 
    233 TEST_F(NotificationControllerTest, Buttons) {
    234   message_center::RichNotificationData optional;
    235   message_center::ButtonInfo button1(UTF8ToUTF16("button1"));
    236   optional.buttons.push_back(button1);
    237   message_center::ButtonInfo button2(UTF8ToUTF16("button2"));
    238   optional.buttons.push_back(button2);
    239 
    240   scoped_ptr<message_center::Notification> notification(
    241       new message_center::Notification(
    242           message_center::NOTIFICATION_TYPE_BASE_FORMAT,
    243           "an_id",
    244           base::string16(),
    245           base::string16(),
    246           gfx::Image(),
    247           base::string16(),
    248           DummyNotifierId(),
    249           optional,
    250           NULL));
    251   MockMessageCenter message_center;
    252 
    253   base::scoped_nsobject<MCNotificationController> controller(
    254       [[MCNotificationController alloc] initWithNotification:notification.get()
    255                                                messageCenter:&message_center]);
    256   [controller view];
    257 
    258   [[controller secondButton] performClick:nil];
    259 
    260   EXPECT_EQ("an_id", message_center.last_clicked_id());
    261   EXPECT_EQ(1, message_center.last_clicked_index());
    262 }
    263 
    264 TEST_F(NotificationControllerTest, Image) {
    265   scoped_ptr<message_center::Notification> notification(
    266       new message_center::Notification(
    267           message_center::NOTIFICATION_TYPE_BASE_FORMAT,
    268           "an_id",
    269           base::string16(),
    270           base::string16(),
    271           gfx::Image(),
    272           base::string16(),
    273           DummyNotifierId(),
    274           message_center::RichNotificationData(),
    275           NULL));
    276   NSImage* image = [NSImage imageNamed:NSImageNameFolder];
    277   notification->set_image(gfx::Image([image retain]));
    278 
    279   MockMessageCenter message_center;
    280 
    281   base::scoped_nsobject<MCNotificationController> controller(
    282       [[MCNotificationController alloc] initWithNotification:notification.get()
    283                                                messageCenter:&message_center]);
    284   [controller view];
    285 
    286   ASSERT_EQ(1u, [[controller bottomSubviews] count]);
    287   ASSERT_TRUE([[[[controller bottomSubviews] lastObject] contentView]
    288       isKindOfClass:[NSImageView class]]);
    289   EXPECT_EQ(image,
    290       [[[[controller bottomSubviews] lastObject] contentView] image]);
    291 }
    292 
    293 TEST_F(NotificationControllerTest, List) {
    294   message_center::RichNotificationData optional;
    295   message_center::NotificationItem item1(
    296       UTF8ToUTF16("First title"), UTF8ToUTF16("first message"));
    297   optional.items.push_back(item1);
    298   message_center::NotificationItem item2(
    299       UTF8ToUTF16("Second title"),
    300       UTF8ToUTF16("second slightly longer message"));
    301   optional.items.push_back(item2);
    302   message_center::NotificationItem item3(
    303       UTF8ToUTF16(""),    // Test for empty string.
    304       UTF8ToUTF16(" "));  // Test for string containing only spaces.
    305   optional.items.push_back(item3);
    306   optional.context_message = UTF8ToUTF16("Context Message");
    307 
    308   scoped_ptr<message_center::Notification> notification(
    309       new message_center::Notification(
    310           message_center::NOTIFICATION_TYPE_BASE_FORMAT,
    311           "an_id",
    312           UTF8ToUTF16("Notification Title"),
    313           UTF8ToUTF16("Notification Message - should be hidden"),
    314           gfx::Image(),
    315           base::string16(),
    316           DummyNotifierId(),
    317           optional,
    318           NULL));
    319 
    320   MockMessageCenter message_center;
    321   base::scoped_nsobject<MCNotificationController> controller(
    322       [[MCNotificationController alloc] initWithNotification:notification.get()
    323                                                messageCenter:&message_center]);
    324   [controller view];
    325 
    326   EXPECT_FALSE([[controller titleView] isHidden]);
    327   EXPECT_TRUE([[controller messageView] isHidden]);
    328   EXPECT_FALSE([[controller contextMessageView] isHidden]);
    329 
    330   EXPECT_EQ(3u, [[[controller listView] subviews] count]);
    331   EXPECT_LT(NSMaxY([[controller listView] frame]),
    332             NSMinY([[controller titleView] frame]));
    333 }
    334 
    335 TEST_F(NotificationControllerTest, NoMessage) {
    336   message_center::RichNotificationData optional;
    337   optional.context_message = UTF8ToUTF16("Context Message");
    338 
    339   scoped_ptr<message_center::Notification> notification(
    340       new message_center::Notification(
    341           message_center::NOTIFICATION_TYPE_BASE_FORMAT,
    342           "an_id",
    343           UTF8ToUTF16("Notification Title"),
    344           UTF8ToUTF16(""),
    345           gfx::Image(),
    346           base::string16(),
    347           DummyNotifierId(),
    348           optional,
    349           NULL));
    350 
    351   MockMessageCenter message_center;
    352   base::scoped_nsobject<MCNotificationController> controller(
    353       [[MCNotificationController alloc] initWithNotification:notification.get()
    354                                                messageCenter:&message_center]);
    355   [controller view];
    356 
    357   EXPECT_FALSE([[controller titleView] isHidden]);
    358   EXPECT_TRUE([[controller messageView] isHidden]);
    359   EXPECT_FALSE([[controller contextMessageView] isHidden]);
    360 }
    361 
    362 TEST_F(NotificationControllerTest, MessageSize) {
    363   message_center::RichNotificationData data;
    364   std::string id("id");
    365   NotifierId notifier_id(NotifierId::APPLICATION, "notifier");
    366   scoped_ptr<Notification> notification(new Notification(
    367       NOTIFICATION_TYPE_BASE_FORMAT,
    368       id,
    369       base::UTF8ToUTF16(""),
    370       ASCIIToUTF16("And\neven\nthe\nmessage is long.\nThis sure is wordy"),
    371       gfx::Image(),
    372       base::string16() /* display_source */,
    373       notifier_id,
    374       data,
    375       NULL /* delegate */));
    376 
    377   base::scoped_nsobject<MCNotificationController> controller(
    378       [[MCNotificationController alloc] initWithNotification:notification.get()
    379                                                messageCenter:NULL]);
    380 
    381   // Set up the default layout.
    382   [controller view];
    383 
    384   auto compute_message_lines = ^{
    385       NSString* string = [[[controller messageView] textStorage] string];
    386       unsigned numberOfLines, index, stringLength = [string length];
    387       for (index = 0, numberOfLines = 0; index < stringLength; numberOfLines++)
    388         index = NSMaxRange([string lineRangeForRange:NSMakeRange(index, 0)]);
    389 
    390       return numberOfLines;
    391   };
    392 
    393   // Message and no title: 5 lines.
    394   EXPECT_EQ(5u, compute_message_lines());
    395 
    396   // Message and one line title: 5 lines.
    397   notification->set_title(ASCIIToUTF16("one line"));
    398   [controller updateNotification:notification.get()];
    399   EXPECT_EQ(5u, compute_message_lines());
    400 
    401   // Message and two line title: 3 lines.
    402   notification->set_title(ASCIIToUTF16("two\nlines"));
    403   [controller updateNotification:notification.get()];
    404   EXPECT_EQ(3u, compute_message_lines());
    405 
    406   // Message, image and no title: 2 lines.
    407   SkBitmap bitmap;
    408   bitmap.allocN32Pixels(2, 2);
    409   bitmap.eraseColor(SK_ColorGREEN);
    410   notification->set_title(ASCIIToUTF16(""));
    411   notification->set_image(gfx::Image::CreateFrom1xBitmap(bitmap));
    412   [controller updateNotification:notification.get()];
    413   EXPECT_EQ(2u, compute_message_lines());
    414 
    415   // Message, image and one line title: 2 lines.
    416   notification->set_title(ASCIIToUTF16("one line"));
    417   [controller updateNotification:notification.get()];
    418   EXPECT_EQ(2u, compute_message_lines());
    419 
    420   // Message, image and two line title: 1 lines.
    421   notification->set_title(ASCIIToUTF16("two\nlines"));
    422   [controller updateNotification:notification.get()];
    423   EXPECT_EQ(1u, compute_message_lines());
    424 
    425   // Same as above, but context message takes away from message lines.
    426   notification->set_context_message(base::UTF8ToUTF16("foo"));
    427   notification->set_title(ASCIIToUTF16(""));
    428   [controller updateNotification:notification.get()];
    429   EXPECT_EQ(1u, compute_message_lines());
    430 
    431   notification->set_title(ASCIIToUTF16("one line"));
    432   [controller updateNotification:notification.get()];
    433   EXPECT_EQ(1u, compute_message_lines());
    434 
    435   notification->set_title(ASCIIToUTF16("two\nlines"));
    436   [controller updateNotification:notification.get()];
    437   EXPECT_EQ(0u, compute_message_lines());
    438 }
    439 
    440 }  // namespace message_center
    441