• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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