// Copyright 2018 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 "ash/app_menu/notification_menu_controller.h" #include "ash/app_menu/app_menu_model_adapter.h" #include "ash/test/ash_test_base.h" #include "base/callback.h" #include "base/strings/utf_string_conversions.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/base/models/simple_menu_model.h" #include "ui/views/controls/menu/menu_item_view.h" #include "ui/views/controls/menu/submenu_view.h" namespace ash { namespace { constexpr char kTestAppId[] = "test-app-id"; void BuildAndSendNotification(const std::string& app_id, const std::string& notification_id) { const message_center::NotifierId notifier_id( message_center::NotifierType::APPLICATION, app_id); std::unique_ptr notification = std::make_unique( message_center::NOTIFICATION_TYPE_SIMPLE, notification_id, u"Test Web Notification", u"Notification message body.", ui::ImageModel(), u"www.test.org", GURL(), notifier_id, message_center::RichNotificationData(), nullptr /* delegate */); message_center::MessageCenter::Get()->AddNotification( std::move(notification)); } class TestAppMenuModelAdapter : public AppMenuModelAdapter { public: TestAppMenuModelAdapter(const std::string& app_id, std::unique_ptr model) : AppMenuModelAdapter(app_id, std::move(model), nullptr, ui::MENU_SOURCE_TYPE_LAST, base::OnceClosure(), false /* is_tablet_mode */) {} TestAppMenuModelAdapter(const TestAppMenuModelAdapter&) = delete; TestAppMenuModelAdapter& operator=(const TestAppMenuModelAdapter&) = delete; private: // AppMenuModelAdapter overrides: void RecordHistogramOnMenuClosed() override {} }; } // namespace class NotificationMenuControllerTest : public AshTestBase { public: NotificationMenuControllerTest() = default; NotificationMenuControllerTest(const NotificationMenuControllerTest&) = delete; NotificationMenuControllerTest& operator=( const NotificationMenuControllerTest&) = delete; ~NotificationMenuControllerTest() override {} // Overridden from AshTestBase: void TearDown() override { // NotificationMenuController removes itself from MessageCenter's observer // list in the dtor, so force it to happen first to prevent a crash. This // crash does not repro in production. notification_menu_controller_.reset(); AshTestBase::TearDown(); } void BuildMenu() { test_app_menu_model_adapter_ = std::make_unique( kTestAppId, std::make_unique( nullptr /*ui::SimpleMenuModel::Delegate not required*/)); test_app_menu_model_adapter_->model()->AddItem(0, u"item 0"); test_app_menu_model_adapter_->model()->AddItem(1, u"item 1"); root_menu_item_view_ = new views::MenuItemView(test_app_menu_model_adapter_.get()); host_view_ = std::make_unique(); host_view_->AddChildView(root_menu_item_view_); test_app_menu_model_adapter_->BuildMenu(root_menu_item_view_); notification_menu_controller_ = std::make_unique( kTestAppId, root_menu_item_view_, test_app_menu_model_adapter_.get()); } views::MenuItemView* root_menu_item_view() { return root_menu_item_view_; } private: // The root MenuItemView. Owned by |host_view_|. views::MenuItemView* root_menu_item_view_ = nullptr; // Allows the dtor to access the restricted views::MenuItemView dtor. std::unique_ptr host_view_; std::unique_ptr notification_menu_controller_; std::unique_ptr test_app_menu_model_adapter_; }; // Tests that NotificationMenuController does not add the // NotificationMenuView container until a notification arrives. TEST_F(NotificationMenuControllerTest, NotificationsArriveAfterBuilt) { // Build the context menu without adding a notification for // kTestAppId. BuildMenu(); // There should only be two items in the context menu. EXPECT_EQ(2u, root_menu_item_view()->GetSubmenu()->children().size()); // Add a notification. BuildAndSendNotification(kTestAppId, std::string("notification_id")); // NotificationMenuController should have added a third item, the // container for NotificationMenuView, to the menu. EXPECT_EQ(3u, root_menu_item_view()->GetSubmenu()->children().size()); } // Tests that NotificationMenuController adds and removes the container // MenuItemView when notifications come in before and after the menu has been // built. TEST_F(NotificationMenuControllerTest, NotificationsExistBeforeMenuIsBuilt) { // Add the notification before the menu is built. const std::string notification_id("notification_id"); BuildAndSendNotification(kTestAppId, notification_id); // Build the menu, the container should be added. BuildMenu(); EXPECT_EQ(3u, root_menu_item_view()->GetSubmenu()->children().size()); // Remove the notification, this should result in the NotificationMenuView // container being removed. message_center::MessageCenter::Get()->RemoveNotification(notification_id, true); EXPECT_EQ(2u, root_menu_item_view()->GetSubmenu()->children().size()); // Add the same notification. BuildAndSendNotification(kTestAppId, notification_id); // The container MenuItemView should be added again. EXPECT_EQ(3u, root_menu_item_view()->GetSubmenu()->children().size()); } // Tests that adding multiple notifications for kTestAppId does not add // additional containers beyond the first. TEST_F(NotificationMenuControllerTest, MultipleNotifications) { // Add two notifications, then build the menu. const std::string notification_id_0("notification_id_0"); BuildAndSendNotification(kTestAppId, notification_id_0); const std::string notification_id_1("notification_id_1"); BuildAndSendNotification(kTestAppId, notification_id_1); BuildMenu(); // Only one container MenuItemView should be added. EXPECT_EQ(3u, root_menu_item_view()->GetSubmenu()->children().size()); message_center::MessageCenter* message_center = message_center::MessageCenter::Get(); // Remove one of the notifications. message_center->RemoveNotification(notification_id_0, true); // The container should still exist. EXPECT_EQ(3u, root_menu_item_view()->GetSubmenu()->children().size()); // Remove the final notification. message_center->RemoveNotification(notification_id_1, true); // The container should be removed. EXPECT_EQ(2u, root_menu_item_view()->GetSubmenu()->children().size()); } } // namespace ash