Add ICU message format support
[chromium-blink-merge.git] / ui / message_center / views / notification_view_unittest.cc
blob054dfbd97bc7a6c3d351bc25268a5360c166da34
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.
5 #include "ui/message_center/views/notification_view.h"
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 "third_party/skia/include/core/SkCanvas.h"
12 #include "third_party/skia/include/core/SkColor.h"
13 #include "ui/events/event_utils.h"
14 #include "ui/gfx/canvas.h"
15 #include "ui/gfx/geometry/rect.h"
16 #include "ui/gfx/geometry/size.h"
17 #include "ui/gfx/image/image.h"
18 #include "ui/message_center/message_center_style.h"
19 #include "ui/message_center/notification.h"
20 #include "ui/message_center/notification_list.h"
21 #include "ui/message_center/notification_types.h"
22 #include "ui/message_center/views/constants.h"
23 #include "ui/message_center/views/message_center_controller.h"
24 #include "ui/message_center/views/notification_button.h"
25 #include "ui/message_center/views/proportional_image_view.h"
26 #include "ui/views/layout/fill_layout.h"
27 #include "ui/views/test/views_test_base.h"
28 #include "ui/views/widget/widget_delegate.h"
30 namespace message_center {
32 /* Test fixture ***************************************************************/
34 class NotificationViewTest : public views::ViewsTestBase,
35 public MessageCenterController {
36 public:
37 NotificationViewTest();
38 ~NotificationViewTest() override;
40 void SetUp() override;
41 void TearDown() override;
43 views::Widget* widget() { return notification_view_->GetWidget(); }
44 NotificationView* notification_view() { return notification_view_.get(); }
45 Notification* notification() { return notification_.get(); }
46 RichNotificationData* data() { return data_.get(); }
48 // Overridden from MessageCenterController:
49 void ClickOnNotification(const std::string& notification_id) override;
50 void RemoveNotification(const std::string& notification_id,
51 bool by_user) override;
52 scoped_ptr<ui::MenuModel> CreateMenuModel(
53 const NotifierId& notifier_id,
54 const base::string16& display_source) override;
55 bool HasClickedListener(const std::string& notification_id) override;
56 void ClickOnNotificationButton(const std::string& notification_id,
57 int button_index) override;
59 protected:
60 // Used to fill bitmaps returned by CreateBitmap().
61 static const SkColor kBitmapColor = SK_ColorGREEN;
63 const gfx::Image CreateTestImage(int width, int height) {
64 return gfx::Image::CreateFrom1xBitmap(CreateBitmap(width, height));
67 const SkBitmap CreateBitmap(int width, int height) {
68 SkBitmap bitmap;
69 bitmap.allocN32Pixels(width, height);
70 bitmap.eraseColor(kBitmapColor);
71 return bitmap;
74 std::vector<ButtonInfo> CreateButtons(int number) {
75 ButtonInfo info(base::ASCIIToUTF16("Test button title."));
76 info.icon = CreateTestImage(4, 4);
77 return std::vector<ButtonInfo>(number, info);
80 // Paints |view| and returns the size that the original image (which must have
81 // been created by CreateBitmap()) was scaled to.
82 gfx::Size GetImagePaintSize(ProportionalImageView* view) {
83 CHECK(view);
84 if (view->bounds().IsEmpty())
85 return gfx::Size();
87 gfx::Size canvas_size = view->bounds().size();
88 gfx::Canvas canvas(canvas_size, 1.0 /* image_scale */,
89 true /* is_opaque */);
90 COMPILE_ASSERT(kBitmapColor != SK_ColorBLACK,
91 bitmap_color_matches_background_color);
92 canvas.DrawColor(SK_ColorBLACK);
93 view->OnPaint(&canvas);
95 SkBitmap bitmap;
96 bitmap.allocN32Pixels(canvas_size.width(), canvas_size.height());
97 canvas.sk_canvas()->readPixels(&bitmap, 0, 0);
99 // Incrementally inset each edge at its midpoint to find the bounds of the
100 // rect containing the image's color. This assumes that the image is
101 // centered in the canvas.
102 const int kHalfWidth = canvas_size.width() / 2;
103 const int kHalfHeight = canvas_size.height() / 2;
104 gfx::Rect rect(canvas_size);
105 while (rect.width() > 0 &&
106 bitmap.getColor(rect.x(), kHalfHeight) != kBitmapColor)
107 rect.Inset(1, 0, 0, 0);
108 while (rect.height() > 0 &&
109 bitmap.getColor(kHalfWidth, rect.y()) != kBitmapColor)
110 rect.Inset(0, 1, 0, 0);
111 while (rect.width() > 0 &&
112 bitmap.getColor(rect.right() - 1, kHalfHeight) != kBitmapColor)
113 rect.Inset(0, 0, 1, 0);
114 while (rect.height() > 0 &&
115 bitmap.getColor(kHalfWidth, rect.bottom() - 1) != kBitmapColor)
116 rect.Inset(0, 0, 0, 1);
118 return rect.size();
121 void CheckVerticalOrderInNotification() {
122 std::vector<views::View*> vertical_order;
123 vertical_order.push_back(notification_view()->top_view_);
124 vertical_order.push_back(notification_view()->image_view_);
125 std::copy(notification_view()->action_buttons_.begin(),
126 notification_view()->action_buttons_.end(),
127 std::back_inserter(vertical_order));
128 std::vector<views::View*>::iterator current = vertical_order.begin();
129 std::vector<views::View*>::iterator last = current++;
130 while (current != vertical_order.end()) {
131 gfx::Point last_point = (*last)->bounds().origin();
132 views::View::ConvertPointToTarget(
133 (*last), notification_view(), &last_point);
135 gfx::Point current_point = (*current)->bounds().origin();
136 views::View::ConvertPointToTarget(
137 (*current), notification_view(), &current_point);
139 EXPECT_LT(last_point.y(), current_point.y());
140 last = current++;
144 void UpdateNotificationViews() {
145 notification_view()->CreateOrUpdateViews(*notification());
146 notification_view()->Layout();
149 private:
150 scoped_ptr<RichNotificationData> data_;
151 scoped_ptr<Notification> notification_;
152 scoped_ptr<NotificationView> notification_view_;
154 DISALLOW_COPY_AND_ASSIGN(NotificationViewTest);
157 NotificationViewTest::NotificationViewTest() {
160 NotificationViewTest::~NotificationViewTest() {
163 void NotificationViewTest::SetUp() {
164 views::ViewsTestBase::SetUp();
165 // Create a dummy notification.
166 SkBitmap bitmap;
167 data_.reset(new RichNotificationData());
168 notification_.reset(
169 new Notification(NOTIFICATION_TYPE_BASE_FORMAT,
170 std::string("notification id"),
171 base::UTF8ToUTF16("title"),
172 base::UTF8ToUTF16("message"),
173 CreateTestImage(80, 80),
174 base::UTF8ToUTF16("display source"),
175 NotifierId(NotifierId::APPLICATION, "extension_id"),
176 *data_,
177 NULL));
178 notification_->set_small_image(CreateTestImage(16, 16));
179 notification_->set_image(CreateTestImage(320, 240));
181 // Then create a new NotificationView with that single notification.
182 notification_view_.reset(
183 NotificationView::Create(this, *notification_, true));
184 notification_view_->set_owned_by_client();
186 views::Widget::InitParams init_params(
187 CreateParams(views::Widget::InitParams::TYPE_POPUP));
188 views::Widget* widget = new views::Widget();
189 widget->Init(init_params);
190 widget->SetContentsView(notification_view_.get());
191 widget->SetSize(notification_view_->GetPreferredSize());
194 void NotificationViewTest::TearDown() {
195 widget()->Close();
196 notification_view_.reset();
197 views::ViewsTestBase::TearDown();
200 void NotificationViewTest::ClickOnNotification(
201 const std::string& notification_id) {
202 // For this test, this method should not be invoked.
203 NOTREACHED();
206 void NotificationViewTest::RemoveNotification(
207 const std::string& notification_id,
208 bool by_user) {
209 // For this test, this method should not be invoked.
210 NOTREACHED();
213 scoped_ptr<ui::MenuModel> NotificationViewTest::CreateMenuModel(
214 const NotifierId& notifier_id,
215 const base::string16& display_source) {
216 // For this test, this method should not be invoked.
217 NOTREACHED();
218 return nullptr;
221 bool NotificationViewTest::HasClickedListener(
222 const std::string& notification_id) {
223 return true;
226 void NotificationViewTest::ClickOnNotificationButton(
227 const std::string& notification_id,
228 int button_index) {
229 // For this test, this method should not be invoked.
230 NOTREACHED();
233 /* Unit tests *****************************************************************/
235 TEST_F(NotificationViewTest, CreateOrUpdateTest) {
236 EXPECT_TRUE(NULL != notification_view()->title_view_);
237 EXPECT_TRUE(NULL != notification_view()->message_view_);
238 EXPECT_TRUE(NULL != notification_view()->icon_view_);
239 EXPECT_TRUE(NULL != notification_view()->image_view_);
241 notification()->set_image(gfx::Image());
242 notification()->set_title(base::ASCIIToUTF16(""));
243 notification()->set_message(base::ASCIIToUTF16(""));
244 notification()->set_icon(gfx::Image());
246 notification_view()->CreateOrUpdateViews(*notification());
247 EXPECT_TRUE(NULL == notification_view()->title_view_);
248 EXPECT_TRUE(NULL == notification_view()->message_view_);
249 EXPECT_TRUE(NULL == notification_view()->image_view_);
250 // We still expect an icon view for all layouts.
251 EXPECT_TRUE(NULL != notification_view()->icon_view_);
254 TEST_F(NotificationViewTest, TestLineLimits) {
255 notification()->set_image(CreateTestImage(0, 0));
256 notification()->set_context_message(base::ASCIIToUTF16(""));
257 notification_view()->CreateOrUpdateViews(*notification());
259 EXPECT_EQ(5, notification_view()->GetMessageLineLimit(0, 360));
260 EXPECT_EQ(5, notification_view()->GetMessageLineLimit(1, 360));
261 EXPECT_EQ(3, notification_view()->GetMessageLineLimit(2, 360));
263 notification()->set_image(CreateTestImage(2, 2));
264 notification_view()->CreateOrUpdateViews(*notification());
266 EXPECT_EQ(2, notification_view()->GetMessageLineLimit(0, 360));
267 EXPECT_EQ(2, notification_view()->GetMessageLineLimit(1, 360));
268 EXPECT_EQ(1, notification_view()->GetMessageLineLimit(2, 360));
270 notification()->set_context_message(base::UTF8ToUTF16("foo"));
271 notification_view()->CreateOrUpdateViews(*notification());
273 EXPECT_TRUE(notification_view()->context_message_view_ != NULL);
275 EXPECT_EQ(1, notification_view()->GetMessageLineLimit(0, 360));
276 EXPECT_EQ(1, notification_view()->GetMessageLineLimit(1, 360));
277 EXPECT_EQ(0, notification_view()->GetMessageLineLimit(2, 360));
280 TEST_F(NotificationViewTest, TestIconSizing) {
281 notification()->set_type(NOTIFICATION_TYPE_SIMPLE);
282 ProportionalImageView* view = notification_view()->icon_view_;
284 // Icons smaller than the legacy size should be scaled up to it.
285 notification()->set_icon(CreateTestImage(kLegacyIconSize / 2,
286 kLegacyIconSize / 2));
287 UpdateNotificationViews();
288 EXPECT_EQ(gfx::Size(kLegacyIconSize, kLegacyIconSize).ToString(),
289 GetImagePaintSize(view).ToString());
291 // Icons at the legacy size should be unscaled.
292 notification()->set_icon(CreateTestImage(kLegacyIconSize, kLegacyIconSize));
293 UpdateNotificationViews();
294 EXPECT_EQ(gfx::Size(kLegacyIconSize, kLegacyIconSize).ToString(),
295 GetImagePaintSize(view).ToString());
297 // Icons slightly smaller than the preferred size should be scaled down to the
298 // legacy size to avoid having tiny borders (http://crbug.com/232966).
299 notification()->set_icon(CreateTestImage(kIconSize - 1, kIconSize - 1));
300 UpdateNotificationViews();
301 EXPECT_EQ(gfx::Size(kLegacyIconSize, kLegacyIconSize).ToString(),
302 GetImagePaintSize(view).ToString());
304 // Icons at the preferred size or above should be scaled down to the preferred
305 // size.
306 notification()->set_icon(CreateTestImage(kIconSize, kIconSize));
307 UpdateNotificationViews();
308 EXPECT_EQ(gfx::Size(kIconSize, kIconSize).ToString(),
309 GetImagePaintSize(view).ToString());
311 notification()->set_icon(CreateTestImage(2 * kIconSize, 2 * kIconSize));
312 UpdateNotificationViews();
313 EXPECT_EQ(gfx::Size(kIconSize, kIconSize).ToString(),
314 GetImagePaintSize(view).ToString());
316 // Large, non-square images' aspect ratios should be preserved.
317 notification()->set_icon(CreateTestImage(4 * kIconSize, 2 * kIconSize));
318 UpdateNotificationViews();
319 EXPECT_EQ(gfx::Size(kIconSize, kIconSize / 2).ToString(),
320 GetImagePaintSize(view).ToString());
323 TEST_F(NotificationViewTest, TestImageSizing) {
324 ProportionalImageView* view = notification_view()->image_view_;
325 const gfx::Size kIdealSize(kNotificationPreferredImageWidth,
326 kNotificationPreferredImageHeight);
328 // Images should be scaled to the ideal size.
329 notification()->set_image(CreateTestImage(kIdealSize.width() / 2,
330 kIdealSize.height() / 2));
331 UpdateNotificationViews();
332 EXPECT_EQ(kIdealSize.ToString(), GetImagePaintSize(view).ToString());
334 notification()->set_image(CreateTestImage(kIdealSize.width(),
335 kIdealSize.height()));
336 UpdateNotificationViews();
337 EXPECT_EQ(kIdealSize.ToString(), GetImagePaintSize(view).ToString());
339 notification()->set_image(CreateTestImage(kIdealSize.width() * 2,
340 kIdealSize.height() * 2));
341 UpdateNotificationViews();
342 EXPECT_EQ(kIdealSize.ToString(), GetImagePaintSize(view).ToString());
344 // Original aspect ratios should be preserved.
345 gfx::Size orig_size(kIdealSize.width() * 2, kIdealSize.height());
346 notification()->set_image(
347 CreateTestImage(orig_size.width(), orig_size.height()));
348 UpdateNotificationViews();
349 gfx::Size paint_size = GetImagePaintSize(view);
350 gfx::Size container_size = kIdealSize;
351 container_size.Enlarge(-2 * kNotificationImageBorderSize,
352 -2 * kNotificationImageBorderSize);
353 EXPECT_EQ(GetImageSizeForContainerSize(container_size, orig_size).ToString(),
354 paint_size.ToString());
355 ASSERT_GT(paint_size.height(), 0);
356 EXPECT_EQ(orig_size.width() / orig_size.height(),
357 paint_size.width() / paint_size.height());
359 orig_size.SetSize(kIdealSize.width(), kIdealSize.height() * 2);
360 notification()->set_image(
361 CreateTestImage(orig_size.width(), orig_size.height()));
362 UpdateNotificationViews();
363 paint_size = GetImagePaintSize(view);
364 EXPECT_EQ(GetImageSizeForContainerSize(container_size, orig_size).ToString(),
365 paint_size.ToString());
366 ASSERT_GT(paint_size.height(), 0);
367 EXPECT_EQ(orig_size.width() / orig_size.height(),
368 paint_size.width() / paint_size.height());
371 TEST_F(NotificationViewTest, UpdateButtonsStateTest) {
372 notification()->set_buttons(CreateButtons(2));
373 notification_view()->CreateOrUpdateViews(*notification());
374 widget()->Show();
376 EXPECT_EQ(views::CustomButton::STATE_NORMAL,
377 notification_view()->action_buttons_[0]->state());
379 // Now construct a mouse move event 1 pixel inside the boundary of the action
380 // button.
381 gfx::Point cursor_location(1, 1);
382 views::View::ConvertPointToWidget(notification_view()->action_buttons_[0],
383 &cursor_location);
384 ui::MouseEvent move(ui::ET_MOUSE_MOVED, cursor_location, cursor_location,
385 ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE);
386 widget()->OnMouseEvent(&move);
388 EXPECT_EQ(views::CustomButton::STATE_HOVERED,
389 notification_view()->action_buttons_[0]->state());
391 notification_view()->CreateOrUpdateViews(*notification());
393 EXPECT_EQ(views::CustomButton::STATE_HOVERED,
394 notification_view()->action_buttons_[0]->state());
396 // Now construct a mouse move event 1 pixel outside the boundary of the
397 // widget.
398 cursor_location = gfx::Point(-1, -1);
399 move = ui::MouseEvent(ui::ET_MOUSE_MOVED, cursor_location, cursor_location,
400 ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE);
401 widget()->OnMouseEvent(&move);
403 EXPECT_EQ(views::CustomButton::STATE_NORMAL,
404 notification_view()->action_buttons_[0]->state());
407 TEST_F(NotificationViewTest, UpdateButtonCountTest) {
408 notification()->set_buttons(CreateButtons(2));
409 notification_view()->CreateOrUpdateViews(*notification());
410 widget()->Show();
412 EXPECT_EQ(views::CustomButton::STATE_NORMAL,
413 notification_view()->action_buttons_[0]->state());
414 EXPECT_EQ(views::CustomButton::STATE_NORMAL,
415 notification_view()->action_buttons_[1]->state());
417 // Now construct a mouse move event 1 pixel inside the boundary of the action
418 // button.
419 gfx::Point cursor_location(1, 1);
420 views::View::ConvertPointToWidget(notification_view()->action_buttons_[0],
421 &cursor_location);
422 ui::MouseEvent move(ui::ET_MOUSE_MOVED, cursor_location, cursor_location,
423 ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE);
424 widget()->OnMouseEvent(&move);
426 EXPECT_EQ(views::CustomButton::STATE_HOVERED,
427 notification_view()->action_buttons_[0]->state());
428 EXPECT_EQ(views::CustomButton::STATE_NORMAL,
429 notification_view()->action_buttons_[1]->state());
431 notification()->set_buttons(CreateButtons(1));
432 notification_view()->CreateOrUpdateViews(*notification());
434 EXPECT_EQ(views::CustomButton::STATE_HOVERED,
435 notification_view()->action_buttons_[0]->state());
436 EXPECT_EQ(1u, notification_view()->action_buttons_.size());
438 // Now construct a mouse move event 1 pixel outside the boundary of the
439 // widget.
440 cursor_location = gfx::Point(-1, -1);
441 move = ui::MouseEvent(ui::ET_MOUSE_MOVED, cursor_location, cursor_location,
442 ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE);
443 widget()->OnMouseEvent(&move);
445 EXPECT_EQ(views::CustomButton::STATE_NORMAL,
446 notification_view()->action_buttons_[0]->state());
449 TEST_F(NotificationViewTest, ViewOrderingTest) {
450 // Tests that views are created in the correct vertical order.
451 notification()->set_buttons(CreateButtons(2));
453 // Layout the initial views.
454 UpdateNotificationViews();
456 // Double-check that vertical order is correct.
457 CheckVerticalOrderInNotification();
459 // Tests that views remain in that order even after an update.
460 UpdateNotificationViews();
461 CheckVerticalOrderInNotification();
464 } // namespace message_center