1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181

ash / app_menu / notification_menu_controller_unittest.cc [blame]

// 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/mojom/menu_source_type.mojom.h"
#include "ui/menus/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::mojom::MenuSourceType::kMaxValue,
                            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