Commit c650008c authored by Alex Newcomer's avatar Alex Newcomer Committed by Commit Bot

Cros: Make notifications in context menus swipe/click-able

Add a NotificationItemView::NotificationItemViewDelegate, which:
 - Activates notifications.
 - Acts as the SlideOutController::Delegate.
 - Allows for easier testing of NotificationMenuView.

Make NotificationMenuController a NotificationItemViewDelegate.

Pass NotificationMenuController AppMenuModelAdapter:
 - Allows NotificationMenuController to close the menu.

Also added tests.

Bug: 843761
Change-Id: I20474838a0fb5360196a3d0585b7e84620b86e9f
Reviewed-on: https://chromium-review.googlesource.com/1120842
Commit-Queue: Alex Newcomer <newcomer@chromium.org>
Reviewed-by: default avatarJames Cook <jamescook@chromium.org>
Cr-Commit-Position: refs/heads/master@{#571993}
parent 4ddbb634
......@@ -54,8 +54,7 @@ void AppMenuModelAdapter::Run(const gfx::Rect& menu_anchor_rect,
root_ = CreateMenu();
if (features::IsNotificationIndicatorEnabled()) {
notification_menu_controller_ =
std::make_unique<NotificationMenuController>(app_id_, root_,
model_.get());
std::make_unique<NotificationMenuController>(app_id_, root_, this);
}
menu_runner_ = std::make_unique<views::MenuRunner>(root_, run_types);
menu_runner_->RunMenuAt(menu_owner_->GetWidget(), nullptr /* MenuButton */,
......
......@@ -54,14 +54,13 @@ class APP_MENU_EXPORT AppMenuModelAdapter : public views::MenuModelAdapter {
void ExecuteCommand(int id, int mouse_event_flags) override;
void OnMenuClosed(views::MenuItemView* menu) override;
ui::SimpleMenuModel* model() { return model_.get(); }
protected:
const std::string& app_id() const { return app_id_; }
base::TimeTicks menu_open_time() const { return menu_open_time_; }
ui::MenuSourceType source_type() const { return source_type_; }
ui::SimpleMenuModel* model() { return model_.get(); }
const ui::SimpleMenuModel* model() const { return model_.get(); }
// Helper method to record ExecuteCommand() histograms.
void RecordExecuteCommandHistogram(int command_id);
......
......@@ -9,6 +9,7 @@
#include "ui/gfx/image/image.h"
#include "ui/gfx/text_elider.h"
#include "ui/message_center/views/proportional_image_view.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/menu/menu_config.h"
......@@ -38,14 +39,31 @@ constexpr SkColor kNotificationTitleTextColor =
} // namespace
NotificationItemView::NotificationItemView(const base::string16& title,
const base::string16& message,
const gfx::Image& icon,
const std::string notification_id)
: title_(title), message_(message), notification_id_(notification_id) {
NotificationItemView::NotificationItemView(
NotificationItemView::Delegate* delegate,
message_center::SlideOutController::Delegate* slide_out_controller_delegate,
const base::string16& title,
const base::string16& message,
const gfx::Image& icon,
const std::string& notification_id)
: delegate_(delegate),
slide_out_controller_(
std::make_unique<message_center::SlideOutController>(
this,
slide_out_controller_delegate)),
title_(title),
message_(message),
notification_id_(notification_id) {
DCHECK(delegate_);
DCHECK(slide_out_controller_delegate);
// Paint to a new layer so |slide_out_controller_| can control the opacity.
SetPaintToLayer();
layer()->SetFillsBoundsOpaquely(true);
SetBorder(views::CreateEmptyBorder(
gfx::Insets(kNotificationVerticalPadding, kNotificationHorizontalPadding,
kNotificationVerticalPadding, kIconHorizontalPadding)));
SetBackground(views::CreateSolidBackground(SK_ColorWHITE));
text_container_ = new views::View();
text_container_->SetLayoutManager(std::make_unique<views::BoxLayout>(
......@@ -100,4 +118,35 @@ void NotificationItemView::Layout() {
kProportionalIconViewSize.height());
}
bool NotificationItemView::OnMousePressed(const ui::MouseEvent& event) {
return true;
}
bool NotificationItemView::OnMouseDragged(const ui::MouseEvent& event) {
return true;
}
void NotificationItemView::OnMouseReleased(const ui::MouseEvent& event) {
gfx::Point location(event.location());
views::View::ConvertPointToScreen(this, &location);
if (!event.IsOnlyLeftMouseButton() ||
!GetBoundsInScreen().Contains(location)) {
return;
}
delegate_->ActivateNotificationAndClose(notification_id_);
}
void NotificationItemView::OnGestureEvent(ui::GestureEvent* event) {
// Drag gestures are handled by |slide_out_controller_|.
switch (event->type()) {
case ui::ET_GESTURE_TAP:
event->SetHandled();
delegate_->ActivateNotificationAndClose(notification_id_);
return;
default:
return;
}
}
} // namespace ash
......@@ -5,10 +5,12 @@
#ifndef ASH_APP_MENU_NOTIFICATION_ITEM_VIEW_H_
#define ASH_APP_MENU_NOTIFICATION_ITEM_VIEW_H_
#include <memory>
#include <string>
#include "ash/app_menu/app_menu_export.h"
#include "base/strings/string16.h"
#include "ui/message_center/views/slide_out_controller.h"
#include "ui/views/view.h"
namespace gfx {
......@@ -25,16 +27,32 @@ namespace ash {
// The view which contains the details of a notification.
class APP_MENU_EXPORT NotificationItemView : public views::View {
public:
NotificationItemView(const base::string16& title,
// Used to activate NotificationItemView.
class Delegate {
public:
// Activates the notification corresponding with |notification_id| and
// closes the menu.
virtual void ActivateNotificationAndClose(
const std::string& notification_id) = 0;
};
NotificationItemView(NotificationItemView::Delegate* delegate,
message_center::SlideOutController::Delegate*
slide_out_controller_delegate,
const base::string16& title,
const base::string16& message,
const gfx::Image& icon,
const std::string notification_id);
const std::string& notification_id);
~NotificationItemView() override;
// views::View overrides:
gfx::Size CalculatePreferredSize() const override;
void Layout() override;
bool OnMousePressed(const ui::MouseEvent& event) override;
bool OnMouseDragged(const ui::MouseEvent& event) override;
void OnMouseReleased(const ui::MouseEvent& event) override;
void OnGestureEvent(ui::GestureEvent* event) override;
const std::string& notification_id() const { return notification_id_; }
const base::string16& title() const { return title_; }
......@@ -47,6 +65,12 @@ class APP_MENU_EXPORT NotificationItemView : public views::View {
// Holds the notification's icon. Owned by the views hierarchy.
message_center::ProportionalImageView* proportional_icon_view_ = nullptr;
// Owned by AppMenuModelAdapter. Used to activate notifications.
Delegate* const delegate_;
// Controls the sideways gesture drag behavior.
std::unique_ptr<message_center::SlideOutController> slide_out_controller_;
// Notification properties.
const base::string16 title_;
const base::string16 message_;
......
......@@ -4,6 +4,7 @@
#include "ash/app_menu/notification_menu_controller.h"
#include "ash/app_menu/app_menu_model_adapter.h"
#include "ash/app_menu/notification_menu_view.h"
#include "ash/public/cpp/app_menu_constants.h"
#include "ui/views/controls/menu/menu_item_view.h"
......@@ -14,11 +15,12 @@ namespace ash {
NotificationMenuController::NotificationMenuController(
const std::string& app_id,
views::MenuItemView* root_menu,
ui::SimpleMenuModel* model)
AppMenuModelAdapter* app_menu_model_adapter)
: app_id_(app_id),
root_menu_(root_menu),
model_(model),
app_menu_model_adapter_(app_menu_model_adapter),
message_center_observer_(this) {
DCHECK(app_menu_model_adapter_);
message_center_observer_.Add(message_center::MessageCenter::Get());
InitializeNotificationMenuView();
}
......@@ -60,9 +62,9 @@ void NotificationMenuController::OnNotificationRemoved(
// |root_menu_|, and remove the entry from the model.
const views::View* container = notification_menu_view_->parent();
root_menu_->RemoveMenuItemAt(root_menu_->GetSubmenu()->GetIndexOf(container));
// TODO(newcomer): move NOTIFICATION_CONTAINER and other CommandId enums to a
// shared constant file in ash/public/cpp.
model_->RemoveItemAt(model_->GetIndexOfCommandId(NOTIFICATION_CONTAINER));
app_menu_model_adapter_->model()->RemoveItemAt(
app_menu_model_adapter_->model()->GetIndexOfCommandId(
NOTIFICATION_CONTAINER));
notification_menu_view_ = nullptr;
// Notify the root MenuItemView so it knows to resize and re-calculate the
......@@ -70,6 +72,28 @@ void NotificationMenuController::OnNotificationRemoved(
root_menu_->ChildrenChanged();
}
ui::Layer* NotificationMenuController::GetSlideOutLayer() {
return notification_menu_view_ ? notification_menu_view_->GetSlideOutLayer()
: nullptr;
}
void NotificationMenuController::OnSlideChanged() {}
void NotificationMenuController::OnSlideOut() {
// Results in |this| being deleted if there are no more notifications to show.
// Only the displayed NotificationItemView can call OnSlideOut.
message_center::MessageCenter::Get()->RemoveNotification(
notification_menu_view_->GetDisplayedNotificationID(), true);
}
void NotificationMenuController::ActivateNotificationAndClose(
const std::string& notification_id) {
message_center::MessageCenter::Get()->ClickOnNotification(notification_id);
// Results in |this| being deleted.
app_menu_model_adapter_->Cancel();
}
void NotificationMenuController::InitializeNotificationMenuView() {
DCHECK(!notification_menu_view_);
......@@ -80,11 +104,12 @@ void NotificationMenuController::InitializeNotificationMenuView() {
return;
}
model_->AddItem(NOTIFICATION_CONTAINER, base::string16());
app_menu_model_adapter_->model()->AddItem(NOTIFICATION_CONTAINER,
base::string16());
// Add the container MenuItemView to |root_menu_|.
views::MenuItemView* container = root_menu_->AppendMenuItem(
NOTIFICATION_CONTAINER, base::string16(), views::MenuItemView::NORMAL);
notification_menu_view_ = new NotificationMenuView(app_id_);
notification_menu_view_ = new NotificationMenuView(this, this, app_id_);
container->AddChildView(notification_menu_view_);
for (auto* notification :
......
......@@ -6,13 +6,11 @@
#define ASH_APP_MENU_NOTIFICATION_MENU_CONTROLLER_H_
#include "ash/app_menu/app_menu_export.h"
#include "ash/app_menu/notification_item_view.h"
#include "base/scoped_observer.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/message_center_observer.h"
namespace ui {
class SimpleMenuModel;
}
#include "ui/message_center/views/slide_out_controller.h"
namespace views {
class MenuItemView;
......@@ -20,17 +18,20 @@ class MenuItemView;
namespace ash {
class AppMenuModelAdapter;
class NotificationMenuView;
// Handles adding/removing NotificationMenuView from the root MenuItemView,
// adding the container model entry, and updating the NotificationMenuView
// as notifications come and go.
class APP_MENU_EXPORT NotificationMenuController
: public message_center::MessageCenterObserver {
: public message_center::MessageCenterObserver,
public message_center::SlideOutController::Delegate,
public NotificationItemView::Delegate {
public:
NotificationMenuController(const std::string& app_id,
views::MenuItemView* root_menu,
ui::SimpleMenuModel* model);
AppMenuModelAdapter* app_menu_model_adapter);
~NotificationMenuController() override;
......@@ -39,6 +40,15 @@ class APP_MENU_EXPORT NotificationMenuController
void OnNotificationRemoved(const std::string& notification_id,
bool by_user) override;
// message_center::SlideOutController::Delegate overrides:
ui::Layer* GetSlideOutLayer() override;
void OnSlideChanged() override;
void OnSlideOut() override;
// NotificationItemView::Delegate overrides:
void ActivateNotificationAndClose(
const std::string& notification_id) override;
private:
// Adds a container MenuItemView to |root_menu_|, adds NOTIFICATION_CONTAINER
// to |model_|, creates and initializes NotificationMenuView, and adds
......@@ -51,8 +61,8 @@ class APP_MENU_EXPORT NotificationMenuController
// The top level MenuItemView. Owned by |AppMenuModelAdapter::menu_runner_|.
views::MenuItemView* const root_menu_;
// Owned by AppMenuModelAdapter.
ui::SimpleMenuModel* const model_;
// Manages showing the menu. Owned by the view requesting a menu.
AppMenuModelAdapter* const app_menu_model_adapter_;
// The view which shows all active notifications for |app_id_|. Owned by the
// views hierarchy.
......
......@@ -13,7 +13,6 @@
#include "ui/views/controls/menu/submenu_view.h"
namespace ash {
namespace test {
namespace {
constexpr char kTestAppId[] = "test-app-id";
......@@ -33,6 +32,23 @@ void BuildAndSendNotification(const std::string& app_id,
std::move(notification));
}
class TestAppMenuModelAdapter : public AppMenuModelAdapter {
public:
TestAppMenuModelAdapter(const std::string& app_id,
std::unique_ptr<ui::SimpleMenuModel> model)
: AppMenuModelAdapter(app_id,
std::move(model),
nullptr,
ui::MENU_SOURCE_TYPE_LAST,
base::OnceClosure()) {}
private:
// AppMenuModelAdapter overrides:
void RecordHistogramOnMenuClosed() override {}
DISALLOW_COPY_AND_ASSIGN(TestAppMenuModelAdapter);
};
} // namespace
class NotificationMenuControllerTest : public AshTestBase {
......@@ -50,20 +66,25 @@ class NotificationMenuControllerTest : public AshTestBase {
}
void BuildMenu() {
model_ = std::make_unique<ui::SimpleMenuModel>(
nullptr /*ui::SimpleMenuModel::Delegate not required*/);
model_->AddItem(0, base::ASCIIToUTF16("item 1"));
model_->AddItem(1, base::ASCIIToUTF16("item 2"));
delegate_ = std::make_unique<views::MenuModelAdapter>(model_.get());
root_menu_item_view_ = new views::MenuItemView(delegate_.get());
test_app_menu_model_adapter_ = std::make_unique<TestAppMenuModelAdapter>(
kTestAppId,
std::make_unique<ui::SimpleMenuModel>(
nullptr /*ui::SimpleMenuModel::Delegate not required*/));
test_app_menu_model_adapter_->model()->AddItem(
0, base::ASCIIToUTF16("item 0"));
test_app_menu_model_adapter_->model()->AddItem(
1, base::ASCIIToUTF16("item 1"));
root_menu_item_view_ =
new views::MenuItemView(test_app_menu_model_adapter_.get());
host_view_ = std::make_unique<views::View>();
host_view_->AddChildView(root_menu_item_view_);
delegate_->BuildMenu(root_menu_item_view_);
test_app_menu_model_adapter_->BuildMenu(root_menu_item_view_);
notification_menu_controller_ =
std::make_unique<NotificationMenuController>(
kTestAppId, root_menu_item_view_, model_.get());
kTestAppId, root_menu_item_view_,
test_app_menu_model_adapter_.get());
}
views::MenuItemView* root_menu_item_view() { return root_menu_item_view_; }
......@@ -74,8 +95,7 @@ class NotificationMenuControllerTest : public AshTestBase {
// Allows the dtor to access the restricted views::MenuItemView dtor.
std::unique_ptr<views::View> host_view_;
std::unique_ptr<NotificationMenuController> notification_menu_controller_;
std::unique_ptr<ui::SimpleMenuModel> model_;
std::unique_ptr<views::MenuModelAdapter> delegate_;
std::unique_ptr<TestAppMenuModelAdapter> test_app_menu_model_adapter_;
DISALLOW_COPY_AND_ASSIGN(NotificationMenuControllerTest);
};
......@@ -152,5 +172,4 @@ TEST_F(NotificationMenuControllerTest, MultipleNotifications) {
EXPECT_EQ(2, root_menu_item_view()->GetSubmenu()->child_count());
}
} // namespace test
} // namespace ash
......@@ -15,10 +15,19 @@
namespace ash {
NotificationMenuView::NotificationMenuView(const std::string& app_id)
: app_id_(app_id) {
NotificationMenuView::NotificationMenuView(
NotificationItemView::Delegate* notification_item_view_delegate,
message_center::SlideOutController::Delegate* slide_out_controller_delegate,
const std::string& app_id)
: app_id_(app_id),
notification_item_view_delegate_(notification_item_view_delegate),
slide_out_controller_delegate_(slide_out_controller_delegate) {
DCHECK(notification_item_view_delegate_);
DCHECK(slide_out_controller_delegate_);
DCHECK(!app_id_.empty())
<< "Only context menus for applications can show notifications.";
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical));
......@@ -46,6 +55,7 @@ void NotificationMenuView::AddNotificationItemView(
RemoveChildView(notification_item_views_.front().get());
notification_item_views_.push_front(std::make_unique<NotificationItemView>(
notification_item_view_delegate_, slide_out_controller_delegate_,
notification.title(), notification.message(), notification.icon(),
notification.id()));
notification_item_views_.front()->set_owned_by_client();
......@@ -74,4 +84,15 @@ void NotificationMenuView::RemoveNotificationItemView(
AddChildView(notification_item_views_.front().get());
}
ui::Layer* NotificationMenuView::GetSlideOutLayer() {
if (notification_item_views_.empty())
return nullptr;
return notification_item_views_.front()->layer();
}
const std::string& NotificationMenuView::GetDisplayedNotificationID() {
DCHECK(!notification_item_views_.empty());
return notification_item_views_.front()->notification_id();
}
} // namespace ash
......@@ -9,6 +9,8 @@
#include <string>
#include "ash/app_menu/app_menu_export.h"
#include "ash/app_menu/notification_item_view.h"
#include "ui/message_center/views/slide_out_controller.h"
#include "ui/views/view.h"
namespace message_center {
......@@ -18,13 +20,16 @@ class Notification;
namespace ash {
class NotificationMenuHeaderView;
class NotificationItemView;
// A view inserted into a container MenuItemView which shows a
// NotificationItemView and a NotificationMenuHeaderView.
class APP_MENU_EXPORT NotificationMenuView : public views::View {
public:
explicit NotificationMenuView(const std::string& app_id);
explicit NotificationMenuView(
NotificationItemView::Delegate* notification_item_view_delegate,
message_center::SlideOutController::Delegate*
slide_out_controller_delegate,
const std::string& app_id);
~NotificationMenuView() override;
// Whether |notifications_for_this_app_| is empty.
......@@ -40,6 +45,12 @@ class APP_MENU_EXPORT NotificationMenuView : public views::View {
// next one if available.
void RemoveNotificationItemView(const std::string& notification_id);
// Gets the slide out layer, used to move the displayed NotificationItemView.
ui::Layer* GetSlideOutLayer();
// Gets the notification id of the displayed NotificationItemView.
const std::string& GetDisplayedNotificationID();
// views::View overrides:
gfx::Size CalculatePreferredSize() const override;
......@@ -49,6 +60,13 @@ class APP_MENU_EXPORT NotificationMenuView : public views::View {
// Identifies the app for this menu.
const std::string app_id_;
// Owned by AppMenuModelAdapter.
NotificationItemView::Delegate* const notification_item_view_delegate_;
// Owned by AppMenuModelAdapter.
message_center::SlideOutController::Delegate* const
slide_out_controller_delegate_;
// The deque of NotificationItemViews. The front item in the deque is the view
// which is shown.
std::deque<std::unique_ptr<NotificationItemView>> notification_item_views_;
......
......@@ -9,19 +9,57 @@
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/transform.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/views/controls/label.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget_delegate.h"
namespace ash {
namespace test {
namespace {
// The app id used in tests.
constexpr char kTestAppId[] = "test-app-id";
class MockNotificationMenuController
: public message_center::SlideOutController::Delegate,
public NotificationItemView::Delegate {
public:
MockNotificationMenuController() = default;
virtual ~MockNotificationMenuController() = default;
void ActivateNotificationAndClose(
const std::string& notification_id) override {
activation_count_++;
}
ui::Layer* GetSlideOutLayer() override {
return notification_menu_view_->GetSlideOutLayer();
}
void OnSlideChanged() override {}
void OnSlideOut() override { slide_out_count_++; }
void set_notification_menu_view(
NotificationMenuView* notification_menu_view) {
notification_menu_view_ = notification_menu_view;
}
int slide_out_count_ = 0;
int activation_count_ = 0;
// Owned by NotificationMenuViewTest.
NotificationMenuView* notification_menu_view_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(MockNotificationMenuController);
};
} // namespace
class NotificationMenuViewTest : public views::ViewsTestBase {
......@@ -32,10 +70,45 @@ class NotificationMenuViewTest : public views::ViewsTestBase {
// views::ViewsTestBase:
void SetUp() override {
views::ViewsTestBase::SetUp();
notification_menu_view_ =
std::make_unique<NotificationMenuView>(kTestAppId);
zero_duration_scope_ =
std::make_unique<ui::ScopedAnimationDurationScaleMode>(
ui::ScopedAnimationDurationScaleMode::ZERO_DURATION);
mock_notification_menu_controller_ =
std::make_unique<MockNotificationMenuController>();
notification_menu_view_ = std::make_unique<NotificationMenuView>(
mock_notification_menu_controller_.get(),
mock_notification_menu_controller_.get(), kTestAppId);
notification_menu_view_->set_owned_by_client();
// Set the NotificationMenuView so |mock_notification_menu_controller_|
// can get the slide out layer. In production NotificationMenuController is
// the NotificationItemViewDelegate, and it gets a reference to
// NotificationMenuView when it is created.
mock_notification_menu_controller_->set_notification_menu_view(
notification_menu_view());
test_api_ = std::make_unique<NotificationMenuViewTestAPI>(
notification_menu_view_.get());
widget_ = std::make_unique<views::Widget>();
views::Widget::InitParams init_params(
CreateParams(views::Widget::InitParams::TYPE_POPUP));
init_params.ownership =
views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
init_params.activatable = views::Widget::InitParams::ACTIVATABLE_YES;
widget_->Init(init_params);
widget_->SetContentsView(notification_menu_view_.get());
widget_->SetSize(notification_menu_view_->GetPreferredSize());
widget_->Show();
widget_->Activate();
}
void TearDown() override {
widget_->Close();
views::ViewsTestBase::TearDown();
}
message_center::Notification AddNotification(
......@@ -50,6 +123,7 @@ class NotificationMenuViewTest : public views::ViewsTestBase {
notifier_id, message_center::RichNotificationData(),
nullptr /* delegate */);
notification_menu_view_->AddNotificationItemView(notification);
notification_menu_view_->Layout();
return notification;
}
......@@ -64,15 +138,54 @@ class NotificationMenuViewTest : public views::ViewsTestBase {
EXPECT_EQ(item_view->message(), notification.message());
}
void BeginScroll() {
DispatchGesture(ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN));
}
void EndScroll() {
DispatchGesture(ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_END));
}
void ScrollBy(int dx) {
DispatchGesture(
ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_UPDATE, dx, 0));
}
void DispatchGesture(const ui::GestureEventDetails& details) {
ui::test::EventGenerator generator(
notification_menu_view_->GetWidget()->GetNativeWindow());
ui::GestureEvent event(
0,
test_api()->GetDisplayedNotificationItemView()->GetBoundsInScreen().y(),
0, ui::EventTimeForNow(), details);
generator.Dispatch(&event);
}
float GetSlideAmount() const {
return notification_menu_view_->GetSlideOutLayer()
->transform()
.To2dTranslation()
.x();
}
NotificationMenuView* notification_menu_view() {
return notification_menu_view_.get();
}
NotificationMenuViewTestAPI* test_api() { return test_api_.get(); }
MockNotificationMenuController* mock_notification_menu_controller() {
return mock_notification_menu_controller_.get();
}
private:
std::unique_ptr<MockNotificationMenuController>
mock_notification_menu_controller_;
std::unique_ptr<NotificationMenuView> notification_menu_view_;
std::unique_ptr<NotificationMenuViewTestAPI> test_api_;
std::unique_ptr<views::Widget> widget_;
std::unique_ptr<ui::ScopedAnimationDurationScaleMode> zero_duration_scope_;
DISALLOW_COPY_AND_ASSIGN(NotificationMenuViewTest);
};
......@@ -132,5 +245,92 @@ TEST_F(NotificationMenuViewTest, RemoveOlderNotification) {
CheckDisplayedNotification(notification_1);
}
} // namespace test
// Tests that the displayed NotificationItemView is only dismissed when dragged
// beyond the threshold.
TEST_F(NotificationMenuViewTest, SlideOut) {
AddNotification("notification_id", base::ASCIIToUTF16("title"),
base::ASCIIToUTF16("message"));
EXPECT_EQ(0, mock_notification_menu_controller()->slide_out_count_);
BeginScroll();
// Scroll by a small amount, the notification should move but not slide out.
ScrollBy(-10);
EXPECT_EQ(0, mock_notification_menu_controller()->slide_out_count_);
EXPECT_EQ(-10.f, GetSlideAmount());
// End the scroll gesture, the notifications should return to its resting
// place.
EndScroll();
EXPECT_EQ(0, mock_notification_menu_controller()->slide_out_count_);
EXPECT_EQ(0.f, GetSlideAmount());
BeginScroll();
// Scroll beyond the threshold but do not release the gesture scroll.
ScrollBy(-200);
EXPECT_EQ(-200.f, GetSlideAmount());
// Release the gesture, the notification should slide out.
EndScroll();
EXPECT_EQ(1, mock_notification_menu_controller()->slide_out_count_);
EXPECT_EQ(0, mock_notification_menu_controller()->activation_count_);
}
// Tests that tapping a notification activates it.
TEST_F(NotificationMenuViewTest, TapNotification) {
AddNotification("notification_id", base::ASCIIToUTF16("title"),
base::ASCIIToUTF16("message"));
EXPECT_EQ(0, mock_notification_menu_controller()->activation_count_);
DispatchGesture(ui::GestureEventDetails(ui::ET_GESTURE_TAP));
EXPECT_EQ(1, mock_notification_menu_controller()->activation_count_);
}
// Tests that an in bounds mouse release activates a notification.
TEST_F(NotificationMenuViewTest, ClickNotification) {
AddNotification("notification_id", base::ASCIIToUTF16("title"),
base::ASCIIToUTF16("message"));
EXPECT_EQ(0, mock_notification_menu_controller()->activation_count_);
const gfx::Point cursor_location = test_api()
->GetDisplayedNotificationItemView()
->GetBoundsInScreen()
.origin();
ui::MouseEvent press(ui::ET_MOUSE_PRESSED, cursor_location, cursor_location,
ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
ui::EF_NONE);
notification_menu_view()->GetWidget()->OnMouseEvent(&press);
EXPECT_EQ(0, mock_notification_menu_controller()->activation_count_);
ui::MouseEvent release(ui::ET_MOUSE_RELEASED, cursor_location,
cursor_location, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_NONE);
notification_menu_view()->GetWidget()->OnMouseEvent(&release);
EXPECT_EQ(1, mock_notification_menu_controller()->activation_count_);
}
// Tests that an out of bounds mouse release does not activate a notification.
TEST_F(NotificationMenuViewTest, OutOfBoundsClick) {
AddNotification("notification_id", base::ASCIIToUTF16("title"),
base::ASCIIToUTF16("message"));
EXPECT_EQ(0, mock_notification_menu_controller()->activation_count_);
const gfx::Point cursor_location = test_api()
->GetDisplayedNotificationItemView()
->GetBoundsInScreen()
.origin();
ui::MouseEvent press(ui::ET_MOUSE_PRESSED, cursor_location, cursor_location,
ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
ui::EF_NONE);
notification_menu_view()->GetWidget()->OnMouseEvent(&press);
EXPECT_EQ(0, mock_notification_menu_controller()->activation_count_);
const gfx::Point out_of_bounds;
ui::MouseEvent out_of_bounds_release(ui::ET_MOUSE_RELEASED, out_of_bounds,
out_of_bounds, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_NONE);
notification_menu_view()->GetWidget()->OnMouseEvent(&out_of_bounds_release);
EXPECT_EQ(0, mock_notification_menu_controller()->activation_count_);
}
} // namespace ash
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment