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
24 namespace message_center {
25 namespace test {
26
27 class MessagePopupCollectionTest : public views::ViewsTestBase {
28 public:
SetUp()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
TearDown()45 virtual void TearDown() OVERRIDE {
46 collection_.reset();
47 MessageCenter::Shutdown();
48 views::ViewsTestBase::TearDown();
49 }
50
51 protected:
collection()52 MessagePopupCollection* collection() { return collection_.get(); }
53
GetToastCounts()54 size_t GetToastCounts() {
55 return collection_->toasts_.size();
56 }
57
MouseInCollection()58 bool MouseInCollection() {
59 return collection_->latest_toast_entered_ != NULL;
60 }
61
IsToastShown(const std::string & id)62 bool IsToastShown(const std::string& id) {
63 views::Widget* widget = collection_->GetWidgetForTest(id);
64 return widget && widget->IsVisible();
65 }
66
GetWidget(const std::string & id)67 views::Widget* GetWidget(const std::string& id) {
68 return collection_->GetWidgetForTest(id);
69 }
70
SetDisplayInfo(const gfx::Rect & work_area,const gfx::Rect & display_bounds)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
GetWorkArea()80 gfx::Rect GetWorkArea() {
81 return alignment_delegate_->work_area_;
82 }
83
GetToast(const std::string & id)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
AddNotification()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
PrepareForWait()110 void PrepareForWait() { collection_->CreateRunLoopForTest(); }
111
112 // Assumes there is non-zero pending work.
WaitForTransitionsDone()113 void WaitForTransitionsDone() {
114 collection_->WaitForTest();
115 collection_->CreateRunLoopForTest();
116 }
117
CloseAllToasts()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
GetToastRectAt(size_t index)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
TEST_F(MessagePopupCollectionTest,DismissOnClick)134 TEST_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
TEST_F(MessagePopupCollectionTest,ShutdownDuringShowing)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));
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
TEST_F(MessagePopupCollectionTest,DefaultPositioning)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();
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
TEST_F(MessagePopupCollectionTest,DefaultPositioningWithRightTaskbar)210 TEST_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
TEST_F(MessagePopupCollectionTest,TopDownPositioningWithTopTaskbar)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();
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
TEST_F(MessagePopupCollectionTest,TopDownPositioningWithLeftAndTopTaskbar)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.
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
TEST_F(MessagePopupCollectionTest,TopDownPositioningWithBottomAndTopTaskbar)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.
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
TEST_F(MessagePopupCollectionTest,LeftPositioningWithLeftTaskbar)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();
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
TEST_F(MessagePopupCollectionTest,DetectMouseHover)361 TEST_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.
TEST_F(MessagePopupCollectionTest,DetectMouseHoverWithUserClose)396 TEST_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