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 24namespace message_center { 25namespace test { 26 27class MessagePopupCollectionTest : public views::ViewsTestBase { 28 public: 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 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 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 80 gfx::Rect GetWorkArea() { 81 return alignment_delegate_->work_area_; 82 } 83 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 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 110 void PrepareForWait() { collection_->CreateRunLoopForTest(); } 111 112 // Assumes there is non-zero pending work. 113 void WaitForTransitionsDone() { 114 collection_->WaitForTest(); 115 collection_->CreateRunLoopForTest(); 116 } 117 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 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 134TEST_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 158TEST_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 172TEST_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 210TEST_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 240TEST_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 268TEST_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 299TEST_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 330TEST_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 361TEST_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. 396TEST_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