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 void TearDown() override {
39 collection_.reset(); // Close all popups.
40 ui::CocoaTest::TearDown();
43 ~PopupCollectionTest() override { message_center::MessageCenter::Shutdown(); }
45 message_center::NotifierId DummyNotifierId() {
46 return message_center::NotifierId();
49 void AddThreeNotifications() {
50 scoped_ptr<message_center::Notification> notification;
51 notification.reset(new message_center::Notification(
52 message_center::NOTIFICATION_TYPE_SIMPLE, "1", ASCIIToUTF16("One"),
53 ASCIIToUTF16("This is the first notification to"
55 gfx::Image(), base::string16(), GURL(), DummyNotifierId(),
56 message_center::RichNotificationData(), NULL));
57 center_->AddNotification(notification.Pass());
59 notification.reset(new message_center::Notification(
60 message_center::NOTIFICATION_TYPE_SIMPLE, "2", ASCIIToUTF16("Two"),
61 ASCIIToUTF16("This is the second notification."), gfx::Image(),
62 base::string16(), GURL(), DummyNotifierId(),
63 message_center::RichNotificationData(), NULL));
64 center_->AddNotification(notification.Pass());
66 notification.reset(new message_center::Notification(
67 message_center::NOTIFICATION_TYPE_SIMPLE, "3", ASCIIToUTF16("Three"),
68 ASCIIToUTF16("This is the third notification "
69 "that has a much longer body "
70 "than the other notifications. It "
71 "may not fit on the screen if we "
72 "set the screen size too small or "
73 "if the notification is way too big"),
74 gfx::Image(), base::string16(), GURL(), DummyNotifierId(),
75 message_center::RichNotificationData(), NULL));
76 center_->AddNotification(notification.Pass());
77 WaitForAnimationEnded();
80 bool CheckSpacingBetween(MCPopupController* upper, MCPopupController* lower) {
81 CGFloat minY = NSMinY([[upper window] frame]);
82 CGFloat maxY = NSMaxY([[lower window] frame]);
83 CGFloat delta = minY - maxY;
84 EXPECT_EQ(message_center::kMarginBetweenItems, delta);
85 return delta == message_center::kMarginBetweenItems;
88 void WaitForAnimationEnded() {
89 if (![collection_ isAnimating])
91 nested_run_loop_.reset(new base::RunLoop());
92 nested_run_loop_->Run();
93 nested_run_loop_.reset();
96 base::MessageLoopForUI message_loop_;
97 scoped_ptr<base::RunLoop> nested_run_loop_;
98 message_center::MessageCenter* center_;
99 base::scoped_nsobject<MCPopupCollection> collection_;
102 TEST_F(PopupCollectionTest, AddThreeCloseOne) {
103 EXPECT_EQ(0u, [[collection_ popups] count]);
104 AddThreeNotifications();
105 EXPECT_EQ(3u, [[collection_ popups] count]);
107 center_->RemoveNotification("2", true);
108 WaitForAnimationEnded();
109 EXPECT_EQ(2u, [[collection_ popups] count]);
112 TEST_F(PopupCollectionTest, AttemptFourOneOffscreen) {
113 [collection_ setScreenFrame:NSMakeRect(0, 0, 800, 300)];
115 EXPECT_EQ(0u, [[collection_ popups] count]);
116 AddThreeNotifications();
117 EXPECT_EQ(2u, [[collection_ popups] count]); // "3" does not fit on screen.
119 scoped_ptr<message_center::Notification> notification;
121 notification.reset(new message_center::Notification(
122 message_center::NOTIFICATION_TYPE_SIMPLE, "4", ASCIIToUTF16("Four"),
123 ASCIIToUTF16("This is the fourth notification."), gfx::Image(),
124 base::string16(), GURL(), DummyNotifierId(),
125 message_center::RichNotificationData(), NULL));
126 center_->AddNotification(notification.Pass());
127 WaitForAnimationEnded();
129 // Remove "1" and "3" should fit on screen.
130 center_->RemoveNotification("1", true);
131 WaitForAnimationEnded();
132 ASSERT_EQ(2u, [[collection_ popups] count]);
134 EXPECT_EQ("2", [[[collection_ popups] objectAtIndex:0] notificationID]);
135 EXPECT_EQ("3", [[[collection_ popups] objectAtIndex:1] notificationID]);
137 // Remove "2" and "4" should fit on screen.
138 center_->RemoveNotification("2", true);
139 WaitForAnimationEnded();
140 ASSERT_EQ(2u, [[collection_ popups] count]);
142 EXPECT_EQ("3", [[[collection_ popups] objectAtIndex:0] notificationID]);
143 EXPECT_EQ("4", [[[collection_ popups] objectAtIndex:1] notificationID]);
146 TEST_F(PopupCollectionTest, LayoutSpacing) {
147 const CGFloat kScreenSize = 500;
148 [collection_ setScreenFrame:NSMakeRect(0, 0, kScreenSize, kScreenSize)];
150 AddThreeNotifications();
151 NSArray* popups = [collection_ popups];
153 EXPECT_EQ(message_center::kMarginBetweenItems,
154 kScreenSize - NSMaxY([[[popups objectAtIndex:0] window] frame]));
156 EXPECT_TRUE(CheckSpacingBetween([popups objectAtIndex:0],
157 [popups objectAtIndex:1]));
158 EXPECT_TRUE(CheckSpacingBetween([popups objectAtIndex:1],
159 [popups objectAtIndex:2]));
161 // Set priority so that kMaxVisiblePopupNotifications does not hide it.
162 message_center::RichNotificationData optional;
163 optional.priority = message_center::HIGH_PRIORITY;
164 scoped_ptr<message_center::Notification> notification;
165 notification.reset(new message_center::Notification(
166 message_center::NOTIFICATION_TYPE_SIMPLE, "4", ASCIIToUTF16("Four"),
167 ASCIIToUTF16("This is the fourth notification."), gfx::Image(),
168 base::string16(), GURL(), DummyNotifierId(), optional, NULL));
169 center_->AddNotification(notification.Pass());
170 WaitForAnimationEnded();
171 EXPECT_TRUE(CheckSpacingBetween([popups objectAtIndex:2],
172 [popups objectAtIndex:3]));
175 center_->RemoveNotification("2", true);
176 WaitForAnimationEnded();
177 EXPECT_TRUE(CheckSpacingBetween([popups objectAtIndex:0],
178 [popups objectAtIndex:1]));
179 EXPECT_TRUE(CheckSpacingBetween([popups objectAtIndex:1],
180 [popups objectAtIndex:2]));
183 center_->RemoveNotification("2", true);
184 WaitForAnimationEnded();
185 EXPECT_EQ(message_center::kMarginBetweenItems,
186 kScreenSize - NSMaxY([[[popups objectAtIndex:0] window] frame]));
187 EXPECT_TRUE(CheckSpacingBetween([popups objectAtIndex:0],
188 [popups objectAtIndex:1]));
191 TEST_F(PopupCollectionTest, TinyScreen) {
192 [collection_ setScreenFrame:NSMakeRect(0, 0, 800, 100)];
194 EXPECT_EQ(0u, [[collection_ popups] count]);
195 scoped_ptr<message_center::Notification> notification;
196 notification.reset(new message_center::Notification(
197 message_center::NOTIFICATION_TYPE_SIMPLE, "1", ASCIIToUTF16("One"),
198 ASCIIToUTF16("This is the first notification to"
200 gfx::Image(), base::string16(), GURL(), DummyNotifierId(),
201 message_center::RichNotificationData(), NULL));
202 center_->AddNotification(notification.Pass());
203 WaitForAnimationEnded();
204 EXPECT_EQ(1u, [[collection_ popups] count]);
206 // Now give the notification a longer message so that it no longer fits.
207 notification.reset(new message_center::Notification(
208 message_center::NOTIFICATION_TYPE_SIMPLE, "1", ASCIIToUTF16("One"),
209 ASCIIToUTF16("This is now a very very very very "
210 "very very very very very very very "
211 "very very very very very very very "
212 "very very very very very very very "
213 "very very very very very very very "
214 "very very very very very very very "
215 "very very very very very very very "
216 "long notification."),
217 gfx::Image(), base::string16(), GURL(), DummyNotifierId(),
218 message_center::RichNotificationData(), NULL));
219 center_->UpdateNotification("1", notification.Pass());
220 WaitForAnimationEnded();
221 EXPECT_EQ(0u, [[collection_ popups] count]);
224 TEST_F(PopupCollectionTest, UpdateIconAndBody) {
225 AddThreeNotifications();
226 NSArray* popups = [collection_ popups];
228 EXPECT_EQ(3u, [popups count]);
231 MCNotificationController* controller =
232 [[popups objectAtIndex:1] notificationController];
233 EXPECT_FALSE([[controller iconView] image]);
234 center_->SetNotificationIcon("2",
235 gfx::Image([[NSImage imageNamed:NSImageNameUser] retain]));
236 WaitForAnimationEnded();
237 EXPECT_TRUE([[controller iconView] image]);
239 EXPECT_EQ(3u, [popups count]);
240 EXPECT_TRUE(CheckSpacingBetween([popups objectAtIndex:0],
241 [popups objectAtIndex:1]));
242 EXPECT_TRUE(CheckSpacingBetween([popups objectAtIndex:1],
243 [popups objectAtIndex:2]));
246 controller = [[popups objectAtIndex:0] notificationController];
247 NSRect old_frame = [[controller view] frame];
248 scoped_ptr<message_center::Notification> notification;
249 notification.reset(new message_center::Notification(
250 message_center::NOTIFICATION_TYPE_SIMPLE, "1",
251 ASCIIToUTF16("One is going to get a much longer "
252 "title than it previously had."),
253 ASCIIToUTF16("This is the first notification to "
254 "be displayed, but it will also be "
255 "updated to have a significantly "
257 gfx::Image(), base::string16(), GURL(), DummyNotifierId(),
258 message_center::RichNotificationData(), NULL));
259 center_->AddNotification(notification.Pass());
260 WaitForAnimationEnded();
261 EXPECT_GT(NSHeight([[controller view] frame]), NSHeight(old_frame));
263 // Test updated spacing.
264 EXPECT_EQ(3u, [popups count]);
265 EXPECT_TRUE(CheckSpacingBetween([popups objectAtIndex:0],
266 [popups objectAtIndex:1]));
267 EXPECT_TRUE(CheckSpacingBetween([popups objectAtIndex:1],
268 [popups objectAtIndex:2]));
269 EXPECT_EQ("1", [[popups objectAtIndex:0] notificationID]);
270 EXPECT_EQ("2", [[popups objectAtIndex:1] notificationID]);
271 EXPECT_EQ("3", [[popups objectAtIndex:2] notificationID]);
274 TEST_F(PopupCollectionTest, UpdatePriority) {
275 scoped_ptr<message_center::Notification> notification;
276 notification.reset(new message_center::Notification(
277 message_center::NOTIFICATION_TYPE_SIMPLE, "1", ASCIIToUTF16("One"),
278 ASCIIToUTF16("This notification should not yet toast."), gfx::Image(),
279 base::string16(), GURL(), DummyNotifierId(),
280 message_center::RichNotificationData(), NULL));
281 notification->set_priority(-1);
283 center_->AddNotification(notification.Pass());
284 WaitForAnimationEnded();
285 NSArray* popups = [collection_ popups];
286 EXPECT_EQ(0u, [popups count]);
288 // Raise priority -1 to 1. Notification should display.
289 notification.reset(new message_center::Notification(
290 message_center::NOTIFICATION_TYPE_SIMPLE, "1", ASCIIToUTF16("One"),
291 ASCIIToUTF16("This notification should now toast"), gfx::Image(),
292 base::string16(), GURL(), DummyNotifierId(),
293 message_center::RichNotificationData(), NULL));
294 notification->set_priority(1);
296 center_->UpdateNotification("1", notification.Pass());
297 WaitForAnimationEnded();
298 EXPECT_EQ(1u, [popups count]);
301 TEST_F(PopupCollectionTest, CloseCollectionBeforeNewPopupAnimationEnds) {
302 // Add a notification and don't wait for the animation to finish.
303 scoped_ptr<message_center::Notification> notification;
304 notification.reset(new message_center::Notification(
305 message_center::NOTIFICATION_TYPE_SIMPLE, "1", ASCIIToUTF16("One"),
306 ASCIIToUTF16("This is the first notification to"
308 gfx::Image(), base::string16(), GURL(), DummyNotifierId(),
309 message_center::RichNotificationData(), NULL));
310 center_->AddNotification(notification.Pass());
312 // Release the popup collection before the animation ends. No crash should
317 TEST_F(PopupCollectionTest, CloseCollectionBeforeClosePopupAnimationEnds) {
318 AddThreeNotifications();
320 // Remove a notification and don't wait for the animation to finish.
321 center_->RemoveNotification("1", true);
323 // Release the popup collection before the animation ends. No crash should
328 TEST_F(PopupCollectionTest, CloseCollectionBeforeUpdatePopupAnimationEnds) {
329 AddThreeNotifications();
331 // Update a notification and don't wait for the animation to finish.
332 scoped_ptr<message_center::Notification> notification;
333 notification.reset(new message_center::Notification(
334 message_center::NOTIFICATION_TYPE_SIMPLE, "1", ASCIIToUTF16("One"),
335 ASCIIToUTF16("New message."), gfx::Image(), base::string16(), GURL(),
336 DummyNotifierId(), message_center::RichNotificationData(), NULL));
337 center_->UpdateNotification("1", notification.Pass());
339 // Release the popup collection before the animation ends. No crash should
344 } // namespace message_center