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/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" 22 23 namespace message_center { 24 namespace test { 25 26 class MessagePopupCollectionTest : public views::ViewsTestBase { 27 public: 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 39 // taskbar at the 40 // bottom. 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 gfx::Rect GetWorkArea() { 72 return collection_->work_area_; 73 } 74 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) 80 return *iter; 81 } 82 return NULL; 83 } 84 85 std::string AddNotification() { 86 std::string id = base::IntToString(id_++); 87 scoped_ptr<Notification> notification( 88 new Notification(NOTIFICATION_TYPE_BASE_FORMAT, 89 id, 90 base::UTF8ToUTF16("test title"), 91 base::UTF8ToUTF16("test message"), 92 gfx::Image(), 93 base::string16() /* display_source */, 94 NotifierId(), 95 message_center::RichNotificationData(), 96 NULL /* delegate */)); 97 MessageCenter::Get()->AddNotification(notification.Pass()); 98 return id; 99 } 100 101 void PrepareForWait() { collection_->CreateRunLoopForTest(); } 102 103 // Assumes there is non-zero pending work. 104 void WaitForTransitionsDone() { 105 collection_->WaitForTest(); 106 collection_->CreateRunLoopForTest(); 107 } 108 109 void CloseAllToasts() { 110 // Assumes there is at least one toast to close. 111 EXPECT_TRUE(GetToastCounts() > 0); 112 MessageCenter::Get()->RemoveAllNotifications(false); 113 } 114 115 gfx::Rect GetToastRectAt(size_t index) { 116 return collection_->GetToastRectAt(index); 117 } 118 119 private: 120 scoped_ptr<MessagePopupCollection> collection_; 121 int id_; 122 }; 123 124 TEST_F(MessagePopupCollectionTest, DismissOnClick) { 125 126 std::string id1 = AddNotification(); 127 std::string id2 = AddNotification(); 128 WaitForTransitionsDone(); 129 130 EXPECT_EQ(2u, GetToastCounts()); 131 EXPECT_TRUE(IsToastShown(id1)); 132 EXPECT_TRUE(IsToastShown(id2)); 133 134 MessageCenter::Get()->ClickOnNotification(id2); 135 WaitForTransitionsDone(); 136 137 EXPECT_EQ(1u, GetToastCounts()); 138 EXPECT_TRUE(IsToastShown(id1)); 139 EXPECT_FALSE(IsToastShown(id2)); 140 141 MessageCenter::Get()->ClickOnNotificationButton(id1, 0); 142 WaitForTransitionsDone(); 143 EXPECT_EQ(0u, GetToastCounts()); 144 EXPECT_FALSE(IsToastShown(id1)); 145 EXPECT_FALSE(IsToastShown(id2)); 146 } 147 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)); 155 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)); 160 } 161 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(); 168 169 gfx::Rect r0 = GetToastRectAt(0); 170 gfx::Rect r1 = GetToastRectAt(1); 171 gfx::Rect r2 = GetToastRectAt(2); 172 gfx::Rect r3 = GetToastRectAt(3); 173 174 // 3 toasts are shown, equal size, vertical stack. 175 EXPECT_TRUE(IsToastShown(id0)); 176 EXPECT_TRUE(IsToastShown(id1)); 177 EXPECT_TRUE(IsToastShown(id2)); 178 179 EXPECT_EQ(r0.width(), r1.width()); 180 EXPECT_EQ(r1.width(), r2.width()); 181 182 EXPECT_EQ(r0.height(), r1.height()); 183 EXPECT_EQ(r1.height(), r2.height()); 184 185 EXPECT_GT(r0.y(), r1.y()); 186 EXPECT_GT(r1.y(), r2.y()); 187 188 EXPECT_EQ(r0.x(), r1.x()); 189 EXPECT_EQ(r1.x(), r2.x()); 190 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()); 195 196 CloseAllToasts(); 197 EXPECT_EQ(0u, GetToastCounts()); 198 } 199 200 TEST_F(MessagePopupCollectionTest, DefaultPositioningWithRightTaskbar) { 201 // If taskbar is on the right we show the toasts bottom to top as usual. 202 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(); 209 210 gfx::Rect r0 = GetToastRectAt(0); 211 gfx::Rect r1 = GetToastRectAt(1); 212 213 // 2 toasts are shown, equal size, vertical stack. 214 EXPECT_TRUE(IsToastShown(id0)); 215 EXPECT_TRUE(IsToastShown(id1)); 216 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()); 221 222 CloseAllToasts(); 223 EXPECT_EQ(0u, GetToastCounts()); 224 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. 228 } 229 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(); 237 238 gfx::Rect r0 = GetToastRectAt(0); 239 gfx::Rect r1 = GetToastRectAt(1); 240 241 // 2 toasts are shown, equal size, vertical stack. 242 EXPECT_TRUE(IsToastShown(id0)); 243 EXPECT_TRUE(IsToastShown(id1)); 244 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()); 249 250 CloseAllToasts(); 251 EXPECT_EQ(0u, GetToastCounts()); 252 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. 256 } 257 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. 261 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(); 268 269 gfx::Rect r0 = GetToastRectAt(0); 270 gfx::Rect r1 = GetToastRectAt(1); 271 272 // 2 toasts are shown, equal size, vertical stack. 273 EXPECT_TRUE(IsToastShown(id0)); 274 EXPECT_TRUE(IsToastShown(id1)); 275 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()); 280 281 CloseAllToasts(); 282 EXPECT_EQ(0u, GetToastCounts()); 283 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. 287 } 288 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. 292 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(); 299 300 gfx::Rect r0 = GetToastRectAt(0); 301 gfx::Rect r1 = GetToastRectAt(1); 302 303 // 2 toasts are shown, equal size, vertical stack. 304 EXPECT_TRUE(IsToastShown(id0)); 305 EXPECT_TRUE(IsToastShown(id1)); 306 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()); 311 312 CloseAllToasts(); 313 EXPECT_EQ(0u, GetToastCounts()); 314 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. 318 } 319 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(); 327 328 gfx::Rect r0 = GetToastRectAt(0); 329 gfx::Rect r1 = GetToastRectAt(1); 330 331 // 2 toasts are shown, equal size, vertical stack. 332 EXPECT_TRUE(IsToastShown(id0)); 333 EXPECT_TRUE(IsToastShown(id1)); 334 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()); 339 340 // Ensure that toasts are on the left. 341 EXPECT_LT(r1.x(), GetWorkArea().CenterPoint().x()); 342 343 CloseAllToasts(); 344 EXPECT_EQ(0u, GetToastCounts()); 345 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. 349 } 350 351 TEST_F(MessagePopupCollectionTest, DetectMouseHover) { 352 std::string id0 = AddNotification(); 353 std::string id1 = AddNotification(); 354 WaitForTransitionsDone(); 355 356 views::WidgetDelegateView* toast0 = GetToast(id0); 357 EXPECT_TRUE(toast0 != NULL); 358 views::WidgetDelegateView* toast1 = GetToast(id1); 359 EXPECT_TRUE(toast1 != NULL); 360 361 ui::MouseEvent event(ui::ET_MOUSE_MOVED, gfx::Point(), gfx::Point(), 0, 0); 362 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()); 372 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()); 382 } 383 384 // TODO(dimich): Test repositioning - both normal one and when user is closing 385 // the toasts. 386 TEST_F(MessagePopupCollectionTest, DetectMouseHoverWithUserClose) { 387 std::string id0 = AddNotification(); 388 std::string id1 = AddNotification(); 389 WaitForTransitionsDone(); 390 391 views::WidgetDelegateView* toast0 = GetToast(id0); 392 EXPECT_TRUE(toast0 != NULL); 393 views::WidgetDelegateView* toast1 = GetToast(id1); 394 ASSERT_TRUE(toast1 != NULL); 395 396 ui::MouseEvent event(ui::ET_MOUSE_MOVED, gfx::Point(), gfx::Point(), 0, 0); 397 toast1->OnMouseEntered(event); 398 static_cast<MessageCenterObserver*>(collection())->OnNotificationRemoved( 399 id1, true); 400 401 EXPECT_FALSE(MouseInCollection()); 402 std::string id2 = AddNotification(); 403 404 WaitForTransitionsDone(); 405 views::WidgetDelegateView* toast2 = GetToast(id2); 406 EXPECT_TRUE(toast2 != NULL); 407 } 408 409 410 } // namespace test 411 } // namespace message_center 412