1 // Copyright 2014 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/notification_view.h" 6 7 #include "base/memory/scoped_ptr.h" 8 #include "base/strings/utf_string_conversions.h" 9 #include "testing/gtest/include/gtest/gtest.h" 10 #include "third_party/skia/include/core/SkBitmap.h" 11 #include "ui/gfx/image/image.h" 12 #include "ui/message_center/notification.h" 13 #include "ui/message_center/notification_list.h" 14 #include "ui/message_center/notification_types.h" 15 #include "ui/message_center/views/message_center_controller.h" 16 #include "ui/message_center/views/notification_button.h" 17 #include "ui/views/layout/fill_layout.h" 18 #include "ui/views/test/views_test_base.h" 19 #include "ui/views/widget/widget_delegate.h" 20 21 namespace message_center { 22 23 /* Test fixture ***************************************************************/ 24 25 class NotificationViewTest : public views::ViewsTestBase, 26 public MessageCenterController { 27 public: 28 NotificationViewTest(); 29 virtual ~NotificationViewTest(); 30 31 virtual void SetUp() OVERRIDE; 32 virtual void TearDown() OVERRIDE; 33 34 views::Widget* widget() { return notification_view_->GetWidget(); } 35 NotificationView* notification_view() { return notification_view_.get(); } 36 Notification* notification() { return notification_.get(); } 37 RichNotificationData* data() { return data_.get(); } 38 39 // Overridden from MessageCenterController: 40 virtual void ClickOnNotification(const std::string& notification_id) OVERRIDE; 41 virtual void RemoveNotification(const std::string& notification_id, 42 bool by_user) OVERRIDE; 43 virtual scoped_ptr<ui::MenuModel> CreateMenuModel( 44 const NotifierId& notifier_id, 45 const base::string16& display_source) OVERRIDE; 46 virtual bool HasClickedListener(const std::string& notification_id) OVERRIDE; 47 virtual void ClickOnNotificationButton(const std::string& notification_id, 48 int button_index) OVERRIDE; 49 50 protected: 51 const gfx::Image CreateTestImage(int width, int height) { 52 return gfx::Image::CreateFrom1xBitmap(CreateBitmap(width, height)); 53 } 54 55 const SkBitmap CreateBitmap(int width, int height) { 56 SkBitmap bitmap; 57 bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height); 58 bitmap.allocPixels(); 59 bitmap.eraseRGB(0, 255, 0); 60 return bitmap; 61 } 62 63 std::vector<ButtonInfo> CreateButtons(int number) { 64 ButtonInfo info(base::ASCIIToUTF16("Test button title.")); 65 info.icon = CreateTestImage(4, 4); 66 return std::vector<ButtonInfo>(number, info); 67 } 68 69 void CheckVerticalOrderInNotification() { 70 std::vector<views::View*> vertical_order; 71 vertical_order.push_back(notification_view()->top_view_); 72 vertical_order.push_back(notification_view()->image_view_); 73 std::copy(notification_view()->action_buttons_.begin(), 74 notification_view()->action_buttons_.end(), 75 std::back_inserter(vertical_order)); 76 std::vector<views::View*>::iterator current = vertical_order.begin(); 77 std::vector<views::View*>::iterator last = current++; 78 while (current != vertical_order.end()) { 79 gfx::Point last_point = (*last)->bounds().origin(); 80 views::View::ConvertPointToTarget( 81 (*last), notification_view(), &last_point); 82 83 gfx::Point current_point = (*current)->bounds().origin(); 84 views::View::ConvertPointToTarget( 85 (*current), notification_view(), ¤t_point); 86 87 EXPECT_LT(last_point.y(), current_point.y()); 88 last = current++; 89 } 90 } 91 92 void UpdateNotificationViews() { 93 notification_view()->CreateOrUpdateViews(*notification()); 94 notification_view()->Layout(); 95 } 96 97 private: 98 scoped_ptr<RichNotificationData> data_; 99 scoped_ptr<Notification> notification_; 100 scoped_ptr<NotificationView> notification_view_; 101 102 DISALLOW_COPY_AND_ASSIGN(NotificationViewTest); 103 }; 104 105 NotificationViewTest::NotificationViewTest() { 106 } 107 108 NotificationViewTest::~NotificationViewTest() { 109 } 110 111 void NotificationViewTest::SetUp() { 112 views::ViewsTestBase::SetUp(); 113 // Create a dummy notification. 114 SkBitmap bitmap; 115 data_.reset(new RichNotificationData()); 116 notification_.reset( 117 new Notification(NOTIFICATION_TYPE_BASE_FORMAT, 118 std::string("notification id"), 119 base::UTF8ToUTF16("title"), 120 base::UTF8ToUTF16("message"), 121 CreateTestImage(80, 80), 122 base::UTF8ToUTF16("display source"), 123 NotifierId(NotifierId::APPLICATION, "extension_id"), 124 *data_, 125 NULL)); 126 notification_->set_small_image(CreateTestImage(16, 16)); 127 notification_->set_image(CreateTestImage(320, 240)); 128 129 // Then create a new NotificationView with that single notification. 130 notification_view_.reset( 131 NotificationView::Create(this, *notification_, true)); 132 notification_view_->set_owned_by_client(); 133 134 views::Widget::InitParams init_params( 135 CreateParams(views::Widget::InitParams::TYPE_POPUP)); 136 views::Widget* widget = new views::Widget(); 137 widget->Init(init_params); 138 widget->SetContentsView(notification_view_.get()); 139 widget->SetSize(notification_view_->GetPreferredSize()); 140 } 141 142 void NotificationViewTest::TearDown() { 143 widget()->Close(); 144 notification_view_.reset(); 145 views::ViewsTestBase::TearDown(); 146 } 147 148 void NotificationViewTest::ClickOnNotification( 149 const std::string& notification_id) { 150 // For this test, this method should not be invoked. 151 NOTREACHED(); 152 } 153 154 void NotificationViewTest::RemoveNotification( 155 const std::string& notification_id, 156 bool by_user) { 157 // For this test, this method should not be invoked. 158 NOTREACHED(); 159 } 160 161 scoped_ptr<ui::MenuModel> NotificationViewTest::CreateMenuModel( 162 const NotifierId& notifier_id, 163 const base::string16& display_source) { 164 // For this test, this method should not be invoked. 165 NOTREACHED(); 166 return scoped_ptr<ui::MenuModel>(); 167 } 168 169 bool NotificationViewTest::HasClickedListener( 170 const std::string& notification_id) { 171 return true; 172 } 173 174 void NotificationViewTest::ClickOnNotificationButton( 175 const std::string& notification_id, 176 int button_index) { 177 // For this test, this method should not be invoked. 178 NOTREACHED(); 179 } 180 181 /* Unit tests *****************************************************************/ 182 183 TEST_F(NotificationViewTest, CreateOrUpdateTest) { 184 EXPECT_TRUE(NULL != notification_view()->title_view_); 185 EXPECT_TRUE(NULL != notification_view()->message_view_); 186 EXPECT_TRUE(NULL != notification_view()->icon_view_); 187 EXPECT_TRUE(NULL != notification_view()->image_view_); 188 189 notification()->set_image(gfx::Image()); 190 notification()->set_title(base::ASCIIToUTF16("")); 191 notification()->set_message(base::ASCIIToUTF16("")); 192 notification()->set_icon(gfx::Image()); 193 194 notification_view()->CreateOrUpdateViews(*notification()); 195 EXPECT_TRUE(NULL == notification_view()->title_view_); 196 EXPECT_TRUE(NULL == notification_view()->message_view_); 197 EXPECT_TRUE(NULL == notification_view()->image_view_); 198 // We still expect an icon view for all layouts. 199 EXPECT_TRUE(NULL != notification_view()->icon_view_); 200 } 201 202 TEST_F(NotificationViewTest, TestLineLimits) { 203 notification()->set_image(CreateTestImage(0, 0)); 204 notification()->set_context_message(base::ASCIIToUTF16("")); 205 notification_view()->CreateOrUpdateViews(*notification()); 206 207 EXPECT_EQ(5, notification_view()->GetMessageLineLimit(0, 360)); 208 EXPECT_EQ(5, notification_view()->GetMessageLineLimit(1, 360)); 209 EXPECT_EQ(3, notification_view()->GetMessageLineLimit(2, 360)); 210 211 notification()->set_image(CreateTestImage(2, 2)); 212 notification_view()->CreateOrUpdateViews(*notification()); 213 214 EXPECT_EQ(2, notification_view()->GetMessageLineLimit(0, 360)); 215 EXPECT_EQ(2, notification_view()->GetMessageLineLimit(1, 360)); 216 EXPECT_EQ(1, notification_view()->GetMessageLineLimit(2, 360)); 217 218 notification()->set_context_message(base::UTF8ToUTF16("foo")); 219 notification_view()->CreateOrUpdateViews(*notification()); 220 221 EXPECT_TRUE(notification_view()->context_message_view_ != NULL); 222 223 EXPECT_EQ(1, notification_view()->GetMessageLineLimit(0, 360)); 224 EXPECT_EQ(1, notification_view()->GetMessageLineLimit(1, 360)); 225 EXPECT_EQ(0, notification_view()->GetMessageLineLimit(2, 360)); 226 } 227 228 TEST_F(NotificationViewTest, UpdateButtonsStateTest) { 229 notification()->set_buttons(CreateButtons(2)); 230 notification_view()->CreateOrUpdateViews(*notification()); 231 widget()->Show(); 232 233 EXPECT_TRUE(NULL == notification_view()->action_buttons_[0]->background()); 234 235 // Now construct a mouse move event 1 pixel inside the boundary of the action 236 // button. 237 gfx::Point cursor_location(1, 1); 238 views::View::ConvertPointToWidget(notification_view()->action_buttons_[0], 239 &cursor_location); 240 ui::MouseEvent move(ui::ET_MOUSE_MOVED, 241 cursor_location, 242 cursor_location, 243 ui::EF_NONE, 244 ui::EF_NONE); 245 widget()->OnMouseEvent(&move); 246 247 EXPECT_TRUE(NULL != notification_view()->action_buttons_[0]->background()); 248 249 notification_view()->CreateOrUpdateViews(*notification()); 250 251 EXPECT_TRUE(NULL != notification_view()->action_buttons_[0]->background()); 252 253 // Now construct a mouse move event 1 pixel outside the boundary of the 254 // widget. 255 cursor_location = gfx::Point(-1, -1); 256 move = ui::MouseEvent(ui::ET_MOUSE_MOVED, 257 cursor_location, 258 cursor_location, 259 ui::EF_NONE, 260 ui::EF_NONE); 261 widget()->OnMouseEvent(&move); 262 263 EXPECT_TRUE(NULL == notification_view()->action_buttons_[0]->background()); 264 } 265 266 TEST_F(NotificationViewTest, UpdateButtonCountTest) { 267 notification()->set_buttons(CreateButtons(2)); 268 notification_view()->CreateOrUpdateViews(*notification()); 269 widget()->Show(); 270 271 EXPECT_TRUE(NULL == notification_view()->action_buttons_[0]->background()); 272 EXPECT_TRUE(NULL == notification_view()->action_buttons_[1]->background()); 273 274 // Now construct a mouse move event 1 pixel inside the boundary of the action 275 // button. 276 gfx::Point cursor_location(1, 1); 277 views::View::ConvertPointToWidget(notification_view()->action_buttons_[0], 278 &cursor_location); 279 ui::MouseEvent move(ui::ET_MOUSE_MOVED, 280 cursor_location, 281 cursor_location, 282 ui::EF_NONE, 283 ui::EF_NONE); 284 widget()->OnMouseEvent(&move); 285 286 EXPECT_TRUE(NULL != notification_view()->action_buttons_[0]->background()); 287 EXPECT_TRUE(NULL == notification_view()->action_buttons_[1]->background()); 288 289 notification()->set_buttons(CreateButtons(1)); 290 notification_view()->CreateOrUpdateViews(*notification()); 291 292 EXPECT_TRUE(NULL != notification_view()->action_buttons_[0]->background()); 293 EXPECT_EQ(1u, notification_view()->action_buttons_.size()); 294 295 // Now construct a mouse move event 1 pixel outside the boundary of the 296 // widget. 297 cursor_location = gfx::Point(-1, -1); 298 move = ui::MouseEvent(ui::ET_MOUSE_MOVED, 299 cursor_location, 300 cursor_location, 301 ui::EF_NONE, 302 ui::EF_NONE); 303 widget()->OnMouseEvent(&move); 304 305 EXPECT_TRUE(NULL == notification_view()->action_buttons_[0]->background()); 306 } 307 308 TEST_F(NotificationViewTest, ViewOrderingTest) { 309 // Tests that views are created in the correct vertical order. 310 notification()->set_buttons(CreateButtons(2)); 311 312 // Layout the initial views. 313 UpdateNotificationViews(); 314 315 // Double-check that vertical order is correct. 316 CheckVerticalOrderInNotification(); 317 318 // Tests that views remain in that order even after an update. 319 UpdateNotificationViews(); 320 CheckVerticalOrderInNotification(); 321 } 322 323 } // namespace message_center 324