chromium/ash/app_menu/notification_menu_controller_unittest.cc

// Copyright 2018 The Chromium Authors
// 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/functional/callback.h"
#include "base/memory/raw_ptr.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<message_center::Notification> notification =
      std::make_unique<message_center::Notification>(
          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<ui::SimpleMenuModel> 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 {
    root_menu_item_view_.reset();
    // 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<TestAppMenuModelAdapter>(
        kTestAppId,
        std::make_unique<ui::SimpleMenuModel>(
            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_ = std::make_unique<views::MenuItemView>(
        test_app_menu_model_adapter_.get());
    test_app_menu_model_adapter_->BuildMenu(root_menu_item_view());

    notification_menu_controller_ =
        std::make_unique<NotificationMenuController>(
            kTestAppId, root_menu_item_view(),
            test_app_menu_model_adapter_.get());
  }

  views::MenuItemView* root_menu_item_view() {
    return root_menu_item_view_.get();
  }

 private:
  // The root `MenuItemView`. In production, it is created and owned by the
  // `AppMenuModelAdapter`. In this test setup, the test fixture owns it.
  std::unique_ptr<views::MenuItemView> root_menu_item_view_;
  std::unique_ptr<NotificationMenuController> notification_menu_controller_;
  std::unique_ptr<TestAppMenuModelAdapter> 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