Chromium Code Reviews| Index: ui/message_center/views/notification_view_md_unittest.cc |
| diff --git a/ui/message_center/views/notification_view_md_unittest.cc b/ui/message_center/views/notification_view_md_unittest.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..863038d4228ea57ef8d0a821a45905221cae6dc5 |
| --- /dev/null |
| +++ b/ui/message_center/views/notification_view_md_unittest.cc |
| @@ -0,0 +1,493 @@ |
| +// Copyright 2017 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "ui/message_center/views/notification_view_md.h" |
| + |
| +#include "base/strings/string_util.h" |
| +#include "base/strings/utf_string_conversions.h" |
| +#include "build/build_config.h" |
| +#include "testing/gtest/include/gtest/gtest.h" |
| +#include "ui/compositor/scoped_animation_duration_scale_mode.h" |
| +#include "ui/events/event_processor.h" |
| +#include "ui/events/event_utils.h" |
| +#include "ui/events/test/event_generator.h" |
| +#include "ui/gfx/canvas.h" |
| +#include "ui/message_center/message_center_style.h" |
| +#include "ui/message_center/views/message_center_controller.h" |
| +#include "ui/message_center/views/notification_header_view.h" |
| +#include "ui/message_center/views/proportional_image_view.h" |
| +#include "ui/views/controls/button/image_button.h" |
| +#include "ui/views/controls/button/label_button.h" |
| +#include "ui/views/test/views_test_base.h" |
| +#include "ui/views/test/widget_test.h" |
| + |
| +namespace message_center { |
| + |
| +/* Test fixture ***************************************************************/ |
| + |
| +class NotificationViewMDTest : public views::ViewsTestBase, |
| + public MessageCenterController { |
| + public: |
| + NotificationViewMDTest(); |
| + ~NotificationViewMDTest() override; |
| + |
| + // Overridden from ViewsTestBase: |
| + void SetUp() override; |
| + void TearDown() override; |
| + |
| + // Overridden from MessageCenterController: |
| + void ClickOnNotification(const std::string& notification_id) override; |
| + void RemoveNotification(const std::string& notification_id, |
| + bool by_user) override; |
| + std::unique_ptr<ui::MenuModel> CreateMenuModel( |
| + const NotifierId& notifier_id, |
| + const base::string16& display_source) override; |
| + bool HasClickedListener(const std::string& notification_id) override; |
| + void ClickOnNotificationButton(const std::string& notification_id, |
| + int button_index) override; |
| + void ClickOnSettingsButton(const std::string& notification_id) override; |
| + void UpdateNotificationSize(const std::string& notification_id) override; |
| + |
| + NotificationViewMD* notification_view() const { |
| + return notification_view_.get(); |
| + } |
| + Notification* notification() { return notification_.get(); } |
|
yoshiki
2017/07/07 02:48:30
nit: const
tetsui
2017/07/07 04:29:04
Done.
|
| + 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.
|
| + |
| + protected: |
| + // Used to fill bitmaps returned by CreateBitmap(). |
| + 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.
|
| + |
| + const gfx::Image CreateTestImage(int width, int height); |
| + const SkBitmap CreateBitmap(int width, int height); |
| + std::vector<ButtonInfo> CreateButtons(int number); |
| + |
| + // Paints |view| and returns the size that the original image (which must have |
| + // been created by CreateBitmap()) was scaled to. |
| + gfx::Size GetImagePaintSize(ProportionalImageView* view); |
| + |
| + void UpdateNotificationViews(); |
| + float GetNotificationSlideAmount() const; |
| + bool IsRemoved(const std::string& notification_id) const; |
| + void DispatchGesture(const ui::GestureEventDetails& details); |
| + void BeginScroll(); |
| + void EndScroll(); |
| + void ScrollBy(int dx); |
| + views::ImageButton* GetCloseButton(); |
| + |
| + private: |
| + std::set<std::string> removed_ids_; |
| + |
| + std::unique_ptr<RichNotificationData> data_; |
| + std::unique_ptr<Notification> notification_; |
| + std::unique_ptr<NotificationViewMD> notification_view_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(NotificationViewMDTest); |
| +}; |
| + |
| +NotificationViewMDTest::NotificationViewMDTest() = default; |
| +NotificationViewMDTest::~NotificationViewMDTest() = default; |
| + |
| +void NotificationViewMDTest::SetUp() { |
| + views::ViewsTestBase::SetUp(); |
| + // Create a dummy notification. |
| + data_.reset(new RichNotificationData()); |
| + notification_.reset(new Notification( |
| + NOTIFICATION_TYPE_BASE_FORMAT, std::string("notification id"), |
| + base::UTF8ToUTF16("title"), base::UTF8ToUTF16("message"), |
| + CreateTestImage(80, 80), base::UTF8ToUTF16("display source"), GURL(), |
| + NotifierId(NotifierId::APPLICATION, "extension_id"), *data_, nullptr)); |
| + notification_->set_small_image(CreateTestImage(16, 16)); |
| + notification_->set_image(CreateTestImage(320, 240)); |
| + |
| + // Then create a new NotificationView with that single notification. |
| + notification_view_.reset(new NotificationViewMD(this, *notification_)); |
| + notification_view_->SetIsNested(); // TODO(tetsui): ? |
|
yoshiki
2017/07/07 02:48:30
nit: could you add detailed information about TODO
|
| + notification_view_->set_owned_by_client(); |
| + |
| + views::Widget::InitParams init_params( |
| + CreateParams(views::Widget::InitParams::TYPE_POPUP)); |
| + views::Widget* widget = new views::Widget(); |
| + widget->Init(init_params); |
| + widget->SetContentsView(notification_view_.get()); |
| + widget->SetSize(notification_view_->GetPreferredSize()); |
| + widget->Show(); |
| +} |
| + |
| +void NotificationViewMDTest::TearDown() { |
| + widget()->Close(); |
| + notification_view_.reset(); |
| + views::ViewsTestBase::TearDown(); |
| +} |
| + |
| +void NotificationViewMDTest::ClickOnNotification( |
| + const std::string& notification_id) { |
| + // For this test, this method should not be invoked. |
| + NOTREACHED(); |
| +} |
| + |
| +void NotificationViewMDTest::RemoveNotification( |
| + const std::string& notification_id, |
| + bool by_user) { |
| + removed_ids_.insert(notification_id); |
| +} |
| + |
| +std::unique_ptr<ui::MenuModel> NotificationViewMDTest::CreateMenuModel( |
| + const NotifierId& notifier_id, |
| + const base::string16& display_source) { |
| + // For this test, this method should not be invoked. |
| + NOTREACHED(); |
| + return nullptr; |
| +} |
| + |
| +bool NotificationViewMDTest::HasClickedListener( |
| + const std::string& notification_id) { |
| + return true; |
| +} |
| + |
| +void NotificationViewMDTest::ClickOnNotificationButton( |
| + const std::string& notification_id, |
| + int button_index) { |
| + // For this test, this method should not be invoked. |
| + NOTREACHED(); |
| +} |
| + |
| +void NotificationViewMDTest::ClickOnSettingsButton( |
| + const std::string& notification_id) { |
| + // For this test, this method should not be invoked. |
| + NOTREACHED(); |
| +} |
| + |
| +void NotificationViewMDTest::UpdateNotificationSize( |
| + const std::string& notification_id) { |
| + widget()->SetSize(notification_view()->GetPreferredSize()); |
| +} |
| + |
| +const gfx::Image NotificationViewMDTest::CreateTestImage(int width, |
| + int height) { |
| + return gfx::Image::CreateFrom1xBitmap(CreateBitmap(width, height)); |
| +} |
| + |
| +const SkBitmap NotificationViewMDTest::CreateBitmap(int width, int height) { |
| + SkBitmap bitmap; |
| + bitmap.allocN32Pixels(width, height); |
| + bitmap.eraseColor(kBitmapColor); |
| + return bitmap; |
| +} |
| + |
| +std::vector<ButtonInfo> NotificationViewMDTest::CreateButtons(int number) { |
| + ButtonInfo info(base::ASCIIToUTF16("Test button.")); |
| + info.icon = CreateTestImage(4, 4); |
| + return std::vector<ButtonInfo>(number, info); |
| +} |
| + |
| +gfx::Size NotificationViewMDTest::GetImagePaintSize( |
| + ProportionalImageView* view) { |
| + CHECK(view); |
| + if (view->bounds().IsEmpty()) |
| + return gfx::Size(); |
| + |
| + gfx::Size canvas_size = view->bounds().size(); |
| + gfx::Canvas canvas(canvas_size, 1.0 /* image_scale */, true /* is_opaque */); |
| + static_assert(kBitmapColor != SK_ColorBLACK, |
| + "The bitmap color must match the background color"); |
| + canvas.DrawColor(SK_ColorBLACK); |
| + view->OnPaint(&canvas); |
| + |
| + SkBitmap bitmap = canvas.GetBitmap(); |
| + // Incrementally inset each edge at its midpoint to find the bounds of the |
| + // rect containing the image's color. This assumes that the image is |
| + // centered in the canvas. |
| + const int kHalfWidth = canvas_size.width() / 2; |
| + const int kHalfHeight = canvas_size.height() / 2; |
| + gfx::Rect rect(canvas_size); |
| + while (rect.width() > 0 && |
| + bitmap.getColor(rect.x(), kHalfHeight) != kBitmapColor) |
| + rect.Inset(1, 0, 0, 0); |
| + while (rect.height() > 0 && |
| + bitmap.getColor(kHalfWidth, rect.y()) != kBitmapColor) |
| + rect.Inset(0, 1, 0, 0); |
| + while (rect.width() > 0 && |
| + bitmap.getColor(rect.right() - 1, kHalfHeight) != kBitmapColor) |
| + rect.Inset(0, 0, 1, 0); |
| + while (rect.height() > 0 && |
| + bitmap.getColor(kHalfWidth, rect.bottom() - 1) != kBitmapColor) |
| + rect.Inset(0, 0, 0, 1); |
| + |
| + return rect.size(); |
| +} |
| + |
| +void NotificationViewMDTest::UpdateNotificationViews() { |
| + notification_view()->UpdateWithNotification(*notification()); |
| +} |
| + |
| +float NotificationViewMDTest::GetNotificationSlideAmount() const { |
| + return notification_view_->GetSlideOutLayer() |
| + ->transform() |
| + .To2dTranslation() |
| + .x(); |
| +} |
| + |
| +bool NotificationViewMDTest::IsRemoved( |
| + const std::string& notification_id) const { |
| + return (removed_ids_.find(notification_id) != removed_ids_.end()); |
| +} |
| + |
| +void NotificationViewMDTest::DispatchGesture( |
| + const ui::GestureEventDetails& details) { |
| + ui::test::EventGenerator generator( |
| + notification_view()->GetWidget()->GetNativeWindow()); |
| + ui::GestureEvent event(0, 0, 0, ui::EventTimeForNow(), details); |
| + generator.Dispatch(&event); |
| +} |
| + |
| +void NotificationViewMDTest::BeginScroll() { |
| + DispatchGesture(ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN)); |
| +} |
| + |
| +void NotificationViewMDTest::EndScroll() { |
| + DispatchGesture(ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_END)); |
| +} |
| + |
| +void NotificationViewMDTest::ScrollBy(int dx) { |
| + DispatchGesture(ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_UPDATE, dx, 0)); |
| +} |
| + |
| +views::ImageButton* NotificationViewMDTest::GetCloseButton() { |
| + return notification_view()->header_row_->close_button(); |
| +} |
| + |
| +/* Unit tests *****************************************************************/ |
| + |
| +// TODO(tetsui): Following tests are not yet ported from NotificationViewTest. |
| +// * CreateOrUpdateTestSettingsButton |
| +// * TestLineLimits |
| +// * TestImageSizing |
| +// * SettingsButtonTest |
| +// * ViewOrderingTest |
| +// * FormatContextMessageTest |
| + |
| +TEST_F(NotificationViewMDTest, CreateOrUpdateTest) { |
| + 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.
|
| + EXPECT_TRUE(nullptr != notification_view()->message_view_); |
| + EXPECT_TRUE(nullptr != notification_view()->icon_view_); |
| + EXPECT_TRUE(nullptr != notification_view()->image_view_); |
| + |
| + notification()->set_image(gfx::Image()); |
| + 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.
|
| + notification()->set_message(base::ASCIIToUTF16("")); |
| + notification()->set_icon(gfx::Image()); |
| + |
| + notification_view()->CreateOrUpdateViews(*notification()); |
| + |
| + EXPECT_TRUE(nullptr == notification_view()->title_view_); |
| + EXPECT_TRUE(nullptr == notification_view()->message_view_); |
| + EXPECT_TRUE(nullptr == notification_view()->image_view_); |
| + // We still expect an icon view for all layouts. |
| + EXPECT_TRUE(nullptr != notification_view()->icon_view_); |
| +} |
| + |
| +TEST_F(NotificationViewMDTest, TestIconSizing) { |
| + // TODO(tetsui): Remove duplicated integer literal in CreateOrUpdateIconView. |
| + const int kNotificationIconSize = 30; |
| + |
| + notification()->set_type(NOTIFICATION_TYPE_SIMPLE); |
| + ProportionalImageView* view = notification_view()->icon_view_; |
| + |
| + // Icons smaller than the maximum size should remain unscaled. |
| + notification()->set_icon( |
| + CreateTestImage(kNotificationIconSize / 2, kNotificationIconSize / 4)); |
| + UpdateNotificationViews(); |
| + EXPECT_EQ(gfx::Size(kNotificationIconSize / 2, kNotificationIconSize / 4) |
| + .ToString(), |
| + GetImagePaintSize(view).ToString()); |
| + |
| + // Icons of exactly the intended icon size should remain unscaled. |
| + notification()->set_icon( |
| + CreateTestImage(kNotificationIconSize, kNotificationIconSize)); |
| + UpdateNotificationViews(); |
| + EXPECT_EQ(gfx::Size(kNotificationIconSize, kNotificationIconSize).ToString(), |
| + GetImagePaintSize(view).ToString()); |
| + |
| + // Icons over the maximum size should be scaled down, maintaining proportions. |
| + notification()->set_icon( |
| + CreateTestImage(2 * kNotificationIconSize, 2 * kNotificationIconSize)); |
| + UpdateNotificationViews(); |
| + EXPECT_EQ(gfx::Size(kNotificationIconSize, kNotificationIconSize).ToString(), |
| + GetImagePaintSize(view).ToString()); |
| + |
| + notification()->set_icon( |
| + CreateTestImage(4 * kNotificationIconSize, 2 * kNotificationIconSize)); |
| + UpdateNotificationViews(); |
| + EXPECT_EQ( |
| + gfx::Size(kNotificationIconSize, kNotificationIconSize / 2).ToString(), |
| + GetImagePaintSize(view).ToString()); |
| +} |
| + |
| +TEST_F(NotificationViewMDTest, UpdateButtonsStateTest) { |
| + notification()->set_buttons(CreateButtons(2)); |
| + notification_view()->CreateOrUpdateViews(*notification()); |
| + widget()->Show(); |
| + |
| + // Action buttons are hidden by collapsed state. |
| + if (!notification_view()->expanded_) |
| + notification_view()->ToggleExpanded(); |
| + EXPECT_TRUE(notification_view()->actions_row_->visible()); |
| + |
| + EXPECT_EQ(views::CustomButton::STATE_NORMAL, |
| + notification_view()->action_buttons_[0]->state()); |
| + |
| + // Now construct a mouse move event 1 pixel inside the boundary of the action |
| + // button. |
| + gfx::Point cursor_location(1, 1); |
| + views::View::ConvertPointToWidget(notification_view()->action_buttons_[0], |
| + &cursor_location); |
| + ui::MouseEvent move(ui::ET_MOUSE_MOVED, cursor_location, cursor_location, |
| + ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE); |
| + widget()->OnMouseEvent(&move); |
| + |
| + EXPECT_EQ(views::CustomButton::STATE_HOVERED, |
| + notification_view()->action_buttons_[0]->state()); |
| + |
| + notification_view()->CreateOrUpdateViews(*notification()); |
| + |
| + EXPECT_EQ(views::CustomButton::STATE_HOVERED, |
| + notification_view()->action_buttons_[0]->state()); |
| + |
| + // Now construct a mouse move event 1 pixel outside the boundary of the |
| + // widget. |
| + cursor_location = gfx::Point(-1, -1); |
| + move = ui::MouseEvent(ui::ET_MOUSE_MOVED, cursor_location, cursor_location, |
| + ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE); |
| + widget()->OnMouseEvent(&move); |
| + |
| + EXPECT_EQ(views::CustomButton::STATE_NORMAL, |
| + notification_view()->action_buttons_[0]->state()); |
| +} |
| + |
| +TEST_F(NotificationViewMDTest, UpdateButtonCountTest) { |
| + notification()->set_buttons(CreateButtons(2)); |
| + notification_view()->UpdateWithNotification(*notification()); |
| + widget()->Show(); |
| + |
| + // Action buttons are hidden by collapsed state. |
| + if (!notification_view()->expanded_) |
| + notification_view()->ToggleExpanded(); |
| + EXPECT_TRUE(notification_view()->actions_row_->visible()); |
| + |
| + EXPECT_EQ(views::CustomButton::STATE_NORMAL, |
| + notification_view()->action_buttons_[0]->state()); |
| + EXPECT_EQ(views::CustomButton::STATE_NORMAL, |
| + notification_view()->action_buttons_[1]->state()); |
| + |
| + // Now construct a mouse move event 1 pixel inside the boundary of the action |
| + // button. |
| + gfx::Point cursor_location(1, 1); |
| + views::View::ConvertPointToScreen(notification_view()->action_buttons_[0], |
| + &cursor_location); |
| + ui::MouseEvent move(ui::ET_MOUSE_MOVED, cursor_location, cursor_location, |
| + ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE); |
| + ui::EventDispatchDetails details = |
| + views::test::WidgetTest::GetEventSink(widget())->OnEventFromSource(&move); |
| + EXPECT_FALSE(details.dispatcher_destroyed); |
| + |
| + EXPECT_EQ(views::CustomButton::STATE_HOVERED, |
| + notification_view()->action_buttons_[0]->state()); |
| + EXPECT_EQ(views::CustomButton::STATE_NORMAL, |
| + notification_view()->action_buttons_[1]->state()); |
| + |
| + notification()->set_buttons(CreateButtons(1)); |
| + notification_view()->UpdateWithNotification(*notification()); |
| + |
| + EXPECT_EQ(views::CustomButton::STATE_HOVERED, |
| + notification_view()->action_buttons_[0]->state()); |
| + EXPECT_EQ(1u, notification_view()->action_buttons_.size()); |
| + |
| + // Now construct a mouse move event 1 pixel outside the boundary of the |
| + // widget. |
| + cursor_location = gfx::Point(-1, -1); |
| + move = ui::MouseEvent(ui::ET_MOUSE_MOVED, cursor_location, cursor_location, |
| + ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE); |
| + widget()->OnMouseEvent(&move); |
| + |
| + EXPECT_EQ(views::CustomButton::STATE_NORMAL, |
| + notification_view()->action_buttons_[0]->state()); |
| +} |
| + |
| +TEST_F(NotificationViewMDTest, SlideOut) { |
| + ui::ScopedAnimationDurationScaleMode zero_duration_scope( |
| + ui::ScopedAnimationDurationScaleMode::ZERO_DURATION); |
| + |
| + UpdateNotificationViews(); |
| + std::string notification_id = notification()->id(); |
| + |
| + BeginScroll(); |
| + ScrollBy(-10); |
| + EXPECT_FALSE(IsRemoved(notification_id)); |
| + EXPECT_EQ(-10.f, GetNotificationSlideAmount()); |
| + EndScroll(); |
| + EXPECT_FALSE(IsRemoved(notification_id)); |
| + EXPECT_EQ(0.f, GetNotificationSlideAmount()); |
| + |
| + BeginScroll(); |
| + ScrollBy(-200); |
| + EXPECT_FALSE(IsRemoved(notification_id)); |
| + EXPECT_EQ(-200.f, GetNotificationSlideAmount()); |
| + EndScroll(); |
| + EXPECT_TRUE(IsRemoved(notification_id)); |
| +} |
| + |
| +TEST_F(NotificationViewMDTest, SlideOutNested) { |
| + ui::ScopedAnimationDurationScaleMode zero_duration_scope( |
| + ui::ScopedAnimationDurationScaleMode::ZERO_DURATION); |
| + |
| + UpdateNotificationViews(); |
| + notification_view()->SetIsNested(); |
| + std::string notification_id = notification()->id(); |
| + |
| + BeginScroll(); |
| + ScrollBy(-10); |
| + EXPECT_FALSE(IsRemoved(notification_id)); |
| + EXPECT_EQ(-10.f, GetNotificationSlideAmount()); |
| + EndScroll(); |
| + EXPECT_FALSE(IsRemoved(notification_id)); |
| + EXPECT_EQ(0.f, GetNotificationSlideAmount()); |
| + |
| + BeginScroll(); |
| + ScrollBy(-200); |
| + EXPECT_FALSE(IsRemoved(notification_id)); |
| + EXPECT_EQ(-200.f, GetNotificationSlideAmount()); |
| + EndScroll(); |
| + EXPECT_TRUE(IsRemoved(notification_id)); |
| +} |
| + |
| +// Pinning notification is ChromeOS only feature. |
| +#if defined(OS_CHROMEOS) |
| + |
| +TEST_F(NotificationViewMDTest, SlideOutPinned) { |
| + ui::ScopedAnimationDurationScaleMode zero_duration_scope( |
| + ui::ScopedAnimationDurationScaleMode::ZERO_DURATION); |
| + |
| + notification()->set_pinned(true); |
| + UpdateNotificationViews(); |
| + std::string notification_id = notification()->id(); |
| + |
| + BeginScroll(); |
| + ScrollBy(-200); |
| + EXPECT_FALSE(IsRemoved(notification_id)); |
| + EXPECT_LT(-200.f, GetNotificationSlideAmount()); |
| + EndScroll(); |
| + EXPECT_FALSE(IsRemoved(notification_id)); |
| +} |
| + |
| +TEST_F(NotificationViewMDTest, Pinned) { |
| + notification()->set_pinned(true); |
| + |
| + UpdateNotificationViews(); |
| + EXPECT_FALSE(GetCloseButton()->visible()); |
| +} |
| + |
| +#endif // defined(OS_CHROMEOS) |
| + |
| +} // namespace message_center |