// Copyright 2021 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/system/notification_center/notification_grouping_controller.h"
#include <memory>
#include "ash/constants/ash_constants.h"
#include "ash/constants/ash_features.h"
#include "ash/system/notification_center/ash_message_popup_collection.h"
#include "ash/system/notification_center/message_center_utils.h"
#include "ash/system/notification_center/notification_center_tray.h"
#include "ash/system/notification_center/views/ash_notification_view.h"
#include "ash/system/notification_center/views/notification_center_view.h"
#include "ash/system/notification_center/views/notification_list_view.h"
#include "ash/system/tray/tray_background_view.h"
#include "ash/system/unified/unified_system_tray.h"
#include "ash/test/ash_test_base.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "ui/color/color_id.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/compositor/test/layer_animation_stopped_waiter.h"
#include "ui/gfx/vector_icon_types.h"
#include "ui/message_center/notification_view_controller.h"
#include "ui/message_center/public/cpp/notification_types.h"
#include "ui/message_center/public/cpp/notifier_id.h"
#include "ui/message_center/views/message_popup_view.h"
#include "ui/message_center/views/message_view.h"
#include "ui/views/animation/slide_out_controller.h"
using message_center::MessageCenter;
using message_center::Notification;
namespace ash {
namespace {
const char kIdFormat[] = "id%ld";
class TestNotificationViewController
: public message_center::NotificationViewController {
public:
TestNotificationViewController() = default;
TestNotificationViewController(const TestNotificationViewController& other) =
delete;
TestNotificationViewController& operator=(
const TestNotificationViewController& other) = delete;
~TestNotificationViewController() override = default;
// message_center::NotificationViewController:
message_center::MessageView* GetMessageViewForNotificationId(
const std::string& notification_id) override {
return nullptr;
}
void AnimateResize() override {}
void ConvertNotificationViewToGroupedNotificationView(
const std::string& ungrouped_notification_id,
const std::string& new_grouped_notification_id) override {}
void ConvertGroupedNotificationViewToNotificationView(
const std::string& grouped_notification_id,
const std::string& new_single_notification_id) override {}
void OnChildNotificationViewUpdated(
const std::string& parent_notification_id,
const std::string& child_notification_id) override {
on_child_updated_called_ = true;
on_child_updated_parent_id_ = parent_notification_id;
on_child_updated_child_id_ = child_notification_id;
}
bool on_child_updated_called() { return on_child_updated_called_; }
std::string on_child_updated_parent_id() {
return on_child_updated_parent_id_;
}
std::string on_child_updated_child_id() { return on_child_updated_child_id_; }
private:
bool on_child_updated_called_ = false;
std::string on_child_updated_parent_id_;
std::string on_child_updated_child_id_;
};
class TestNotificationGroupingController
: public NotificationGroupingController {
public:
explicit TestNotificationGroupingController(
NotificationCenterTray* notification_tray)
: NotificationGroupingController(notification_tray) {
test_view_controller_ = std::make_unique<TestNotificationViewController>();
}
TestNotificationGroupingController(
const TestNotificationGroupingController& other) = delete;
TestNotificationGroupingController& operator=(
const TestNotificationGroupingController& other) = delete;
~TestNotificationGroupingController() override = default;
// NotificationGroupingController:
message_center::NotificationViewController*
GetActiveNotificationViewController() override {
return test_view_controller_.get();
}
private:
std::unique_ptr<TestNotificationViewController> test_view_controller_;
};
} // namespace
class NotificationGroupingControllerTest : public AshTestBase {
public:
NotificationGroupingControllerTest() = default;
NotificationGroupingControllerTest(
const NotificationGroupingControllerTest& other) = delete;
NotificationGroupingControllerTest& operator=(
const NotificationGroupingControllerTest& other) = delete;
~NotificationGroupingControllerTest() override = default;
protected:
std::string AddNotificationWithOriginUrl(const GURL& origin_url) {
std::string id;
MessageCenter::Get()->AddNotification(MakeNotification(id, origin_url));
return id;
}
void AnimateUntilIdle() {
AshMessagePopupCollection* popup_collection =
GetPrimaryNotificationCenterTray()->popup_collection();
while (popup_collection->animation()->is_animating()) {
popup_collection->animation()->SetCurrentValue(1.0);
popup_collection->animation()->End();
}
}
message_center::MessagePopupView* GetPopupView(const std::string& id) {
return GetPrimaryNotificationCenterTray()
->popup_collection()
->GetPopupViewForNotificationID(id);
}
// Construct a new notification for testing.
std::unique_ptr<Notification> MakeNotification(std::string& id_out,
const GURL& origin_url) {
id_out = base::StringPrintf(kIdFormat, notifications_counter_);
message_center::NotifierId notifier_id;
notifier_id.profile_id = "abc@gmail.com";
notifier_id.type = message_center::NotifierType::WEB_PAGE;
notifier_id.url = origin_url;
auto notification = std::make_unique<Notification>(
message_center::NOTIFICATION_TYPE_SIMPLE, id_out,
u"id" + base::NumberToString16(notifications_counter_),
u"message" + base::NumberToString16(notifications_counter_),
ui::ImageModel(), u"src", origin_url, notifier_id,
message_center::RichNotificationData(), nullptr);
notifications_counter_++;
return notification;
}
std::unique_ptr<Notification> MakeNotificationWithNotifierId(
std::string& id_out,
const GURL& origin_url,
message_center::NotifierId notifier_id) {
id_out = base::StringPrintf(kIdFormat, notifications_counter_);
auto notification = std::make_unique<Notification>(
message_center::NOTIFICATION_TYPE_SIMPLE, id_out,
u"id" + base::NumberToString16(notifications_counter_),
u"message" + base::NumberToString16(notifications_counter_),
ui::ImageModel(), u"src", origin_url, notifier_id,
message_center::RichNotificationData(), nullptr);
notifications_counter_++;
return notification;
}
void GenerateGestureEvent(const ui::GestureEventDetails& details,
views::SlideOutController* slide_out_controller) {
ui::GestureEvent gesture_event(0, 0, ui::EF_NONE, base::TimeTicks(),
details);
slide_out_controller->OnGestureEvent(&gesture_event);
base::RunLoop().RunUntilIdle();
}
void GenerateSwipe(int swipe_amount,
views::SlideOutController* slide_out_controller) {
GenerateGestureEvent(
ui::GestureEventDetails(ui::EventType::kGestureScrollBegin),
slide_out_controller);
GenerateGestureEvent(
ui::GestureEventDetails(ui::EventType::kGestureScrollUpdate,
swipe_amount, 0),
slide_out_controller);
GenerateGestureEvent(
ui::GestureEventDetails(ui::EventType::kGestureScrollEnd),
slide_out_controller);
}
views::SlideOutController* GetSlideOutController(AshNotificationView* view) {
return view->slide_out_controller_for_test();
}
bool IsNotificationListViewAnimating() const {
return GetPrimaryNotificationCenterTray()
->GetNotificationListView()
->IsAnimating();
}
size_t notifications_counter_ = 0;
};
TEST_F(NotificationGroupingControllerTest, BasicGrouping) {
auto* message_center = MessageCenter::Get();
std::string id0, id1, id2;
const GURL url(u"http://test-url.com/");
id0 = AddNotificationWithOriginUrl(url);
id1 = AddNotificationWithOriginUrl(url);
id2 = AddNotificationWithOriginUrl(url);
EXPECT_TRUE(message_center->FindNotificationById(id0)->group_child());
EXPECT_TRUE(message_center->FindNotificationById(id1)->group_child());
EXPECT_TRUE(message_center->FindNotificationById(id2)->group_child());
std::string id_parent =
id0 + message_center_utils::GenerateGroupParentNotificationIdSuffix(
message_center->FindNotificationById(id0)->notifier_id());
EXPECT_TRUE(message_center->FindNotificationById(id_parent)->group_parent());
}
// Test a situation where a notification being added is already marked
// as a parent.
// In this case, make sure it's added as such and no parent_copy
// notification is added.
TEST_F(NotificationGroupingControllerTest, AddPreparentNotificationGroupsCorrectly) {
auto* message_center = MessageCenter::Get();
std::string id0, id1;
const GURL url(u"http://test-url.com/");
auto parent_notification = MakeNotification(id0, url);
auto child_notification = MakeNotification(id1, url);
parent_notification->SetGroupParent();
message_center->AddNotification(std::move(parent_notification));
message_center->AddNotification(std::move(child_notification));
EXPECT_TRUE(message_center->FindNotificationById(id0)->group_parent());
EXPECT_TRUE(message_center->FindNotificationById(id1)->group_child());
std::string id_parent =
id0 + message_center_utils::GenerateGroupParentNotificationIdSuffix(
message_center->FindNotificationById(id0)->notifier_id());
EXPECT_FALSE(message_center->FindNotificationById(id_parent));
}
TEST_F(NotificationGroupingControllerTest, BasicRemoval) {
std::string id0, id1, id2;
const GURL url(u"http://test-url.com");
id0 = AddNotificationWithOriginUrl(url);
id1 = AddNotificationWithOriginUrl(url);
id2 = AddNotificationWithOriginUrl(url);
auto* message_center = MessageCenter::Get();
std::string id_parent =
id0 + message_center_utils::GenerateGroupParentNotificationIdSuffix(
message_center->FindNotificationById(id0)->notifier_id());
// Group notification should stay intact if a single group notification is
// removed.
message_center->RemoveNotification(id1, true);
EXPECT_TRUE(message_center->FindNotificationById(id_parent)->group_parent());
// Adding and removing a non group notification should have no impact.
std::string tmp = AddNotificationWithOriginUrl(GURL(u"tmp"));
message_center->RemoveNotification(tmp, true);
EXPECT_TRUE(message_center->FindNotificationById(id0)->group_child());
EXPECT_TRUE(message_center->FindNotificationById(id2)->group_child());
EXPECT_TRUE(message_center->FindNotificationById(id_parent)->group_parent());
}
// Tests that having a grouped notification as the latest notification does not
// animate the notification list. This happened because latest notifications get
// expanded and it was rapidly being collapsed due to it being grouped.
TEST_F(NotificationGroupingControllerTest, LatestNotificationDoesNotAnimate) {
// Add two grouped notifications.
const GURL url(u"http://test-url.com");
AddNotificationWithOriginUrl(url);
AddNotificationWithOriginUrl(url);
// Show the notification center.
GetPrimaryNotificationCenterTray()->ShowBubble();
// List should not be animating when the latest notification is grouped.
EXPECT_FALSE(IsNotificationListViewAnimating());
}
TEST_F(NotificationGroupingControllerTest,
ParentNotificationReshownWithNewChild) {
TrayBackgroundView* tray;
tray = GetPrimaryNotificationCenterTray();
std::string id0;
const GURL url(u"http://test-url.com");
id0 = AddNotificationWithOriginUrl(url);
std::string tmp;
tmp = AddNotificationWithOriginUrl(url);
std::string parent_id =
id0 + message_center_utils::GenerateGroupParentNotificationIdSuffix(
MessageCenter::Get()->FindNotificationById(id0)->notifier_id());
EXPECT_TRUE(GetPopupView(parent_id));
// Toggle the system tray to dismiss all popups.
tray->ShowBubble();
tray->CloseBubble();
EXPECT_FALSE(GetPopupView(parent_id));
// Adding notification with a different notifier id should have no effect.
AddNotificationWithOriginUrl(GURL("tmp"));
EXPECT_FALSE(GetPopupView(parent_id));
AddNotificationWithOriginUrl(url);
// Move down or fade in animation might happen before showing the popup.
AnimateUntilIdle();
EXPECT_TRUE(GetPopupView(parent_id));
}
TEST_F(NotificationGroupingControllerTest,
RemovingParentRemovesChildGroupNotifications) {
std::string id0;
const GURL url(u"http://test-url.com");
id0 = AddNotificationWithOriginUrl(url);
std::string tmp;
AddNotificationWithOriginUrl(url);
AddNotificationWithOriginUrl(url);
auto* message_center = MessageCenter::Get();
MessageCenter::Get()->RemoveNotification(
id0 + message_center_utils::GenerateGroupParentNotificationIdSuffix(
message_center->FindNotificationById(id0)->notifier_id()),
true);
ASSERT_FALSE(message_center->HasPopupNotifications());
}
TEST_F(NotificationGroupingControllerTest,
RepopulatedParentNotificationRemoval) {
auto* message_center = MessageCenter::Get();
std::string id0, id1, id2, id3, id4;
const GURL url(u"http://test-url.com");
id0 = AddNotificationWithOriginUrl(url);
id1 = AddNotificationWithOriginUrl(url);
id2 = AddNotificationWithOriginUrl(url);
id3 = AddNotificationWithOriginUrl(url);
std::string parent_id =
id0 + message_center_utils::GenerateGroupParentNotificationIdSuffix(
message_center->FindNotificationById(id0)->notifier_id());
// Toggle the notification tray to dismiss all popups.
GetPrimaryNotificationCenterTray()->ShowBubble();
GetPrimaryNotificationCenterTray()->CloseBubble();
ASSERT_FALSE(MessageCenter::Get()->HasPopupNotifications());
id4 = AddNotificationWithOriginUrl(url);
AnimateUntilIdle();
message_center->RemoveNotification(id0, true);
message_center->RemoveNotification(id1, true);
message_center->RemoveNotification(id2, true);
message_center->RemoveNotification(id3, true);
auto* last_child = MessageCenter::Get()->FindNotificationById(id4);
auto* parent = MessageCenter::Get()->FindNotificationById(parent_id);
EXPECT_TRUE(last_child->group_child());
EXPECT_TRUE(parent->group_parent());
}
TEST_F(NotificationGroupingControllerTest, ParentNotificationMetadata) {
auto* message_center = MessageCenter::Get();
std::string id0, id1, id2;
const GURL url(u"http://test-url.com/");
const auto icon = gfx::VectorIcon();
const auto small_image = gfx::Image();
const std::u16string display_source0 = u"test_display_source0";
auto notification = MakeNotification(id0, url);
notification->set_accent_color_id(ui::kColorAshSystemUIMenuIcon);
notification->set_accent_color(SK_ColorRED);
notification->set_parent_vector_small_image(icon);
notification->SetSmallImage(small_image);
notification->set_display_source(display_source0);
message_center->AddNotification(std::move(notification));
id1 = AddNotificationWithOriginUrl(url);
id2 = AddNotificationWithOriginUrl(url);
auto* parent_notification = message_center->FindNotificationById(
id0 + message_center_utils::GenerateGroupParentNotificationIdSuffix(
message_center->FindNotificationById(id0)->notifier_id()));
EXPECT_TRUE(parent_notification->group_parent());
// Parent notification should inherit attributes from the child.
EXPECT_EQ(ui::kColorAshSystemUIMenuIcon,
parent_notification->accent_color_id());
EXPECT_EQ(SK_ColorRED, parent_notification->accent_color());
EXPECT_EQ(&icon, &parent_notification->vector_small_image());
EXPECT_EQ(small_image, parent_notification->small_image());
EXPECT_EQ(display_source0, parent_notification->display_source());
}
// Parent notification's priority should always match the priority of the last
// added notification to the group.
TEST_F(NotificationGroupingControllerTest, ParentNotificationPriority) {
auto* message_center = MessageCenter::Get();
std::string id1, id2, id3;
const GURL url(u"http://test-url.com/");
auto notification = MakeNotification(id1, url);
notification->set_priority(message_center::LOW_PRIORITY);
message_center->AddNotification(std::move(notification));
auto notification2 = MakeNotification(id2, url);
notification2->set_priority(message_center::LOW_PRIORITY);
message_center->AddNotification(std::move(notification2));
auto* parent_notification = message_center->FindNotificationById(
id1 + message_center_utils::GenerateGroupParentNotificationIdSuffix(
message_center->FindNotificationById(id1)->notifier_id()));
EXPECT_TRUE(parent_notification->group_parent());
EXPECT_EQ(message_center::LOW_PRIORITY, parent_notification->priority());
auto notification3 = MakeNotification(id3, url);
notification3->set_priority(message_center::HIGH_PRIORITY);
message_center->AddNotification(std::move(notification3));
EXPECT_EQ(message_center::HIGH_PRIORITY, parent_notification->priority());
}
TEST_F(NotificationGroupingControllerTest,
NotificationsGroupingOnMultipleScreens) {
UpdateDisplay("800x600,800x600");
auto* message_center = MessageCenter::Get();
std::string id0, id1, id2;
const GURL url(u"http://test-url.com/");
id0 = AddNotificationWithOriginUrl(url);
id1 = AddNotificationWithOriginUrl(url);
id2 = AddNotificationWithOriginUrl(url);
EXPECT_TRUE(message_center->FindNotificationById(id0)->group_child());
EXPECT_TRUE(message_center->FindNotificationById(id1)->group_child());
EXPECT_TRUE(message_center->FindNotificationById(id2)->group_child());
std::string id_parent =
id0 + message_center_utils::GenerateGroupParentNotificationIdSuffix(
message_center->FindNotificationById(id0)->notifier_id());
EXPECT_TRUE(message_center->FindNotificationById(id_parent)->group_parent());
// Make sure there is only a single popup (there would be more popups if
// grouping didn't work)
EXPECT_EQ(1u, message_center->GetPopupNotifications().size());
}
// Even though it is not a web notification, privacy indicators notification
// should group together.
TEST_F(NotificationGroupingControllerTest, GroupPrivacyIndicatorsNotification) {
auto* message_center = MessageCenter::Get();
std::string id0, id1;
const GURL url0(u"http://test-url1.com/");
const GURL url1(u"http://test-url2.com/");
auto notifier_id =
message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT,
kPrivacyIndicatorsNotifierId,
NotificationCatalogName::kPrivacyIndicators);
auto notification0 = MakeNotificationWithNotifierId(id0, url0, notifier_id);
auto notification1 = MakeNotificationWithNotifierId(id1, url1, notifier_id);
message_center->AddNotification(std::move(notification0));
message_center->AddNotification(std::move(notification1));
std::string id_parent =
id0 + message_center_utils::GenerateGroupParentNotificationIdSuffix(
message_center->FindNotificationById(id0)->notifier_id());
EXPECT_TRUE(message_center->FindNotificationById(id_parent));
}
// Create a group notification while the message center bubble is
// is shown.
TEST_F(NotificationGroupingControllerTest,
NotificationsGroupingMessageCenterBubbleShown) {
GetPrimaryNotificationCenterTray()->ShowBubble();
auto* message_center = MessageCenter::Get();
std::string id0, id1, id2;
const GURL url(u"http://test-url.com/");
id0 = AddNotificationWithOriginUrl(url);
id1 = AddNotificationWithOriginUrl(url);
id2 = AddNotificationWithOriginUrl(url);
EXPECT_TRUE(message_center->FindNotificationById(id0)->group_child());
EXPECT_TRUE(message_center->FindNotificationById(id1)->group_child());
std::string id_parent =
id0 + message_center_utils::GenerateGroupParentNotificationIdSuffix(
message_center->FindNotificationById(id0)->notifier_id());
EXPECT_TRUE(message_center->FindNotificationById(id_parent)->group_parent());
}
TEST_F(NotificationGroupingControllerTest,
GroupedNotificationRemovedDuringAnimation) {
auto* message_center = MessageCenter::Get();
std::string id0, id1;
const GURL url(u"http://test-url.com/");
// Enable animations.
ui::ScopedAnimationDurationScaleMode duration(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
id0 = AddNotificationWithOriginUrl(url);
id1 = AddNotificationWithOriginUrl(url);
// Remove notification with `id` before the animation completes.
message_center->RemoveNotification(id1, true);
// Wait for the animation to end to ensure there is no crash
ui::LayerAnimationStoppedWaiter waiter;
waiter.Wait(GetPopupView(id0)->message_view()->layer());
}
TEST_F(NotificationGroupingControllerTest,
ParentNotificationRemovedDuringAnimation) {
// Enable animations.
ui::ScopedAnimationDurationScaleMode duration(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
auto* message_center = MessageCenter::Get();
std::string id0, id1;
const GURL url(u"http://test-url.com/");
id0 = AddNotificationWithOriginUrl(url);
id1 = AddNotificationWithOriginUrl(url);
// Remove the first notification before the animation completes.
message_center->RemoveNotification(id0, true);
// Wait for the animation to end to ensure there is no crash
ui::LayerAnimationStoppedWaiter waiter;
waiter.Wait(GetPopupView(id0)->message_view()->layer());
// Make sure the second notification is still there.
EXPECT_FALSE(message_center->FindNotificationById(id0));
EXPECT_TRUE(message_center->FindNotificationById(id1));
}
// Regression test for b/251686768. Tests that a grouped notification is
// correctly dismissed when swiped in the collapse state rather than moved into
// the center of the screen. Also, tests that the correct notifications are
// dismissed by swiping in the expanded state.
TEST_F(NotificationGroupingControllerTest, NotificationSwipeGestureBehavior) {
auto* message_center = MessageCenter::Get();
std::string parent_id, id0, id1, id2, id3;
const GURL url(u"http://test-url.com/");
id0 = AddNotificationWithOriginUrl(url);
id1 = AddNotificationWithOriginUrl(url);
id2 = AddNotificationWithOriginUrl(url);
id3 = AddNotificationWithOriginUrl(url);
parent_id =
id0 + message_center_utils::GenerateGroupParentNotificationIdSuffix(
message_center->FindNotificationById(id0)->notifier_id());
AshNotificationView* parent_message_view = static_cast<AshNotificationView*>(
GetPopupView(parent_id)->message_view());
auto* message_view_2 =
GetPopupView(parent_id)->message_view()->FindGroupNotificationView(id2);
auto* message_view_3 =
GetPopupView(parent_id)->message_view()->FindGroupNotificationView(id3);
parent_message_view->ToggleExpand();
EXPECT_TRUE(parent_message_view->IsExpanded());
// Swiping out a group child notification while the parent notification is
// expanded should only slide and remove the group child notification.
GenerateSwipe(300, GetSlideOutController(
static_cast<AshNotificationView*>(message_view_3)));
EXPECT_FALSE(message_center->FindNotificationById(id3));
EXPECT_TRUE(message_center->FindNotificationById(parent_id));
parent_message_view->ToggleExpand();
EXPECT_FALSE(parent_message_view->IsExpanded());
// Swiping out a group child notification while the parent notification is
// collapsed should dismiss the popup but keep all notifications in the
// notification center.
GenerateSwipe(300, GetSlideOutController(
static_cast<AshNotificationView*>(message_view_2)));
EXPECT_FALSE(message_center->FindPopupNotificationById(parent_id));
EXPECT_TRUE(message_center->FindNotificationById(id1));
EXPECT_TRUE(message_center->FindNotificationById(id2));
}
// Regression test for b/251684908. Tests that a duplicate `AddNotification`
// event does not cause the associated notification popup to be dismissed or the
// original notification to be grouped incorrectly.
TEST_F(NotificationGroupingControllerTest, DuplicateAddNotificationNotGrouped) {
std::string id = AddNotificationWithOriginUrl(GURL(u"http://test-url.com/"));
auto* popup = GetPopupView(id);
EXPECT_TRUE(popup->GetVisible());
auto* message_center = message_center::MessageCenter::Get();
// Add a copy of the original notification.
auto* original_notification = message_center->FindNotificationById(id);
message_center->AddNotification(
std::make_unique<Notification>(*original_notification));
// Add a new notification to force an update to all notification popups.
AddNotificationWithOriginUrl(GURL(u"http://other-url.com/"));
// Make sure the popup for the `original_notification` still exists and is
// visible. Also, make sure the `original_notification` was not grouped.
EXPECT_TRUE(GetPopupView(id));
EXPECT_TRUE(popup->GetVisible());
EXPECT_FALSE(message_center->FindNotificationById(id)->group_child());
}
TEST_F(NotificationGroupingControllerTest, ChildNotificationUpdate) {
auto* message_center = MessageCenter::Get();
std::string id0, id1, id2;
const GURL url(u"http://test-url.com/");
id0 = AddNotificationWithOriginUrl(url);
id1 = AddNotificationWithOriginUrl(url);
EXPECT_TRUE(message_center->FindNotificationById(id0)->group_child());
// Update the notification.
auto notification = MakeNotification(id2, url);
auto updated_notification =
std::make_unique<Notification>(id0, *notification.get());
message_center->UpdateNotification(id0, std::move(updated_notification));
// Make sure the updated notification is still a group child.
EXPECT_TRUE(message_center->FindNotificationById(id0)->group_child());
}
TEST_F(NotificationGroupingControllerTest, ChildNotificationViewUpdate) {
TestNotificationGroupingController test_controller(
GetPrimaryNotificationCenterTray());
auto* message_center = MessageCenter::Get();
std::string id0, id1, id2;
const GURL url(u"http://test-url.com/");
id0 = AddNotificationWithOriginUrl(url);
id1 = AddNotificationWithOriginUrl(url);
std::string parent_id =
id0 + message_center_utils::GenerateGroupParentNotificationIdSuffix(
message_center->FindNotificationById(id0)->notifier_id());
EXPECT_TRUE(message_center->FindNotificationById(id0)->group_child());
// Update the notification.
auto notification = MakeNotification(id2, url);
auto updated_notification =
std::make_unique<Notification>(id0, *notification.get());
message_center->UpdateNotification(id0, std::move(updated_notification));
auto* notification_view_controller =
static_cast<TestNotificationViewController*>(
test_controller.GetActiveNotificationViewController());
// When a child notification is updated, `OnChildNotificationViewUpdated()`
// should be called with the correct parent and child ids.
EXPECT_TRUE(notification_view_controller->on_child_updated_called());
EXPECT_EQ(parent_id,
notification_view_controller->on_child_updated_parent_id());
EXPECT_EQ(id0, notification_view_controller->on_child_updated_child_id());
}
// When the last child of the group notification is removed, its parent
// notification should be removed as well. We are testing in the case where
// there is no popup or notification center is not showing.
// TODO(crbug.com/1417929): Re-enable this test
TEST_F(NotificationGroupingControllerTest, DISABLED_ChildNotificationRemove) {
auto* message_center = MessageCenter::Get();
std::string id0, id1;
const GURL url(u"http://test-url.com/");
id0 = AddNotificationWithOriginUrl(url);
id1 = AddNotificationWithOriginUrl(url);
std::string id_parent =
id0 + message_center_utils::GenerateGroupParentNotificationIdSuffix(
message_center->FindNotificationById(id0)->notifier_id());
// Toggle the system tray to dismiss all popups.
GetPrimaryUnifiedSystemTray()->ShowBubble();
GetPrimaryUnifiedSystemTray()->CloseBubble();
EXPECT_EQ(3u, message_center->GetVisibleNotifications().size());
// Remove one child. Parent notification is still retained.
message_center->RemoveNotification(id1, /*by_user=*/false);
EXPECT_EQ(2u, message_center->GetVisibleNotifications().size());
EXPECT_TRUE(message_center->FindNotificationById(id_parent));
EXPECT_FALSE(message_center->FindNotificationById(id1));
EXPECT_TRUE(message_center->FindNotificationById(id0));
// Remove the last child notification. Parent notification should be removed.
message_center->RemoveNotification(id0, /*by_user=*/false);
EXPECT_EQ(0u, message_center->GetVisibleNotifications().size());
EXPECT_FALSE(message_center->FindNotificationById(id_parent));
EXPECT_FALSE(message_center->FindNotificationById(id0));
}
TEST_F(NotificationGroupingControllerTest,
ChildNotificationsWithDifferentPriorities) {
auto* message_center = MessageCenter::Get();
std::string id0, id1, id2;
const GURL url(u"http://test-url.com/");
// Create 2 notifications with low priority, the parent notification should be
// low priority as well.
auto notification = MakeNotification(id0, url);
notification->set_priority(message_center::LOW_PRIORITY);
message_center->AddNotification(std::move(notification));
notification = MakeNotification(id1, url);
notification->set_priority(message_center::LOW_PRIORITY);
message_center->AddNotification(std::move(notification));
std::string id_parent =
id0 + message_center_utils::GenerateGroupParentNotificationIdSuffix(
message_center->FindNotificationById(id0)->notifier_id());
auto* parent_notification = message_center->FindNotificationById(id_parent);
ASSERT_TRUE(parent_notification->group_parent());
ASSERT_EQ(message_center::LOW_PRIORITY, parent_notification->priority());
// Now create another notification in the group with normal priority. Parent
// should still have the same id and have normal priority now.
id2 = AddNotificationWithOriginUrl(url);
parent_notification = message_center->FindNotificationById(id_parent);
ASSERT_TRUE(parent_notification);
ASSERT_EQ(message_center::DEFAULT_PRIORITY, parent_notification->priority());
// There should be 4 notifications now (no duplicates created).
EXPECT_EQ(4u, message_center->GetVisibleNotifications().size());
// Remove one child then add back. Parent notification is still retained with
// same id.
message_center->RemoveNotification(id0,
/*by_user=*/false);
notification = MakeNotification(id0, url);
notification->set_priority(message_center::LOW_PRIORITY);
message_center->AddNotification(std::move(notification));
parent_notification = message_center->FindNotificationById(id_parent);
ASSERT_TRUE(parent_notification->group_parent());
// Should have no duplicates.
EXPECT_EQ(4u, message_center->GetVisibleNotifications().size());
}
TEST_F(NotificationGroupingControllerTest, ChildNotificationsPinned) {
auto* message_center = MessageCenter::Get();
std::string id0, id1, id2;
const GURL url(u"http://test-url.com/");
// Creates 2 notifications with one is pinned, the parent notification should
// be pinned as well.
auto notification = MakeNotification(id0, url);
notification->set_pinned(false);
message_center->AddNotification(std::move(notification));
notification = MakeNotification(id1, url);
notification->set_pinned(true);
message_center->AddNotification(std::move(notification));
std::string id_parent =
id0 + message_center_utils::GenerateGroupParentNotificationIdSuffix(
message_center->FindNotificationById(id0)->notifier_id());
auto* parent_notification = message_center->FindNotificationById(id_parent);
ASSERT_TRUE(parent_notification->group_parent());
EXPECT_TRUE(parent_notification->pinned());
// Adds another pinned notification. Parent should still be pinned.
notification = MakeNotification(id2, url);
notification->set_pinned(true);
message_center->AddNotification(std::move(notification));
EXPECT_TRUE(parent_notification->pinned());
// Removes one pinned notification. Parent should still be pinned.
message_center->RemoveNotification(id1,
/*by_user=*/false);
EXPECT_TRUE(parent_notification->pinned());
// Removes the other pinned notification. Parent then should not be pinned.
message_center->RemoveNotification(id2,
/*by_user=*/false);
EXPECT_FALSE(parent_notification->pinned());
// Adds back the pinned notification. Parent should now be pinned.
notification = MakeNotification(id2, url);
notification->set_pinned(true);
message_center->AddNotification(std::move(notification));
EXPECT_TRUE(parent_notification->pinned());
}
TEST_F(NotificationGroupingControllerTest, ChildNotificationsUpdatePinned) {
auto* message_center = MessageCenter::Get();
std::string id0, id1, id2, id3;
const GURL url(u"http://test-url.com/");
// Creates 2 un-pinned notifications, the parent notification should be
// un-pinned as well.
auto notification = MakeNotification(id0, url);
notification->set_pinned(false);
message_center->AddNotification(std::move(notification));
notification = MakeNotification(id1, url);
notification->set_pinned(false);
message_center->AddNotification(std::move(notification));
std::string id_parent =
id0 + message_center_utils::GenerateGroupParentNotificationIdSuffix(
message_center->FindNotificationById(id0)->notifier_id());
auto* parent_notification = message_center->FindNotificationById(id_parent);
ASSERT_TRUE(parent_notification->group_parent());
EXPECT_FALSE(parent_notification->pinned());
// Updates one to pinned, the parent should be pinned now.
notification = MakeNotification(id2, url);
notification->set_pinned(true);
message_center->UpdateNotification(id1, std::move(notification));
EXPECT_TRUE(parent_notification->pinned());
// Updates again to un-pinned, the parent should be un-pinned.
notification = MakeNotification(id3, url);
notification->set_pinned(false);
message_center->UpdateNotification(id2, std::move(notification));
EXPECT_FALSE(parent_notification->pinned());
}
TEST_F(NotificationGroupingControllerTest,
ArcNotificationGroupingWithoutGroupKey) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(features::kRenderArcNotificationsByChrome);
auto* message_center = MessageCenter::Get();
const GURL url(u"http://test-url.com/");
std::string id0;
auto arc_notifier_id = message_center::NotifierId(
message_center::NotifierType::ARC_APPLICATION, "test-id");
// Add 4 ARC notifications.
message_center->AddNotification(
MakeNotificationWithNotifierId(id0, url, arc_notifier_id));
for (int i = 0; i < 3; i++) {
std::string tmp;
message_center->AddNotification(
MakeNotificationWithNotifierId(tmp, url, arc_notifier_id));
}
// Make sure there is no grouping with 4 ARC notifications.
auto notifications = message_center->GetVisibleNotifications();
EXPECT_EQ(notifications.size(), 4u);
for (Notification* n : notifications) {
EXPECT_FALSE(n->group_child() || n->group_parent());
}
for (int i = 0; i < 3; i++) {
std::string tmp;
message_center->AddNotification(
MakeNotificationWithNotifierId(tmp, url, arc_notifier_id));
}
// Make sure there is one notification set as the parent and all others are
// set to group children.
std::string parent_id =
id0 + message_center_utils::GenerateGroupParentNotificationIdSuffix(
message_center->FindNotificationById(id0)->notifier_id());
notifications = message_center->GetVisibleNotifications();
for (Notification* n : notifications) {
if (n->id() == parent_id) {
EXPECT_TRUE(n->group_parent());
continue;
}
EXPECT_TRUE(n->group_child());
}
}
// Test to make sure `web_app_id` in the `NotifierId` is used to determine
// grouping.
TEST_F(NotificationGroupingControllerTest, WebAppIdImpactsGrouping) {
GURL origin_url = GURL("http://test-url.com");
auto notifier_id = message_center::NotifierId(origin_url);
auto web_app_notifier_id = message_center::NotifierId(notifier_id);
web_app_notifier_id.web_app_id = "test-web-app";
auto* message_center = MessageCenter::Get();
std::string id0, id1, id2;
message_center->AddNotification(
MakeNotificationWithNotifierId(id0, origin_url, notifier_id));
message_center->AddNotification(
MakeNotificationWithNotifierId(id1, origin_url, web_app_notifier_id));
// Make sure notifications with the same origin don't get grouped if one of
// them has a populated `web_app_id`.
EXPECT_FALSE(message_center->FindNotificationById(id0)->group_child());
EXPECT_FALSE(message_center->FindNotificationById(id1)->group_child());
// Add another notification with `web_notifier_id` and expect the
// notifications with it to be grouped while the notification without the
// `web_app_id` should remain single.
message_center->AddNotification(
MakeNotificationWithNotifierId(id2, origin_url, web_app_notifier_id));
EXPECT_FALSE(message_center->FindNotificationById(id0)->group_child());
EXPECT_TRUE(message_center->FindNotificationById(id1)->group_child());
EXPECT_TRUE(message_center->FindNotificationById(id1)->group_child());
}
// Test to make sure a notification update coming from a PWA for an existing web
// notification is handled appropriately.
TEST_F(NotificationGroupingControllerTest, PWANotificationUpdate) {
GURL origin_url = GURL("http://test-url.com");
auto notifier_id = message_center::NotifierId(origin_url);
auto web_app_notifier_id = message_center::NotifierId(notifier_id);
web_app_notifier_id.web_app_id = "test-web-app";
auto* message_center = MessageCenter::Get();
std::string id0, id1, id2, id3;
message_center->AddNotification(
MakeNotificationWithNotifierId(id0, origin_url, notifier_id));
message_center->AddNotification(
MakeNotificationWithNotifierId(id1, origin_url, notifier_id));
message_center->AddNotification(
MakeNotificationWithNotifierId(id2, origin_url, notifier_id));
// Add a notification with `id0` to trigger an update with a different
// notifier_id.
std::string temp_id;
auto updated_notification =
MakeNotificationWithNotifierId(temp_id, origin_url, web_app_notifier_id);
message_center->AddNotification(
std::make_unique<message_center::Notification>(id0,
*updated_notification));
// Make sure the notification `id0` is no longer grouped and the rest of the
// group is unchanged.
auto* notification0 = message_center->FindNotificationById(id0);
auto* notification1 = message_center->FindNotificationById(id1);
EXPECT_FALSE(notification0->group_child());
EXPECT_TRUE(notification1->group_child());
EXPECT_TRUE(message_center->FindNotificationById(id2)->group_child());
// Adding another notification with the `web_app_notifier_id` should result in
// 2 separate grouped notifications.
message_center->AddNotification(
MakeNotificationWithNotifierId(id3, origin_url, web_app_notifier_id));
auto* parent_notification0 =
message_center->FindParentNotification(notification0);
auto* parent_notification1 =
message_center->FindParentNotification(notification1);
EXPECT_TRUE(parent_notification0);
EXPECT_TRUE(parent_notification1);
EXPECT_NE(parent_notification0, parent_notification1);
}
} // namespace ash