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 20using base::ASCIIToUTF16; 21 22namespace message_center { 23 24class 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 119TEST_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 129TEST_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 168TEST_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 219TEST_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 262TEST_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 316TEST_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 353TEST_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 374TEST_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 385TEST_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