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..9baa68cd0bd87ba625a2281411219f8d92d63228 |
--- /dev/null |
+++ b/ui/message_center/views/notification_view_md_unittest.cc |
@@ -0,0 +1,501 @@ |
+// 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 ***************************************************************/ |
+ |
+// Used to fill bitmaps returned by CreateBitmap(). |
+static const SkColor kBitmapColor = SK_ColorGREEN; |
+ |
+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() const { return notification_.get(); } |
+ views::Widget* widget() const { |
+ DCHECK_EQ(widget_, notification_view()->GetWidget()); |
+ return widget_; |
+ } |
+ |
+ protected: |
+ 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_; |
+ views::Widget* widget_; |
+ |
+ 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. |
+ // In the actual code path, this is instantiated by |
+ // MessageViewFactory::Create. |
+ // TODO(tetsui): Confirm that NotificationViewMD options are same as one |
+ // created by the method. |
+ notification_view_.reset(new NotificationViewMD(this, *notification_)); |
+ notification_view_->SetIsNested(); |
+ notification_view_->set_owned_by_client(); |
+ |
+ views::Widget::InitParams init_params( |
+ CreateParams(views::Widget::InitParams::TYPE_POPUP)); |
+ 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_NE(nullptr, notification_view()->title_view_); |
+ EXPECT_NE(nullptr, notification_view()->message_view_); |
+ EXPECT_NE(nullptr, notification_view()->icon_view_); |
+ EXPECT_NE(nullptr, notification_view()->image_view_); |
+ |
+ notification()->set_image(gfx::Image()); |
+ notification()->set_title(base::string16()); |
+ notification()->set_message(base::string16()); |
+ notification()->set_icon(gfx::Image()); |
+ |
+ notification_view()->CreateOrUpdateViews(*notification()); |
+ |
+ EXPECT_EQ(nullptr, notification_view()->title_view_); |
+ EXPECT_EQ(nullptr, notification_view()->message_view_); |
+ EXPECT_EQ(nullptr, notification_view()->image_view_); |
+ // We still expect an icon view for all layouts. |
+ EXPECT_NE(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 |