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/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"
24 namespace message_center
{
27 class MessagePopupCollectionTest
: public views::ViewsTestBase
{
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));
45 virtual void TearDown() OVERRIDE
{
47 MessageCenter::Shutdown();
48 views::ViewsTestBase::TearDown();
52 MessagePopupCollection
* collection() { return collection_
.get(); }
54 size_t GetToastCounts() {
55 return collection_
->toasts_
.size();
58 bool MouseInCollection() {
59 return collection_
->latest_toast_entered_
!= NULL
;
62 bool IsToastShown(const std::string
& id
) {
63 views::Widget
* widget
= collection_
->GetWidgetForTest(id
);
64 return widget
&& widget
->IsVisible();
67 views::Widget
* GetWidget(const std::string
& id
) {
68 return collection_
->GetWidgetForTest(id
);
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
);
80 gfx::Rect
GetWorkArea() {
81 return alignment_delegate_
->work_area_
;
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
)
94 std::string
AddNotification() {
95 std::string id
= base::IntToString(id_
++);
96 scoped_ptr
<Notification
> notification(
97 new Notification(NOTIFICATION_TYPE_BASE_FORMAT
,
99 base::UTF8ToUTF16("test title"),
100 base::UTF8ToUTF16("test message"),
102 base::string16() /* display_source */,
104 message_center::RichNotificationData(),
105 NULL
/* delegate */));
106 MessageCenter::Get()->AddNotification(notification
.Pass());
110 void PrepareForWait() { collection_
->CreateRunLoopForTest(); }
112 // Assumes there is non-zero pending work.
113 void WaitForTransitionsDone() {
114 collection_
->WaitForTest();
115 collection_
->CreateRunLoopForTest();
118 void CloseAllToasts() {
119 // Assumes there is at least one toast to close.
120 EXPECT_TRUE(GetToastCounts() > 0);
121 MessageCenter::Get()->RemoveAllNotifications(false);
124 gfx::Rect
GetToastRectAt(size_t index
) {
125 return collection_
->GetToastRectAt(index
);
129 scoped_ptr
<MessagePopupCollection
> collection_
;
130 scoped_ptr
<DesktopPopupAlignmentDelegate
> alignment_delegate_
;
134 TEST_F(MessagePopupCollectionTest
, DismissOnClick
) {
136 std::string id1
= AddNotification();
137 std::string id2
= AddNotification();
138 WaitForTransitionsDone();
140 EXPECT_EQ(2u, GetToastCounts());
141 EXPECT_TRUE(IsToastShown(id1
));
142 EXPECT_TRUE(IsToastShown(id2
));
144 MessageCenter::Get()->ClickOnNotification(id2
);
145 WaitForTransitionsDone();
147 EXPECT_EQ(1u, GetToastCounts());
148 EXPECT_TRUE(IsToastShown(id1
));
149 EXPECT_FALSE(IsToastShown(id2
));
151 MessageCenter::Get()->ClickOnNotificationButton(id1
, 0);
152 WaitForTransitionsDone();
153 EXPECT_EQ(0u, GetToastCounts());
154 EXPECT_FALSE(IsToastShown(id1
));
155 EXPECT_FALSE(IsToastShown(id2
));
158 TEST_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
));
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
));
172 TEST_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();
179 gfx::Rect r0
= GetToastRectAt(0);
180 gfx::Rect r1
= GetToastRectAt(1);
181 gfx::Rect r2
= GetToastRectAt(2);
182 gfx::Rect r3
= GetToastRectAt(3);
184 // 3 toasts are shown, equal size, vertical stack.
185 EXPECT_TRUE(IsToastShown(id0
));
186 EXPECT_TRUE(IsToastShown(id1
));
187 EXPECT_TRUE(IsToastShown(id2
));
189 EXPECT_EQ(r0
.width(), r1
.width());
190 EXPECT_EQ(r1
.width(), r2
.width());
192 EXPECT_EQ(r0
.height(), r1
.height());
193 EXPECT_EQ(r1
.height(), r2
.height());
195 EXPECT_GT(r0
.y(), r1
.y());
196 EXPECT_GT(r1
.y(), r2
.y());
198 EXPECT_EQ(r0
.x(), r1
.x());
199 EXPECT_EQ(r1
.x(), r2
.x());
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());
207 EXPECT_EQ(0u, GetToastCounts());
210 TEST_F(MessagePopupCollectionTest
, DefaultPositioningWithRightTaskbar
) {
211 // If taskbar is on the right we show the toasts bottom to top as usual.
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();
220 gfx::Rect r0
= GetToastRectAt(0);
221 gfx::Rect r1
= GetToastRectAt(1);
223 // 2 toasts are shown, equal size, vertical stack.
224 EXPECT_TRUE(IsToastShown(id0
));
225 EXPECT_TRUE(IsToastShown(id1
));
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());
233 EXPECT_EQ(0u, GetToastCounts());
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.
240 TEST_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();
248 gfx::Rect r0
= GetToastRectAt(0);
249 gfx::Rect r1
= GetToastRectAt(1);
251 // 2 toasts are shown, equal size, vertical stack.
252 EXPECT_TRUE(IsToastShown(id0
));
253 EXPECT_TRUE(IsToastShown(id1
));
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());
261 EXPECT_EQ(0u, GetToastCounts());
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.
268 TEST_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.
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();
279 gfx::Rect r0
= GetToastRectAt(0);
280 gfx::Rect r1
= GetToastRectAt(1);
282 // 2 toasts are shown, equal size, vertical stack.
283 EXPECT_TRUE(IsToastShown(id0
));
284 EXPECT_TRUE(IsToastShown(id1
));
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());
292 EXPECT_EQ(0u, GetToastCounts());
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.
299 TEST_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.
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();
310 gfx::Rect r0
= GetToastRectAt(0);
311 gfx::Rect r1
= GetToastRectAt(1);
313 // 2 toasts are shown, equal size, vertical stack.
314 EXPECT_TRUE(IsToastShown(id0
));
315 EXPECT_TRUE(IsToastShown(id1
));
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());
323 EXPECT_EQ(0u, GetToastCounts());
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.
330 TEST_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();
338 gfx::Rect r0
= GetToastRectAt(0);
339 gfx::Rect r1
= GetToastRectAt(1);
341 // 2 toasts are shown, equal size, vertical stack.
342 EXPECT_TRUE(IsToastShown(id0
));
343 EXPECT_TRUE(IsToastShown(id1
));
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());
350 // Ensure that toasts are on the left.
351 EXPECT_LT(r1
.x(), GetWorkArea().CenterPoint().x());
354 EXPECT_EQ(0u, GetToastCounts());
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.
361 TEST_F(MessagePopupCollectionTest
, DetectMouseHover
) {
362 std::string id0
= AddNotification();
363 std::string id1
= AddNotification();
364 WaitForTransitionsDone();
366 views::WidgetDelegateView
* toast0
= GetToast(id0
);
367 EXPECT_TRUE(toast0
!= NULL
);
368 views::WidgetDelegateView
* toast1
= GetToast(id1
);
369 EXPECT_TRUE(toast1
!= NULL
);
371 ui::MouseEvent
event(ui::ET_MOUSE_MOVED
, gfx::Point(), gfx::Point(), 0, 0);
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());
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());
394 // TODO(dimich): Test repositioning - both normal one and when user is closing
396 TEST_F(MessagePopupCollectionTest
, DetectMouseHoverWithUserClose
) {
397 std::string id0
= AddNotification();
398 std::string id1
= AddNotification();
399 WaitForTransitionsDone();
401 views::WidgetDelegateView
* toast0
= GetToast(id0
);
402 EXPECT_TRUE(toast0
!= NULL
);
403 views::WidgetDelegateView
* toast1
= GetToast(id1
);
404 ASSERT_TRUE(toast1
!= NULL
);
406 ui::MouseEvent
event(ui::ET_MOUSE_MOVED
, gfx::Point(), gfx::Point(), 0, 0);
407 toast1
->OnMouseEntered(event
);
408 static_cast<MessageCenterObserver
*>(collection())->OnNotificationRemoved(
411 EXPECT_FALSE(MouseInCollection());
412 std::string id2
= AddNotification();
414 WaitForTransitionsDone();
415 views::WidgetDelegateView
* toast2
= GetToast(id2
);
416 EXPECT_TRUE(toast2
!= NULL
);
421 } // namespace message_center