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