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 20using base::ASCIIToUTF16; 21using base::UTF8ToUTF16; 22 23namespace { 24 25class 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 104namespace message_center { 105 106class 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 118TEST_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 148TEST_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 172TEST_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 198TEST_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 233TEST_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 264TEST_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 293TEST_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 335TEST_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 362TEST_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