blob: 1cf97e325f69bc17836b033cb08777d99e216b38 [file] [log] [blame]
// 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/system/message_center/unified_message_list_view.h"
#include "ash/bubble/bubble_constants.h"
#include "ash/constants/ash_features.h"
#include "ash/system/message_center/message_center_constants.h"
#include "ash/system/tray/tray_constants.h"
#include "ash/system/unified/unified_system_tray_model.h"
#include "ash/test/ash_test_base.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animator.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/views/message_view.h"
#include "ui/message_center/views/notification_view.h"
using message_center::MessageCenter;
using message_center::MessageView;
using message_center::Notification;
namespace ash {
namespace {
class TestNotificationView : public message_center::NotificationView {
public:
TestNotificationView(const message_center::Notification& notification)
: NotificationView(notification) {
layer()->GetAnimator()->set_preemption_strategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
}
TestNotificationView(const TestNotificationView&) = delete;
TestNotificationView& operator=(const TestNotificationView&) = delete;
~TestNotificationView() override = default;
// message_center::NotificationView:
void UpdateCornerRadius(int top_radius, int bottom_radius) override {
top_radius_ = top_radius;
bottom_radius_ = bottom_radius;
message_center::NotificationViewBase::UpdateCornerRadius(top_radius,
bottom_radius);
}
int top_radius() const { return top_radius_; }
int bottom_radius() const { return bottom_radius_; }
private:
int top_radius_ = 0;
int bottom_radius_ = 0;
};
class TestUnifiedMessageListView : public UnifiedMessageListView {
public:
explicit TestUnifiedMessageListView(UnifiedSystemTrayModel* model)
: UnifiedMessageListView(nullptr, model) {}
TestUnifiedMessageListView(const TestUnifiedMessageListView&) = delete;
TestUnifiedMessageListView& operator=(const TestUnifiedMessageListView&) =
delete;
~TestUnifiedMessageListView() override = default;
void set_stacked_notification_count(int stacked_notification_count) {
stacked_notifications_.clear();
notification_id_list_.clear();
for (int i = 0; i < stacked_notification_count; i++) {
std::string id = base::NumberToString(0);
auto notification = std::make_unique<Notification>(
message_center::NOTIFICATION_TYPE_BASE_FORMAT, id, u"test title",
u"test message", gfx::Image(), std::u16string() /* display_source */,
GURL(), message_center::NotifierId(),
message_center::RichNotificationData(),
new message_center::NotificationDelegate());
stacked_notifications_.push_back(notification.get());
notification_id_list_.push_back(id);
}
}
// UnifiedMessageListView:
message_center::MessageView* CreateMessageView(
const message_center::Notification& notification) override {
auto* view = new TestNotificationView(notification);
view->SetIsNested();
return view;
}
std::vector<message_center::Notification*> GetStackedNotifications()
const override {
return stacked_notifications_;
}
std::vector<std::string> GetNonVisibleNotificationIdsInViewHierarchy()
const override {
return notification_id_list_;
}
private:
std::vector<message_center::Notification*> stacked_notifications_;
std::vector<std::string> notification_id_list_;
};
} // namespace
// The base test class, has no params so tests with no params can inherit from
// this.
class UnifiedMessageListViewTest : public AshTestBase,
public views::ViewObserver {
public:
UnifiedMessageListViewTest() = default;
UnifiedMessageListViewTest(const UnifiedMessageListViewTest&) = delete;
UnifiedMessageListViewTest& operator=(const UnifiedMessageListViewTest&) =
delete;
~UnifiedMessageListViewTest() override = default;
void SetUp() override {
AshTestBase::SetUp();
model_ = base::MakeRefCounted<UnifiedSystemTrayModel>(nullptr);
}
void TearDown() override {
message_list_view_.reset();
model_.reset();
AshTestBase::TearDown();
}
// views::ViewObserver:
void OnViewPreferredSizeChanged(views::View* view) override {
view->SetBoundsRect(gfx::Rect(view->GetPreferredSize()));
view->Layout();
++size_changed_count_;
}
protected:
std::string AddNotification(bool pinned = false, bool expandable = false) {
std::string id = base::NumberToString(id_++);
// Make the message long enough to be collapsible. Generated by SpaceIpsum.
auto notification = std::make_unique<Notification>(
message_center::NOTIFICATION_TYPE_BASE_FORMAT, id,
u"Message To Flight Control",
expandable ? u"From this day forward, Flight Control will be known by "
u"two words: "
u"‘Tough’ and ‘Competent.’ Tough means we are forever "
u"accountable for "
u"what we do or what we fail to do. We will never again "
u"compromise our "
u"responsibilities. Every time we walk into Mission "
u"Control we will "
u"know what we stand for. Competent means we will never "
u"take anything "
u"for granted. We will never be found short in our "
u"knowledge and in "
u"our skills. Mission Control will be perfect."
: u"Hey Flight Control, who brought donuts?",
gfx::Image(), std::u16string() /* display_source */, GURL(),
message_center::NotifierId(), message_center::RichNotificationData(),
new message_center::NotificationDelegate());
notification->set_pinned(pinned);
MessageCenter::Get()->AddNotification(std::move(notification));
return id;
}
void OffsetNotificationTimestamp(const std::string& id,
const int milliseconds) {
MessageCenter::Get()->FindVisibleNotificationById(id)->set_timestamp(
base::Time::Now() - base::Milliseconds(milliseconds));
}
void CreateMessageListView() {
message_list_view_ =
std::make_unique<TestUnifiedMessageListView>(model_.get());
message_list_view_->Init();
message_list_view_->AddObserver(this);
OnViewPreferredSizeChanged(message_list_view_.get());
size_changed_count_ = 0;
}
void DestroyMessageListView() { message_list_view_.reset(); }
TestNotificationView* GetMessageViewAt(size_t index) const {
return static_cast<TestNotificationView*>(
message_list_view()->children()[index]->children()[1]);
}
gfx::Rect GetMessageViewBounds(size_t index) const {
return message_list_view()->children()[index]->bounds();
}
void FinishSlideOutAnimation() { base::RunLoop().RunUntilIdle(); }
void AnimateToMiddle() {
EXPECT_TRUE(IsAnimating());
message_list_view()->animation_->SetCurrentValue(0.5);
message_list_view()->AnimationProgressed(
message_list_view()->animation_.get());
}
void AnimateToEnd() { message_list_view()->animation_->End(); }
void AnimateUntilIdle() {
while (message_list_view()->animation_->is_animating())
message_list_view()->animation_->End();
}
bool IsAnimating() { return message_list_view()->animation_->is_animating(); }
TestUnifiedMessageListView* message_list_view() const {
return message_list_view_.get();
}
int size_changed_count() const { return size_changed_count_; }
ui::LayerAnimator* LayerAnimatorAt(int i) {
return GetMessageViewAt(i)->layer()->GetAnimator();
}
private:
int id_ = 0;
int size_changed_count_ = 0;
scoped_refptr<UnifiedSystemTrayModel> model_;
std::unique_ptr<TestUnifiedMessageListView> message_list_view_;
};
// Tests with NotificationsRefresh enabled and disabled.
class ParameterizedUnifiedMessageListViewTest
: public UnifiedMessageListViewTest,
public testing::WithParamInterface<bool> {
public:
ParameterizedUnifiedMessageListViewTest() = default;
ParameterizedUnifiedMessageListViewTest(
const ParameterizedUnifiedMessageListViewTest&) = delete;
ParameterizedUnifiedMessageListViewTest& operator=(
const ParameterizedUnifiedMessageListViewTest&) = delete;
~ParameterizedUnifiedMessageListViewTest() override = default;
// AshTestBase:
void SetUp() override {
scoped_feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list_->InitWithFeatureState(features::kNotificationsRefresh,
IsNotificationsRefreshEnabled());
UnifiedMessageListViewTest::SetUp();
}
bool IsNotificationsRefreshEnabled() const { return GetParam(); }
private:
std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_;
};
INSTANTIATE_TEST_SUITE_P(All,
ParameterizedUnifiedMessageListViewTest,
testing::Bool() /* IsNotificationsRefreshEnabled() */);
TEST_P(ParameterizedUnifiedMessageListViewTest, Open) {
auto id0 = AddNotification();
auto id1 = AddNotification();
auto id2 = AddNotification();
CreateMessageListView();
EXPECT_EQ(3u, message_list_view()->children().size());
EXPECT_EQ(id0, GetMessageViewAt(0)->notification_id());
EXPECT_EQ(id1, GetMessageViewAt(1)->notification_id());
EXPECT_EQ(id2, GetMessageViewAt(2)->notification_id());
EXPECT_FALSE(GetMessageViewAt(0)->IsExpanded());
EXPECT_FALSE(GetMessageViewAt(1)->IsExpanded());
EXPECT_TRUE(GetMessageViewAt(2)->IsExpanded());
// Check the position of notifications within the list. When the new feature
// is enabled, we have extra spacing between notifications.
if (IsNotificationsRefreshEnabled()) {
EXPECT_EQ(
GetMessageViewBounds(0).bottom() + kMessageListNotificationSpacing,
GetMessageViewBounds(1).y());
EXPECT_EQ(
GetMessageViewBounds(1).bottom() + kMessageListNotificationSpacing,
GetMessageViewBounds(2).y());
} else {
EXPECT_EQ(GetMessageViewBounds(0).bottom(), GetMessageViewBounds(1).y());
EXPECT_EQ(GetMessageViewBounds(1).bottom(), GetMessageViewBounds(2).y());
}
EXPECT_EQ(0, GetMessageViewAt(0)->top_radius());
EXPECT_EQ(0, GetMessageViewAt(1)->top_radius());
EXPECT_EQ(0, GetMessageViewAt(2)->top_radius());
EXPECT_EQ(0, GetMessageViewAt(0)->bottom_radius());
EXPECT_EQ(0, GetMessageViewAt(1)->bottom_radius());
// Check rounded corners when the feature is not enabled (when the feature is
// enabled we round corners in the scroll view).
if (!IsNotificationsRefreshEnabled())
EXPECT_EQ(kBubbleCornerRadius, GetMessageViewAt(2)->bottom_radius());
EXPECT_LT(0, message_list_view()->GetPreferredSize().height());
}
TEST_P(ParameterizedUnifiedMessageListViewTest, AddNotifications) {
CreateMessageListView();
EXPECT_EQ(0, message_list_view()->GetPreferredSize().height());
auto id0 = AddNotification();
EXPECT_EQ(1, size_changed_count());
EXPECT_EQ(1u, message_list_view()->children().size());
EXPECT_EQ(id0, GetMessageViewAt(0)->notification_id());
// Check rounded corners when the feature is not enabled (when the feature is
// enabled we round corners in the scroll view).
if (!IsNotificationsRefreshEnabled()) {
EXPECT_EQ(kBubbleCornerRadius, GetMessageViewAt(0)->top_radius());
EXPECT_EQ(kBubbleCornerRadius, GetMessageViewAt(0)->bottom_radius());
}
int previous_message_list_view_height =
message_list_view()->GetPreferredSize().height();
EXPECT_LT(0, previous_message_list_view_height);
gfx::Rect previous_bounds = GetMessageViewBounds(0);
auto id1 = AddNotification();
EXPECT_EQ(2, size_changed_count());
EXPECT_EQ(2u, message_list_view()->children().size());
EXPECT_EQ(id1, GetMessageViewAt(1)->notification_id());
EXPECT_LT(previous_message_list_view_height,
message_list_view()->GetPreferredSize().height());
if (!IsNotificationsRefreshEnabled()) {
// 1dip larger because now it has separator border.
previous_bounds.Inset(gfx::Insets(0, 0, -1, 0));
}
EXPECT_EQ(previous_bounds, GetMessageViewBounds(0));
// When the new feature is enabled, we have extra spacing between
// notifications.
if (IsNotificationsRefreshEnabled()) {
EXPECT_EQ(
GetMessageViewBounds(0).bottom() + kMessageListNotificationSpacing,
GetMessageViewBounds(1).y());
} else {
EXPECT_EQ(GetMessageViewBounds(0).bottom(), GetMessageViewBounds(1).y());
}
// The top radius is zero because:
// - We round corners in the scroll view when the feature is enabled.
// - The stacking bar is shown when the feature is disabled.
EXPECT_EQ(0, GetMessageViewAt(0)->top_radius());
EXPECT_EQ(0, GetMessageViewAt(1)->top_radius());
EXPECT_EQ(0, GetMessageViewAt(0)->bottom_radius());
// Check rounded corners when the feature is not enabled (when the feature is
// enabled, we round corners in the scroll view).
if (!IsNotificationsRefreshEnabled())
EXPECT_EQ(kBubbleCornerRadius, GetMessageViewAt(1)->bottom_radius());
}
TEST_P(ParameterizedUnifiedMessageListViewTest, RemoveNotification) {
auto id0 = AddNotification();
auto id1 = AddNotification();
CreateMessageListView();
int previous_height = message_list_view()->GetPreferredSize().height();
EXPECT_EQ(2u, message_list_view()->children().size());
EXPECT_EQ(0, GetMessageViewAt(0)->top_radius());
EXPECT_EQ(0, GetMessageViewAt(0)->bottom_radius());
gfx::Rect previous_bounds = GetMessageViewBounds(0);
MessageCenter::Get()->RemoveNotification(id0, true /* by_user */);
FinishSlideOutAnimation();
AnimateUntilIdle();
EXPECT_EQ(1u, message_list_view()->children().size());
EXPECT_EQ(previous_bounds.y(), GetMessageViewBounds(0).y());
EXPECT_LT(0, message_list_view()->GetPreferredSize().height());
EXPECT_GT(previous_height, message_list_view()->GetPreferredSize().height());
// Check rounded corners when the feature is not enabled (when the feature is
// enabled, we round corners in the scroll view).
if (!IsNotificationsRefreshEnabled()) {
EXPECT_EQ(kBubbleCornerRadius, GetMessageViewAt(0)->top_radius());
EXPECT_EQ(kBubbleCornerRadius, GetMessageViewAt(0)->bottom_radius());
}
MessageCenter::Get()->RemoveNotification(id1, true /* by_user */);
FinishSlideOutAnimation();
AnimateUntilIdle();
EXPECT_EQ(0u, message_list_view()->children().size());
EXPECT_EQ(0, message_list_view()->GetPreferredSize().height());
}
TEST_P(ParameterizedUnifiedMessageListViewTest, CollapseOlderNotifications) {
AddNotification();
CreateMessageListView();
EXPECT_TRUE(GetMessageViewAt(0)->IsExpanded());
AddNotification();
EXPECT_FALSE(GetMessageViewAt(0)->IsExpanded());
EXPECT_TRUE(GetMessageViewAt(1)->IsExpanded());
AddNotification();
EXPECT_FALSE(GetMessageViewAt(0)->IsExpanded());
EXPECT_FALSE(GetMessageViewAt(1)->IsExpanded());
EXPECT_TRUE(GetMessageViewAt(2)->IsExpanded());
GetMessageViewAt(1)->SetExpanded(true);
GetMessageViewAt(1)->SetManuallyExpandedOrCollapsed(true);
AddNotification();
EXPECT_FALSE(GetMessageViewAt(0)->IsExpanded());
EXPECT_TRUE(GetMessageViewAt(1)->IsExpanded());
EXPECT_FALSE(GetMessageViewAt(2)->IsExpanded());
EXPECT_TRUE(GetMessageViewAt(3)->IsExpanded());
}
TEST_P(ParameterizedUnifiedMessageListViewTest, RemovingNotificationAnimation) {
auto id0 = AddNotification(/*pinned=*/false);
auto id1 = AddNotification();
auto id2 = AddNotification();
CreateMessageListView();
int previous_height = message_list_view()->GetPreferredSize().height();
gfx::Rect bounds0 = GetMessageViewBounds(0);
gfx::Rect bounds1 = GetMessageViewBounds(1);
MessageCenter::Get()->RemoveNotification(id1, true /* by_user */);
FinishSlideOutAnimation();
AnimateToEnd();
EXPECT_GT(previous_height, message_list_view()->GetPreferredSize().height());
previous_height = message_list_view()->GetPreferredSize().height();
if (!IsNotificationsRefreshEnabled()) {
// Now it lost separator border.
bounds1.Inset(gfx::Insets(0, 0, 1, 0));
}
EXPECT_EQ(bounds0, GetMessageViewBounds(0));
EXPECT_EQ(bounds1, GetMessageViewBounds(1));
MessageCenter::Get()->RemoveNotification(id2, true /* by_user */);
FinishSlideOutAnimation();
AnimateToEnd();
EXPECT_GT(previous_height, message_list_view()->GetPreferredSize().height());
previous_height = message_list_view()->GetPreferredSize().height();
if (!IsNotificationsRefreshEnabled()) {
// Now it lost separator border.
bounds0.Inset(gfx::Insets(0, 0, 1, 0));
}
EXPECT_EQ(bounds0, GetMessageViewBounds(0));
MessageCenter::Get()->RemoveNotification(id0, true /* by_user */);
FinishSlideOutAnimation();
AnimateToEnd();
EXPECT_GT(previous_height, message_list_view()->GetPreferredSize().height());
previous_height = message_list_view()->GetPreferredSize().height();
EXPECT_EQ(0, message_list_view()->GetPreferredSize().height());
}
TEST_P(ParameterizedUnifiedMessageListViewTest, ResetAnimation) {
auto id0 = AddNotification();
auto id1 = AddNotification();
CreateMessageListView();
MessageCenter::Get()->RemoveNotification(id0, true /* by_user */);
FinishSlideOutAnimation();
EXPECT_TRUE(IsAnimating());
AnimateToMiddle();
// New event resets the animation.
auto id2 = AddNotification();
EXPECT_FALSE(IsAnimating());
EXPECT_EQ(2u, message_list_view()->children().size());
EXPECT_EQ(id1, GetMessageViewAt(0)->notification_id());
EXPECT_EQ(id2, GetMessageViewAt(1)->notification_id());
}
TEST_P(ParameterizedUnifiedMessageListViewTest, KeepManuallyExpanded) {
AddNotification();
AddNotification();
CreateMessageListView();
EXPECT_FALSE(GetMessageViewAt(0)->IsExpanded());
EXPECT_TRUE(GetMessageViewAt(1)->IsExpanded());
EXPECT_FALSE(GetMessageViewAt(0)->IsManuallyExpandedOrCollapsed());
EXPECT_FALSE(GetMessageViewAt(1)->IsManuallyExpandedOrCollapsed());
// Manually expand the first notification & manually collapse the second one.
GetMessageViewAt(0)->SetExpanded(true);
GetMessageViewAt(0)->SetManuallyExpandedOrCollapsed(true);
GetMessageViewAt(1)->SetExpanded(false);
GetMessageViewAt(1)->SetManuallyExpandedOrCollapsed(true);
DestroyMessageListView();
// Reopen and confirm the expanded state & manually expanded flags are kept.
CreateMessageListView();
EXPECT_TRUE(GetMessageViewAt(0)->IsExpanded());
EXPECT_FALSE(GetMessageViewAt(1)->IsExpanded());
EXPECT_TRUE(GetMessageViewAt(0)->IsManuallyExpandedOrCollapsed());
EXPECT_TRUE(GetMessageViewAt(1)->IsManuallyExpandedOrCollapsed());
DestroyMessageListView();
// Add a new notification.
AddNotification();
CreateMessageListView();
// Confirm the new notification isn't affected & others are still kept.
EXPECT_TRUE(GetMessageViewAt(0)->IsExpanded());
EXPECT_FALSE(GetMessageViewAt(1)->IsExpanded());
EXPECT_TRUE(GetMessageViewAt(2)->IsExpanded());
EXPECT_TRUE(GetMessageViewAt(0)->IsManuallyExpandedOrCollapsed());
EXPECT_TRUE(GetMessageViewAt(1)->IsManuallyExpandedOrCollapsed());
EXPECT_FALSE(GetMessageViewAt(2)->IsManuallyExpandedOrCollapsed());
}
TEST_P(ParameterizedUnifiedMessageListViewTest,
ClearAllWithOnlyVisibleNotifications) {
AddNotification();
AddNotification();
CreateMessageListView();
EXPECT_EQ(2u, message_list_view()->children().size());
int previous_height = message_list_view()->GetPreferredSize().height();
gfx::Rect previous_bounds = GetMessageViewBounds(0);
message_list_view()->ClearAllWithAnimation();
AnimateToMiddle();
EXPECT_LT(previous_bounds.x(), GetMessageViewBounds(0).x());
EXPECT_EQ(previous_height, message_list_view()->GetPreferredSize().height());
AnimateToEnd();
EXPECT_EQ(1u, message_list_view()->children().size());
EXPECT_EQ(previous_height, message_list_view()->GetPreferredSize().height());
previous_bounds = GetMessageViewBounds(0);
AnimateToMiddle();
EXPECT_LT(previous_bounds.x(), GetMessageViewBounds(0).x());
EXPECT_GT(previous_height, message_list_view()->GetPreferredSize().height());
previous_height = message_list_view()->GetPreferredSize().height();
AnimateToEnd();
EXPECT_GT(previous_height, message_list_view()->GetPreferredSize().height());
previous_height = message_list_view()->GetPreferredSize().height();
EXPECT_EQ(0u, message_list_view()->children().size());
AnimateToMiddle();
EXPECT_GT(previous_height, message_list_view()->GetPreferredSize().height());
previous_height = message_list_view()->GetPreferredSize().height();
AnimateToEnd();
EXPECT_GT(previous_height, message_list_view()->GetPreferredSize().height());
EXPECT_EQ(0, message_list_view()->GetPreferredSize().height());
EXPECT_TRUE(MessageCenter::Get()->GetVisibleNotifications().empty());
EXPECT_FALSE(IsAnimating());
}
TEST_P(ParameterizedUnifiedMessageListViewTest,
ClearAllWithStackingNotifications) {
AddNotification();
AddNotification();
AddNotification();
CreateMessageListView();
message_list_view()->set_stacked_notification_count(2);
EXPECT_EQ(3u, message_list_view()->children().size());
message_list_view()->ClearAllWithAnimation();
EXPECT_EQ(2u, message_list_view()->children().size());
message_list_view()->set_stacked_notification_count(1);
int previous_height = message_list_view()->GetPreferredSize().height();
gfx::Rect previous_bounds = GetMessageViewBounds(1);
AnimateToMiddle();
EXPECT_EQ(previous_height, message_list_view()->GetPreferredSize().height());
EXPECT_EQ(previous_bounds, GetMessageViewBounds(1));
AnimateToEnd();
EXPECT_EQ(1u, message_list_view()->children().size());
message_list_view()->set_stacked_notification_count(0);
previous_height = message_list_view()->GetPreferredSize().height();
AnimateToMiddle();
EXPECT_EQ(previous_height, message_list_view()->GetPreferredSize().height());
AnimateToEnd();
EXPECT_EQ(1u, message_list_view()->children().size());
previous_bounds = GetMessageViewBounds(0);
AnimateToMiddle();
EXPECT_LT(previous_bounds.x(), GetMessageViewBounds(0).x());
AnimateToEnd();
EXPECT_EQ(0u, message_list_view()->children().size());
previous_height = message_list_view()->GetPreferredSize().height();
AnimateToMiddle();
EXPECT_GT(previous_height, message_list_view()->GetPreferredSize().height());
previous_height = message_list_view()->GetPreferredSize().height();
AnimateToEnd();
EXPECT_EQ(0u, message_list_view()->children().size());
EXPECT_GT(previous_height, message_list_view()->GetPreferredSize().height());
EXPECT_EQ(0, message_list_view()->GetPreferredSize().height());
EXPECT_FALSE(IsAnimating());
}
TEST_P(ParameterizedUnifiedMessageListViewTest, ClearAllClosedInTheMiddle) {
AddNotification();
AddNotification();
AddNotification();
CreateMessageListView();
message_list_view()->ClearAllWithAnimation();
AnimateToMiddle();
DestroyMessageListView();
EXPECT_TRUE(MessageCenter::Get()->GetVisibleNotifications().empty());
}
TEST_P(ParameterizedUnifiedMessageListViewTest, ClearAllInterrupted) {
AddNotification();
AddNotification();
AddNotification();
CreateMessageListView();
message_list_view()->ClearAllWithAnimation();
AnimateToMiddle();
auto new_id = AddNotification();
EXPECT_EQ(1u, MessageCenter::Get()->GetVisibleNotifications().size());
EXPECT_TRUE(MessageCenter::Get()->FindVisibleNotificationById(new_id));
}
TEST_P(ParameterizedUnifiedMessageListViewTest,
ClearAllWithPinnedNotifications) {
AddNotification(/*pinned=*/true);
AddNotification();
AddNotification();
CreateMessageListView();
message_list_view()->ClearAllWithAnimation();
AnimateUntilIdle();
EXPECT_EQ(1u, message_list_view()->children().size());
}
TEST_P(ParameterizedUnifiedMessageListViewTest, UserSwipesAwayNotification) {
// Show message list with two notifications.
AddNotification();
auto id1 = AddNotification();
CreateMessageListView();
// Start swiping the notification away.
GetMessageViewAt(1)->OnSlideStarted();
GetMessageViewAt(1)->OnSlideChanged(true);
EXPECT_EQ(2u, MessageCenter::Get()->GetVisibleNotifications().size());
EXPECT_EQ(2u, message_list_view()->children().size());
// Swiping away the notification should remove it both in the MessageCenter
// and the MessageListView.
MessageCenter::Get()->RemoveNotification(id1, true /* by_user */);
FinishSlideOutAnimation();
EXPECT_EQ(1u, MessageCenter::Get()->GetVisibleNotifications().size());
EXPECT_EQ(1u, message_list_view()->children().size());
// The next and only animation should be the move down animation.
int previous_height = message_list_view()->GetPreferredSize().height();
AnimateToEnd();
EXPECT_GT(previous_height, message_list_view()->GetPreferredSize().height());
EXPECT_FALSE(message_list_view()->IsAnimating());
}
TEST_P(ParameterizedUnifiedMessageListViewTest, InitInSortedOrder) {
// MessageViews should be ordered, from top down: [ id1, id2, id0 ].
auto id0 = AddNotification(/*pinned=*/true);
OffsetNotificationTimestamp(id0, 2000 /* milliseconds */);
auto id1 = AddNotification();
OffsetNotificationTimestamp(id1, 1000 /* milliseconds */);
auto id2 = AddNotification();
CreateMessageListView();
EXPECT_EQ(3u, message_list_view()->children().size());
EXPECT_EQ(id1, GetMessageViewAt(0)->notification_id());
EXPECT_EQ(id2, GetMessageViewAt(1)->notification_id());
EXPECT_EQ(id0, GetMessageViewAt(2)->notification_id());
}
TEST_P(ParameterizedUnifiedMessageListViewTest,
NotificationAddedInSortedOrder) {
auto id0 = AddNotification(/*pinned=*/true);
OffsetNotificationTimestamp(id0, 3000 /* milliseconds */);
auto id1 = AddNotification();
OffsetNotificationTimestamp(id1, 2000 /* milliseconds */);
auto id2 = AddNotification();
OffsetNotificationTimestamp(id2, 1000 /* milliseconds */);
CreateMessageListView();
// New pinned notification should be added to the end.
auto id3 = AddNotification(/*pinned=*/true);
EXPECT_EQ(4u, message_list_view()->children().size());
EXPECT_EQ(id3, GetMessageViewAt(3)->notification_id());
// New non-pinned notification should be added before pinned notifications.
auto id4 = AddNotification();
EXPECT_EQ(5u, message_list_view()->children().size());
EXPECT_EQ(id1, GetMessageViewAt(0)->notification_id());
EXPECT_EQ(id2, GetMessageViewAt(1)->notification_id());
EXPECT_EQ(id4, GetMessageViewAt(2)->notification_id());
EXPECT_EQ(id0, GetMessageViewAt(3)->notification_id());
EXPECT_EQ(id3, GetMessageViewAt(4)->notification_id());
}
// Tests only with NotificationsRefresh enabled.
class RefreshedUnifiedMessageListView : public UnifiedMessageListViewTest {
public:
RefreshedUnifiedMessageListView() = default;
RefreshedUnifiedMessageListView(const RefreshedUnifiedMessageListView&) =
delete;
RefreshedUnifiedMessageListView& operator=(
const RefreshedUnifiedMessageListView&) = delete;
~RefreshedUnifiedMessageListView() override = default;
void SetUp() override {
scoped_feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list_->InitAndEnableFeature(features::kNotificationsRefresh);
UnifiedMessageListViewTest::SetUp();
}
private:
std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_;
};
// Tests that preferred size changes upon toggle of expand/collapse.
TEST_F(RefreshedUnifiedMessageListView, PreferredSizeChangesOnToggle) {
AddNotification(/*pinned=*/false, /*expandable=*/true);
AddNotification(/*pinned=*/false, /*expandable=*/true);
CreateMessageListView();
auto* message_view = GetMessageViewAt(1);
ASSERT_TRUE(message_view->IsExpanded());
gfx::Size old_preferred_size =
message_list_view()->children()[1]->GetPreferredSize();
EXPECT_FALSE(IsAnimating());
message_view->SetExpanded(/*expanded=*/false);
EXPECT_TRUE(IsAnimating());
EXPECT_TRUE(message_list_view()->IsAnimatingExpandOrCollapseContainer(
message_list_view()->children()[1]));
EXPECT_EQ(old_preferred_size.height(),
message_list_view()->children()[1]->GetPreferredSize().height());
old_preferred_size = message_list_view()->children()[1]->GetPreferredSize();
AnimateToMiddle();
EXPECT_GT(old_preferred_size.height(),
message_list_view()->children()[1]->GetPreferredSize().height());
AnimateToEnd();
FinishSlideOutAnimation();
EXPECT_FALSE(IsAnimating());
EXPECT_FALSE(message_list_view()->IsAnimatingExpandOrCollapseContainer(
message_list_view()->children()[1]));
}
// Tests that expanding a notification while a different notification is
// expanding is handled gracefully.
TEST_F(RefreshedUnifiedMessageListView, TwoExpandsInARow) {
AddNotification(/*pinned=*/false, /*expandable=*/true);
AddNotification(/*pinned=*/false, /*expandable=*/true);
CreateMessageListView();
// First expand the notification in `first_notification_container`.
auto* first_notification_container = message_list_view()->children()[0];
auto* message_view = GetMessageViewAt(0);
ASSERT_FALSE(message_view->IsExpanded());
message_view->SetExpanded(/*expanded=*/true);
AnimateToMiddle();
const gfx::Size first_notification_middle_of_animation_size =
first_notification_container->GetPreferredSize();
// Collapse the second notification as `message_view` is still animating.
auto* second_notification_container = message_list_view()->children()[1];
const gfx::Size second_notification_initial_size =
second_notification_container->GetPreferredSize();
message_view = GetMessageViewAt(1);
message_view->SetExpanded(/*expanded=*/false);
EXPECT_TRUE(IsAnimating());
EXPECT_FALSE(message_list_view()->IsAnimatingExpandOrCollapseContainer(
first_notification_container));
EXPECT_TRUE(message_list_view()->IsAnimatingExpandOrCollapseContainer(
second_notification_container));
// The originally animating container should have been snapped to its final
// bounds.
EXPECT_LT(first_notification_middle_of_animation_size.height(),
first_notification_container->GetPreferredSize().height());
AnimateToEnd();
FinishSlideOutAnimation();
// `second_notification_container` should animate to its final bounds.
EXPECT_GT(second_notification_initial_size.height(),
second_notification_container->GetPreferredSize().height());
}
// Tests that collapsing/expanding is reversible.
TEST_F(RefreshedUnifiedMessageListView, ReverseExpand) {
AddNotification(/*pinned=*/false, /*expandable=*/true);
AddNotification(/*pinned=*/false, /*expandable=*/true);
CreateMessageListView();
auto* message_view = GetMessageViewAt(1);
auto* second_notification_container = message_list_view()->children()[1];
message_view->SetExpanded(/*expanded=*/false);
AnimateToMiddle();
const gfx::Size middle_of_collapsed_size =
second_notification_container->GetPreferredSize();
// Animate to expanded in the middle of the collapse animation. This should
// stop the collapse animation and set the view to its final bounds, then
// animate to expanded.
message_view->SetExpanded(/*expanded=*/true);
const gfx::Size final_collapsed_size =
second_notification_container->GetPreferredSize();
EXPECT_LT(final_collapsed_size.height(), middle_of_collapsed_size.height());
// Animate to the end. The container view should be fully expanded.
AnimateToEnd();
EXPECT_LT(middle_of_collapsed_size.height(),
second_notification_container->GetPreferredSize().height());
}
// Tests that destroying during a collapse animation does not crash.
TEST_F(RefreshedUnifiedMessageListView, DestroyMessageListViewDuringCollapse) {
AddNotification(/*pinned=*/false, /*expandable=*/true);
AddNotification(/*pinned=*/false, /*expandable=*/true);
CreateMessageListView();
auto* message_view = GetMessageViewAt(1);
message_view->SetExpanded(/*expanded=*/false);
AnimateToMiddle();
DestroyMessageListView();
}
// Tests that closing a notification while its collapse animation is ongoing
// works properly.
TEST_F(RefreshedUnifiedMessageListView, RemoveNotificationDuringCollapse) {
auto id1 = AddNotification(/*pinned=*/false, /*expandable=*/true);
CreateMessageListView();
auto* message_view = GetMessageViewAt(0);
message_view->SetExpanded(/*expanded=*/false);
AnimateToMiddle();
auto* notification_container = message_list_view()->children()[0];
const gfx::Size middle_of_collapsed_size =
notification_container->GetPreferredSize();
// Remove the notification for `message_view`. The view should snap to
// collapsed bounds, then slide out.
MessageCenter::Get()->RemoveNotification(id1, /*by_user=*/true);
EXPECT_LE(notification_container->GetPreferredSize().height(),
middle_of_collapsed_size.height());
FinishSlideOutAnimation();
AnimateUntilIdle();
EXPECT_EQ(0u, message_list_view()->children().size());
EXPECT_EQ(0, message_list_view()->GetPreferredSize().height());
}
// Tests that expanding a notification at various stages while it is being
// closed does not result in an animation.
TEST_F(RefreshedUnifiedMessageListView,
CollapseDuringCloseResultsInNoCollapseAnimation) {
auto id1 = AddNotification(/*pinned=*/false, /*expandable=*/true);
AddNotification(/*pinned=*/false, /*expandable=*/true);
CreateMessageListView();
auto* notification_container = message_list_view()->children()[0];
const gfx::Size pre_remove_size = notification_container->GetPreferredSize();
// Remove the notification, this should activate the "slide out" animation.
MessageCenter::Get()->RemoveNotification(id1, /*by_user=*/true);
EXPECT_EQ(notification_container->GetPreferredSize(), pre_remove_size);
// Removing the notification does not trigger an animation at the level of
// UnifiedMessageListView
EXPECT_FALSE(message_list_view()->IsAnimating());
EXPECT_FALSE(message_list_view()->IsAnimatingExpandOrCollapseContainer(
notification_container));
// Trigger the collapse before slide out completes, this should not trigger an
// animation for UnifiedMessageListView, and no animation should occur.
// SlideOut animation happens at a lower level. Also, size changes should be
// ignored when being removed.
GetMessageViewAt(0)->SetExpanded(/*expanded=*/false);
EXPECT_FALSE(message_list_view()->IsAnimating());
EXPECT_FALSE(message_list_view()->IsAnimatingExpandOrCollapseContainer(
notification_container));
EXPECT_EQ(pre_remove_size, notification_container->GetPreferredSize());
// Finish the slide out animation. Then an animation should begin to shrink
// MessageListView to contain the remaining notifications via
// State::MOVE_DOWN. Only one notification should remain.
FinishSlideOutAnimation();
EXPECT_TRUE(message_list_view()->IsAnimating());
EXPECT_EQ(1u, message_list_view()->children().size());
}
// Tests that collapsing a notification while it is being moved automatically
// completes both animations.
TEST_F(RefreshedUnifiedMessageListView, CollapseDuringMoveNoAnimation) {
auto to_be_removed_notification =
AddNotification(/*pinned=*/false, /*expandable=*/true);
auto to_be_collapsed_notification =
AddNotification(/*pinned=*/false, /*expandable=*/true);
CreateMessageListView();
auto* to_be_collapsed_message_view_container =
message_list_view()->children()[1];
auto* to_be_collapsed_message_view = GetMessageViewAt(1);
const gfx::Size pre_collapse_size =
to_be_collapsed_message_view_container->GetPreferredSize();
ASSERT_TRUE(to_be_collapsed_message_view->IsExpanded());
// Delete the first notification. This should begin the slide out animation.
// Let that finish, then State::MOVE_DOWN should begin.
MessageCenter::Get()->RemoveNotification(to_be_removed_notification,
/*by_user=*/true);
FinishSlideOutAnimation();
EXPECT_TRUE(message_list_view()->IsAnimating());
EXPECT_FALSE(message_list_view()->IsAnimatingExpandOrCollapseContainer(
to_be_collapsed_message_view_container));
// Animate to the middle, then attempt to collapse an existing notification.
// All animations should complete.
AnimateToMiddle();
to_be_collapsed_message_view->SetExpanded(false);
EXPECT_FALSE(message_list_view()->IsAnimating());
EXPECT_FALSE(message_list_view()->IsAnimatingExpandOrCollapseContainer(
to_be_collapsed_message_view_container));
EXPECT_GT(
pre_collapse_size.height(),
to_be_collapsed_message_view_container->GetPreferredSize().height());
}
// Tests that moving a notification while it is already collapsing completes
// both animations.
TEST_F(RefreshedUnifiedMessageListView, MoveDuringCollapseNoAnimation) {
auto to_be_removed_notification =
AddNotification(/*pinned=*/false, /*expandable=*/true);
auto to_be_collapsed_notification =
AddNotification(/*pinned=*/false, /*expandable=*/true);
CreateMessageListView();
auto* to_be_collapsed_message_view_container =
message_list_view()->children()[1];
auto* to_be_collapsed_message_view = GetMessageViewAt(1);
const gfx::Size pre_collapse_size =
to_be_collapsed_message_view_container->GetPreferredSize();
ASSERT_TRUE(to_be_collapsed_message_view->IsExpanded());
// Collapse the second notification, then delete the first.
to_be_collapsed_message_view->SetExpanded(false);
AnimateToMiddle();
EXPECT_TRUE(message_list_view()->IsAnimatingExpandOrCollapseContainer(
to_be_collapsed_message_view_container));
EXPECT_TRUE(message_list_view()->IsAnimating());
MessageCenter::Get()->RemoveNotification(to_be_removed_notification,
/*by_user=*/true);
EXPECT_FALSE(message_list_view()->IsAnimatingExpandOrCollapseContainer(
to_be_collapsed_message_view_container));
EXPECT_FALSE(message_list_view()->IsAnimating());
EXPECT_GT(
pre_collapse_size.height(),
to_be_collapsed_message_view_container->GetPreferredSize().height());
}
} // namespace ash