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/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"
23 namespace message_center
{
26 class MessagePopupCollectionTest
: public views::ViewsTestBase
{
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
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 gfx::Rect
GetWorkArea() {
72 return collection_
->work_area_
;
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
)
85 std::string
AddNotification() {
86 std::string id
= base::IntToString(id_
++);
87 scoped_ptr
<Notification
> notification(
88 new Notification(NOTIFICATION_TYPE_BASE_FORMAT
,
90 base::UTF8ToUTF16("test title"),
91 base::UTF8ToUTF16("test message"),
93 base::string16() /* display_source */,
95 message_center::RichNotificationData(),
96 NULL
/* delegate */));
97 MessageCenter::Get()->AddNotification(notification
.Pass());
101 void PrepareForWait() { collection_
->CreateRunLoopForTest(); }
103 // Assumes there is non-zero pending work.
104 void WaitForTransitionsDone() {
105 collection_
->WaitForTest();
106 collection_
->CreateRunLoopForTest();
109 void CloseAllToasts() {
110 // Assumes there is at least one toast to close.
111 EXPECT_TRUE(GetToastCounts() > 0);
112 MessageCenter::Get()->RemoveAllNotifications(false);
115 gfx::Rect
GetToastRectAt(size_t index
) {
116 return collection_
->GetToastRectAt(index
);
120 scoped_ptr
<MessagePopupCollection
> collection_
;
124 TEST_F(MessagePopupCollectionTest
, DismissOnClick
) {
126 std::string id1
= AddNotification();
127 std::string id2
= AddNotification();
128 WaitForTransitionsDone();
130 EXPECT_EQ(2u, GetToastCounts());
131 EXPECT_TRUE(IsToastShown(id1
));
132 EXPECT_TRUE(IsToastShown(id2
));
134 MessageCenter::Get()->ClickOnNotification(id2
);
135 WaitForTransitionsDone();
137 EXPECT_EQ(1u, GetToastCounts());
138 EXPECT_TRUE(IsToastShown(id1
));
139 EXPECT_FALSE(IsToastShown(id2
));
141 MessageCenter::Get()->ClickOnNotificationButton(id1
, 0);
142 WaitForTransitionsDone();
143 EXPECT_EQ(0u, GetToastCounts());
144 EXPECT_FALSE(IsToastShown(id1
));
145 EXPECT_FALSE(IsToastShown(id2
));
148 TEST_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
));
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
));
162 TEST_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();
169 gfx::Rect r0
= GetToastRectAt(0);
170 gfx::Rect r1
= GetToastRectAt(1);
171 gfx::Rect r2
= GetToastRectAt(2);
172 gfx::Rect r3
= GetToastRectAt(3);
174 // 3 toasts are shown, equal size, vertical stack.
175 EXPECT_TRUE(IsToastShown(id0
));
176 EXPECT_TRUE(IsToastShown(id1
));
177 EXPECT_TRUE(IsToastShown(id2
));
179 EXPECT_EQ(r0
.width(), r1
.width());
180 EXPECT_EQ(r1
.width(), r2
.width());
182 EXPECT_EQ(r0
.height(), r1
.height());
183 EXPECT_EQ(r1
.height(), r2
.height());
185 EXPECT_GT(r0
.y(), r1
.y());
186 EXPECT_GT(r1
.y(), r2
.y());
188 EXPECT_EQ(r0
.x(), r1
.x());
189 EXPECT_EQ(r1
.x(), r2
.x());
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());
197 EXPECT_EQ(0u, GetToastCounts());
200 TEST_F(MessagePopupCollectionTest
, DefaultPositioningWithRightTaskbar
) {
201 // If taskbar is on the right we show the toasts bottom to top as usual.
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();
210 gfx::Rect r0
= GetToastRectAt(0);
211 gfx::Rect r1
= GetToastRectAt(1);
213 // 2 toasts are shown, equal size, vertical stack.
214 EXPECT_TRUE(IsToastShown(id0
));
215 EXPECT_TRUE(IsToastShown(id1
));
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());
223 EXPECT_EQ(0u, GetToastCounts());
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.
230 TEST_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();
238 gfx::Rect r0
= GetToastRectAt(0);
239 gfx::Rect r1
= GetToastRectAt(1);
241 // 2 toasts are shown, equal size, vertical stack.
242 EXPECT_TRUE(IsToastShown(id0
));
243 EXPECT_TRUE(IsToastShown(id1
));
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());
251 EXPECT_EQ(0u, GetToastCounts());
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.
258 TEST_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.
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();
269 gfx::Rect r0
= GetToastRectAt(0);
270 gfx::Rect r1
= GetToastRectAt(1);
272 // 2 toasts are shown, equal size, vertical stack.
273 EXPECT_TRUE(IsToastShown(id0
));
274 EXPECT_TRUE(IsToastShown(id1
));
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());
282 EXPECT_EQ(0u, GetToastCounts());
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.
289 TEST_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.
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();
300 gfx::Rect r0
= GetToastRectAt(0);
301 gfx::Rect r1
= GetToastRectAt(1);
303 // 2 toasts are shown, equal size, vertical stack.
304 EXPECT_TRUE(IsToastShown(id0
));
305 EXPECT_TRUE(IsToastShown(id1
));
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());
313 EXPECT_EQ(0u, GetToastCounts());
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.
320 TEST_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();
328 gfx::Rect r0
= GetToastRectAt(0);
329 gfx::Rect r1
= GetToastRectAt(1);
331 // 2 toasts are shown, equal size, vertical stack.
332 EXPECT_TRUE(IsToastShown(id0
));
333 EXPECT_TRUE(IsToastShown(id1
));
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());
340 // Ensure that toasts are on the left.
341 EXPECT_LT(r1
.x(), GetWorkArea().CenterPoint().x());
344 EXPECT_EQ(0u, GetToastCounts());
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.
351 TEST_F(MessagePopupCollectionTest
, DetectMouseHover
) {
352 std::string id0
= AddNotification();
353 std::string id1
= AddNotification();
354 WaitForTransitionsDone();
356 views::WidgetDelegateView
* toast0
= GetToast(id0
);
357 EXPECT_TRUE(toast0
!= NULL
);
358 views::WidgetDelegateView
* toast1
= GetToast(id1
);
359 EXPECT_TRUE(toast1
!= NULL
);
361 ui::MouseEvent
event(ui::ET_MOUSE_MOVED
, gfx::Point(), gfx::Point(), 0, 0);
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());
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());
384 // TODO(dimich): Test repositioning - both normal one and when user is closing
386 TEST_F(MessagePopupCollectionTest
, DetectMouseHoverWithUserClose
) {
387 std::string id0
= AddNotification();
388 std::string id1
= AddNotification();
389 WaitForTransitionsDone();
391 views::WidgetDelegateView
* toast0
= GetToast(id0
);
392 EXPECT_TRUE(toast0
!= NULL
);
393 views::WidgetDelegateView
* toast1
= GetToast(id1
);
394 ASSERT_TRUE(toast1
!= NULL
);
396 ui::MouseEvent
event(ui::ET_MOUSE_MOVED
, gfx::Point(), gfx::Point(), 0, 0);
397 toast1
->OnMouseEntered(event
);
398 static_cast<MessageCenterObserver
*>(collection())->OnNotificationRemoved(
401 EXPECT_FALSE(MouseInCollection());
402 std::string id2
= AddNotification();
404 WaitForTransitionsDone();
405 views::WidgetDelegateView
* toast2
= GetToast(id2
);
406 EXPECT_TRUE(toast2
!= NULL
);
411 } // namespace message_center