OLD | NEW |
| (Empty) |
1 // Copyright 2017 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_md.h" | |
6 | |
7 #include "base/strings/string_util.h" | |
8 #include "base/strings/utf_string_conversions.h" | |
9 #include "build/build_config.h" | |
10 #include "testing/gtest/include/gtest/gtest.h" | |
11 #include "ui/compositor/scoped_animation_duration_scale_mode.h" | |
12 #include "ui/events/event_processor.h" | |
13 #include "ui/events/event_utils.h" | |
14 #include "ui/events/test/event_generator.h" | |
15 #include "ui/gfx/canvas.h" | |
16 #include "ui/message_center/message_center_style.h" | |
17 #include "ui/message_center/views/message_center_controller.h" | |
18 #include "ui/message_center/views/notification_header_view.h" | |
19 #include "ui/message_center/views/proportional_image_view.h" | |
20 #include "ui/views/controls/button/image_button.h" | |
21 #include "ui/views/controls/button/label_button.h" | |
22 #include "ui/views/test/views_test_base.h" | |
23 #include "ui/views/test/widget_test.h" | |
24 | |
25 namespace message_center { | |
26 | |
27 /* Test fixture ***************************************************************/ | |
28 | |
29 // Used to fill bitmaps returned by CreateBitmap(). | |
30 static const SkColor kBitmapColor = SK_ColorGREEN; | |
31 | |
32 class NotificationViewMDTest : public views::ViewsTestBase, | |
33 public MessageCenterController { | |
34 public: | |
35 NotificationViewMDTest(); | |
36 ~NotificationViewMDTest() override; | |
37 | |
38 // Overridden from ViewsTestBase: | |
39 void SetUp() override; | |
40 void TearDown() override; | |
41 | |
42 // Overridden from MessageCenterController: | |
43 void ClickOnNotification(const std::string& notification_id) override; | |
44 void RemoveNotification(const std::string& notification_id, | |
45 bool by_user) override; | |
46 std::unique_ptr<ui::MenuModel> CreateMenuModel( | |
47 const NotifierId& notifier_id, | |
48 const base::string16& display_source) override; | |
49 bool HasClickedListener(const std::string& notification_id) override; | |
50 void ClickOnNotificationButton(const std::string& notification_id, | |
51 int button_index) override; | |
52 void ClickOnSettingsButton(const std::string& notification_id) override; | |
53 void UpdateNotificationSize(const std::string& notification_id) override; | |
54 | |
55 NotificationViewMD* notification_view() const { | |
56 return notification_view_.get(); | |
57 } | |
58 Notification* notification() const { return notification_.get(); } | |
59 views::Widget* widget() const { | |
60 DCHECK_EQ(widget_, notification_view()->GetWidget()); | |
61 return widget_; | |
62 } | |
63 | |
64 protected: | |
65 const gfx::Image CreateTestImage(int width, int height); | |
66 const SkBitmap CreateBitmap(int width, int height); | |
67 std::vector<ButtonInfo> CreateButtons(int number); | |
68 | |
69 // Paints |view| and returns the size that the original image (which must have | |
70 // been created by CreateBitmap()) was scaled to. | |
71 gfx::Size GetImagePaintSize(ProportionalImageView* view); | |
72 | |
73 void UpdateNotificationViews(); | |
74 float GetNotificationSlideAmount() const; | |
75 bool IsRemoved(const std::string& notification_id) const; | |
76 void DispatchGesture(const ui::GestureEventDetails& details); | |
77 void BeginScroll(); | |
78 void EndScroll(); | |
79 void ScrollBy(int dx); | |
80 views::ImageButton* GetCloseButton(); | |
81 | |
82 private: | |
83 std::set<std::string> removed_ids_; | |
84 | |
85 std::unique_ptr<RichNotificationData> data_; | |
86 std::unique_ptr<Notification> notification_; | |
87 std::unique_ptr<NotificationViewMD> notification_view_; | |
88 views::Widget* widget_; | |
89 | |
90 DISALLOW_COPY_AND_ASSIGN(NotificationViewMDTest); | |
91 }; | |
92 | |
93 NotificationViewMDTest::NotificationViewMDTest() = default; | |
94 NotificationViewMDTest::~NotificationViewMDTest() = default; | |
95 | |
96 void NotificationViewMDTest::SetUp() { | |
97 views::ViewsTestBase::SetUp(); | |
98 // Create a dummy notification. | |
99 data_.reset(new RichNotificationData()); | |
100 notification_.reset(new Notification( | |
101 NOTIFICATION_TYPE_BASE_FORMAT, std::string("notification id"), | |
102 base::UTF8ToUTF16("title"), base::UTF8ToUTF16("message"), | |
103 CreateTestImage(80, 80), base::UTF8ToUTF16("display source"), GURL(), | |
104 NotifierId(NotifierId::APPLICATION, "extension_id"), *data_, nullptr)); | |
105 notification_->set_small_image(CreateTestImage(16, 16)); | |
106 notification_->set_image(CreateTestImage(320, 240)); | |
107 | |
108 // Then create a new NotificationView with that single notification. | |
109 // In the actual code path, this is instantiated by | |
110 // MessageViewFactory::Create. | |
111 // TODO(tetsui): Confirm that NotificationViewMD options are same as one | |
112 // created by the method. | |
113 notification_view_.reset(new NotificationViewMD(this, *notification_)); | |
114 notification_view_->SetIsNested(); | |
115 notification_view_->set_owned_by_client(); | |
116 | |
117 views::Widget::InitParams init_params( | |
118 CreateParams(views::Widget::InitParams::TYPE_POPUP)); | |
119 widget_ = new views::Widget(); | |
120 widget_->Init(init_params); | |
121 widget_->SetContentsView(notification_view_.get()); | |
122 widget_->SetSize(notification_view_->GetPreferredSize()); | |
123 widget_->Show(); | |
124 } | |
125 | |
126 void NotificationViewMDTest::TearDown() { | |
127 widget()->Close(); | |
128 notification_view_.reset(); | |
129 views::ViewsTestBase::TearDown(); | |
130 } | |
131 | |
132 void NotificationViewMDTest::ClickOnNotification( | |
133 const std::string& notification_id) { | |
134 // For this test, this method should not be invoked. | |
135 NOTREACHED(); | |
136 } | |
137 | |
138 void NotificationViewMDTest::RemoveNotification( | |
139 const std::string& notification_id, | |
140 bool by_user) { | |
141 removed_ids_.insert(notification_id); | |
142 } | |
143 | |
144 std::unique_ptr<ui::MenuModel> NotificationViewMDTest::CreateMenuModel( | |
145 const NotifierId& notifier_id, | |
146 const base::string16& display_source) { | |
147 // For this test, this method should not be invoked. | |
148 NOTREACHED(); | |
149 return nullptr; | |
150 } | |
151 | |
152 bool NotificationViewMDTest::HasClickedListener( | |
153 const std::string& notification_id) { | |
154 return true; | |
155 } | |
156 | |
157 void NotificationViewMDTest::ClickOnNotificationButton( | |
158 const std::string& notification_id, | |
159 int button_index) { | |
160 // For this test, this method should not be invoked. | |
161 NOTREACHED(); | |
162 } | |
163 | |
164 void NotificationViewMDTest::ClickOnSettingsButton( | |
165 const std::string& notification_id) { | |
166 // For this test, this method should not be invoked. | |
167 NOTREACHED(); | |
168 } | |
169 | |
170 void NotificationViewMDTest::UpdateNotificationSize( | |
171 const std::string& notification_id) { | |
172 widget()->SetSize(notification_view()->GetPreferredSize()); | |
173 } | |
174 | |
175 const gfx::Image NotificationViewMDTest::CreateTestImage(int width, | |
176 int height) { | |
177 return gfx::Image::CreateFrom1xBitmap(CreateBitmap(width, height)); | |
178 } | |
179 | |
180 const SkBitmap NotificationViewMDTest::CreateBitmap(int width, int height) { | |
181 SkBitmap bitmap; | |
182 bitmap.allocN32Pixels(width, height); | |
183 bitmap.eraseColor(kBitmapColor); | |
184 return bitmap; | |
185 } | |
186 | |
187 std::vector<ButtonInfo> NotificationViewMDTest::CreateButtons(int number) { | |
188 ButtonInfo info(base::ASCIIToUTF16("Test button.")); | |
189 info.icon = CreateTestImage(4, 4); | |
190 return std::vector<ButtonInfo>(number, info); | |
191 } | |
192 | |
193 gfx::Size NotificationViewMDTest::GetImagePaintSize( | |
194 ProportionalImageView* view) { | |
195 CHECK(view); | |
196 if (view->bounds().IsEmpty()) | |
197 return gfx::Size(); | |
198 | |
199 gfx::Size canvas_size = view->bounds().size(); | |
200 gfx::Canvas canvas(canvas_size, 1.0 /* image_scale */, true /* is_opaque */); | |
201 static_assert(kBitmapColor != SK_ColorBLACK, | |
202 "The bitmap color must match the background color"); | |
203 canvas.DrawColor(SK_ColorBLACK); | |
204 view->OnPaint(&canvas); | |
205 | |
206 SkBitmap bitmap = canvas.GetBitmap(); | |
207 // Incrementally inset each edge at its midpoint to find the bounds of the | |
208 // rect containing the image's color. This assumes that the image is | |
209 // centered in the canvas. | |
210 const int kHalfWidth = canvas_size.width() / 2; | |
211 const int kHalfHeight = canvas_size.height() / 2; | |
212 gfx::Rect rect(canvas_size); | |
213 while (rect.width() > 0 && | |
214 bitmap.getColor(rect.x(), kHalfHeight) != kBitmapColor) | |
215 rect.Inset(1, 0, 0, 0); | |
216 while (rect.height() > 0 && | |
217 bitmap.getColor(kHalfWidth, rect.y()) != kBitmapColor) | |
218 rect.Inset(0, 1, 0, 0); | |
219 while (rect.width() > 0 && | |
220 bitmap.getColor(rect.right() - 1, kHalfHeight) != kBitmapColor) | |
221 rect.Inset(0, 0, 1, 0); | |
222 while (rect.height() > 0 && | |
223 bitmap.getColor(kHalfWidth, rect.bottom() - 1) != kBitmapColor) | |
224 rect.Inset(0, 0, 0, 1); | |
225 | |
226 return rect.size(); | |
227 } | |
228 | |
229 void NotificationViewMDTest::UpdateNotificationViews() { | |
230 notification_view()->UpdateWithNotification(*notification()); | |
231 } | |
232 | |
233 float NotificationViewMDTest::GetNotificationSlideAmount() const { | |
234 return notification_view_->GetSlideOutLayer() | |
235 ->transform() | |
236 .To2dTranslation() | |
237 .x(); | |
238 } | |
239 | |
240 bool NotificationViewMDTest::IsRemoved( | |
241 const std::string& notification_id) const { | |
242 return (removed_ids_.find(notification_id) != removed_ids_.end()); | |
243 } | |
244 | |
245 void NotificationViewMDTest::DispatchGesture( | |
246 const ui::GestureEventDetails& details) { | |
247 ui::test::EventGenerator generator( | |
248 notification_view()->GetWidget()->GetNativeWindow()); | |
249 ui::GestureEvent event(0, 0, 0, ui::EventTimeForNow(), details); | |
250 generator.Dispatch(&event); | |
251 } | |
252 | |
253 void NotificationViewMDTest::BeginScroll() { | |
254 DispatchGesture(ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN)); | |
255 } | |
256 | |
257 void NotificationViewMDTest::EndScroll() { | |
258 DispatchGesture(ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_END)); | |
259 } | |
260 | |
261 void NotificationViewMDTest::ScrollBy(int dx) { | |
262 DispatchGesture(ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_UPDATE, dx, 0)); | |
263 } | |
264 | |
265 views::ImageButton* NotificationViewMDTest::GetCloseButton() { | |
266 return notification_view()->header_row_->close_button(); | |
267 } | |
268 | |
269 /* Unit tests *****************************************************************/ | |
270 | |
271 // TODO(tetsui): Following tests are not yet ported from NotificationViewTest. | |
272 // * CreateOrUpdateTestSettingsButton | |
273 // * TestLineLimits | |
274 // * TestImageSizing | |
275 // * SettingsButtonTest | |
276 // * ViewOrderingTest | |
277 // * FormatContextMessageTest | |
278 | |
279 TEST_F(NotificationViewMDTest, CreateOrUpdateTest) { | |
280 EXPECT_NE(nullptr, notification_view()->title_view_); | |
281 EXPECT_NE(nullptr, notification_view()->message_view_); | |
282 EXPECT_NE(nullptr, notification_view()->icon_view_); | |
283 EXPECT_NE(nullptr, notification_view()->image_view_); | |
284 | |
285 notification()->set_image(gfx::Image()); | |
286 notification()->set_title(base::string16()); | |
287 notification()->set_message(base::string16()); | |
288 notification()->set_icon(gfx::Image()); | |
289 | |
290 notification_view()->CreateOrUpdateViews(*notification()); | |
291 | |
292 EXPECT_EQ(nullptr, notification_view()->title_view_); | |
293 EXPECT_EQ(nullptr, notification_view()->message_view_); | |
294 EXPECT_EQ(nullptr, notification_view()->image_view_); | |
295 // We still expect an icon view for all layouts. | |
296 EXPECT_NE(nullptr, notification_view()->icon_view_); | |
297 } | |
298 | |
299 TEST_F(NotificationViewMDTest, TestIconSizing) { | |
300 // TODO(tetsui): Remove duplicated integer literal in CreateOrUpdateIconView. | |
301 const int kNotificationIconSize = 30; | |
302 | |
303 notification()->set_type(NOTIFICATION_TYPE_SIMPLE); | |
304 ProportionalImageView* view = notification_view()->icon_view_; | |
305 | |
306 // Icons smaller than the maximum size should remain unscaled. | |
307 notification()->set_icon( | |
308 CreateTestImage(kNotificationIconSize / 2, kNotificationIconSize / 4)); | |
309 UpdateNotificationViews(); | |
310 EXPECT_EQ(gfx::Size(kNotificationIconSize / 2, kNotificationIconSize / 4) | |
311 .ToString(), | |
312 GetImagePaintSize(view).ToString()); | |
313 | |
314 // Icons of exactly the intended icon size should remain unscaled. | |
315 notification()->set_icon( | |
316 CreateTestImage(kNotificationIconSize, kNotificationIconSize)); | |
317 UpdateNotificationViews(); | |
318 EXPECT_EQ(gfx::Size(kNotificationIconSize, kNotificationIconSize).ToString(), | |
319 GetImagePaintSize(view).ToString()); | |
320 | |
321 // Icons over the maximum size should be scaled down, maintaining proportions. | |
322 notification()->set_icon( | |
323 CreateTestImage(2 * kNotificationIconSize, 2 * kNotificationIconSize)); | |
324 UpdateNotificationViews(); | |
325 EXPECT_EQ(gfx::Size(kNotificationIconSize, kNotificationIconSize).ToString(), | |
326 GetImagePaintSize(view).ToString()); | |
327 | |
328 notification()->set_icon( | |
329 CreateTestImage(4 * kNotificationIconSize, 2 * kNotificationIconSize)); | |
330 UpdateNotificationViews(); | |
331 EXPECT_EQ( | |
332 gfx::Size(kNotificationIconSize, kNotificationIconSize / 2).ToString(), | |
333 GetImagePaintSize(view).ToString()); | |
334 } | |
335 | |
336 TEST_F(NotificationViewMDTest, UpdateButtonsStateTest) { | |
337 notification()->set_buttons(CreateButtons(2)); | |
338 notification_view()->CreateOrUpdateViews(*notification()); | |
339 widget()->Show(); | |
340 | |
341 // Action buttons are hidden by collapsed state. | |
342 if (!notification_view()->expanded_) | |
343 notification_view()->ToggleExpanded(); | |
344 EXPECT_TRUE(notification_view()->actions_row_->visible()); | |
345 | |
346 EXPECT_EQ(views::CustomButton::STATE_NORMAL, | |
347 notification_view()->action_buttons_[0]->state()); | |
348 | |
349 // Now construct a mouse move event 1 pixel inside the boundary of the action | |
350 // button. | |
351 gfx::Point cursor_location(1, 1); | |
352 views::View::ConvertPointToWidget(notification_view()->action_buttons_[0], | |
353 &cursor_location); | |
354 ui::MouseEvent move(ui::ET_MOUSE_MOVED, cursor_location, cursor_location, | |
355 ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE); | |
356 widget()->OnMouseEvent(&move); | |
357 | |
358 EXPECT_EQ(views::CustomButton::STATE_HOVERED, | |
359 notification_view()->action_buttons_[0]->state()); | |
360 | |
361 notification_view()->CreateOrUpdateViews(*notification()); | |
362 | |
363 EXPECT_EQ(views::CustomButton::STATE_HOVERED, | |
364 notification_view()->action_buttons_[0]->state()); | |
365 | |
366 // Now construct a mouse move event 1 pixel outside the boundary of the | |
367 // widget. | |
368 cursor_location = gfx::Point(-1, -1); | |
369 move = ui::MouseEvent(ui::ET_MOUSE_MOVED, cursor_location, cursor_location, | |
370 ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE); | |
371 widget()->OnMouseEvent(&move); | |
372 | |
373 EXPECT_EQ(views::CustomButton::STATE_NORMAL, | |
374 notification_view()->action_buttons_[0]->state()); | |
375 } | |
376 | |
377 TEST_F(NotificationViewMDTest, UpdateButtonCountTest) { | |
378 notification()->set_buttons(CreateButtons(2)); | |
379 notification_view()->UpdateWithNotification(*notification()); | |
380 widget()->Show(); | |
381 | |
382 // Action buttons are hidden by collapsed state. | |
383 if (!notification_view()->expanded_) | |
384 notification_view()->ToggleExpanded(); | |
385 EXPECT_TRUE(notification_view()->actions_row_->visible()); | |
386 | |
387 EXPECT_EQ(views::CustomButton::STATE_NORMAL, | |
388 notification_view()->action_buttons_[0]->state()); | |
389 EXPECT_EQ(views::CustomButton::STATE_NORMAL, | |
390 notification_view()->action_buttons_[1]->state()); | |
391 | |
392 // Now construct a mouse move event 1 pixel inside the boundary of the action | |
393 // button. | |
394 gfx::Point cursor_location(1, 1); | |
395 views::View::ConvertPointToScreen(notification_view()->action_buttons_[0], | |
396 &cursor_location); | |
397 ui::MouseEvent move(ui::ET_MOUSE_MOVED, cursor_location, cursor_location, | |
398 ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE); | |
399 ui::EventDispatchDetails details = | |
400 views::test::WidgetTest::GetEventSink(widget())->OnEventFromSource(&move); | |
401 EXPECT_FALSE(details.dispatcher_destroyed); | |
402 | |
403 EXPECT_EQ(views::CustomButton::STATE_HOVERED, | |
404 notification_view()->action_buttons_[0]->state()); | |
405 EXPECT_EQ(views::CustomButton::STATE_NORMAL, | |
406 notification_view()->action_buttons_[1]->state()); | |
407 | |
408 notification()->set_buttons(CreateButtons(1)); | |
409 notification_view()->UpdateWithNotification(*notification()); | |
410 | |
411 EXPECT_EQ(views::CustomButton::STATE_HOVERED, | |
412 notification_view()->action_buttons_[0]->state()); | |
413 EXPECT_EQ(1u, notification_view()->action_buttons_.size()); | |
414 | |
415 // Now construct a mouse move event 1 pixel outside the boundary of the | |
416 // widget. | |
417 cursor_location = gfx::Point(-1, -1); | |
418 move = ui::MouseEvent(ui::ET_MOUSE_MOVED, cursor_location, cursor_location, | |
419 ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE); | |
420 widget()->OnMouseEvent(&move); | |
421 | |
422 EXPECT_EQ(views::CustomButton::STATE_NORMAL, | |
423 notification_view()->action_buttons_[0]->state()); | |
424 } | |
425 | |
426 TEST_F(NotificationViewMDTest, SlideOut) { | |
427 ui::ScopedAnimationDurationScaleMode zero_duration_scope( | |
428 ui::ScopedAnimationDurationScaleMode::ZERO_DURATION); | |
429 | |
430 UpdateNotificationViews(); | |
431 std::string notification_id = notification()->id(); | |
432 | |
433 BeginScroll(); | |
434 ScrollBy(-10); | |
435 EXPECT_FALSE(IsRemoved(notification_id)); | |
436 EXPECT_EQ(-10.f, GetNotificationSlideAmount()); | |
437 EndScroll(); | |
438 EXPECT_FALSE(IsRemoved(notification_id)); | |
439 EXPECT_EQ(0.f, GetNotificationSlideAmount()); | |
440 | |
441 BeginScroll(); | |
442 ScrollBy(-200); | |
443 EXPECT_FALSE(IsRemoved(notification_id)); | |
444 EXPECT_EQ(-200.f, GetNotificationSlideAmount()); | |
445 EndScroll(); | |
446 EXPECT_TRUE(IsRemoved(notification_id)); | |
447 } | |
448 | |
449 TEST_F(NotificationViewMDTest, SlideOutNested) { | |
450 ui::ScopedAnimationDurationScaleMode zero_duration_scope( | |
451 ui::ScopedAnimationDurationScaleMode::ZERO_DURATION); | |
452 | |
453 UpdateNotificationViews(); | |
454 notification_view()->SetIsNested(); | |
455 std::string notification_id = notification()->id(); | |
456 | |
457 BeginScroll(); | |
458 ScrollBy(-10); | |
459 EXPECT_FALSE(IsRemoved(notification_id)); | |
460 EXPECT_EQ(-10.f, GetNotificationSlideAmount()); | |
461 EndScroll(); | |
462 EXPECT_FALSE(IsRemoved(notification_id)); | |
463 EXPECT_EQ(0.f, GetNotificationSlideAmount()); | |
464 | |
465 BeginScroll(); | |
466 ScrollBy(-200); | |
467 EXPECT_FALSE(IsRemoved(notification_id)); | |
468 EXPECT_EQ(-200.f, GetNotificationSlideAmount()); | |
469 EndScroll(); | |
470 EXPECT_TRUE(IsRemoved(notification_id)); | |
471 } | |
472 | |
473 // Pinning notification is ChromeOS only feature. | |
474 #if defined(OS_CHROMEOS) | |
475 | |
476 TEST_F(NotificationViewMDTest, SlideOutPinned) { | |
477 ui::ScopedAnimationDurationScaleMode zero_duration_scope( | |
478 ui::ScopedAnimationDurationScaleMode::ZERO_DURATION); | |
479 | |
480 notification()->set_pinned(true); | |
481 UpdateNotificationViews(); | |
482 std::string notification_id = notification()->id(); | |
483 | |
484 BeginScroll(); | |
485 ScrollBy(-200); | |
486 EXPECT_FALSE(IsRemoved(notification_id)); | |
487 EXPECT_LT(-200.f, GetNotificationSlideAmount()); | |
488 EndScroll(); | |
489 EXPECT_FALSE(IsRemoved(notification_id)); | |
490 } | |
491 | |
492 TEST_F(NotificationViewMDTest, Pinned) { | |
493 notification()->set_pinned(true); | |
494 | |
495 UpdateNotificationViews(); | |
496 EXPECT_FALSE(GetCloseButton()->visible()); | |
497 } | |
498 | |
499 #endif // defined(OS_CHROMEOS) | |
500 | |
501 } // namespace message_center | |
OLD | NEW |