// 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/app_list/views/app_list_nudge_controller.h"
#include <memory>
#include "ash/app_list/app_list_controller_impl.h"
#include "ash/app_list/test/app_list_test_helper.h"
#include "ash/app_list/views/app_list_bubble_apps_page.h"
#include "ash/app_list/views/app_list_toast_container_view.h"
#include "ash/app_list/views/apps_container_view.h"
#include "ash/app_list/views/search_box_view.h"
#include "ash/public/cpp/app_list/app_list_types.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "base/functional/callback.h"
#include "base/test/task_environment.h"
#include "ui/display/screen.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/wm/core/window_util.h"
namespace ash {
namespace {
bool IsTabletMode() {
return display::Screen::GetScreen()->InTabletMode();
}
// Returns the number of times the nudge has been shown. Note that the count
// will be updated only when the nudge becomes invisible.
int GetReorderNudgeShownCount() {
PrefService* pref_service =
Shell::Get()->session_controller()->GetLastActiveUserPrefService();
return AppListNudgeController::GetShownCount(
pref_service, AppListNudgeController::NudgeType::kReorderNudge);
}
} // namespace
class AppListNudgeControllerTest : public AshTestBase {
public:
AppListNudgeControllerTest()
: AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
AppListNudgeControllerTest(const AppListNudgeControllerTest&) = delete;
AppListNudgeControllerTest& operator=(const AppListNudgeControllerTest&) =
delete;
~AppListNudgeControllerTest() override = default;
void SetUp() override {
AshTestBase::SetUp();
GetAppListTestHelper()->DisableAppListNudge(false);
}
AppListNudgeController* GetNudgeController() {
if (!IsTabletMode()) {
return GetAppListTestHelper()
->GetBubbleAppsPage()
->app_list_nudge_controller();
}
return GetAppListTestHelper()
->GetAppsContainerView()
->app_list_nudge_controller();
}
AppListToastContainerView* GetToastContainerView() {
if (!IsTabletMode()) {
return GetAppListTestHelper()
->GetBubbleAppsPage()
->toast_container_for_test();
}
return GetAppListTestHelper()->GetAppsContainerView()->toast_container();
}
// Show app list and wait long enough for the nudge to be considered shown.
void ShowAppListAndWait() {
Shell::Get()->app_list_controller()->ShowAppList(
AppListShowSource::kSearchKey);
task_environment()->AdvanceClock(base::Seconds(1));
}
void DismissAppList() { GetAppListTestHelper()->Dismiss(); }
};
TEST_F(AppListNudgeControllerTest, Basic) {
// Simulate a user login.
SimulateUserLogin("[email protected]");
// The reorder nudge should show 3 times to the users.
ShowAppListAndWait();
EXPECT_TRUE(GetToastContainerView()->IsToastVisible());
EXPECT_EQ(AppListToastType::kReorderNudge,
GetToastContainerView()->current_toast());
DismissAppList();
ShowAppListAndWait();
EXPECT_TRUE(GetToastContainerView()->IsToastVisible());
EXPECT_EQ(AppListToastType::kReorderNudge,
GetToastContainerView()->current_toast());
DismissAppList();
ShowAppListAndWait();
EXPECT_TRUE(GetToastContainerView()->IsToastVisible());
EXPECT_EQ(AppListToastType::kReorderNudge,
GetToastContainerView()->current_toast());
DismissAppList();
// After the fourth time opening the app list, the nudge should be removed.
ShowAppListAndWait();
EXPECT_FALSE(GetToastContainerView()->IsToastVisible());
EXPECT_EQ(AppListToastType::kNone, GetToastContainerView()->current_toast());
DismissAppList();
}
TEST_F(AppListNudgeControllerTest, StopShowingNudgeAfterReordering) {
// Simulate a user login.
SimulateUserLogin("[email protected]");
// The reorder nudge should show for the first time.
ShowAppListAndWait();
EXPECT_TRUE(GetToastContainerView()->IsToastVisible());
EXPECT_EQ(AppListToastType::kReorderNudge,
GetToastContainerView()->current_toast());
// Simulate that the app list is reordered by name.
Shell::Get()->app_list_controller()->UpdateAppListWithNewTemporarySortOrder(
AppListSortOrder::kNameAlphabetical, /*animate=*/false,
base::OnceClosure());
EXPECT_TRUE(GetToastContainerView()->IsToastVisible());
EXPECT_EQ(AppListToastType::kReorderUndo,
GetToastContainerView()->current_toast());
DismissAppList();
// If the app list was reordered, remove the nudge from the app list when the
// app list is opened next time.
ShowAppListAndWait();
EXPECT_EQ(GetNudgeController()->current_nudge(),
AppListNudgeController::NudgeType::kNone);
DismissAppList();
}
TEST_F(AppListNudgeControllerTest, TabletModeVisibilityTest) {
// Simulate a user login.
SimulateUserLogin("[email protected]");
ShowAppListAndWait();
EXPECT_TRUE(GetToastContainerView()->IsToastVisible());
EXPECT_EQ(AppListToastType::kReorderNudge,
GetToastContainerView()->current_toast());
// Change to tablet mode. The bubble app list is hidden and fullscreen app
// list is showing.
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
EXPECT_EQ(1, GetReorderNudgeShownCount());
EXPECT_TRUE(GetToastContainerView()->IsToastVisible());
EXPECT_EQ(AppListToastType::kReorderNudge,
GetToastContainerView()->current_toast());
// Wait for long enough for the nudge to be considered shown.
task_environment()->AdvanceClock(base::Seconds(1));
// Open a window to make the app list invisible. This will update the prefs in
// nudge controller.
std::unique_ptr<aura::Window> window = AshTestBase::CreateTestWindow();
wm::ActivateWindow(window.get());
EXPECT_EQ(2, GetReorderNudgeShownCount());
// Close the window and return back to app list.
window->Hide();
EXPECT_TRUE(GetToastContainerView()->IsToastVisible());
EXPECT_EQ(AppListToastType::kReorderNudge,
GetToastContainerView()->current_toast());
// Wait for long enough for the nudge to be considered shown.
task_environment()->AdvanceClock(base::Seconds(1));
// Activate the search box. The nudge will become inactive but the nudge view
// still exists.
auto* search_box = GetAppListTestHelper()->GetSearchBoxView();
search_box->SetSearchBoxActive(true, ui::EventType::kMousePressed);
// For the case where the nudge is visible but inactive, the count doesn't
// increment as the nudge is still visible.
EXPECT_EQ(2, GetReorderNudgeShownCount());
EXPECT_TRUE(GetToastContainerView()->IsToastVisible());
// Exit the search view. The nudge should be visible and active now.
search_box->SetSearchBoxActive(false, ui::EventType::kMousePressed);
EXPECT_TRUE(GetToastContainerView()->IsToastVisible());
EXPECT_EQ(AppListToastType::kReorderNudge,
GetToastContainerView()->current_toast());
// Change to tablet mode. The nudge should be removed when the next time the
// app list is shown.
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(false);
EXPECT_EQ(3, GetReorderNudgeShownCount());
ShowAppListAndWait();
EXPECT_FALSE(GetToastContainerView()->IsToastVisible());
EXPECT_EQ(AppListToastType::kNone, GetToastContainerView()->current_toast());
}
TEST_F(AppListNudgeControllerTest, ReorderNudgeDismissButton) {
// Simulate a user login.
SimulateUserLogin("[email protected]");
ShowAppListAndWait();
EXPECT_TRUE(GetToastContainerView()->IsToastVisible());
EXPECT_EQ(AppListToastType::kReorderNudge,
GetToastContainerView()->current_toast());
// Dismiss the reorder nudge and check that it is no longer visible.
auto* event_generator = GetEventGenerator();
event_generator->MoveMouseTo(GetToastContainerView()
->GetToastButton()
->GetBoundsInScreen()
.CenterPoint());
event_generator->ClickLeftButton();
EXPECT_FALSE(GetToastContainerView()->IsToastVisible());
// Close and reopen app list to make sure that the reorder nudge is no longer
// shown after being dismissed.
DismissAppList();
ShowAppListAndWait();
EXPECT_FALSE(GetToastContainerView()->IsToastVisible());
EXPECT_EQ(AppListToastType::kNone, GetToastContainerView()->current_toast());
}
TEST_F(AppListNudgeControllerTest, ReorderUndoCloseButton) {
// Simulate a user login.
SimulateUserLogin("[email protected]");
ShowAppListAndWait();
// Simulate that the app list is reordered by name and check that the reorder
// undo nudge is shown.
Shell::Get()->app_list_controller()->UpdateAppListWithNewTemporarySortOrder(
AppListSortOrder::kNameAlphabetical, /*animate=*/false,
base::OnceClosure());
EXPECT_TRUE(GetToastContainerView()->IsToastVisible());
EXPECT_EQ(AppListToastType::kReorderUndo,
GetToastContainerView()->current_toast());
GetToastContainerView()->GetWidget()->LayoutRootViewIfNecessary();
// Click the close button and check that the nudge is no longer visible.
auto* event_generator = GetEventGenerator();
event_generator->MoveMouseTo(GetToastContainerView()
->GetCloseButton()
->GetBoundsInScreen()
.CenterPoint());
event_generator->ClickLeftButton();
EXPECT_FALSE(GetToastContainerView()->IsToastVisible());
}
} // namespace ash