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/base/events/event.h" 14 #include "ui/base/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 collection_.reset(new MessagePopupCollection( 32 GetContext(), MessageCenter::Get(), NULL, false)); 33 // This size fits test machines resolution and also can keep a few toasts 34 // w/o ill effects of hitting the screen overflow. This allows us to assume 35 // and verify normal layout of the toast stack. 36 collection_->SetDisplayInfo(gfx::Rect(0, 0, 600, 390), 37 gfx::Rect(0, 0, 600, 400)); // Simulate a 38 // taskbar at the 39 // bottom. 40 id_ = 0; 41 } 42 43 virtual void TearDown() OVERRIDE { 44 collection_->CloseAllWidgets(); 45 collection_.reset(); 46 MessageCenter::Shutdown(); 47 views::ViewsTestBase::TearDown(); 48 } 49 50 protected: 51 MessagePopupCollection* collection() { return collection_.get(); } 52 53 size_t GetToastCounts() { 54 return collection_->toasts_.size(); 55 } 56 57 bool MouseInCollection() { 58 return collection_->latest_toast_entered_ != NULL; 59 } 60 61 bool IsToastShown(const std::string& id) { 62 views::Widget* widget = collection_->GetWidgetForTest(id); 63 return widget && widget->IsVisible(); 64 } 65 66 views::Widget* GetWidget(const std::string& id) { 67 return collection_->GetWidgetForTest(id); 68 } 69 70 gfx::Rect GetWorkArea() { 71 return collection_->work_area_; 72 } 73 74 ToastContentsView* GetToast(const std::string& id) { 75 for (MessagePopupCollection::Toasts::iterator iter = 76 collection_->toasts_.begin(); 77 iter != collection_->toasts_.end(); ++iter) { 78 if ((*iter)->id() == id) 79 return *iter; 80 } 81 return NULL; 82 } 83 84 std::string AddNotification() { 85 std::string id = base::IntToString(id_++); 86 scoped_ptr<Notification> notification( 87 new Notification(NOTIFICATION_TYPE_BASE_FORMAT, 88 id, 89 UTF8ToUTF16("test title"), 90 UTF8ToUTF16("test message"), 91 gfx::Image(), 92 string16() /* display_source */, 93 "" /* extension_id */, 94 message_center::RichNotificationData(), 95 NULL /* delegate */)); 96 MessageCenter::Get()->AddNotification(notification.Pass()); 97 return id; 98 } 99 100 // Assumes there is non-zero pending work. 101 void WaitForTransitionsDone() { 102 collection_->RunLoopForTest(); 103 } 104 105 void CloseAllToastsAndWait() { 106 // Assumes there is at least one toast to close. 107 EXPECT_TRUE(GetToastCounts() > 0); 108 MessageCenter::Get()->RemoveAllNotifications(false); 109 WaitForTransitionsDone(); 110 } 111 112 gfx::Rect GetToastRectAt(size_t index) { 113 return collection_->GetToastRectAt(index); 114 } 115 116 private: 117 scoped_ptr<MessagePopupCollection> collection_; 118 int id_; 119 }; 120 121 TEST_F(MessagePopupCollectionTest, DismissOnClick) { 122 std::string id1 = AddNotification(); 123 std::string id2 = AddNotification(); 124 WaitForTransitionsDone(); 125 EXPECT_EQ(2u, GetToastCounts()); 126 EXPECT_TRUE(IsToastShown(id1)); 127 EXPECT_TRUE(IsToastShown(id2)); 128 129 MessageCenter::Get()->ClickOnNotification(id2); 130 WaitForTransitionsDone(); 131 EXPECT_EQ(1u, GetToastCounts()); 132 EXPECT_TRUE(IsToastShown(id1)); 133 EXPECT_FALSE(IsToastShown(id2)); 134 135 MessageCenter::Get()->ClickOnNotificationButton(id1, 0); 136 WaitForTransitionsDone(); 137 EXPECT_EQ(0u, GetToastCounts()); 138 EXPECT_FALSE(IsToastShown(id1)); 139 EXPECT_FALSE(IsToastShown(id2)); 140 } 141 142 TEST_F(MessagePopupCollectionTest, ShutdownDuringShowing) { 143 std::string id1 = AddNotification(); 144 std::string id2 = AddNotification(); 145 WaitForTransitionsDone(); 146 EXPECT_EQ(2u, GetToastCounts()); 147 EXPECT_TRUE(IsToastShown(id1)); 148 EXPECT_TRUE(IsToastShown(id2)); 149 150 // Finish without cleanup of notifications, which may cause use-after-free. 151 // See crbug.com/236448 152 GetWidget(id1)->CloseNow(); 153 collection()->OnMouseExited(GetToast(id2)); 154 } 155 156 TEST_F(MessagePopupCollectionTest, DefaultPositioning) { 157 std::string id0 = AddNotification(); 158 std::string id1 = AddNotification(); 159 std::string id2 = AddNotification(); 160 std::string id3 = AddNotification(); 161 WaitForTransitionsDone(); 162 163 gfx::Rect r0 = GetToastRectAt(0); 164 gfx::Rect r1 = GetToastRectAt(1); 165 gfx::Rect r2 = GetToastRectAt(2); 166 gfx::Rect r3 = GetToastRectAt(3); 167 168 // 3 toasts are shown, equal size, vertical stack. 169 EXPECT_TRUE(IsToastShown(id0)); 170 EXPECT_TRUE(IsToastShown(id1)); 171 EXPECT_TRUE(IsToastShown(id2)); 172 173 EXPECT_EQ(r0.width(), r1.width()); 174 EXPECT_EQ(r1.width(), r2.width()); 175 176 EXPECT_EQ(r0.height(), r1.height()); 177 EXPECT_EQ(r1.height(), r2.height()); 178 179 EXPECT_GT(r0.y(), r1.y()); 180 EXPECT_GT(r1.y(), r2.y()); 181 182 EXPECT_EQ(r0.x(), r1.x()); 183 EXPECT_EQ(r1.x(), r2.x()); 184 185 // The 4th toast is not shown yet. 186 EXPECT_FALSE(IsToastShown(id3)); 187 EXPECT_EQ(0, r3.width()); 188 EXPECT_EQ(0, r3.height()); 189 190 CloseAllToastsAndWait(); 191 EXPECT_EQ(0u, GetToastCounts()); 192 } 193 194 TEST_F(MessagePopupCollectionTest, DefaultPositioningWithRightTaskbar) { 195 // If taskbar is on the right we show the toasts bottom to top as usual. 196 197 // Simulate a taskbar at the right. 198 collection()->SetDisplayInfo(gfx::Rect(0, 0, 590, 400), // Work-area. 199 gfx::Rect(0, 0, 600, 400)); // Display-bounds. 200 std::string id0 = AddNotification(); 201 std::string id1 = AddNotification(); 202 WaitForTransitionsDone(); 203 204 gfx::Rect r0 = GetToastRectAt(0); 205 gfx::Rect r1 = GetToastRectAt(1); 206 207 // 2 toasts are shown, equal size, vertical stack. 208 EXPECT_TRUE(IsToastShown(id0)); 209 EXPECT_TRUE(IsToastShown(id1)); 210 211 EXPECT_EQ(r0.width(), r1.width()); 212 EXPECT_EQ(r0.height(), r1.height()); 213 EXPECT_GT(r0.y(), r1.y()); 214 EXPECT_EQ(r0.x(), r1.x()); 215 216 CloseAllToastsAndWait(); 217 EXPECT_EQ(0u, GetToastCounts()); 218 219 // Restore simulated taskbar position to bottom. 220 collection()->SetDisplayInfo(gfx::Rect(0, 0, 600, 390), // Work-area. 221 gfx::Rect(0, 0, 600, 400)); // Display-bounds. 222 } 223 224 TEST_F(MessagePopupCollectionTest, TopDownPositioningWithTopTaskbar) { 225 // Simulate a taskbar at the top. 226 collection()->SetDisplayInfo(gfx::Rect(0, 10, 600, 390), // Work-area. 227 gfx::Rect(0, 0, 600, 400)); // Display-bounds. 228 std::string id0 = AddNotification(); 229 std::string id1 = AddNotification(); 230 WaitForTransitionsDone(); 231 232 gfx::Rect r0 = GetToastRectAt(0); 233 gfx::Rect r1 = GetToastRectAt(1); 234 235 // 2 toasts are shown, equal size, vertical stack. 236 EXPECT_TRUE(IsToastShown(id0)); 237 EXPECT_TRUE(IsToastShown(id1)); 238 239 EXPECT_EQ(r0.width(), r1.width()); 240 EXPECT_EQ(r0.height(), r1.height()); 241 EXPECT_LT(r0.y(), r1.y()); 242 EXPECT_EQ(r0.x(), r1.x()); 243 244 CloseAllToastsAndWait(); 245 EXPECT_EQ(0u, GetToastCounts()); 246 247 // Restore simulated taskbar position to bottom. 248 collection()->SetDisplayInfo(gfx::Rect(0, 0, 600, 390), // Work-area. 249 gfx::Rect(0, 0, 600, 400)); // Display-bounds. 250 } 251 252 TEST_F(MessagePopupCollectionTest, TopDownPositioningWithLeftAndTopTaskbar) { 253 // If there "seems" to be a taskbar on left and top (like in Unity), it is 254 // assumed that the actual taskbar is the top one. 255 256 // Simulate a taskbar at the top and left. 257 collection()->SetDisplayInfo(gfx::Rect(10, 10, 590, 390), // Work-area. 258 gfx::Rect(0, 0, 600, 400)); // Display-bounds. 259 std::string id0 = AddNotification(); 260 std::string id1 = AddNotification(); 261 WaitForTransitionsDone(); 262 263 gfx::Rect r0 = GetToastRectAt(0); 264 gfx::Rect r1 = GetToastRectAt(1); 265 266 // 2 toasts are shown, equal size, vertical stack. 267 EXPECT_TRUE(IsToastShown(id0)); 268 EXPECT_TRUE(IsToastShown(id1)); 269 270 EXPECT_EQ(r0.width(), r1.width()); 271 EXPECT_EQ(r0.height(), r1.height()); 272 EXPECT_LT(r0.y(), r1.y()); 273 EXPECT_EQ(r0.x(), r1.x()); 274 275 CloseAllToastsAndWait(); 276 EXPECT_EQ(0u, GetToastCounts()); 277 278 // Restore simulated taskbar position to bottom. 279 collection()->SetDisplayInfo(gfx::Rect(0, 0, 600, 390), // Work-area. 280 gfx::Rect(0, 0, 600, 400)); // Display-bounds. 281 } 282 283 TEST_F(MessagePopupCollectionTest, TopDownPositioningWithBottomAndTopTaskbar) { 284 // If there "seems" to be a taskbar on bottom and top (like in Gnome), it is 285 // assumed that the actual taskbar is the top one. 286 287 // Simulate a taskbar at the top and bottom. 288 collection()->SetDisplayInfo(gfx::Rect(0, 10, 580, 400), // Work-area. 289 gfx::Rect(0, 0, 600, 400)); // Display-bounds. 290 std::string id0 = AddNotification(); 291 std::string id1 = AddNotification(); 292 WaitForTransitionsDone(); 293 294 gfx::Rect r0 = GetToastRectAt(0); 295 gfx::Rect r1 = GetToastRectAt(1); 296 297 // 2 toasts are shown, equal size, vertical stack. 298 EXPECT_TRUE(IsToastShown(id0)); 299 EXPECT_TRUE(IsToastShown(id1)); 300 301 EXPECT_EQ(r0.width(), r1.width()); 302 EXPECT_EQ(r0.height(), r1.height()); 303 EXPECT_LT(r0.y(), r1.y()); 304 EXPECT_EQ(r0.x(), r1.x()); 305 306 CloseAllToastsAndWait(); 307 EXPECT_EQ(0u, GetToastCounts()); 308 309 // Restore simulated taskbar position to bottom. 310 collection()->SetDisplayInfo(gfx::Rect(0, 0, 600, 390), // Work-area. 311 gfx::Rect(0, 0, 600, 400)); // Display-bounds. 312 } 313 314 TEST_F(MessagePopupCollectionTest, LeftPositioningWithLeftTaskbar) { 315 // Simulate a taskbar at the left. 316 collection()->SetDisplayInfo(gfx::Rect(10, 0, 590, 400), // Work-area. 317 gfx::Rect(0, 0, 600, 400)); // Display-bounds. 318 std::string id0 = AddNotification(); 319 std::string id1 = AddNotification(); 320 WaitForTransitionsDone(); 321 322 gfx::Rect r0 = GetToastRectAt(0); 323 gfx::Rect r1 = GetToastRectAt(1); 324 325 // 2 toasts are shown, equal size, vertical stack. 326 EXPECT_TRUE(IsToastShown(id0)); 327 EXPECT_TRUE(IsToastShown(id1)); 328 329 EXPECT_EQ(r0.width(), r1.width()); 330 EXPECT_EQ(r0.height(), r1.height()); 331 EXPECT_GT(r0.y(), r1.y()); 332 EXPECT_EQ(r0.x(), r1.x()); 333 334 // Ensure that toasts are on the left. 335 EXPECT_LT(r1.x(), GetWorkArea().CenterPoint().x()); 336 337 CloseAllToastsAndWait(); 338 EXPECT_EQ(0u, GetToastCounts()); 339 340 // Restore simulated taskbar position to bottom. 341 collection()->SetDisplayInfo(gfx::Rect(0, 0, 600, 390), // Work-area. 342 gfx::Rect(0, 0, 600, 400)); // Display-bounds. 343 } 344 345 TEST_F(MessagePopupCollectionTest, DetectMouseHover) { 346 std::string id0 = AddNotification(); 347 std::string id1 = AddNotification(); 348 WaitForTransitionsDone(); 349 350 views::WidgetDelegateView* toast0 = GetToast(id0); 351 EXPECT_TRUE(toast0 != NULL); 352 views::WidgetDelegateView* toast1 = GetToast(id1); 353 EXPECT_TRUE(toast1 != NULL); 354 355 ui::MouseEvent event(ui::ET_MOUSE_MOVED, gfx::Point(), gfx::Point(), 0); 356 357 // Test that mouse detection logic works in presence of out-of-order events. 358 toast0->OnMouseEntered(event); 359 EXPECT_TRUE(MouseInCollection()); 360 toast1->OnMouseEntered(event); 361 EXPECT_TRUE(MouseInCollection()); 362 toast0->OnMouseExited(event); 363 EXPECT_TRUE(MouseInCollection()); 364 toast1->OnMouseExited(event); 365 EXPECT_FALSE(MouseInCollection()); 366 367 // Test that mouse detection logic works in presence of WindowClosing events. 368 toast0->OnMouseEntered(event); 369 EXPECT_TRUE(MouseInCollection()); 370 toast1->OnMouseEntered(event); 371 EXPECT_TRUE(MouseInCollection()); 372 toast0->WindowClosing(); 373 EXPECT_TRUE(MouseInCollection()); 374 toast1->WindowClosing(); 375 EXPECT_FALSE(MouseInCollection()); 376 } 377 378 // TODO(dimich): Test repositioning - both normal one and when user is closing 379 // the toasts. 380 381 } // namespace test 382 } // namespace message_center 383