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