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#include "ui/message_center/views/message_popup_collection.h"
6
7#include <list>
8
9#include "base/message_loop/message_loop.h"
10#include "base/strings/string_number_conversions.h"
11#include "base/strings/utf_string_conversions.h"
12#include "testing/gtest/include/gtest/gtest.h"
13#include "ui/events/event.h"
14#include "ui/events/event_constants.h"
15#include "ui/gfx/display.h"
16#include "ui/gfx/rect.h"
17#include "ui/message_center/fake_message_center.h"
18#include "ui/message_center/views/toast_contents_view.h"
19#include "ui/views/test/views_test_base.h"
20#include "ui/views/widget/widget.h"
21#include "ui/views/widget/widget_delegate.h"
22
23namespace message_center {
24namespace test {
25
26class MessagePopupCollectionTest : public views::ViewsTestBase {
27 public:
28  virtual void SetUp() OVERRIDE {
29    views::ViewsTestBase::SetUp();
30    MessageCenter::Initialize();
31    MessageCenter::Get()->DisableTimersForTest();
32    collection_.reset(new MessagePopupCollection(
33        GetContext(), MessageCenter::Get(), NULL, false));
34    // This size fits test machines resolution and also can keep a few toasts
35    // w/o ill effects of hitting the screen overflow. This allows us to assume
36    // and verify normal layout of the toast stack.
37    collection_->SetDisplayInfo(gfx::Rect(0, 0, 600, 390),
38                                gfx::Rect(0, 0, 600, 400));  // Simulate a
39                                                             // taskbar at the
40                                                             // bottom.
41    id_ = 0;
42    PrepareForWait();
43  }
44
45  virtual void TearDown() OVERRIDE {
46    collection_.reset();
47    MessageCenter::Shutdown();
48    views::ViewsTestBase::TearDown();
49  }
50
51 protected:
52  MessagePopupCollection* collection() { return collection_.get(); }
53
54  size_t GetToastCounts() {
55    return collection_->toasts_.size();
56  }
57
58  bool MouseInCollection() {
59    return collection_->latest_toast_entered_ != NULL;
60  }
61
62  bool IsToastShown(const std::string& id) {
63    views::Widget* widget = collection_->GetWidgetForTest(id);
64    return widget && widget->IsVisible();
65  }
66
67  views::Widget* GetWidget(const std::string& id) {
68    return collection_->GetWidgetForTest(id);
69  }
70
71  gfx::Rect GetWorkArea() {
72    return collection_->work_area_;
73  }
74
75  ToastContentsView* GetToast(const std::string& id) {
76    for (MessagePopupCollection::Toasts::iterator iter =
77             collection_->toasts_.begin();
78         iter != collection_->toasts_.end(); ++iter) {
79      if ((*iter)->id() == id)
80        return *iter;
81    }
82    return NULL;
83  }
84
85  std::string AddNotification() {
86    std::string id = base::IntToString(id_++);
87    scoped_ptr<Notification> notification(
88        new Notification(NOTIFICATION_TYPE_BASE_FORMAT,
89                         id,
90                         UTF8ToUTF16("test title"),
91                         UTF8ToUTF16("test message"),
92                         gfx::Image(),
93                         string16() /* display_source */,
94                         NotifierId(),
95                         message_center::RichNotificationData(),
96                         NULL /* delegate */));
97    MessageCenter::Get()->AddNotification(notification.Pass());
98    return id;
99  }
100
101  void PrepareForWait() { collection_->CreateRunLoopForTest(); }
102
103  // Assumes there is non-zero pending work.
104  void WaitForTransitionsDone() {
105    collection_->WaitForTest();
106    collection_->CreateRunLoopForTest();
107  }
108
109  void CloseAllToasts() {
110    // Assumes there is at least one toast to close.
111    EXPECT_TRUE(GetToastCounts() > 0);
112    MessageCenter::Get()->RemoveAllNotifications(false);
113  }
114
115  gfx::Rect GetToastRectAt(size_t index) {
116    return collection_->GetToastRectAt(index);
117  }
118
119 private:
120  scoped_ptr<MessagePopupCollection> collection_;
121  int id_;
122};
123
124TEST_F(MessagePopupCollectionTest, DismissOnClick) {
125
126  std::string id1 = AddNotification();
127  std::string id2 = AddNotification();
128  WaitForTransitionsDone();
129
130  EXPECT_EQ(2u, GetToastCounts());
131  EXPECT_TRUE(IsToastShown(id1));
132  EXPECT_TRUE(IsToastShown(id2));
133
134  MessageCenter::Get()->ClickOnNotification(id2);
135  WaitForTransitionsDone();
136
137  EXPECT_EQ(1u, GetToastCounts());
138  EXPECT_TRUE(IsToastShown(id1));
139  EXPECT_FALSE(IsToastShown(id2));
140
141  MessageCenter::Get()->ClickOnNotificationButton(id1, 0);
142  WaitForTransitionsDone();
143  EXPECT_EQ(0u, GetToastCounts());
144  EXPECT_FALSE(IsToastShown(id1));
145  EXPECT_FALSE(IsToastShown(id2));
146}
147
148TEST_F(MessagePopupCollectionTest, ShutdownDuringShowing) {
149  std::string id1 = AddNotification();
150  std::string id2 = AddNotification();
151  WaitForTransitionsDone();
152  EXPECT_EQ(2u, GetToastCounts());
153  EXPECT_TRUE(IsToastShown(id1));
154  EXPECT_TRUE(IsToastShown(id2));
155
156  // Finish without cleanup of notifications, which may cause use-after-free.
157  // See crbug.com/236448
158  GetWidget(id1)->CloseNow();
159  collection()->OnMouseExited(GetToast(id2));
160}
161
162TEST_F(MessagePopupCollectionTest, DefaultPositioning) {
163  std::string id0 = AddNotification();
164  std::string id1 = AddNotification();
165  std::string id2 = AddNotification();
166  std::string id3 = AddNotification();
167  WaitForTransitionsDone();
168
169  gfx::Rect r0 = GetToastRectAt(0);
170  gfx::Rect r1 = GetToastRectAt(1);
171  gfx::Rect r2 = GetToastRectAt(2);
172  gfx::Rect r3 = GetToastRectAt(3);
173
174  // 3 toasts are shown, equal size, vertical stack.
175  EXPECT_TRUE(IsToastShown(id0));
176  EXPECT_TRUE(IsToastShown(id1));
177  EXPECT_TRUE(IsToastShown(id2));
178
179  EXPECT_EQ(r0.width(), r1.width());
180  EXPECT_EQ(r1.width(), r2.width());
181
182  EXPECT_EQ(r0.height(), r1.height());
183  EXPECT_EQ(r1.height(), r2.height());
184
185  EXPECT_GT(r0.y(), r1.y());
186  EXPECT_GT(r1.y(), r2.y());
187
188  EXPECT_EQ(r0.x(), r1.x());
189  EXPECT_EQ(r1.x(), r2.x());
190
191  // The 4th toast is not shown yet.
192  EXPECT_FALSE(IsToastShown(id3));
193  EXPECT_EQ(0, r3.width());
194  EXPECT_EQ(0, r3.height());
195
196  CloseAllToasts();
197  EXPECT_EQ(0u, GetToastCounts());
198}
199
200TEST_F(MessagePopupCollectionTest, DefaultPositioningWithRightTaskbar) {
201  // If taskbar is on the right we show the toasts bottom to top as usual.
202
203  // Simulate a taskbar at the right.
204  collection()->SetDisplayInfo(gfx::Rect(0, 0, 590, 400),   // Work-area.
205                               gfx::Rect(0, 0, 600, 400));  // Display-bounds.
206  std::string id0 = AddNotification();
207  std::string id1 = AddNotification();
208  WaitForTransitionsDone();
209
210  gfx::Rect r0 = GetToastRectAt(0);
211  gfx::Rect r1 = GetToastRectAt(1);
212
213  // 2 toasts are shown, equal size, vertical stack.
214  EXPECT_TRUE(IsToastShown(id0));
215  EXPECT_TRUE(IsToastShown(id1));
216
217  EXPECT_EQ(r0.width(), r1.width());
218  EXPECT_EQ(r0.height(), r1.height());
219  EXPECT_GT(r0.y(), r1.y());
220  EXPECT_EQ(r0.x(), r1.x());
221
222  CloseAllToasts();
223  EXPECT_EQ(0u, GetToastCounts());
224
225  // Restore simulated taskbar position to bottom.
226  collection()->SetDisplayInfo(gfx::Rect(0, 0, 600, 390),  // Work-area.
227                               gfx::Rect(0, 0, 600, 400)); // Display-bounds.
228}
229
230TEST_F(MessagePopupCollectionTest, TopDownPositioningWithTopTaskbar) {
231  // Simulate a taskbar at the top.
232  collection()->SetDisplayInfo(gfx::Rect(0, 10, 600, 390),  // Work-area.
233                               gfx::Rect(0, 0, 600, 400));  // Display-bounds.
234  std::string id0 = AddNotification();
235  std::string id1 = AddNotification();
236  WaitForTransitionsDone();
237
238  gfx::Rect r0 = GetToastRectAt(0);
239  gfx::Rect r1 = GetToastRectAt(1);
240
241  // 2 toasts are shown, equal size, vertical stack.
242  EXPECT_TRUE(IsToastShown(id0));
243  EXPECT_TRUE(IsToastShown(id1));
244
245  EXPECT_EQ(r0.width(), r1.width());
246  EXPECT_EQ(r0.height(), r1.height());
247  EXPECT_LT(r0.y(), r1.y());
248  EXPECT_EQ(r0.x(), r1.x());
249
250  CloseAllToasts();
251  EXPECT_EQ(0u, GetToastCounts());
252
253  // Restore simulated taskbar position to bottom.
254  collection()->SetDisplayInfo(gfx::Rect(0, 0, 600, 390),   // Work-area.
255                               gfx::Rect(0, 0, 600, 400));  // Display-bounds.
256}
257
258TEST_F(MessagePopupCollectionTest, TopDownPositioningWithLeftAndTopTaskbar) {
259  // If there "seems" to be a taskbar on left and top (like in Unity), it is
260  // assumed that the actual taskbar is the top one.
261
262  // Simulate a taskbar at the top and left.
263  collection()->SetDisplayInfo(gfx::Rect(10, 10, 590, 390),  // Work-area.
264                               gfx::Rect(0, 0, 600, 400));   // Display-bounds.
265  std::string id0 = AddNotification();
266  std::string id1 = AddNotification();
267  WaitForTransitionsDone();
268
269  gfx::Rect r0 = GetToastRectAt(0);
270  gfx::Rect r1 = GetToastRectAt(1);
271
272  // 2 toasts are shown, equal size, vertical stack.
273  EXPECT_TRUE(IsToastShown(id0));
274  EXPECT_TRUE(IsToastShown(id1));
275
276  EXPECT_EQ(r0.width(), r1.width());
277  EXPECT_EQ(r0.height(), r1.height());
278  EXPECT_LT(r0.y(), r1.y());
279  EXPECT_EQ(r0.x(), r1.x());
280
281  CloseAllToasts();
282  EXPECT_EQ(0u, GetToastCounts());
283
284  // Restore simulated taskbar position to bottom.
285  collection()->SetDisplayInfo(gfx::Rect(0, 0, 600, 390),   // Work-area.
286                               gfx::Rect(0, 0, 600, 400));  // Display-bounds.
287}
288
289TEST_F(MessagePopupCollectionTest, TopDownPositioningWithBottomAndTopTaskbar) {
290  // If there "seems" to be a taskbar on bottom and top (like in Gnome), it is
291  // assumed that the actual taskbar is the top one.
292
293  // Simulate a taskbar at the top and bottom.
294  collection()->SetDisplayInfo(gfx::Rect(0, 10, 580, 400),  // Work-area.
295                               gfx::Rect(0, 0, 600, 400));  // Display-bounds.
296  std::string id0 = AddNotification();
297  std::string id1 = AddNotification();
298  WaitForTransitionsDone();
299
300  gfx::Rect r0 = GetToastRectAt(0);
301  gfx::Rect r1 = GetToastRectAt(1);
302
303  // 2 toasts are shown, equal size, vertical stack.
304  EXPECT_TRUE(IsToastShown(id0));
305  EXPECT_TRUE(IsToastShown(id1));
306
307  EXPECT_EQ(r0.width(), r1.width());
308  EXPECT_EQ(r0.height(), r1.height());
309  EXPECT_LT(r0.y(), r1.y());
310  EXPECT_EQ(r0.x(), r1.x());
311
312  CloseAllToasts();
313  EXPECT_EQ(0u, GetToastCounts());
314
315  // Restore simulated taskbar position to bottom.
316  collection()->SetDisplayInfo(gfx::Rect(0, 0, 600, 390),   // Work-area.
317                               gfx::Rect(0, 0, 600, 400));  // Display-bounds.
318}
319
320TEST_F(MessagePopupCollectionTest, LeftPositioningWithLeftTaskbar) {
321  // Simulate a taskbar at the left.
322  collection()->SetDisplayInfo(gfx::Rect(10, 0, 590, 400),  // Work-area.
323                               gfx::Rect(0, 0, 600, 400));  // Display-bounds.
324  std::string id0 = AddNotification();
325  std::string id1 = AddNotification();
326  WaitForTransitionsDone();
327
328  gfx::Rect r0 = GetToastRectAt(0);
329  gfx::Rect r1 = GetToastRectAt(1);
330
331  // 2 toasts are shown, equal size, vertical stack.
332  EXPECT_TRUE(IsToastShown(id0));
333  EXPECT_TRUE(IsToastShown(id1));
334
335  EXPECT_EQ(r0.width(), r1.width());
336  EXPECT_EQ(r0.height(), r1.height());
337  EXPECT_GT(r0.y(), r1.y());
338  EXPECT_EQ(r0.x(), r1.x());
339
340  // Ensure that toasts are on the left.
341  EXPECT_LT(r1.x(), GetWorkArea().CenterPoint().x());
342
343  CloseAllToasts();
344  EXPECT_EQ(0u, GetToastCounts());
345
346  // Restore simulated taskbar position to bottom.
347  collection()->SetDisplayInfo(gfx::Rect(0, 0, 600, 390),   // Work-area.
348                               gfx::Rect(0, 0, 600, 400));  // Display-bounds.
349}
350
351TEST_F(MessagePopupCollectionTest, DetectMouseHover) {
352  std::string id0 = AddNotification();
353  std::string id1 = AddNotification();
354  WaitForTransitionsDone();
355
356  views::WidgetDelegateView* toast0 = GetToast(id0);
357  EXPECT_TRUE(toast0 != NULL);
358  views::WidgetDelegateView* toast1 = GetToast(id1);
359  EXPECT_TRUE(toast1 != NULL);
360
361  ui::MouseEvent event(ui::ET_MOUSE_MOVED, gfx::Point(), gfx::Point(), 0);
362
363  // Test that mouse detection logic works in presence of out-of-order events.
364  toast0->OnMouseEntered(event);
365  EXPECT_TRUE(MouseInCollection());
366  toast1->OnMouseEntered(event);
367  EXPECT_TRUE(MouseInCollection());
368  toast0->OnMouseExited(event);
369  EXPECT_TRUE(MouseInCollection());
370  toast1->OnMouseExited(event);
371  EXPECT_FALSE(MouseInCollection());
372
373  // Test that mouse detection logic works in presence of WindowClosing events.
374  toast0->OnMouseEntered(event);
375  EXPECT_TRUE(MouseInCollection());
376  toast1->OnMouseEntered(event);
377  EXPECT_TRUE(MouseInCollection());
378  toast0->WindowClosing();
379  EXPECT_TRUE(MouseInCollection());
380  toast1->WindowClosing();
381  EXPECT_FALSE(MouseInCollection());
382}
383
384// TODO(dimich): Test repositioning - both normal one and when user is closing
385// the toasts.
386TEST_F(MessagePopupCollectionTest, DetectMouseHoverWithUserClose) {
387  std::string id0 = AddNotification();
388  std::string id1 = AddNotification();
389  WaitForTransitionsDone();
390
391  views::WidgetDelegateView* toast0 = GetToast(id0);
392  EXPECT_TRUE(toast0 != NULL);
393  views::WidgetDelegateView* toast1 = GetToast(id1);
394  ASSERT_TRUE(toast1 != NULL);
395
396  ui::MouseEvent event(ui::ET_MOUSE_MOVED, gfx::Point(), gfx::Point(), 0);
397  toast1->OnMouseEntered(event);
398  static_cast<MessageCenterObserver*>(collection())->OnNotificationRemoved(
399      id1, true);
400
401  EXPECT_FALSE(MouseInCollection());
402  std::string id2 = AddNotification();
403
404  WaitForTransitionsDone();
405  views::WidgetDelegateView* toast2 = GetToast(id2);
406  EXPECT_TRUE(toast2 != NULL);
407}
408
409
410}  // namespace test
411}  // namespace message_center
412