[go: up one dir, main page]

blob: 7ef592414befc76b51e8b912fab1942173a0f27d [file] [log] [blame]
// Copyright 2020 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 <deque>
#include <string>
#include <vector>
#include "ash/shelf/shelf.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/shell.h"
#include "ash/system/message_center/unified_message_center_bubble.h"
#include "ash/system/message_center/unified_message_center_view.h"
#include "ash/system/status_area_widget.h"
#include "ash/system/unified/unified_system_tray.h"
#include "base/scoped_observation.h"
#include "base/strings/string_util.h"
#include "base/test/bind.h"
#include "base/test/icu_test_util.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/ui/ash/assistant/assistant_test_mixin.h"
#include "chrome/browser/ui/ash/assistant/test_support/test_util.h"
#include "chrome/test/base/mixin_based_in_process_browser_test.h"
#include "chromeos/services/assistant/public/cpp/features.h"
#include "content/public/test/browser_test.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/aura/window.h"
#include "ui/events/test/event_generator.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/message_center_observer.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/message_center/views/notification_view.h"
#include "ui/views/controls/button/label_button.h"
namespace chromeos {
namespace assistant {
namespace {
using message_center::MessageCenter;
using message_center::MessageCenterObserver;
// Please remember to set auth token when *not* running in |kReplay| mode.
constexpr auto kMode = FakeS3Mode::kReplay;
// Update this when you introduce breaking changes to existing tests.
constexpr int kVersion = 1;
// Macros ----------------------------------------------------------------------
#define EXPECT_VISIBLE_NOTIFICATIONS_BY_PREFIXED_ID(prefix_) \
{ \
if (!FindVisibleNotificationsByPrefixedId(prefix_).empty()) { \
return; \
} \
MockMessageCenterObserver mock; \
base::ScopedObservation<MessageCenter, MessageCenterObserver> \
observation_{&mock}; \
observation_.Observe(MessageCenter::Get()); \
\
base::RunLoop run_loop; \
EXPECT_CALL(mock, OnNotificationAdded) \
.WillOnce( \
testing::Invoke([&run_loop](const std::string& notification_id) { \
if (!FindVisibleNotificationsByPrefixedId(prefix_).empty()) \
run_loop.QuitClosure().Run(); \
})); \
run_loop.Run(); \
}
// Helpers ---------------------------------------------------------------------
// Returns the status area widget.
ash::StatusAreaWidget* FindStatusAreaWidget() {
return ash::Shelf::ForWindow(ash::Shell::GetRootWindowForNewWindows())
->shelf_widget()
->status_area_widget();
}
// Returns the set of Assistant notifications (as indicated by application id).
message_center::NotificationList::Notifications FindAssistantNotifications() {
return MessageCenter::Get()->FindNotificationsByAppId("assistant");
}
// Returns the visible notification specified by |id|.
message_center::Notification* FindVisibleNotificationById(
const std::string& id) {
return MessageCenter::Get()->FindVisibleNotificationById(id);
}
// Returns visible notifications having id starting with |prefix|.
std::vector<message_center::Notification*> FindVisibleNotificationsByPrefixedId(
const std::string& prefix) {
std::vector<message_center::Notification*> notifications;
for (auto* notification : MessageCenter::Get()->GetVisibleNotifications()) {
if (base::StartsWith(notification->id(), prefix,
base::CompareCase::SENSITIVE)) {
notifications.push_back(notification);
}
}
return notifications;
}
// Returns the view for the specified |notification|.
message_center::MessageView* FindViewForNotification(
const message_center::Notification* notification) {
ash::UnifiedMessageCenterView* unified_message_center_view =
FindStatusAreaWidget()
->unified_system_tray()
->message_center_bubble()
->message_center_view();
std::vector<message_center::MessageView*> message_views;
FindDescendentsOfClass(unified_message_center_view, &message_views);
for (message_center::MessageView* message_view : message_views) {
if (message_view->notification_id() == notification->id())
return message_view;
}
return nullptr;
}
// Returns the action buttons for the specified |notification|.
std::vector<views::LabelButton*> FindActionButtonsForNotification(
const message_center::Notification* notification) {
auto* notification_view = FindViewForNotification(notification);
std::vector<views::LabelButton*> action_buttons;
FindDescendentsOfClass(notification_view, &action_buttons);
return action_buttons;
}
// Returns the label for the specified |notification| title.
// NOTE: This method assumes that the title string is unique from other strings
// displayed in the notification. This should be safe since we only use this API
// under controlled circumstances.
views::Label* FindTitleLabelForNotification(
const message_center::Notification* notification) {
std::vector<views::Label*> labels;
FindDescendentsOfClass(FindViewForNotification(notification), &labels);
for (auto* label : labels) {
if (label->GetText() == notification->title())
return label;
}
return nullptr;
}
// Performs a tap of the specified |view| and waits until the RunLoop idles.
void TapOnAndWait(const views::View* view) {
auto* root_window = view->GetWidget()->GetNativeWindow()->GetRootWindow();
ui::test::EventGenerator event_generator(root_window);
event_generator.MoveTouch(view->GetBoundsInScreen().CenterPoint());
event_generator.PressTouch();
event_generator.ReleaseTouch();
base::RunLoop().RunUntilIdle();
}
// Performs a tap of the specified |widget| and waits until the RunLoop idles.
void TapOnAndWait(const views::Widget* widget) {
aura::Window* root_window = widget->GetNativeWindow()->GetRootWindow();
ui::test::EventGenerator event_generator(root_window);
event_generator.MoveTouch(widget->GetWindowBoundsInScreen().CenterPoint());
event_generator.PressTouch();
event_generator.ReleaseTouch();
base::RunLoop().RunUntilIdle();
}
// Mocks -----------------------------------------------------------------------
class MockMessageCenterObserver
: public testing::NiceMock<MessageCenterObserver> {
public:
// MessageCenterObserver:
MOCK_METHOD(void,
OnNotificationAdded,
(const std::string& notification_id),
(override));
MOCK_METHOD(void,
OnNotificationUpdated,
(const std::string& notification_id),
(override));
};
} // namespace
// AssistantTimersBrowserTest --------------------------------------------------
class AssistantTimersBrowserTest : public MixinBasedInProcessBrowserTest {
public:
AssistantTimersBrowserTest() {
// TODO(b/190633242): enable sandbox in browser tests.
feature_list_.InitAndDisableFeature(
chromeos::assistant::features::kEnableLibAssistantSandbox);
}
AssistantTimersBrowserTest(const AssistantTimersBrowserTest&) = delete;
AssistantTimersBrowserTest& operator=(const AssistantTimersBrowserTest&) =
delete;
~AssistantTimersBrowserTest() override = default;
void ShowAssistantUi() {
if (!tester()->IsVisible())
tester()->PressAssistantKey();
}
AssistantTestMixin* tester() { return &tester_; }
private:
base::test::ScopedFeatureList feature_list_;
base::test::ScopedRestoreICUDefaultLocale locale_{"en_US"};
AssistantTestMixin tester_{&mixin_host_, this, embedded_test_server(), kMode,
kVersion};
};
// Tests -----------------------------------------------------------------------
// Timer notifications should be dismissed when disabling Assistant in settings.
// Flaky. See https://crbug.com/1196564.
IN_PROC_BROWSER_TEST_F(
AssistantTimersBrowserTest,
DISABLED_ShouldDismissTimerNotificationsWhenDisablingAssistant) {
tester()->StartAssistantAndWaitForReady();
ShowAssistantUi();
EXPECT_TRUE(tester()->IsVisible());
// Confirm no Assistant notifications are currently being shown.
EXPECT_TRUE(FindAssistantNotifications().empty());
// Start a timer for one minute.
tester()->SendTextQuery("Set a timer for 1 minute.");
// Check for a stable substring of the expected answers.
tester()->ExpectTextResponse("1 min.");
// Expect that an Assistant timer notification is now showing.
EXPECT_VISIBLE_NOTIFICATIONS_BY_PREFIXED_ID("assistant/timer");
// Disable Assistant.
tester()->SetAssistantEnabled(false);
base::RunLoop().RunUntilIdle();
// Confirm that our Assistant timer notification has been dismissed.
EXPECT_TRUE(FindAssistantNotifications().empty());
}
// Pressing the "STOP" action button in a timer notification should result in
// the timer being removed.
// Flaky. See https://crbug.com/1196564.
IN_PROC_BROWSER_TEST_F(AssistantTimersBrowserTest,
DISABLED_ShouldRemoveTimerWhenStoppingViaNotification) {
tester()->StartAssistantAndWaitForReady();
ShowAssistantUi();
EXPECT_TRUE(tester()->IsVisible());
// Confirm no Assistant notifications are currently being shown.
EXPECT_TRUE(FindAssistantNotifications().empty());
// Start a timer for five minutes.
tester()->SendTextQuery("Set a timer for 5 minutes");
tester()->ExpectTextResponse("5 min.");
// Tap status area widget (to show notifications in the Message Center).
TapOnAndWait(FindStatusAreaWidget());
// Confirm that an Assistant timer notification is now showing.
auto notifications = FindVisibleNotificationsByPrefixedId("assistant/timer");
ASSERT_EQ(1u, notifications.size());
// Find the action buttons for our notification.
// NOTE: We expect action buttons for "STOP" and "ADD 1 MIN".
auto action_buttons = FindActionButtonsForNotification(notifications.at(0));
EXPECT_EQ(2u, action_buttons.size());
// Tap the "CANCEL" action button in the notification.
EXPECT_EQ(u"CANCEL", action_buttons.at(1)->GetText());
TapOnAndWait(action_buttons.at(1));
ShowAssistantUi();
EXPECT_TRUE(tester()->IsVisible());
// Confirm that no timers exist anymore.
tester()->SendTextQuery("Show my timers");
tester()->ExpectAnyOfTheseTextResponses({
"It looks like you don't have any timers set at the moment.",
});
}
// Verifies that timer notifications are ticked at regular intervals.
IN_PROC_BROWSER_TEST_F(AssistantTimersBrowserTest,
ShouldTickNotificationsAtRegularIntervals) {
// Observe notifications.
MockMessageCenterObserver mock;
base::ScopedObservation<MessageCenter, MessageCenterObserver>
scoped_observation{&mock};
scoped_observation.Observe(MessageCenter::Get());
// Show Assistant UI (once ready).
tester()->StartAssistantAndWaitForReady();
ShowAssistantUi();
EXPECT_TRUE(tester()->IsVisible());
// Start a timer for five seconds.
tester()->SendTextQuery("Set a timer for 5 seconds");
// We're going to cache the time of the last notification update so that we
// can verify updates occur within an expected time frame.
base::Time last_update;
// Expect and wait for our five second timer notification to be created.
base::RunLoop notification_add_run_loop;
EXPECT_CALL(mock, OnNotificationAdded)
.WillRepeatedly(testing::Invoke([&](const std::string& notification_id) {
last_update = base::Time::Now();
// Tap status area widget (to show notifications in the Message Center).
TapOnAndWait(FindStatusAreaWidget());
// Assert that the notification has the expected title.
auto* notification = FindVisibleNotificationById(notification_id);
auto* title_label = FindTitleLabelForNotification(notification);
auto title = base::UTF16ToUTF8(title_label->GetText());
EXPECT_EQ("0:05", title);
// Allow test to proceed.
notification_add_run_loop.QuitClosure().Run();
}));
notification_add_run_loop.Run();
// We are going to assert that updates to our notification occur within an
// expected time frame, allowing a degree of tolerance to reduce flakiness.
constexpr auto kExpectedMillisBetweenUpdates = 1000;
constexpr auto kMillisBetweenUpdatesTolerance = 100;
// We're going to watch notification updates until 5 seconds past fire time.
std::deque<std::string> expected_titles = {"0:04", "0:03", "0:02", "0:01",
"0:00", "-0:01", "-0:02", "-0:03",
"-0:04", "-0:05"};
bool is_first_update = true;
auto* title_label =
FindTitleLabelForNotification(*FindAssistantNotifications().begin());
// Watch |title_label| and await all expected notification updates.
base::RunLoop notification_update_run_loop;
auto notification_update_subscription =
title_label->AddTextChangedCallback(base::BindLambdaForTesting([&]() {
base::Time now = base::Time::Now();
// Assert that the update was received within our expected time frame.
if (is_first_update) {
is_first_update = false;
// Our updates are synced to the nearest full second, meaning our
// first update can come anywhere from 1 ms to 1000 ms from the time
// our notification was shown.
EXPECT_LE((now - last_update).InMilliseconds(),
1000 + kMillisBetweenUpdatesTolerance);
} else {
// Consecutive updates must come regularly.
EXPECT_NEAR((now - last_update).InMilliseconds(),
kExpectedMillisBetweenUpdates,
kMillisBetweenUpdatesTolerance);
}
// Assert that the notification has the expected title.
auto title = base::UTF16ToUTF8(title_label->GetText());
EXPECT_EQ(expected_titles.front(), title);
// Update time of |last_update|.
last_update = now;
// When |expected_titles| is empty, our test is finished.
expected_titles.pop_front();
if (expected_titles.empty())
notification_update_run_loop.QuitClosure().Run();
}));
notification_update_run_loop.Run();
}
} // namespace assistant
} // namespace chromeos