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 #include "ui/message_center/views/message_popup_collection.h"
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/events/event_utils.h"
16 #include "ui/gfx/display.h"
17 #include "ui/gfx/geometry/rect.h"
18 #include "ui/message_center/fake_message_center.h"
19 #include "ui/message_center/views/desktop_popup_alignment_delegate.h"
20 #include "ui/message_center/views/toast_contents_view.h"
21 #include "ui/views/test/views_test_base.h"
22 #include "ui/views/widget/widget.h"
23 #include "ui/views/widget/widget_delegate.h"
25 namespace message_center
{
28 class MessagePopupCollectionTest
: public views::ViewsTestBase
{
30 void SetUp() override
{
31 views::ViewsTestBase::SetUp();
32 MessageCenter::Initialize();
33 MessageCenter::Get()->DisableTimersForTest();
34 alignment_delegate_
.reset(new DesktopPopupAlignmentDelegate
);
35 collection_
.reset(new MessagePopupCollection(
36 GetContext(), MessageCenter::Get(), NULL
, alignment_delegate_
.get()));
37 // This size fits test machines resolution and also can keep a few toasts
38 // w/o ill effects of hitting the screen overflow. This allows us to assume
39 // and verify normal layout of the toast stack.
40 SetDisplayInfo(gfx::Rect(0, 0, 600, 390), // taskbar at the bottom.
41 gfx::Rect(0, 0, 600, 400));
46 void TearDown() override
{
48 MessageCenter::Shutdown();
49 views::ViewsTestBase::TearDown();
53 MessagePopupCollection
* collection() { return collection_
.get(); }
55 size_t GetToastCounts() {
56 return collection_
->toasts_
.size();
59 bool MouseInCollection() {
60 return collection_
->latest_toast_entered_
!= NULL
;
63 bool IsToastShown(const std::string
& id
) {
64 views::Widget
* widget
= collection_
->GetWidgetForTest(id
);
65 return widget
&& widget
->IsVisible();
68 views::Widget
* GetWidget(const std::string
& id
) {
69 return collection_
->GetWidgetForTest(id
);
72 void SetDisplayInfo(const gfx::Rect
& work_area
,
73 const gfx::Rect
& display_bounds
) {
74 gfx::Display dummy_display
;
75 dummy_display
.set_bounds(display_bounds
);
76 dummy_display
.set_work_area(work_area
);
77 alignment_delegate_
->RecomputeAlignment(dummy_display
);
81 gfx::Rect
GetWorkArea() {
82 return alignment_delegate_
->work_area_
;
85 ToastContentsView
* GetToast(const std::string
& id
) {
86 for (MessagePopupCollection::Toasts::iterator iter
=
87 collection_
->toasts_
.begin();
88 iter
!= collection_
->toasts_
.end(); ++iter
) {
89 if ((*iter
)->id() == id
)
95 std::string
AddNotification() {
96 std::string id
= base::IntToString(id_
++);
97 scoped_ptr
<Notification
> notification(
98 new Notification(NOTIFICATION_TYPE_BASE_FORMAT
,
100 base::UTF8ToUTF16("test title"),
101 base::UTF8ToUTF16("test message"),
103 base::string16() /* display_source */,
105 message_center::RichNotificationData(),
106 NULL
/* delegate */));
107 MessageCenter::Get()->AddNotification(notification
.Pass());
111 void PrepareForWait() { collection_
->CreateRunLoopForTest(); }
113 // Assumes there is non-zero pending work.
114 void WaitForTransitionsDone() {
115 collection_
->WaitForTest();
116 collection_
->CreateRunLoopForTest();
119 void CloseAllToasts() {
120 // Assumes there is at least one toast to close.
121 EXPECT_TRUE(GetToastCounts() > 0);
122 MessageCenter::Get()->RemoveAllNotifications(false);
125 gfx::Rect
GetToastRectAt(size_t index
) {
126 return collection_
->GetToastRectAt(index
);
130 scoped_ptr
<MessagePopupCollection
> collection_
;
131 scoped_ptr
<DesktopPopupAlignmentDelegate
> alignment_delegate_
;
135 TEST_F(MessagePopupCollectionTest
, DismissOnClick
) {
137 std::string id1
= AddNotification();
138 std::string id2
= AddNotification();
139 WaitForTransitionsDone();
141 EXPECT_EQ(2u, GetToastCounts());
142 EXPECT_TRUE(IsToastShown(id1
));
143 EXPECT_TRUE(IsToastShown(id2
));
145 MessageCenter::Get()->ClickOnNotification(id2
);
146 WaitForTransitionsDone();
148 EXPECT_EQ(1u, GetToastCounts());
149 EXPECT_TRUE(IsToastShown(id1
));
150 EXPECT_FALSE(IsToastShown(id2
));
152 MessageCenter::Get()->ClickOnNotificationButton(id1
, 0);
153 WaitForTransitionsDone();
154 EXPECT_EQ(0u, GetToastCounts());
155 EXPECT_FALSE(IsToastShown(id1
));
156 EXPECT_FALSE(IsToastShown(id2
));
159 TEST_F(MessagePopupCollectionTest
, ShutdownDuringShowing
) {
160 std::string id1
= AddNotification();
161 std::string id2
= AddNotification();
162 WaitForTransitionsDone();
163 EXPECT_EQ(2u, GetToastCounts());
164 EXPECT_TRUE(IsToastShown(id1
));
165 EXPECT_TRUE(IsToastShown(id2
));
167 // Finish without cleanup of notifications, which may cause use-after-free.
168 // See crbug.com/236448
169 GetWidget(id1
)->CloseNow();
170 collection()->OnMouseExited(GetToast(id2
));
173 TEST_F(MessagePopupCollectionTest
, DefaultPositioning
) {
174 std::string id0
= AddNotification();
175 std::string id1
= AddNotification();
176 std::string id2
= AddNotification();
177 std::string id3
= AddNotification();
178 WaitForTransitionsDone();
180 gfx::Rect r0
= GetToastRectAt(0);
181 gfx::Rect r1
= GetToastRectAt(1);
182 gfx::Rect r2
= GetToastRectAt(2);
183 gfx::Rect r3
= GetToastRectAt(3);
185 // 3 toasts are shown, equal size, vertical stack.
186 EXPECT_TRUE(IsToastShown(id0
));
187 EXPECT_TRUE(IsToastShown(id1
));
188 EXPECT_TRUE(IsToastShown(id2
));
190 EXPECT_EQ(r0
.width(), r1
.width());
191 EXPECT_EQ(r1
.width(), r2
.width());
193 EXPECT_EQ(r0
.height(), r1
.height());
194 EXPECT_EQ(r1
.height(), r2
.height());
196 EXPECT_GT(r0
.y(), r1
.y());
197 EXPECT_GT(r1
.y(), r2
.y());
199 EXPECT_EQ(r0
.x(), r1
.x());
200 EXPECT_EQ(r1
.x(), r2
.x());
202 // The 4th toast is not shown yet.
203 EXPECT_FALSE(IsToastShown(id3
));
204 EXPECT_EQ(0, r3
.width());
205 EXPECT_EQ(0, r3
.height());
208 EXPECT_EQ(0u, GetToastCounts());
211 TEST_F(MessagePopupCollectionTest
, DefaultPositioningWithRightTaskbar
) {
212 // If taskbar is on the right we show the toasts bottom to top as usual.
214 // Simulate a taskbar at the right.
215 SetDisplayInfo(gfx::Rect(0, 0, 590, 400), // Work-area.
216 gfx::Rect(0, 0, 600, 400)); // Display-bounds.
217 std::string id0
= AddNotification();
218 std::string id1
= AddNotification();
219 WaitForTransitionsDone();
221 gfx::Rect r0
= GetToastRectAt(0);
222 gfx::Rect r1
= GetToastRectAt(1);
224 // 2 toasts are shown, equal size, vertical stack.
225 EXPECT_TRUE(IsToastShown(id0
));
226 EXPECT_TRUE(IsToastShown(id1
));
228 EXPECT_EQ(r0
.width(), r1
.width());
229 EXPECT_EQ(r0
.height(), r1
.height());
230 EXPECT_GT(r0
.y(), r1
.y());
231 EXPECT_EQ(r0
.x(), r1
.x());
234 EXPECT_EQ(0u, GetToastCounts());
236 // Restore simulated taskbar position to bottom.
237 SetDisplayInfo(gfx::Rect(0, 0, 600, 390), // Work-area.
238 gfx::Rect(0, 0, 600, 400)); // Display-bounds.
241 TEST_F(MessagePopupCollectionTest
, TopDownPositioningWithTopTaskbar
) {
242 // Simulate a taskbar at the top.
243 SetDisplayInfo(gfx::Rect(0, 10, 600, 390), // Work-area.
244 gfx::Rect(0, 0, 600, 400)); // Display-bounds.
245 std::string id0
= AddNotification();
246 std::string id1
= AddNotification();
247 WaitForTransitionsDone();
249 gfx::Rect r0
= GetToastRectAt(0);
250 gfx::Rect r1
= GetToastRectAt(1);
252 // 2 toasts are shown, equal size, vertical stack.
253 EXPECT_TRUE(IsToastShown(id0
));
254 EXPECT_TRUE(IsToastShown(id1
));
256 EXPECT_EQ(r0
.width(), r1
.width());
257 EXPECT_EQ(r0
.height(), r1
.height());
258 EXPECT_LT(r0
.y(), r1
.y());
259 EXPECT_EQ(r0
.x(), r1
.x());
262 EXPECT_EQ(0u, GetToastCounts());
264 // Restore simulated taskbar position to bottom.
265 SetDisplayInfo(gfx::Rect(0, 0, 600, 390), // Work-area.
266 gfx::Rect(0, 0, 600, 400)); // Display-bounds.
269 TEST_F(MessagePopupCollectionTest
, TopDownPositioningWithLeftAndTopTaskbar
) {
270 // If there "seems" to be a taskbar on left and top (like in Unity), it is
271 // assumed that the actual taskbar is the top one.
273 // Simulate a taskbar at the top and left.
274 SetDisplayInfo(gfx::Rect(10, 10, 590, 390), // Work-area.
275 gfx::Rect(0, 0, 600, 400)); // Display-bounds.
276 std::string id0
= AddNotification();
277 std::string id1
= AddNotification();
278 WaitForTransitionsDone();
280 gfx::Rect r0
= GetToastRectAt(0);
281 gfx::Rect r1
= GetToastRectAt(1);
283 // 2 toasts are shown, equal size, vertical stack.
284 EXPECT_TRUE(IsToastShown(id0
));
285 EXPECT_TRUE(IsToastShown(id1
));
287 EXPECT_EQ(r0
.width(), r1
.width());
288 EXPECT_EQ(r0
.height(), r1
.height());
289 EXPECT_LT(r0
.y(), r1
.y());
290 EXPECT_EQ(r0
.x(), r1
.x());
293 EXPECT_EQ(0u, GetToastCounts());
295 // Restore simulated taskbar position to bottom.
296 SetDisplayInfo(gfx::Rect(0, 0, 600, 390), // Work-area.
297 gfx::Rect(0, 0, 600, 400)); // Display-bounds.
300 TEST_F(MessagePopupCollectionTest
, TopDownPositioningWithBottomAndTopTaskbar
) {
301 // If there "seems" to be a taskbar on bottom and top (like in Gnome), it is
302 // assumed that the actual taskbar is the top one.
304 // Simulate a taskbar at the top and bottom.
305 SetDisplayInfo(gfx::Rect(0, 10, 580, 400), // Work-area.
306 gfx::Rect(0, 0, 600, 400)); // Display-bounds.
307 std::string id0
= AddNotification();
308 std::string id1
= AddNotification();
309 WaitForTransitionsDone();
311 gfx::Rect r0
= GetToastRectAt(0);
312 gfx::Rect r1
= GetToastRectAt(1);
314 // 2 toasts are shown, equal size, vertical stack.
315 EXPECT_TRUE(IsToastShown(id0
));
316 EXPECT_TRUE(IsToastShown(id1
));
318 EXPECT_EQ(r0
.width(), r1
.width());
319 EXPECT_EQ(r0
.height(), r1
.height());
320 EXPECT_LT(r0
.y(), r1
.y());
321 EXPECT_EQ(r0
.x(), r1
.x());
324 EXPECT_EQ(0u, GetToastCounts());
326 // Restore simulated taskbar position to bottom.
327 SetDisplayInfo(gfx::Rect(0, 0, 600, 390), // Work-area.
328 gfx::Rect(0, 0, 600, 400)); // Display-bounds.
331 TEST_F(MessagePopupCollectionTest
, LeftPositioningWithLeftTaskbar
) {
332 // Simulate a taskbar at the left.
333 SetDisplayInfo(gfx::Rect(10, 0, 590, 400), // Work-area.
334 gfx::Rect(0, 0, 600, 400)); // Display-bounds.
335 std::string id0
= AddNotification();
336 std::string id1
= AddNotification();
337 WaitForTransitionsDone();
339 gfx::Rect r0
= GetToastRectAt(0);
340 gfx::Rect r1
= GetToastRectAt(1);
342 // 2 toasts are shown, equal size, vertical stack.
343 EXPECT_TRUE(IsToastShown(id0
));
344 EXPECT_TRUE(IsToastShown(id1
));
346 EXPECT_EQ(r0
.width(), r1
.width());
347 EXPECT_EQ(r0
.height(), r1
.height());
348 EXPECT_GT(r0
.y(), r1
.y());
349 EXPECT_EQ(r0
.x(), r1
.x());
351 // Ensure that toasts are on the left.
352 EXPECT_LT(r1
.x(), GetWorkArea().CenterPoint().x());
355 EXPECT_EQ(0u, GetToastCounts());
357 // Restore simulated taskbar position to bottom.
358 SetDisplayInfo(gfx::Rect(0, 0, 600, 390), // Work-area.
359 gfx::Rect(0, 0, 600, 400)); // Display-bounds.
362 TEST_F(MessagePopupCollectionTest
, DetectMouseHover
) {
363 std::string id0
= AddNotification();
364 std::string id1
= AddNotification();
365 WaitForTransitionsDone();
367 views::WidgetDelegateView
* toast0
= GetToast(id0
);
368 EXPECT_TRUE(toast0
!= NULL
);
369 views::WidgetDelegateView
* toast1
= GetToast(id1
);
370 EXPECT_TRUE(toast1
!= NULL
);
372 ui::MouseEvent
event(ui::ET_MOUSE_MOVED
, gfx::Point(), gfx::Point(),
373 ui::EventTimeForNow(), 0, 0);
375 // Test that mouse detection logic works in presence of out-of-order events.
376 toast0
->OnMouseEntered(event
);
377 EXPECT_TRUE(MouseInCollection());
378 toast1
->OnMouseEntered(event
);
379 EXPECT_TRUE(MouseInCollection());
380 toast0
->OnMouseExited(event
);
381 EXPECT_TRUE(MouseInCollection());
382 toast1
->OnMouseExited(event
);
383 EXPECT_FALSE(MouseInCollection());
385 // Test that mouse detection logic works in presence of WindowClosing events.
386 toast0
->OnMouseEntered(event
);
387 EXPECT_TRUE(MouseInCollection());
388 toast1
->OnMouseEntered(event
);
389 EXPECT_TRUE(MouseInCollection());
390 toast0
->WindowClosing();
391 EXPECT_TRUE(MouseInCollection());
392 toast1
->WindowClosing();
393 EXPECT_FALSE(MouseInCollection());
396 // TODO(dimich): Test repositioning - both normal one and when user is closing
398 TEST_F(MessagePopupCollectionTest
, DetectMouseHoverWithUserClose
) {
399 std::string id0
= AddNotification();
400 std::string id1
= AddNotification();
401 WaitForTransitionsDone();
403 views::WidgetDelegateView
* toast0
= GetToast(id0
);
404 EXPECT_TRUE(toast0
!= NULL
);
405 views::WidgetDelegateView
* toast1
= GetToast(id1
);
406 ASSERT_TRUE(toast1
!= NULL
);
408 ui::MouseEvent
event(ui::ET_MOUSE_MOVED
, gfx::Point(), gfx::Point(),
409 ui::EventTimeForNow(), 0, 0);
410 toast1
->OnMouseEntered(event
);
411 static_cast<MessageCenterObserver
*>(collection())->OnNotificationRemoved(
414 EXPECT_FALSE(MouseInCollection());
415 std::string id2
= AddNotification();
417 WaitForTransitionsDone();
418 views::WidgetDelegateView
* toast2
= GetToast(id2
);
419 EXPECT_TRUE(toast2
!= NULL
);
424 } // namespace message_center