// Copyright 2022 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/message_popup_animation_waiter.h"
#include "ash/system/notification_center/notification_center_test_api.h"
#include "ash/system/notification_center/notification_center_tray.h"
#include "ash/system/notification_center/views/ash_notification_expand_button.h"
#include "ash/system/notification_center/views/ash_notification_view.h"
#include "ash/test/ash_test_base.h"
#include "ash/test/ash_test_util.h"
#include "ash/test/pixel/ash_pixel_differ.h"
#include "ash/test/pixel/ash_pixel_test_init_params.h"
#include "base/test/scoped_feature_list.h"
#include "chromeos/constants/chromeos_features.h"
#include "ui/base/models/image_model.h"
#include "ui/compositor/layer.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/message_center/public/cpp/notifier_id.h"
#include "ui/message_center/views/message_popup_view.h"
#include "ui/message_center/views/notification_control_buttons_view.h"
namespace ash {
namespace {
constexpr char kShortTitleString[] = "Short Title";
constexpr char kMediumTitleString[] = "Test Notification's Multiline Title";
constexpr char kLongTitleString[] =
"Test Notification's Very Very Very Very Very Very Very Very Very Very "
"Very Very Very Very Very Very Very Very Very Very Very Very Very Very "
"Very Very Very Very Long Multiline Title";
constexpr char kLongMessageString[] =
"Test Notification's Very Very Very Very Very Very Very Very Very Very "
"Very Very Very Very Very Very Very Very Very Very Very Very Very Very "
"Very Very Very Very Long Message";
constexpr char kShortTitleScreenshot[] = "ash_notification_short_title";
constexpr char kMediumTitleScreenshot[] =
"ash_notification_multiline_medium_title";
constexpr char kLongTitleScreenshot[] = "ash_notification_multiline_long_title";
const ui::ImageModel test_green_icon = ui::ImageModel::FromImageSkia(
CreateSolidColorTestImage(gfx::Size(/*width=*/48, /*height=*/48),
SK_ColorGREEN));
std::string GetScreenshotName(const std::string& test_name, bool new_width) {
return test_name + (new_width ? "_new_width" : "_old_width");
}
} // namespace
class AshPixelTestBase : public AshTestBase {
public:
// AshTestBase:
std::optional<pixel_test::InitParams> CreatePixelTestInitParams()
const override {
return pixel_test::InitParams();
}
// AshTestBase:
void SetUp() override {
AshTestBase::SetUp();
test_api_ = std::make_unique<NotificationCenterTestApi>();
}
NotificationCenterTestApi* test_api() { return test_api_.get(); }
private:
std::unique_ptr<NotificationCenterTestApi> test_api_;
};
// Pixel tests for Chrome OS Notification views.
class AshNotificationViewPixelTest : public AshPixelTestBase,
public testing::WithParamInterface<bool> {
public:
bool IsNotificationWidthIncreaseEnabled() { return GetParam(); }
void SetUp() override {
scoped_feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list_->InitWithFeatureState(
chromeos::features::kNotificationWidthIncrease,
IsNotificationWidthIncreaseEnabled());
AshPixelTestBase::SetUp();
}
private:
std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_;
};
INSTANTIATE_TEST_SUITE_P(
All,
AshNotificationViewPixelTest,
/*IsNotificationWidthIncreaseEnabled()=*/testing::Bool());
// Tests that a notification's close button is visible when it is focused.
TEST_P(AshNotificationViewPixelTest, CloseButtonFocused) {
// Create a notification and open the notification center bubble to view it.
const auto id = test_api()->AddNotification();
test_api()->ToggleBubble();
// Verify that the close button is neither focused nor visible. Note that the
// close button, as a `views::ImageButton`, will actually be visible in the
// sense of `views::View::GetVisible()`, but its parent's `ui::Layer` will
// have an opacity of zero, making it visually invisible.
auto* notification_view = static_cast<AshNotificationView*>(
test_api()->GetNotificationViewForId(id));
auto* control_buttons_layer =
notification_view->GetControlButtonsView()->layer();
auto* close_button =
notification_view->GetControlButtonsView()->close_button();
EXPECT_EQ(control_buttons_layer->opacity(), 0);
EXPECT_FALSE(close_button->HasFocus());
// Move focus to the close button.
close_button->GetWidget()->widget_delegate()->SetCanActivate(true);
close_button->RequestFocus();
// Verify, with both an assertion and a pixel test, that the close button has
// focus and is visible.
EXPECT_TRUE(close_button->HasFocus());
EXPECT_EQ(control_buttons_layer->opacity(), 1);
EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
GetScreenshotName("close_button_focused",
IsNotificationWidthIncreaseEnabled()),
/*revision_number=*/2, notification_view));
}
// Regression test for http://b/267195370. Tests that a notification with no
// message has its title vertically centered in the collapsed state.
TEST_P(AshNotificationViewPixelTest, DISABLED_CollapsedNoMessage) {
// Create a notification with no message, and open the notification center
// bubble to view it.
const std::string id = test_api()->AddCustomNotification(
u"Notification title", u"", test_green_icon);
test_api()->ToggleBubble();
// Make sure the notification is collapsed.
auto* notification_view = static_cast<AshNotificationView*>(
test_api()->GetNotificationViewForId(id));
notification_view->SetExpanded(false);
ASSERT_FALSE(notification_view->IsExpanded());
// Verify with a pixel test that the notification's title is vertically
// centered.
EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
GetScreenshotName("collapsed_no_message",
IsNotificationWidthIncreaseEnabled()),
/*revision_number=*/3, notification_view));
}
// Tests that a progress notification does not have its title vertically
// centered in the collapsed state.
TEST_P(AshNotificationViewPixelTest, ProgressCollapsed) {
// Create a progress notification and open the notification center bubble to
// view it. Also add a second notification so that the progress notification
// is automatically in its collapsed state when the bubble is toggled.
const std::string id = test_api()->AddProgressNotification();
test_api()->AddNotification();
test_api()->ToggleBubble();
// Verify that the notification is collapsed.
auto* notification_view = static_cast<AshNotificationView*>(
test_api()->GetNotificationViewForId(id));
ASSERT_FALSE(notification_view->IsExpanded());
// Verify with a pixel test that the notification's title is not vertically
// centered.
EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
GetScreenshotName("progress_collapsed",
IsNotificationWidthIncreaseEnabled()),
/*revision_number=*/2, notification_view));
}
// Tests the control buttons UI for the case of a notification with just the
// close button.
TEST_P(AshNotificationViewPixelTest, CloseControlButton) {
// Generate a notification that should show just the close control button.
// Also toggle the notification bubble so that the notification doesn't
// disappear during the test.
const std::string id = test_api()->AddNotification();
test_api()->ToggleBubble();
// Hover the mouse over the notification so that the close control button is
// visible when taking a screenshot.
auto* notification_view = static_cast<AshNotificationView*>(
test_api()->GetNotificationViewForId(id));
GetEventGenerator()->MoveMouseTo(
notification_view->GetBoundsInScreen().CenterPoint(), /*count=*/10);
// Verify with a pixel test that the close control button is visible and has
// the proper placement.
EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
GetScreenshotName("close_control_button",
IsNotificationWidthIncreaseEnabled()),
/*revision_number=*/2, notification_view));
}
// Tests the control buttons UI for the case of a notification with both the
// settings and close buttons.
TEST_P(AshNotificationViewPixelTest, SettingsAndCloseControlButtons) {
// Generate a notification that should show both the settings and close
// control buttons. Also toggle the notification bubble so that the
// notification doesn't disappear during the test.
const std::string id = test_api()->AddNotificationWithSettingsButton();
test_api()->ToggleBubble();
// Hover the mouse over the notification so that the control buttons are
// visible when taking a screenshot.
auto* notification_view = static_cast<AshNotificationView*>(
test_api()->GetNotificationViewForId(id));
GetEventGenerator()->MoveMouseTo(
notification_view->GetBoundsInScreen().CenterPoint(), /*count=*/10);
// Verify with a pixel test that the control buttons are visible and have
// proper spacing between them.
EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
GetScreenshotName("settings_and_close_control_buttons",
IsNotificationWidthIncreaseEnabled()),
/*revision_number=*/2, notification_view));
}
// Tests the inline reply UI for AshNotificationView.
TEST_P(AshNotificationViewPixelTest, InlineReply) {
message_center::RichNotificationData rich_data;
message_center::ButtonInfo button_info(u"Reply");
button_info.placeholder = std::make_optional(u"Send Message");
rich_data.buttons.push_back(button_info);
const std::string id = test_api()->AddCustomNotification(
/*title=*/u"title", /*message=*/u"message", /*icon=*/ui::ImageModel(),
/*display_source=*/std::u16string(), /*url=*/GURL(),
/*notifier_id=*/message_center::NotifierId(),
/*optional_fields=*/rich_data);
test_api()->ToggleBubble();
auto* notification_view = static_cast<AshNotificationView*>(
test_api()->GetNotificationViewForId(id));
LeftClickOn(notification_view->GetActionButtonsForTest().front());
// Verify with a pixel test that the inline reply field is correctly drawn.
EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
GetScreenshotName("inline_reply_focused",
IsNotificationWidthIncreaseEnabled()),
/*revision_number=*/2, notification_view));
}
// Tests the focus ring for the expand button in AshNotificationView.
TEST_P(AshNotificationViewPixelTest, ExpandButtonFocusRing) {
const std::string id = test_api()->AddNotification();
test_api()->ToggleBubble();
auto* notification_view = views::AsViewClass<AshNotificationView>(
test_api()->GetNotificationViewForId(id));
while (!notification_view->expand_button_for_test()->HasFocus()) {
PressAndReleaseKey(ui::VKEY_TAB);
}
EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
GetScreenshotName("expand_button_focus_ring",
IsNotificationWidthIncreaseEnabled()),
/*revision_number=*/2, notification_view));
}
TEST_P(AshNotificationViewPixelTest, NotificationViewFocusRing) {
const std::string id = test_api()->AddNotification();
test_api()->ToggleBubble();
PressAndReleaseKey(ui::VKEY_TAB);
auto* notification_view = test_api()->GetNotificationViewForId(id);
ASSERT_TRUE(notification_view->HasFocus());
EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
GetScreenshotName("notification_view_focus_ring",
IsNotificationWidthIncreaseEnabled()),
/*revision_number=*/2, notification_view));
}
TEST_P(AshNotificationViewPixelTest, NotificationPopupFocusRing) {
const std::string id = test_api()->AddNotification();
// Wait until the notification popup shows.
MessagePopupAnimationWaiter(
GetPrimaryNotificationCenterTray()->popup_collection())
.Wait();
auto* notification_view = test_api()->GetPopupViewForId(id);
notification_view->message_view()->RequestFocus();
ASSERT_TRUE(notification_view->message_view()->HasFocus());
EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
GetScreenshotName("notification_popup_focus_ring",
IsNotificationWidthIncreaseEnabled()),
/*revision_number=*/2, notification_view));
}
// Tests that a notification's icon is sized and positioned correctly at
// different sizes.
class AshNotificationViewIconPixelTest
: public AshPixelTestBase,
public testing::WithParamInterface<std::tuple<int, bool>> {
public:
int GetIconSize() { return std::get<0>(GetParam()); }
bool IsNotificationWidthIncreaseEnabled() { return std::get<1>(GetParam()); }
// AshPixelTestBase:
void SetUp() override {
scoped_feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list_->InitWithFeatureState(
chromeos::features::kNotificationWidthIncrease,
IsNotificationWidthIncreaseEnabled());
AshPixelTestBase::SetUp();
}
private:
std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_;
};
INSTANTIATE_TEST_SUITE_P(
IconTest,
AshNotificationViewIconPixelTest,
/*GetIconSize()=*/
testing::Combine(testing::ValuesIn({
16,
32,
128,
512,
}),
/*IsNotificationWidthIncreaseEnabled()=*/testing::Bool()));
TEST_P(AshNotificationViewIconPixelTest, DISABLED_NotificationIcon) {
int size = GetIconSize();
// Create a notification with an icon with the given `size`.
const std::string id = test_api()->AddCustomNotification(
u"Notification title", u"Notification message",
ui::ImageModel::FromImageSkia(CreateSolidColorTestImage(
gfx::Size(/*width=*/size, /*height=*/size), SK_ColorGREEN)));
test_api()->ToggleBubble();
// Make sure the notification is expanded.
auto* notification_view = static_cast<AshNotificationView*>(
test_api()->GetNotificationViewForId(id));
ASSERT_TRUE(notification_view->IsExpanded());
// Verify with a pixel test that the notification's title is vertically
// centered.
EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
GetScreenshotName(base::StringPrintf("expanded_icon_size_%u", size),
IsNotificationWidthIncreaseEnabled()),
/*revision_number=*/3, notification_view));
notification_view->ToggleExpand();
ASSERT_FALSE(notification_view->IsExpanded());
notification_view = static_cast<AshNotificationView*>(
test_api()->GetNotificationViewForId(id));
// Verify with a pixel test that the notification's title is vertically
// centered.
EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
GetScreenshotName(base::StringPrintf("collapsed_icon_size_%u", size),
IsNotificationWidthIncreaseEnabled()),
/*revision_number=*/3, notification_view));
}
class AshNotificationViewTitlePixelTest
: public AshPixelTestBase,
public testing::WithParamInterface<
std::tuple<const char* /*notification title string*/,
bool /*notification width increase*/>> {
public:
const std::string GetTitle() { return std::get<0>(GetParam()); }
bool IsNotificationWidthIncreaseEnabled() { return std::get<1>(GetParam()); }
// AshPixelTestBase:
void SetUp() override {
scoped_feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list_->InitWithFeatureState(
chromeos::features::kNotificationWidthIncrease,
IsNotificationWidthIncreaseEnabled());
AshPixelTestBase::SetUp();
}
private:
std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_;
};
INSTANTIATE_TEST_SUITE_P(
TitleTest,
AshNotificationViewTitlePixelTest,
testing::Combine(/*GetTitle()=*/testing::ValuesIn({kShortTitleString,
kMediumTitleString,
kLongTitleString}),
/*IsNotificationWidthIncreaseEnabled()=*/testing::Bool()));
// Regression test for b/251686063. Tests that a notification with a medium
// length multiline title and an icon is correctly displayed. This string would
// not be displayed properly without the workaround implemented for b/251686063.
TEST_P(AshNotificationViewTitlePixelTest, DISABLED_NotificationTitleTest) {
// Create a notification with a multiline title and an icon.
const std::string title = GetTitle();
const std::string id = test_api()->AddCustomNotification(
base::UTF8ToUTF16(title), u"Notification Content", test_green_icon);
test_api()->ToggleBubble();
// Make sure the notification view exists and is visible.
message_center::MessageView* notification_view =
test_api()->GetNotificationViewForId(id);
ASSERT_TRUE(notification_view);
EXPECT_TRUE(notification_view->GetVisible());
// Compare pixels.
std::string screenshot_name;
if (title == kShortTitleString) {
screenshot_name = kShortTitleScreenshot;
} else if (title == kMediumTitleString) {
screenshot_name = kMediumTitleScreenshot;
} else {
screenshot_name = kLongTitleScreenshot;
}
EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
::ash::GetScreenshotName(screenshot_name,
IsNotificationWidthIncreaseEnabled()),
/*revision_number=*/3, notification_view));
}
class AshNotificationViewCollapsedLongTextPixelTest
: public AshPixelTestBase,
public testing::WithParamInterface<
std::tuple<bool /*whether there is an icon*/,
bool /*whether there is a settings control button*/,
bool /*notification width increase*/>> {
public:
bool HasIcon() { return std::get<0>(GetParam()); }
bool HasSettingsControlButton() { return std::get<1>(GetParam()); }
bool IsNotificationWidthIncreaseEnabled() { return std::get<2>(GetParam()); }
// AshPixelTestBase
void SetUp() override {
scoped_feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list_->InitWithFeatureState(
chromeos::features::kNotificationWidthIncrease,
IsNotificationWidthIncreaseEnabled());
AshPixelTestBase::SetUp();
}
private:
std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_;
};
INSTANTIATE_TEST_SUITE_P(
All,
AshNotificationViewCollapsedLongTextPixelTest,
testing::Combine(/*HasIcon()=*/testing::Bool(),
/*HasSettingsControlButton()=*/testing::Bool(),
/*IsNotificationWidthIncreaseEnabled()=*/testing::Bool()));
// Tests the spacing between long, elided title/message text content and the
// next element of the notification (either icon or expand/collapse button).
// Also parameterized by the presence/absence of the settings control button.
TEST_P(AshNotificationViewCollapsedLongTextPixelTest,
DISABLED_ElidedTextSpacing) {
// Generate a notification with a long title and message, and view it in the
// notification center. Also add a second notification so that the main
// notification is automatically in its collapsed state when the bubble is
// toggled.
message_center::RichNotificationData optional_fields;
if (HasSettingsControlButton()) {
optional_fields.settings_button_handler =
message_center::SettingsButtonHandler::DELEGATE;
}
const std::string id = test_api()->AddCustomNotification(
base::UTF8ToUTF16(std::string(kLongTitleString)),
base::UTF8ToUTF16(std::string(kLongMessageString)),
/*icon=*/HasIcon() ? test_green_icon : ui::ImageModel(),
/*display_source=*/u"", /*url=*/GURL(),
/*notifier_id=*/message_center::NotifierId(), optional_fields);
test_api()->AddNotification();
test_api()->ToggleBubble();
// Verify that the notification is collapsed.
auto* notification_view = static_cast<AshNotificationView*>(
test_api()->GetNotificationViewForId(id));
ASSERT_FALSE(notification_view->IsExpanded());
// Hover the mouse over the notification so that the control buttons are
// visible when taking a screenshot.
GetEventGenerator()->MoveMouseTo(
notification_view->GetBoundsInScreen().CenterPoint(), /*count=*/10);
// Verify the spacing with a pixel test.
EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
GetScreenshotName("elided_text_spacing",
IsNotificationWidthIncreaseEnabled()),
/*revision_number=*/3, notification_view));
}
} // namespace ash