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