diff options
Diffstat (limited to 'chromium/ui/message_center/views/notification_view_md_unittest.cc')
-rw-r--r-- | chromium/ui/message_center/views/notification_view_md_unittest.cc | 535 |
1 files changed, 535 insertions, 0 deletions
diff --git a/chromium/ui/message_center/views/notification_view_md_unittest.cc b/chromium/ui/message_center/views/notification_view_md_unittest.cc new file mode 100644 index 00000000000..f39b2d565fb --- /dev/null +++ b/chromium/ui/message_center/views/notification_view_md_unittest.cc @@ -0,0 +1,535 @@ +// 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/bounded_label.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_container_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_container_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) + +TEST_F(NotificationViewMDTest, ExpandLongMessage) { + notification()->set_type(NotificationType::NOTIFICATION_TYPE_SIMPLE); + // Test in a case where left_content_ does not have views other than + // message_view_. + // Without doing this, inappropriate fix such as + // message_view_->GetPreferredSize() returning gfx::Size() can pass. + notification()->set_title(base::string16()); + notification()->set_message(base::ASCIIToUTF16( + "consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore " + "et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud " + "exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.")); + + UpdateNotificationViews(); + EXPECT_FALSE(notification_view()->expanded_); + const int collapsed_height = notification_view()->message_view_->height(); + const int collapsed_preferred_height = + notification_view()->GetPreferredSize().height(); + EXPECT_LT(0, collapsed_height); + EXPECT_LT(0, collapsed_preferred_height); + + notification_view()->ToggleExpanded(); + EXPECT_TRUE(notification_view()->expanded_); + EXPECT_LT(collapsed_height, notification_view()->message_view_->height()); + EXPECT_LT(collapsed_preferred_height, + notification_view()->GetPreferredSize().height()); + + notification_view()->ToggleExpanded(); + EXPECT_FALSE(notification_view()->expanded_); + EXPECT_EQ(collapsed_height, notification_view()->message_view_->height()); + EXPECT_EQ(collapsed_preferred_height, + notification_view()->GetPreferredSize().height()); +} + +} // namespace message_center |