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.
5 #import "ui/message_center/cocoa/popup_collection.h"
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"
20 using base::ASCIIToUTF16;
22 namespace message_center {
24 class PopupCollectionTest : public ui::CocoaTest {
26 PopupCollectionTest() {
27 message_center::MessageCenter::Initialize();
28 center_ = message_center::MessageCenter::Get();
30 [[MCPopupCollection alloc] initWithMessageCenter:center_]);
31 [collection_ setAnimationDuration:0.001];
32 [collection_ setAnimationEndedCallback:^{
33 if (nested_run_loop_.get())
34 nested_run_loop_->Quit();
38 virtual void TearDown() override {
39 collection_.reset(); // Close all popups.
40 ui::CocoaTest::TearDown();
43 virtual ~PopupCollectionTest() {
44 message_center::MessageCenter::Shutdown();
47 message_center::NotifierId DummyNotifierId() {
48 return message_center::NotifierId();
51 void AddThreeNotifications() {
52 scoped_ptr<message_center::Notification> notification;
53 notification.reset(new message_center::Notification(
54 message_center::NOTIFICATION_TYPE_SIMPLE,
57 ASCIIToUTF16("This is the first notification to"
62 message_center::RichNotificationData(),
64 center_->AddNotification(notification.Pass());
66 notification.reset(new message_center::Notification(
67 message_center::NOTIFICATION_TYPE_SIMPLE,
70 ASCIIToUTF16("This is the second notification."),
74 message_center::RichNotificationData(),
76 center_->AddNotification(notification.Pass());
78 notification.reset(new message_center::Notification(
79 message_center::NOTIFICATION_TYPE_SIMPLE,
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"),
91 message_center::RichNotificationData(),
93 center_->AddNotification(notification.Pass());
94 WaitForAnimationEnded();
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;
105 void WaitForAnimationEnded() {
106 if (![collection_ isAnimating])
108 nested_run_loop_.reset(new base::RunLoop());
109 nested_run_loop_->Run();
110 nested_run_loop_.reset();
113 base::MessageLoopForUI message_loop_;
114 scoped_ptr<base::RunLoop> nested_run_loop_;
115 message_center::MessageCenter* center_;
116 base::scoped_nsobject<MCPopupCollection> collection_;
119 TEST_F(PopupCollectionTest, AddThreeCloseOne) {
120 EXPECT_EQ(0u, [[collection_ popups] count]);
121 AddThreeNotifications();
122 EXPECT_EQ(3u, [[collection_ popups] count]);
124 center_->RemoveNotification("2", true);
125 WaitForAnimationEnded();
126 EXPECT_EQ(2u, [[collection_ popups] count]);
129 TEST_F(PopupCollectionTest, AttemptFourOneOffscreen) {
130 [collection_ setScreenFrame:NSMakeRect(0, 0, 800, 300)];
132 EXPECT_EQ(0u, [[collection_ popups] count]);
133 AddThreeNotifications();
134 EXPECT_EQ(2u, [[collection_ popups] count]); // "3" does not fit on screen.
136 scoped_ptr<message_center::Notification> notification;
138 notification.reset(new message_center::Notification(
139 message_center::NOTIFICATION_TYPE_SIMPLE,
141 ASCIIToUTF16("Four"),
142 ASCIIToUTF16("This is the fourth notification."),
146 message_center::RichNotificationData(),
148 center_->AddNotification(notification.Pass());
149 WaitForAnimationEnded();
151 // Remove "1" and "3" should fit on screen.
152 center_->RemoveNotification("1", true);
153 WaitForAnimationEnded();
154 ASSERT_EQ(2u, [[collection_ popups] count]);
156 EXPECT_EQ("2", [[[collection_ popups] objectAtIndex:0] notificationID]);
157 EXPECT_EQ("3", [[[collection_ popups] objectAtIndex:1] notificationID]);
159 // Remove "2" and "4" should fit on screen.
160 center_->RemoveNotification("2", true);
161 WaitForAnimationEnded();
162 ASSERT_EQ(2u, [[collection_ popups] count]);
164 EXPECT_EQ("3", [[[collection_ popups] objectAtIndex:0] notificationID]);
165 EXPECT_EQ("4", [[[collection_ popups] objectAtIndex:1] notificationID]);
168 TEST_F(PopupCollectionTest, LayoutSpacing) {
169 const CGFloat kScreenSize = 500;
170 [collection_ setScreenFrame:NSMakeRect(0, 0, kScreenSize, kScreenSize)];
172 AddThreeNotifications();
173 NSArray* popups = [collection_ popups];
175 EXPECT_EQ(message_center::kMarginBetweenItems,
176 kScreenSize - NSMaxY([[[popups objectAtIndex:0] window] frame]));
178 EXPECT_TRUE(CheckSpacingBetween([popups objectAtIndex:0],
179 [popups objectAtIndex:1]));
180 EXPECT_TRUE(CheckSpacingBetween([popups objectAtIndex:1],
181 [popups objectAtIndex:2]));
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,
190 ASCIIToUTF16("Four"),
191 ASCIIToUTF16("This is the fourth notification."),
197 center_->AddNotification(notification.Pass());
198 WaitForAnimationEnded();
199 EXPECT_TRUE(CheckSpacingBetween([popups objectAtIndex:2],
200 [popups objectAtIndex:3]));
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]));
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]));
219 TEST_F(PopupCollectionTest, TinyScreen) {
220 [collection_ setScreenFrame:NSMakeRect(0, 0, 800, 100)];
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,
228 ASCIIToUTF16("This is the first notification to"
233 message_center::RichNotificationData(),
235 center_->AddNotification(notification.Pass());
236 WaitForAnimationEnded();
237 EXPECT_EQ(1u, [[collection_ popups] count]);
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,
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."),
255 message_center::RichNotificationData(),
257 center_->UpdateNotification("1", notification.Pass());
258 WaitForAnimationEnded();
259 EXPECT_EQ(0u, [[collection_ popups] count]);
262 TEST_F(PopupCollectionTest, UpdateIconAndBody) {
263 AddThreeNotifications();
264 NSArray* popups = [collection_ popups];
266 EXPECT_EQ(3u, [popups count]);
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]);
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]));
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,
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 "
299 message_center::RichNotificationData(),
301 center_->AddNotification(notification.Pass());
302 WaitForAnimationEnded();
303 EXPECT_GT(NSHeight([[controller view] frame]), NSHeight(old_frame));
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]);
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,
322 ASCIIToUTF16("This notification should not yet toast."),
326 message_center::RichNotificationData(),
328 notification->set_priority(-1);
330 center_->AddNotification(notification.Pass());
331 WaitForAnimationEnded();
332 NSArray* popups = [collection_ popups];
333 EXPECT_EQ(0u, [popups count]);
335 // Raise priority -1 to 1. Notification should display.
336 notification.reset(new message_center::Notification(
337 message_center::NOTIFICATION_TYPE_SIMPLE,
340 ASCIIToUTF16("This notification should now toast"),
344 message_center::RichNotificationData(),
346 notification->set_priority(1);
348 center_->UpdateNotification("1", notification.Pass());
349 WaitForAnimationEnded();
350 EXPECT_EQ(1u, [popups count]);
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,
360 ASCIIToUTF16("This is the first notification to"
365 message_center::RichNotificationData(),
367 center_->AddNotification(notification.Pass());
369 // Release the popup collection before the animation ends. No crash should
374 TEST_F(PopupCollectionTest, CloseCollectionBeforeClosePopupAnimationEnds) {
375 AddThreeNotifications();
377 // Remove a notification and don't wait for the animation to finish.
378 center_->RemoveNotification("1", true);
380 // Release the popup collection before the animation ends. No crash should
385 TEST_F(PopupCollectionTest, CloseCollectionBeforeUpdatePopupAnimationEnds) {
386 AddThreeNotifications();
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,
394 ASCIIToUTF16("New message."),
398 message_center::RichNotificationData(),
400 center_->UpdateNotification("1", notification.Pass());
402 // Release the popup collection before the animation ends. No crash should
407 // This test reproduces bug.
408 // https://code.google.com/p/chromium/issues/detail?id=418053
409 // It will timeout if problem exists.
410 TEST_F(PopupCollectionTest, AnimationDidEndOutOfOrder) {
411 // Set animation duration to 100 ms.
412 [collection_ setAnimationDuration:0.1];
414 // Add three notifications.
415 AddThreeNotifications();
417 // Wait for animation end.
418 WaitForAnimationEnded();
420 // Add fourth notification.
421 scoped_ptr<message_center::Notification> notification;
422 notification.reset(new message_center::Notification(
423 message_center::NOTIFICATION_TYPE_SIMPLE,
425 ASCIIToUTF16("Fourth"),
426 ASCIIToUTF16("This is the Fourth notification to be displayed"),
430 message_center::RichNotificationData(),
432 center_->AddNotification(notification.Pass());
434 // Mark first notifications as shown,
435 // just as from TimerFinished callback.
436 // Mark it while fourth animation is in progress so old notification
437 // will be marked as pending updates.
438 center_->MarkSinglePopupAsShown("1", false);
439 center_->MarkSinglePopupAsShown("2", false);
440 center_->MarkSinglePopupAsShown("3", false);
442 WaitForAnimationEnded();
443 // Assert that isAnimating flag is false ater animation ends.
444 // If it will be true after all animaions ends,
445 // no new notification can be added and collection will be blocked.
446 EXPECT_FALSE([collection_ isAnimating]);
448 // Release the popup collection before the animation ends. No crash should
453 } // namespace message_center