// 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.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "ash/app_list/app_list_model_provider.h"
#include "ash/app_list/views/app_list_item_view.h"
#include "ash/app_list/views/apps_grid_view.h"
#include "ash/constants/ash_features.h"
#include "ash/drag_drop/drag_drop_controller.h"
#include "ash/public/cpp/accelerators.h"
#include "ash/public/cpp/app_list/app_list_model_delegate.h"
#include "ash/public/cpp/tablet_mode.h"
#include "ash/public/cpp/test/app_list_test_api.h"
#include "ash/public/cpp/test/shell_test_api.h"
#include "ash/shell.h"
#include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
#include "base/feature_list.h"
#include "base/files/file_util.h"
#include "base/functional/callback.h"
#include "base/run_loop.h"
#include "base/strings/safe_sprintf.h"
#include "base/test/metrics/histogram_tester.h"
#include "build/build_config.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/ash/app_list/app_list_client_impl.h"
#include "chrome/browser/ash/app_list/app_list_model_updater.h"
#include "chrome/browser/ash/app_list/app_list_syncable_service_factory.h"
#include "chrome/browser/ash/app_list/test/chrome_app_list_test_support.h"
#include "chrome/browser/ash/login/login_manager_test.h"
#include "chrome/browser/ash/login/test/login_manager_mixin.h"
#include "chrome/browser/ash/login/ui/user_adding_screen.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/services/app_service/public/cpp/icon_loader.h"
#include "content/public/test/browser_test.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkData.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkRefCnt.h"
#include "third_party/skia/include/encode/SkPngEncoder.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/test/layer_animation_stopped_waiter.h"
#include "ui/compositor/test/test_utils.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/views/test/views_test_utils.h"
#include "ui/views/widget/widget.h"
namespace {
using AcceleratorAction = ash::AcceleratorAction;
using MenuType = ash::AppListTestApi::MenuType;
using ReorderAnimationEndState = ash::AppListTestApi::ReorderAnimationEndState;
// An app manifest file's data. The app name and icon paths wait for filling.
constexpr char kManifestData[] =
R"({ )"
/**/ R"("description": "fake",)"
/**/ R"("name": "%s",)"
/**/ R"("manifest_version": 2,)"
/**/ R"("version": "0",)"
/**/ R"("icons": %s,)"
/**/ R"("app": { )"
/**/ R"("launch": {)"
/****/ R"("web_url": "https://www.google.com/")"
/****/ R"(})"
/**/ R"(})"
R"(})";
gfx::ImageSkia CreateImageSkia(int width, int height, SkColor color) {
SkBitmap bitmap;
bitmap.allocN32Pixels(width, height);
bitmap.eraseColor(color);
return gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
}
// Test app icon loader that generates icons for apps in tests. The app icons
// are monochromatic, and icon colors can be configured per app using
// `SetAppIconColor()`. By default, the loader will generate white icons.
class FakeIconLoader : public apps::IconLoader {
public:
FakeIconLoader() = default;
FakeIconLoader(const FakeIconLoader&) = delete;
FakeIconLoader& operator=(const FakeIconLoader&) = delete;
~FakeIconLoader() override = default;
void SetAppIconColor(const std::string& app_id, SkColor color) {
app_icon_colors_[app_id] = color;
}
std::unique_ptr<apps::IconLoader::Releaser> LoadIconFromIconKey(
const std::string& id,
const apps::IconKey& icon_key,
apps::IconType icon_type,
int32_t size_hint_in_dip,
bool allow_placeholder_icon,
apps::LoadIconCallback callback) override {
auto iv = std::make_unique<apps::IconValue>();
iv->icon_type = icon_type;
iv->uncompressed = CreateImageSkia(16, 16, GetIconColor(id, SK_ColorWHITE));
iv->is_placeholder_icon = false;
std::move(callback).Run(std::move(iv));
return nullptr;
}
private:
SkColor GetIconColor(const std::string& app_id, SkColor default_color) {
const auto& color_override = app_icon_colors_.find(app_id);
if (color_override == app_icon_colors_.end()) {
return default_color;
}
return color_override->second;
}
// Contains icon colors registered using `SetAppIconColor()`.
std::map<std::string, SkColor> app_icon_colors_;
};
} // namespace
class AppListSortBrowserTest : public extensions::ExtensionBrowserTest {
public:
AppListSortBrowserTest() = default;
AppListSortBrowserTest(const AppListSortBrowserTest&) = delete;
AppListSortBrowserTest& operator=(const AppListSortBrowserTest&) = delete;
~AppListSortBrowserTest() override = default;
protected:
void ReorderTopLevelAppsGridAndWaitForCompletion(ash::AppListSortOrder order,
MenuType menu_type) {
ReorderAnimationEndState actual_state;
app_list_test_api_.ReorderByMouseClickAtToplevelAppsGridMenu(
order, menu_type, event_generator_.get(),
/*target_state=*/ReorderAnimationEndState::kCompleted, &actual_state);
EXPECT_EQ(ReorderAnimationEndState::kCompleted, actual_state);
}
// Verifies that app list fade out animation is in progress then runs a
// closure to interrupt reorder.
void VerifyFadeOutInProgressAndRunInterruptClosure(
const std::vector<std::string>& expected_ids_in_order,
base::OnceClosure interrupt_closure) {
// Verify that there is active reorder animations.
EXPECT_TRUE(app_list_test_api_.HasAnyWaitingReorderDoneCallback());
// The app order does not change because the fade out animation is running.
EXPECT_EQ(GetAppIdsInOrdinalOrder(), expected_ids_in_order);
// Run the closure to interrupt the fade out animation.
std::move(interrupt_closure).Run();
}
// Registers a closure that turns on/off the tablet mode at the start of the
// fade out animation to interrupt app list reorder. `tablet_mode_enabled` is
// true when switching to the tablet mode; `tablet_mode_enabled` is false when
// switching to the clamshell mode. The tablet mode changes synchronously on
// animation start to avoid the race condition between the fade out animation
// completion and the task to change the tablet mode state.
// See https://crbug.com/1302924
void RegisterModeSwitchClosureOnFadeOutStarted(bool tablet_mode_enabled) {
auto switch_mode_closure = base::BindOnce(
[](bool tablet_mode_enabled) {
ash::ShellTestApi().SetTabletModeEnabledForTest(tablet_mode_enabled);
},
tablet_mode_enabled);
// NOTE: When the fade out animation is interrupted, the app order should
// not change.
app_list_test_api_.AddFadeOutAnimationStartClosure(base::BindOnce(
&AppListSortBrowserTest::VerifyFadeOutInProgressAndRunInterruptClosure,
weak_factory_.GetWeakPtr(),
/*expected_ids_in_order=*/GetAppIdsInOrdinalOrder(),
std::move(switch_mode_closure)));
}
// Returns a list of app ids following the ordinal increasing order.
std::vector<std::string> GetAppIdsInOrdinalOrder(
const std::initializer_list<std::string>& ids) {
AppListModelUpdater* model_updater =
test::GetModelUpdater(AppListClientImpl::GetInstance());
std::vector<std::string> copy_ids(ids);
std::sort(copy_ids.begin(), copy_ids.end(),
[model_updater](const std::string& id1, const std::string& id2) {
return model_updater->FindItem(id1)->position().LessThan(
model_updater->FindItem(id2)->position());
});
return copy_ids;
}
std::vector<std::string> GetAppIdsInOrdinalOrder() {
return GetAppIdsInOrdinalOrder({app1_id_, app2_id_, app3_id_});
}
// Similar to `GetAppIdsInOrdinalOrder()` but app ordinal positions are
// fetched from the app list syncable service rather than the model updater.
std::vector<std::string> GetAppIdsInPermanentOrdinalOrder() {
app_list::AppListSyncableService* app_list_syncable_service =
app_list::AppListSyncableServiceFactory::GetForProfile(profile());
std::vector<std::string> ids({app1_id_, app2_id_, app3_id_});
std::sort(
ids.begin(), ids.end(),
[app_list_syncable_service](const std::string& id1,
const std::string& id2) {
return app_list_syncable_service->GetSyncItem(id1)
->item_ordinal.LessThan(
app_list_syncable_service->GetSyncItem(id2)->item_ordinal);
});
return ids;
}
ash::AppListSortOrder GetPermanentSortingOrder() {
return static_cast<ash::AppListSortOrder>(
profile()->GetPrefs()->GetInteger(prefs::kAppListPreferredOrder));
}
// extensions::ExtensionBrowserTest:
void SetUpOnMainThread() override {
ExtensionBrowserTest::SetUpOnMainThread();
AppListClientImpl* client = AppListClientImpl::GetInstance();
ASSERT_TRUE(client);
client->UpdateProfile();
// Start in tablet mode.
ash::ShellTestApi().SetTabletModeEnabledForTest(true);
WaitForAppListTransitionAnimation();
// Ensure async callbacks are run.
base::RunLoop().RunUntilIdle();
// Shows the app list which is initially behind a window in tablet mode.
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
const int default_app_count = app_list_test_api_.GetTopListItemCount();
// Assume that there are two default apps.
ASSERT_EQ(2, app_list_test_api_.GetTopListItemCount());
apps::AppServiceProxyFactory::GetForProfile(profile())
->OverrideInnerIconLoaderForTesting(&icon_loader_);
app1_id_ = LoadExtension(test_data_dir_.AppendASCII("app1"))->id();
ASSERT_FALSE(app1_id_.empty());
SetTestAppIconColor(app1_id_, SK_ColorBLUE);
app2_id_ = LoadExtension(test_data_dir_.AppendASCII("app2"))->id();
ASSERT_FALSE(app2_id_.empty());
SetTestAppIconColor(app2_id_, SK_ColorRED);
app3_id_ = LoadExtension(test_data_dir_.AppendASCII("app3"))->id();
ASSERT_FALSE(app3_id_.empty());
SetTestAppIconColor(app3_id_, SK_ColorGREEN);
EXPECT_EQ(default_app_count + 3, app_list_test_api_.GetTopListItemCount());
event_generator_ = std::make_unique<ui::test::EventGenerator>(
ash::Shell::GetPrimaryRootWindow());
}
void TearDownOnMainThread() override {
app_list_test_api_.VerifyTopLevelItemVisibility();
extensions::ExtensionBrowserTest::TearDownOnMainThread();
}
void SetTestAppIconColor(const std::string& app_id, SkColor color) {
icon_loader_.SetAppIconColor(app_id, color);
// Force icon reload after setting the test color.
// We cannot call LoadAppIcon directly because we need to invalidate the
// icon color cache. So we use `IncrementIconVersion()` to remove the
// icon color cache entry and trigger icon loading.
test::GetModelUpdater(AppListClientImpl::GetInstance())
->FindItem(app_id)
->IncrementIconVersion();
}
// Helps to prevent flakiness due to conflicting animations (`AppListView`
// closing animation aborts already started bubble launcher sort animation
// indirectly via app list service layer).
void WaitForAppListTransitionAnimation() {
ui::LayerAnimationStoppedWaiter().Wait(
app_list_test_api_.GetAppListViewLayer());
}
ash::AppListTestApi app_list_test_api_;
std::string app1_id_;
std::string app2_id_;
std::string app3_id_;
std::unique_ptr<ui::test::EventGenerator> event_generator_;
FakeIconLoader icon_loader_;
base::WeakPtrFactory<AppListSortBrowserTest> weak_factory_{this};
};
// Verifies that the apps in the top level apps grid can be arranged in the
// alphabetical order or sorted by the apps' icon colors using the context menu
// in apps grid view.
// TODO(crbug.com/1267369): Also add a test that verifies the behavior in tablet
// mode.
IN_PROC_BROWSER_TEST_F(AppListSortBrowserTest, ContextMenuSortItemsInTopLevel) {
ash::ShellTestApi().SetTabletModeEnabledForTest(false);
WaitForAppListTransitionAnimation();
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForBubbleWindow(/*wait_for_opening_animation=*/true);
base::HistogramTester histograms;
ReorderTopLevelAppsGridAndWaitForCompletion(
ash::AppListSortOrder::kNameAlphabetical, MenuType::kAppListPageMenu);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
histograms.ExpectBucketCount(ash::kClamshellReorderActionHistogram,
ash::AppListSortOrder::kNameAlphabetical, 1);
histograms.ExpectTotalCount(ash::kAppListSortDiscoveryDurationAfterNudge, 1);
histograms.ExpectTotalCount(ash::kAppListSortDiscoveryDurationAfterActivation,
1);
ReorderTopLevelAppsGridAndWaitForCompletion(ash::AppListSortOrder::kColor,
MenuType::kAppListPageMenu);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app2_id_, app3_id_, app1_id_}));
histograms.ExpectBucketCount(ash::kClamshellReorderActionHistogram,
ash::AppListSortOrder::kColor, 1);
}
// Verifies that clearing pref order by moving an item works as expected.
IN_PROC_BROWSER_TEST_F(AppListSortBrowserTest, ClearPrefOrderByItemMove) {
ash::ShellTestApi().SetTabletModeEnabledForTest(false);
ash::ShellTestApi().drag_drop_controller()->SetDisableNestedLoopForTesting(
true);
WaitForAppListTransitionAnimation();
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForBubbleWindow(/*wait_for_opening_animation=*/true);
ReorderTopLevelAppsGridAndWaitForCompletion(
ash::AppListSortOrder::kNameAlphabetical, MenuType::kAppListPageMenu);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
EXPECT_EQ(ash::AppListToastType::kReorderUndo,
app_list_test_api_.GetToastType());
std::string app4_id = LoadExtension(test_data_dir_.AppendASCII("app4"))->id();
ASSERT_FALSE(app4_id.empty());
EXPECT_EQ(GetAppIdsInOrdinalOrder({app1_id_, app2_id_, app3_id_, app4_id}),
std::vector<std::string>({app1_id_, app2_id_, app3_id_, app4_id}));
EXPECT_EQ(ash::AppListToastType::kNone, app_list_test_api_.GetToastType());
EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical,
GetPermanentSortingOrder());
base::HistogramTester histograms;
app_list_test_api_.ReorderItemInRootByDragAndDrop(/*source_index=*/0,
/*target_index=*/1);
EXPECT_EQ(ash::AppListSortOrder::kCustom, GetPermanentSortingOrder());
histograms.ExpectBucketCount(ash::kClamshellPrefOrderClearActionHistogram,
ash::AppListOrderUpdateEvent::kItemMoved, 1);
}
// Verifies that the apps in a folder can be arranged in the alphabetical order
// or sorted by the apps' icon colors using the context menu in apps grid view.
// TODO(crbug.com/1267369): Also add a test that verifies the behavior in tablet
// mode.
// Flaky. See https://crbug.com/1423200
IN_PROC_BROWSER_TEST_F(AppListSortBrowserTest,
DISABLED_ContextMenuSortItemsInFolder) {
ash::ShellTestApi().SetTabletModeEnabledForTest(false);
WaitForAppListTransitionAnimation();
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForBubbleWindow(/*wait_for_opening_animation=*/true);
// Move apps to one folder.
const std::string folder_id =
app_list_test_api_.CreateFolderWithApps({app1_id_, app2_id_, app3_id_});
ReorderTopLevelAppsGridAndWaitForCompletion(
ash::AppListSortOrder::kNameAlphabetical, MenuType::kAppListPageMenu);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
ReorderTopLevelAppsGridAndWaitForCompletion(ash::AppListSortOrder::kColor,
MenuType::kAppListPageMenu);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app2_id_, app3_id_, app1_id_}));
}
// Verifies that the apps in the top level apps grid can be arranged in the
// alphabetical order or sorted by the apps' icon colors using the context menu
// in app list item view.
// TODO(crbug.com/1267369): Also add a test that verifies the behavior in tablet
// mode.
IN_PROC_BROWSER_TEST_F(AppListSortBrowserTest,
ContextMenuOnAppListItemSortItemsInTopLevel) {
ash::ShellTestApi().SetTabletModeEnabledForTest(false);
WaitForAppListTransitionAnimation();
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForBubbleWindow(/*wait_for_opening_animation=*/true);
ReorderTopLevelAppsGridAndWaitForCompletion(
ash::AppListSortOrder::kNameAlphabetical,
MenuType::kAppListNonFolderItemMenu);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
ReorderTopLevelAppsGridAndWaitForCompletion(
ash::AppListSortOrder::kColor, MenuType::kAppListNonFolderItemMenu);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app2_id_, app3_id_, app1_id_}));
}
// Verifies that the apps in a folder can be arranged in the alphabetical order
// or sorted by the apps' icon colors using the context menu in app list item
// view.
// TODO(crbug.com/1267369): Also add a test that verifies the behavior in tablet
// mode.
IN_PROC_BROWSER_TEST_F(AppListSortBrowserTest,
ContextMenuOnAppListItemSortItemsInFolder) {
ash::ShellTestApi().SetTabletModeEnabledForTest(false);
WaitForAppListTransitionAnimation();
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForBubbleWindow(/*wait_for_opening_animation=*/true);
// Move apps to one folder.
app_list_test_api_.CreateFolderWithApps({app1_id_, app2_id_, app3_id_});
views::test::RunScheduledLayout(app_list_test_api_.GetTopLevelAppsGridView());
ReorderTopLevelAppsGridAndWaitForCompletion(
ash::AppListSortOrder::kNameAlphabetical,
MenuType::kAppListNonFolderItemMenu);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
ReorderTopLevelAppsGridAndWaitForCompletion(
ash::AppListSortOrder::kColor, MenuType::kAppListNonFolderItemMenu);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app2_id_, app3_id_, app1_id_}));
}
// Verifies that the apps can be arranged in the alphabetical order or sorted by
// the apps' icon colors using the context menu on folder item view.
// TODO(crbug.com/1267369): Also add a test that verifies the behavior in tablet
// mode.
IN_PROC_BROWSER_TEST_F(AppListSortBrowserTest,
ContextMenuOnFolderItemSortItems) {
ash::ShellTestApi().SetTabletModeEnabledForTest(false);
WaitForAppListTransitionAnimation();
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForBubbleWindow(/*wait_for_opening_animation=*/true);
// Move apps to one folder.
app_list_test_api_.CreateFolderWithApps({app1_id_, app2_id_, app3_id_});
views::test::RunScheduledLayout(app_list_test_api_.GetTopLevelAppsGridView());
ReorderTopLevelAppsGridAndWaitForCompletion(
ash::AppListSortOrder::kNameAlphabetical,
MenuType::kAppListFolderItemMenu);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
ReorderTopLevelAppsGridAndWaitForCompletion(ash::AppListSortOrder::kColor,
MenuType::kAppListFolderItemMenu);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app2_id_, app3_id_, app1_id_}));
}
IN_PROC_BROWSER_TEST_F(AppListSortBrowserTest,
SortUsingContextMenuOnFolderChildViewClamshell) {
ash::ShellTestApi().SetTabletModeEnabledForTest(false);
WaitForAppListTransitionAnimation();
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForBubbleWindow(/*wait_for_opening_animation=*/true);
// Create an app list folder.
app_list_test_api_.CreateFolderWithApps({app1_id_, app2_id_});
ash::AppsGridView* top_level_grid =
app_list_test_api_.GetTopLevelAppsGridView();
views::test::RunScheduledLayout(top_level_grid);
// Click on the folder to open it.
base::RunLoop run_loop;
app_list_test_api_.SetFolderViewAnimationCallback(run_loop.QuitClosure());
ash::AppListItemView* folder_item_view =
app_list_test_api_.FindTopLevelFolderItemView();
ASSERT_TRUE(folder_item_view);
event_generator_->MoveMouseTo(
folder_item_view->GetBoundsInScreen().CenterPoint());
event_generator_->ClickLeftButton();
run_loop.Run();
ash::AppsGridView* folder_grid = app_list_test_api_.GetFolderAppsGridView();
EXPECT_TRUE(folder_grid->IsDrawn());
ReorderAnimationEndState actual_state;
app_list_test_api_.ReorderByMouseClickAtContextMenuInAppsGrid(
folder_grid, ash::AppListSortOrder::kNameAlphabetical,
MenuType::kAppListNonFolderItemMenu, event_generator_.get(),
/*target_state=*/ReorderAnimationEndState::kCompleted, &actual_state);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
EXPECT_FALSE(app_list_test_api_.GetFolderAppsGridView()->IsDrawn());
}
IN_PROC_BROWSER_TEST_F(AppListSortBrowserTest,
FolderNotClosedIfTemporarySortIsCommittedClamshell) {
ash::ShellTestApi().SetTabletModeEnabledForTest(false);
WaitForAppListTransitionAnimation();
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForBubbleWindow(/*wait_for_opening_animation=*/true);
// Create an app list folder.
const std::string folder_id =
app_list_test_api_.CreateFolderWithApps({app1_id_, app2_id_});
ash::AppsGridView* top_level_grid =
app_list_test_api_.GetTopLevelAppsGridView();
views::test::RunScheduledLayout(top_level_grid);
// Order apps grid to transition to temporary sort order.
ReorderTopLevelAppsGridAndWaitForCompletion(
ash::AppListSortOrder::kNameAlphabetical,
MenuType::kAppListFolderItemMenu);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
// Click on the folder item to open it.
base::RunLoop run_loop;
app_list_test_api_.SetFolderViewAnimationCallback(run_loop.QuitClosure());
ash::AppListItemView* folder_item_view =
app_list_test_api_.FindTopLevelFolderItemView();
ASSERT_TRUE(folder_item_view);
event_generator_->MoveMouseTo(
folder_item_view->GetBoundsInScreen().CenterPoint());
event_generator_->ClickLeftButton();
run_loop.Run();
ash::AppsGridView* folder_grid = app_list_test_api_.GetFolderAppsGridView();
EXPECT_TRUE(folder_grid->IsDrawn());
EXPECT_EQ(ash::AppListToastType::kReorderUndo,
app_list_test_api_.GetToastType());
// Rename folder to commit the sort order - verify that the folder remained
// open.
ash::AppListModelProvider::Get()->model()->delegate()->RequestFolderRename(
folder_id, "Test folder");
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
EXPECT_TRUE(app_list_test_api_.GetFolderAppsGridView()->IsDrawn());
EXPECT_EQ(ash::AppListToastType::kNone, app_list_test_api_.GetToastType());
}
IN_PROC_BROWSER_TEST_F(AppListSortBrowserTest,
SortUsingContextMenuOnFolderChildViewTablet) {
ash::ShellTestApi().SetTabletModeEnabledForTest(true);
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForAppListShowAnimation(/*is_bubble_window=*/false);
// Create an app list folder.
app_list_test_api_.CreateFolderWithApps({app1_id_, app2_id_});
ash::AppsGridView* top_level_grid =
app_list_test_api_.GetTopLevelAppsGridView();
views::test::RunScheduledLayout(top_level_grid);
// Click on the folder to open it.
base::RunLoop run_loop;
app_list_test_api_.SetFolderViewAnimationCallback(run_loop.QuitClosure());
ash::AppListItemView* folder_item_view =
app_list_test_api_.FindTopLevelFolderItemView();
ASSERT_TRUE(folder_item_view);
event_generator_->MoveMouseTo(
folder_item_view->GetBoundsInScreen().CenterPoint());
event_generator_->ClickLeftButton();
run_loop.Run();
ash::AppsGridView* folder_grid = app_list_test_api_.GetFolderAppsGridView();
EXPECT_TRUE(folder_grid->IsDrawn());
ReorderAnimationEndState actual_state;
app_list_test_api_.ReorderByMouseClickAtContextMenuInAppsGrid(
folder_grid, ash::AppListSortOrder::kNameAlphabetical,
MenuType::kAppListNonFolderItemMenu, event_generator_.get(),
/*target_state=*/ReorderAnimationEndState::kCompleted, &actual_state);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
EXPECT_FALSE(app_list_test_api_.GetFolderAppsGridView()->IsDrawn());
}
IN_PROC_BROWSER_TEST_F(AppListSortBrowserTest,
FolderNotClosedIfTemporarySortIsCommittedTablet) {
ash::ShellTestApi().SetTabletModeEnabledForTest(true);
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForAppListShowAnimation(/*is_bubble_window=*/false);
// Create an app list folder.
const std::string folder_id =
app_list_test_api_.CreateFolderWithApps({app1_id_, app2_id_});
ash::AppsGridView* top_level_grid =
app_list_test_api_.GetTopLevelAppsGridView();
views::test::RunScheduledLayout(top_level_grid);
// Order apps grid to transition to temporary sort order.
base::HistogramTester histograms;
ReorderTopLevelAppsGridAndWaitForCompletion(
ash::AppListSortOrder::kNameAlphabetical,
MenuType::kAppListFolderItemMenu);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
histograms.ExpectTotalCount(ash::kAppListSortDiscoveryDurationAfterNudge, 1);
// Click on the folder item to open it.
base::RunLoop run_loop;
app_list_test_api_.SetFolderViewAnimationCallback(run_loop.QuitClosure());
ash::AppListItemView* folder_item_view =
app_list_test_api_.FindTopLevelFolderItemView();
ASSERT_TRUE(folder_item_view);
event_generator_->MoveMouseTo(
folder_item_view->GetBoundsInScreen().CenterPoint());
event_generator_->ClickLeftButton();
run_loop.Run();
ash::AppsGridView* folder_grid = app_list_test_api_.GetFolderAppsGridView();
EXPECT_TRUE(folder_grid->IsDrawn());
EXPECT_EQ(ash::AppListToastType::kReorderUndo,
app_list_test_api_.GetToastType());
// Rename folder to commit the sort order - verify that the folder remained
// open.
ash::AppListModelProvider::Get()->model()->delegate()->RequestFolderRename(
folder_id, "Test folder");
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
EXPECT_TRUE(app_list_test_api_.GetFolderAppsGridView()->IsDrawn());
EXPECT_EQ(ash::AppListToastType::kNone, app_list_test_api_.GetToastType());
}
// Verify that starting a new reorder before the old animation completes works
// as expected.
IN_PROC_BROWSER_TEST_F(
AppListSortBrowserTest,
ContextMenuOnAppListItemSortItemsInTopLevelWithoutWaiting) {
ash::ShellTestApi().SetTabletModeEnabledForTest(false);
WaitForAppListTransitionAnimation();
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForBubbleWindow(/*wait_for_opening_animation=*/true);
// Verify the default app order.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app3_id_, app2_id_, app1_id_}));
// Trigger name alphabetical sorting.
ReorderAnimationEndState actual_state;
app_list_test_api_.ReorderByMouseClickAtToplevelAppsGridMenu(
ash::AppListSortOrder::kNameAlphabetical, MenuType::kAppListPageMenu,
event_generator_.get(),
/*target_state=*/ReorderAnimationEndState::kFadeOutAborted,
&actual_state);
// Verify that the app order does not change because the animation is ongoing.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app3_id_, app2_id_, app1_id_}));
// Trigger another reorder animation without waiting for the current one and
// wait until the new animation finishes. The previous animation should be
// aborted.
ReorderTopLevelAppsGridAndWaitForCompletion(ash::AppListSortOrder::kColor,
MenuType::kAppListPageMenu);
EXPECT_EQ(ReorderAnimationEndState::kFadeOutAborted, actual_state);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app2_id_, app3_id_, app1_id_}));
}
// Verify that deleting an item during reorder animation works as expected.
IN_PROC_BROWSER_TEST_F(AppListSortBrowserTest,
DeleteItemDuringReorderingAnimation) {
ash::ShellTestApi().SetTabletModeEnabledForTest(false);
WaitForAppListTransitionAnimation();
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForBubbleWindow(/*wait_for_opening_animation=*/true);
// Verify the default app order.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app3_id_, app2_id_, app1_id_}));
// Trigger name alphabetical sorting.
ReorderAnimationEndState actual_state;
app_list_test_api_.ReorderByMouseClickAtToplevelAppsGridMenu(
ash::AppListSortOrder::kNameAlphabetical, MenuType::kAppListPageMenu,
event_generator_.get(),
/*target_state=*/ReorderAnimationEndState::kFadeOutAborted,
&actual_state);
// Verify that the app order does not change because the animation is ongoing.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app3_id_, app2_id_, app1_id_}));
UninstallExtension(app3_id_);
base::RunLoop().RunUntilIdle();
// Uninstallation should abort the ongoing fade out animation.
EXPECT_EQ(ReorderAnimationEndState::kFadeOutAborted, actual_state);
AppListModelUpdater* model_updater =
test::GetModelUpdater(AppListClientImpl::GetInstance());
// Verify that `app3_id_` cannot be found from `model_updater_`.
EXPECT_FALSE(model_updater->FindItem(app3_id_));
// Note that the temporary sorting state ends when uninstalling an app.
// Therefore the remaining apps are placed following the alphabetical order.
EXPECT_TRUE(model_updater->FindItem(app2_id_)->position().GreaterThan(
model_updater->FindItem(app1_id_)->position()));
app_list_test_api_.VerifyTopLevelItemVisibility();
}
// Verifies that clicking at the reorder undo toast should revert the temporary
// sorting order in bubble launcher.
IN_PROC_BROWSER_TEST_F(AppListSortBrowserTest, UndoTemporarySortingClamshell) {
ash::ShellTestApi().SetTabletModeEnabledForTest(false);
WaitForAppListTransitionAnimation();
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForBubbleWindow(/*wait_for_opening_animation=*/true);
// Verify the default app order.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app3_id_, app2_id_, app1_id_}));
base::HistogramTester histograms;
ReorderTopLevelAppsGridAndWaitForCompletion(
ash::AppListSortOrder::kNameAlphabetical, MenuType::kAppListPageMenu);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
// Wait for one additional frame so that the metric data is collected.
ui::Compositor* compositor =
app_list_test_api_.GetTopLevelAppsGridView()->layer()->GetCompositor();
base::IgnoreResult(
ui::WaitForNextFrameToBePresented(compositor, base::Milliseconds(300)));
histograms.ExpectTotalCount(
ash::kClamshellReorderAnimationSmoothnessHistogram, 1);
// Ensure that the reorder undo toast's bounds update.
views::test::RunScheduledLayout(app_list_test_api_.GetTopLevelAppsGridView());
// The toast should be visible.
EXPECT_EQ(ash::AppListToastType::kReorderUndo,
app_list_test_api_.GetToastType());
app_list_test_api_.ClickOnRedoButtonAndWaitForAnimation(
event_generator_.get());
// Wait for the metric data to be collected.
base::IgnoreResult(
ui::WaitForNextFrameToBePresented(compositor, base::Milliseconds(300)));
// Smoothness of the reorder animation triggered by undo button is recorded.
histograms.ExpectTotalCount(
ash::kClamshellReorderAnimationSmoothnessHistogram, 2);
// Verify that the default app order is recovered.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app3_id_, app2_id_, app1_id_}));
// The toast should be hidden.
EXPECT_EQ(ash::AppListToastType::kNone, app_list_test_api_.GetToastType());
}
// Verifies that clicking at the reorder undo toast should revert the temporary
// sorting order in tablet mode.
IN_PROC_BROWSER_TEST_F(AppListSortBrowserTest, UndoTemporarySortingTablet) {
ash::ShellTestApi().SetTabletModeEnabledForTest(true);
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForAppListShowAnimation(/*is_bubble_window=*/false);
// Verify the default app order.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app3_id_, app2_id_, app1_id_}));
base::HistogramTester histograms;
ReorderTopLevelAppsGridAndWaitForCompletion(
ash::AppListSortOrder::kNameAlphabetical,
MenuType::kAppListNonFolderItemMenu);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
histograms.ExpectBucketCount(ash::kTabletReorderActionHistogram,
ash::AppListSortOrder::kNameAlphabetical, 1);
// The toast should be visible.
EXPECT_EQ(ash::AppListToastType::kReorderUndo,
app_list_test_api_.GetToastType());
// Wait for one additional frame so that the metric data is collected.
ui::Compositor* compositor =
app_list_test_api_.GetTopLevelAppsGridView()->layer()->GetCompositor();
base::IgnoreResult(
ui::WaitForNextFrameToBePresented(compositor, base::Milliseconds(300)));
histograms.ExpectTotalCount(ash::kTabletReorderAnimationSmoothnessHistogram,
1);
app_list_test_api_.ClickOnRedoButtonAndWaitForAnimation(
event_generator_.get());
// Verify that the default app order is recovered.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app3_id_, app2_id_, app1_id_}));
// The toast should be hidden.
EXPECT_EQ(ash::AppListToastType::kNone, app_list_test_api_.GetToastType());
// Wait for the metric data to be collected.
base::IgnoreResult(
ui::WaitForNextFrameToBePresented(compositor, base::Milliseconds(300)));
// Smoothness of the reorder animation triggered by undo button is recorded.
histograms.ExpectTotalCount(ash::kTabletReorderAnimationSmoothnessHistogram,
2);
}
IN_PROC_BROWSER_TEST_F(AppListSortBrowserTest, TransitionToTabletCommitsSort) {
ash::ShellTestApi().SetTabletModeEnabledForTest(false);
WaitForAppListTransitionAnimation();
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForBubbleWindow(/*wait_for_opening_animation=*/true);
// Verify the default app order.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app3_id_, app2_id_, app1_id_}));
ReorderTopLevelAppsGridAndWaitForCompletion(
ash::AppListSortOrder::kNameAlphabetical,
MenuType::kAppListNonFolderItemMenu);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
// Ensure that the reorder undo toast's bounds update.
views::test::RunScheduledLayout(app_list_test_api_.GetTopLevelAppsGridView());
// The toast should be visible.
EXPECT_EQ(ash::AppListToastType::kReorderUndo,
app_list_test_api_.GetToastType());
// Transition to tablet mode - verify that the fullscreen launcher does not
// have undo toast, and that the order of apps is still sorted.
ash::ShellTestApi().SetTabletModeEnabledForTest(true);
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForAppListShowAnimation(/*is_bubble_window=*/false);
EXPECT_EQ(ash::AppListToastType::kNone, app_list_test_api_.GetToastType());
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
// Transition back to clamshell, and verify the bubble launcher undo toast is
// now hidden.
ash::ShellTestApi().SetTabletModeEnabledForTest(false);
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForBubbleWindow(/*wait_for_opening_animation=*/true);
EXPECT_EQ(ash::AppListToastType::kNone, app_list_test_api_.GetToastType());
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
}
IN_PROC_BROWSER_TEST_F(AppListSortBrowserTest,
TransitionToClamshellCommitsSort) {
ash::ShellTestApi().SetTabletModeEnabledForTest(true);
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForAppListShowAnimation(/*is_bubble_window=*/false);
// Verify the default app order.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app3_id_, app2_id_, app1_id_}));
ReorderTopLevelAppsGridAndWaitForCompletion(
ash::AppListSortOrder::kNameAlphabetical,
MenuType::kAppListNonFolderItemMenu);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
// The toast should be visible.
EXPECT_EQ(ash::AppListToastType::kReorderUndo,
app_list_test_api_.GetToastType());
// Transition to clamshell mode - verify that the bubble launcher does not
// have undo toast, and that the order of apps is still sorted.
ash::ShellTestApi().SetTabletModeEnabledForTest(false);
WaitForAppListTransitionAnimation();
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForBubbleWindow(/*wait_for_opening_animation=*/true);
EXPECT_EQ(ash::AppListToastType::kNone, app_list_test_api_.GetToastType());
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
// Transition back to tablet mode, and verify the fullscreen launcher undo
// toast is now hidden.
ash::ShellTestApi().SetTabletModeEnabledForTest(true);
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForAppListShowAnimation(/*is_bubble_window=*/false);
EXPECT_EQ(ash::AppListToastType::kNone, app_list_test_api_.GetToastType());
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
}
// Verify that switching to tablet mode when the fade out animation in clamshell
// mode is running works as expected.
IN_PROC_BROWSER_TEST_F(AppListSortBrowserTest,
DISABLED_TransitionToTabletModeDuringFadeOutAnimation) {
ash::ShellTestApi().SetTabletModeEnabledForTest(false);
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForBubbleWindow(/*wait_for_opening_animation=*/true);
// Verify the default app order.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app3_id_, app2_id_, app1_id_}));
RegisterModeSwitchClosureOnFadeOutStarted(/*tablet_mode_enabled=*/true);
ReorderAnimationEndState actual_state;
app_list_test_api_.ReorderByMouseClickAtToplevelAppsGridMenu(
ash::AppListSortOrder::kNameAlphabetical, MenuType::kAppListPageMenu,
event_generator_.get(),
/*target_state=*/ReorderAnimationEndState::kFadeOutAborted,
&actual_state);
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForAppListShowAnimation(/*is_bubble_window=*/false);
// Verify that the reorder animation is aborted.
EXPECT_EQ(ReorderAnimationEndState::kFadeOutAborted, actual_state);
// When switching to the tablet mode, the app list is closed so the
// temporary sorting order should be committed.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
// Verify that reordering in tablet mode works.
base::HistogramTester histograms;
ReorderTopLevelAppsGridAndWaitForCompletion(
ash::AppListSortOrder::kColor, MenuType::kAppListNonFolderItemMenu);
histograms.ExpectBucketCount(ash::kTabletReorderActionHistogram,
ash::AppListSortOrder::kColor, 1);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app2_id_, app3_id_, app1_id_}));
}
// Verify that switching to clamshell mode when the fade out animation in tablet
// mode is running works as expected.
// TODO(crbug.com/40217187): Flaky.
IN_PROC_BROWSER_TEST_F(
AppListSortBrowserTest,
DISABLED_TransitionToClamshellModeDuringFadeOutAnimation) {
ash::ShellTestApi().SetTabletModeEnabledForTest(true);
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForAppListShowAnimation(/*is_bubble_window=*/false);
// Verify the default app order.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app3_id_, app2_id_, app1_id_}));
RegisterModeSwitchClosureOnFadeOutStarted(/*tablet_mode_enabled=*/false);
ReorderAnimationEndState actual_state;
app_list_test_api_.ReorderByMouseClickAtToplevelAppsGridMenu(
ash::AppListSortOrder::kNameAlphabetical,
MenuType::kAppListNonFolderItemMenu, event_generator_.get(),
/*target_state=*/ReorderAnimationEndState::kFadeOutAborted,
&actual_state);
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForBubbleWindow(/*wait_for_opening_animation=*/true);
// Verify that the reorder animation is aborted.
EXPECT_EQ(ReorderAnimationEndState::kFadeOutAborted, actual_state);
// Before switching to the tablet mode, the app list is closed so the
// temporary sorting order is committed.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
// Verify that reordering in tablet mode works.
ReorderTopLevelAppsGridAndWaitForCompletion(
ash::AppListSortOrder::kColor, MenuType::kAppListNonFolderItemMenu);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app2_id_, app3_id_, app1_id_}));
}
// Verify that switching to tablet mode when the fade in animation in clamshell
// is running works as expected.
IN_PROC_BROWSER_TEST_F(AppListSortBrowserTest,
TransitionToTabletModeDuringFadeInAnimation) {
ash::ShellTestApi().SetTabletModeEnabledForTest(false);
WaitForAppListTransitionAnimation();
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForBubbleWindow(/*wait_for_opening_animation=*/true);
// Verify the default app order.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app3_id_, app2_id_, app1_id_}));
ReorderAnimationEndState actual_state;
app_list_test_api_.ReorderByMouseClickAtToplevelAppsGridMenu(
ash::AppListSortOrder::kNameAlphabetical, MenuType::kAppListPageMenu,
event_generator_.get(),
/*target_state=*/ReorderAnimationEndState::kFadeInAborted, &actual_state);
// Verify that there is active reorder animations.
EXPECT_TRUE(app_list_test_api_.HasAnyWaitingReorderDoneCallback());
// The app order should change because the fade out animation ends.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
ash::ShellTestApi().SetTabletModeEnabledForTest(true);
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForAppListShowAnimation(/*is_bubble_window=*/false);
// Verify that the reorder animation is aborted.
EXPECT_EQ(ReorderAnimationEndState::kFadeInAborted, actual_state);
// When switching to the tablet mode, the app list is closed so the
// temporary sorting order should be committed.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
// Verify that reordering in tablet mode works.
ReorderTopLevelAppsGridAndWaitForCompletion(
ash::AppListSortOrder::kColor, MenuType::kAppListNonFolderItemMenu);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app2_id_, app3_id_, app1_id_}));
}
// Verifies that clicking at the toast close button to commit the temporary sort
// order works as expected.
IN_PROC_BROWSER_TEST_F(AppListSortBrowserTest,
CommitTemporaryOrderByClickingAtToastCloseButton) {
ash::ShellTestApi().SetTabletModeEnabledForTest(false);
WaitForAppListTransitionAnimation();
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForBubbleWindow(/*wait_for_opening_animation=*/true);
// Verify the default app order.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app3_id_, app2_id_, app1_id_}));
ReorderTopLevelAppsGridAndWaitForCompletion(
ash::AppListSortOrder::kNameAlphabetical,
MenuType::kAppListNonFolderItemMenu);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
// Before committing the temporary order, the permanent ordinal order should
// not change.
EXPECT_EQ(GetAppIdsInPermanentOrdinalOrder(),
std::vector<std::string>({app3_id_, app2_id_, app1_id_}));
// Commit the temporary order by clicking at the close button. Check that
// the permanent ordinal order changes accordingly.
app_list_test_api_.ClickOnCloseButtonAndWaitForToastAnimation(
event_generator_.get());
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
EXPECT_EQ(GetAppIdsInPermanentOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
}
// Verify that switching to clamshell mode when the fade in animation in tablet
// mode is running works as expected.
IN_PROC_BROWSER_TEST_F(AppListSortBrowserTest,
TransitionToClamshellModeDuringFadeInAnimation) {
ash::ShellTestApi().SetTabletModeEnabledForTest(true);
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForAppListShowAnimation(/*is_bubble_window=*/false);
// Verify the default app order.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app3_id_, app2_id_, app1_id_}));
ReorderAnimationEndState actual_state;
app_list_test_api_.ReorderByMouseClickAtToplevelAppsGridMenu(
ash::AppListSortOrder::kNameAlphabetical,
MenuType::kAppListNonFolderItemMenu, event_generator_.get(),
/*target_state=*/ReorderAnimationEndState::kFadeInAborted, &actual_state);
// Verify that there is active reorder animations.
EXPECT_TRUE(app_list_test_api_.HasAnyWaitingReorderDoneCallback());
// The app order should change because the fade out animation ends.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
ash::ShellTestApi().SetTabletModeEnabledForTest(false);
WaitForAppListTransitionAnimation();
EXPECT_NE(ReorderAnimationEndState::kFadeOutAborted, actual_state);
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForBubbleWindow(/*wait_for_opening_animation=*/true);
// When switching out of the tablet mode, the tablet mode app list gets
// closed so the temporary sorting order is committed.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
ReorderTopLevelAppsGridAndWaitForCompletion(
ash::AppListSortOrder::kColor, MenuType::kAppListNonFolderItemMenu);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app2_id_, app3_id_, app1_id_}));
}
// Verify that switching to clamshell mode when the fade in animation in tablet
// mode is running, and gets aborted during tablet mode transition works as
// expected.
IN_PROC_BROWSER_TEST_F(AppListSortBrowserTest,
TransitionToClamshellModeDuringAbortedFadeInAnimation) {
ash::TabletModeControllerTestApi().EnterTabletMode();
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForAppListShowAnimation(/*is_bubble_window=*/false);
// Verify the default app order.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app3_id_, app2_id_, app1_id_}));
ReorderAnimationEndState actual_state;
app_list_test_api_.ReorderByMouseClickAtToplevelAppsGridMenu(
ash::AppListSortOrder::kNameAlphabetical,
MenuType::kAppListNonFolderItemMenu, event_generator_.get(),
/*target_state=*/ReorderAnimationEndState::kFadeInAborted, &actual_state);
// Verify that there is active reorder animations.
EXPECT_TRUE(app_list_test_api_.HasAnyWaitingReorderDoneCallback());
// The app order should change because the fade out animation ends.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
ash::TabletModeControllerTestApi().LeaveTabletMode();
// Progress tablet mode animation to the end before item fade in animation
// completes - this should hide the tablet mode app list and abort the fade in
// aniamtion.
app_list_test_api_.GetAppListViewLayer()->GetAnimator()->StopAnimating();
EXPECT_EQ(ReorderAnimationEndState::kFadeInAborted, actual_state);
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForBubbleWindow(/*wait_for_opening_animation=*/true);
// When switching out of the tablet mode, the tablet mode app list gets
// closed so the temporary sorting order is committed.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
ReorderTopLevelAppsGridAndWaitForCompletion(
ash::AppListSortOrder::kColor, MenuType::kAppListNonFolderItemMenu);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app2_id_, app3_id_, app1_id_}));
}
// Verify that in clamshell interrupting a fade out animation by starting
// another reorder animation works as expected.
IN_PROC_BROWSER_TEST_F(AppListSortBrowserTest,
InterruptReorderFadeOutAnimationClamshellMode) {
ash::ShellTestApi().SetTabletModeEnabledForTest(false);
WaitForAppListTransitionAnimation();
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForBubbleWindow(/*wait_for_opening_animation=*/true);
// Verify the default app order.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app3_id_, app2_id_, app1_id_}));
ReorderAnimationEndState actual_state;
app_list_test_api_.ReorderByMouseClickAtToplevelAppsGridMenu(
ash::AppListSortOrder::kNameAlphabetical,
MenuType::kAppListNonFolderItemMenu, event_generator_.get(),
/*target_state=*/ReorderAnimationEndState::kFadeOutAborted,
&actual_state);
// Verify that there is active reorder animations.
EXPECT_TRUE(app_list_test_api_.HasAnyWaitingReorderDoneCallback());
// The app order does not change because the fade out animation is running.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app3_id_, app2_id_, app1_id_}));
// Trigger another app list reorder.
ReorderTopLevelAppsGridAndWaitForCompletion(
ash::AppListSortOrder::kColor, MenuType::kAppListNonFolderItemMenu);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app2_id_, app3_id_, app1_id_}));
// Verify that the previous reorder animation is aborted.
EXPECT_EQ(ReorderAnimationEndState::kFadeOutAborted, actual_state);
}
// Verify that in tablet interrupting a fade out animation by starting another
// reorder animation works as expected.
IN_PROC_BROWSER_TEST_F(AppListSortBrowserTest,
InterruptReorderFadeOutAnimationTabletMode) {
ash::ShellTestApi().SetTabletModeEnabledForTest(true);
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForAppListShowAnimation(/*is_bubble_window=*/false);
// Verify the default app order.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app3_id_, app2_id_, app1_id_}));
ReorderAnimationEndState actual_state;
app_list_test_api_.ReorderByMouseClickAtToplevelAppsGridMenu(
ash::AppListSortOrder::kNameAlphabetical,
MenuType::kAppListNonFolderItemMenu, event_generator_.get(),
/*target_state=*/ReorderAnimationEndState::kFadeOutAborted,
&actual_state);
// Verify that there is active reorder animations.
EXPECT_TRUE(app_list_test_api_.HasAnyWaitingReorderDoneCallback());
// The app order does not change because the fade out animation is running.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app3_id_, app2_id_, app1_id_}));
// Trigger another app list reorder.
ReorderTopLevelAppsGridAndWaitForCompletion(
ash::AppListSortOrder::kColor, MenuType::kAppListNonFolderItemMenu);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app2_id_, app3_id_, app1_id_}));
// Verify that the previous reorder animation is aborted.
EXPECT_EQ(ReorderAnimationEndState::kFadeOutAborted, actual_state);
}
// Verify that in clamshell interrupting a fade in animation by starting another
// reorder animation works as expected.
IN_PROC_BROWSER_TEST_F(AppListSortBrowserTest,
InterruptReorderFadeInAnimationClamshellMode) {
ash::ShellTestApi().SetTabletModeEnabledForTest(false);
WaitForAppListTransitionAnimation();
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForBubbleWindow(/*wait_for_opening_animation=*/true);
// Verify the default app order.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app3_id_, app2_id_, app1_id_}));
ReorderAnimationEndState actual_state;
app_list_test_api_.ReorderByMouseClickAtToplevelAppsGridMenu(
ash::AppListSortOrder::kNameAlphabetical,
MenuType::kAppListNonFolderItemMenu, event_generator_.get(),
/*target_state=*/ReorderAnimationEndState::kFadeInAborted, &actual_state);
// Verify that there is active reorder animations.
EXPECT_TRUE(app_list_test_api_.HasAnyWaitingReorderDoneCallback());
// The app order should change because the fade out animation ends.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
// Trigger another app list reorder.
ReorderTopLevelAppsGridAndWaitForCompletion(
ash::AppListSortOrder::kColor, MenuType::kAppListNonFolderItemMenu);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app2_id_, app3_id_, app1_id_}));
// Verify that the previous reorder animation is aborted.
EXPECT_EQ(ReorderAnimationEndState::kFadeInAborted, actual_state);
}
// Verify that in tablet interrupting a fade in animation by starting another
// reorder animation works as expected.
IN_PROC_BROWSER_TEST_F(AppListSortBrowserTest,
InterruptReorderFadeInAnimationTabletMode) {
ash::ShellTestApi().SetTabletModeEnabledForTest(true);
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForAppListShowAnimation(/*is_bubble_window=*/false);
// Verify the default app order.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app3_id_, app2_id_, app1_id_}));
ReorderAnimationEndState actual_state;
app_list_test_api_.ReorderByMouseClickAtToplevelAppsGridMenu(
ash::AppListSortOrder::kNameAlphabetical,
MenuType::kAppListNonFolderItemMenu, event_generator_.get(),
/*target_state=*/ReorderAnimationEndState::kFadeInAborted, &actual_state);
// Verify that there is active reorder animations.
EXPECT_TRUE(app_list_test_api_.HasAnyWaitingReorderDoneCallback());
// The app order should change because the fade out animation ends.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app1_id_, app2_id_, app3_id_}));
// Trigger another app list reorder.
ReorderTopLevelAppsGridAndWaitForCompletion(
ash::AppListSortOrder::kColor, MenuType::kAppListNonFolderItemMenu);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app2_id_, app3_id_, app1_id_}));
// Verify that the previous reorder animation is aborted.
EXPECT_EQ(ReorderAnimationEndState::kFadeInAborted, actual_state);
}
// Verifies that changing an app's icon under color sort works as expected.
IN_PROC_BROWSER_TEST_F(AppListSortBrowserTest, SetIconUnderColorSort) {
ash::ShellTestApi().SetTabletModeEnabledForTest(false);
WaitForAppListTransitionAnimation();
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForBubbleWindow(/*wait_for_opening_animation=*/true);
ReorderTopLevelAppsGridAndWaitForCompletion(
ash::AppListSortOrder::kColor, MenuType::kAppListNonFolderItemMenu);
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app2_id_, app3_id_, app1_id_}));
// Set the app 3's icon color to be black.
auto* model_updater = test::GetModelUpdater(AppListClientImpl::GetInstance());
const syncer::StringOrdinal position_before_setting_black =
model_updater->FindItem(app3_id_)->position();
SetTestAppIconColor(app3_id_, SK_ColorBLACK);
const syncer::StringOrdinal position_after_setting_black =
model_updater->FindItem(app3_id_)->position();
// Verify that the color order is still maintained.
EXPECT_EQ(GetAppIdsInOrdinalOrder(),
std::vector<std::string>({app2_id_, app1_id_, app3_id_}));
// Verify that the app 3's position changes.
EXPECT_FALSE(
position_after_setting_black.Equals(position_before_setting_black));
// Set the app 3's icon color to be magenta.
const std::vector<const ChromeAppListItem*> items_before_setting_magenta =
model_updater->GetItems();
SetTestAppIconColor(app3_id_, SK_ColorMAGENTA);
// Verify that there is no position changes. Because after setting the app3
// should still be placed at the end.
EXPECT_EQ(items_before_setting_magenta.size(), model_updater->ItemCount());
for (const ChromeAppListItem* item : items_before_setting_magenta) {
EXPECT_EQ(item->position(),
model_updater->FindItem(item->id())->position());
}
// Set the app 1's icon color to be white. But the icon is labeled as a
// placeholder.
const std::vector<const ChromeAppListItem*> items_before_setting_white =
model_updater->GetItems();
SetTestAppIconColor(app1_id_, SK_ColorWHITE);
// Verify that there is no position changes because setting a placeholder icon
// should not update item positions.
EXPECT_EQ(items_before_setting_white.size(), model_updater->ItemCount());
for (const ChromeAppListItem* item : items_before_setting_white) {
EXPECT_EQ(item->position(),
model_updater->FindItem(item->id())->position());
}
}
// Verifies color sort features by providing an app with the specified icon.
class AppListSortColorOrderBrowserTest : public AppListSortBrowserTest {
public:
AppListSortColorOrderBrowserTest() = default;
AppListSortColorOrderBrowserTest(const AppListSortColorOrderBrowserTest&) =
delete;
AppListSortColorOrderBrowserTest& operator=(
const AppListSortColorOrderBrowserTest&) = delete;
~AppListSortColorOrderBrowserTest() override = default;
// AppListSortBrowserTest:
void SetUpOnMainThread() override {
AppListSortBrowserTest::SetUpOnMainThread();
ASSERT_TRUE(extension_data_directory_.CreateUniqueTempDir());
extension_path_ = SetUpFakeAppWithPureColorIcon(
/*app_name=*/"yellow_app", /*icon_color=*/SK_ColorYELLOW);
}
base::FilePath extension_path_;
private:
// Sets up the resources of a fake app with the specified name and icon color.
// Returns the path to the fake app data.
base::FilePath SetUpFakeAppWithPureColorIcon(const std::string& app_name,
SkColor icon_color) {
// The export directory for an extension.
const base::FilePath extension_path =
extension_data_directory_.GetPath().Append(app_name);
base::CreateDirectory(extension_path);
// Prepare an icon file.
constexpr char icon_file_name[] = "icon.png";
base::FilePath icon_path = extension_path.AppendASCII(icon_file_name);
base::File icon_file(icon_path,
base::File::FLAG_CREATE | base::File::FLAG_WRITE);
// Write the data of a circular icon in pure color into the icon file.
constexpr int icon_size = 128;
gfx::ImageSkia icon;
icon = gfx::ImageSkiaOperations::CreateImageWithCircleBackground(
icon_size / 2, icon_color, icon);
const sk_sp<SkImage> image = SkImages::RasterFromBitmap(*icon.bitmap());
const sk_sp<SkData> png_data =
SkPngEncoder::Encode(nullptr, image.get(), {});
icon_file.Write(0, (const char*)png_data->data(), png_data->size());
icon_file.Close();
// Prepare the app manifest file.
base::FilePath manifest_path =
extension_path.Append("manifest").AddExtension(".json");
base::File manifest_file(manifest_path,
base::File::FLAG_CREATE | base::File::FLAG_WRITE);
// Write data into the manifest file.
char json_buffer[30];
constexpr char icon_json[] = R"({"%d": "%s"})";
base::strings::SafeSPrintf(json_buffer, icon_json, icon_size,
icon_file_name);
char manifest_buffer[300];
int count = base::strings::SafeSPrintf(manifest_buffer, kManifestData,
app_name.c_str(), json_buffer);
EXPECT_EQ(count, manifest_file.Write(0, manifest_buffer, count));
manifest_file.Close();
return extension_path;
}
// A temporary directory acting as a root directory for extension data.
base::ScopedTempDir extension_data_directory_;
};
// Verify that installing an app under color sort works as expected.
IN_PROC_BROWSER_TEST_F(AppListSortColorOrderBrowserTest,
InstallAppUnderColorSort) {
ash::ShellTestApi().SetTabletModeEnabledForTest(false);
WaitForAppListTransitionAnimation();
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
app_list_test_api_.WaitForBubbleWindow(/*wait_for_opening_animation=*/true);
ReorderTopLevelAppsGridAndWaitForCompletion(
ash::AppListSortOrder::kColor, MenuType::kAppListNonFolderItemMenu);
std::string yellow_app_id = LoadExtension(extension_path_)->id();
EXPECT_FALSE(yellow_app_id.empty());
SetTestAppIconColor(yellow_app_id, SK_ColorYELLOW);
// Verify that the new app's position follows the color order.
EXPECT_EQ(
GetAppIdsInOrdinalOrder({app1_id_, app2_id_, app3_id_, yellow_app_id}),
std::vector<std::string>({app2_id_, yellow_app_id, app3_id_, app1_id_}));
}
class AppListSortLoginTest
: public ash::LoginManagerTest,
public ::testing::WithParamInterface</*in_tablet=*/bool> {
public:
AppListSortLoginTest() : LoginManagerTest() {
login_mixin_.AppendRegularUsers(2);
account_id1_ = login_mixin_.users()[0].account_id;
account_id2_ = login_mixin_.users()[1].account_id;
}
~AppListSortLoginTest() override = default;
void SetUpOnMainThread() override {
ash::ShellTestApi().SetTabletModeEnabledForTest(GetParam());
ash::LoginManagerTest::SetUpOnMainThread();
}
AccountId account_id1_;
AccountId account_id2_;
ash::LoginManagerMixin login_mixin_{&mixin_host_};
};
INSTANTIATE_TEST_SUITE_P(All, AppListSortLoginTest, testing::Bool());
IN_PROC_BROWSER_TEST_P(AppListSortLoginTest,
RecordPrefSortOrderOnSessionStart) {
// Verify that the pref sort order is recorded when a primary user logs in.
base::HistogramTester histogram;
LoginUser(account_id1_);
const char* histogram_name =
GetParam() ? ash::kTabletAppListSortOrderOnSessionStartHistogram
: ash::kClamshellAppListSortOrderOnSessionStartHistogram;
histogram.ExpectBucketCount(histogram_name, ash::AppListSortOrder::kCustom,
1);
// Verify that the pref sort order is recorded when a secondary user logs in.
ash::UserAddingScreen::Get()->Start();
AddUser(account_id2_);
histogram.ExpectBucketCount(histogram_name, ash::AppListSortOrder::kCustom,
2);
// Switch back to the primary user. Verify that the pref sort order is not
// recorded again.
user_manager::UserManager::Get()->SwitchActiveUser(account_id1_);
histogram.ExpectBucketCount(histogram_name, ash::AppListSortOrder::kCustom,
2);
}
// Verifies that the app list sort discovery duration after the education nudge
// shows is recorded as expected.
// TODO(crbug.com/328928228): Re-enable this test
#if BUILDFLAG(IS_CHROMEOS) && defined(MEMORY_SANITIZER)
#define MAYBE_VerifySortAfterNudgeShowMetric \
DISABLED_VerifySortAfterNudgeShowMetric
#else
#define MAYBE_VerifySortAfterNudgeShowMetric VerifySortAfterNudgeShowMetric
#endif
IN_PROC_BROWSER_TEST_P(AppListSortLoginTest,
MAYBE_VerifySortAfterNudgeShowMetric) {
LoginUser(account_id1_);
ash::AcceleratorController::Get()->PerformActionIfEnabled(
AcceleratorAction::kToggleAppList, {});
const bool is_in_tablet = GetParam();
ash::AppListTestApi app_list_test_api;
if (is_in_tablet)
app_list_test_api.WaitForAppListShowAnimation(/*is_bubble_window=*/false);
else
app_list_test_api.WaitForBubbleWindow(/*wait_for_opening_animation=*/true);
// Reorder the app list.
ReorderAnimationEndState actual_state;
base::HistogramTester histogram;
auto event_generator = std::make_unique<ui::test::EventGenerator>(
ash::Shell::GetPrimaryRootWindow());
app_list_test_api.ReorderByMouseClickAtToplevelAppsGridMenu(
ash::AppListSortOrder::kColor, MenuType::kAppListNonFolderItemMenu,
event_generator.get(),
/*target_state=*/ReorderAnimationEndState::kCompleted, &actual_state);
EXPECT_EQ(ReorderAnimationEndState::kCompleted, actual_state);
// Verify that the data is reported with the correct histogram.
histogram.ExpectTotalCount(
ash::kAppListSortDiscoveryDurationAfterNudgeClamshell, !is_in_tablet);
histogram.ExpectTotalCount(ash::kAppListSortDiscoveryDurationAfterNudgeTablet,
is_in_tablet);
}
class AppListSortLoginTalbetTest : public ash::LoginManagerTest {
public:
AppListSortLoginTalbetTest() : LoginManagerTest() {
login_mixin_.AppendRegularUsers(2);
account_id1_ = login_mixin_.users()[0].account_id;
account_id2_ = login_mixin_.users()[1].account_id;
}
AppListSortLoginTalbetTest(const AppListSortLoginTalbetTest&) = delete;
AppListSortLoginTalbetTest& operator=(const AppListSortLoginTalbetTest&) =
delete;
~AppListSortLoginTalbetTest() override = default;
void SetUpOnMainThread() override {
ash::ShellTestApi().SetTabletModeEnabledForTest(true);
ash::LoginManagerTest::SetUpOnMainThread();
event_generator_ = std::make_unique<ui::test::EventGenerator>(
ash::Shell::GetPrimaryRootWindow());
}
ash::AppListTestApi app_list_test_api_;
std::unique_ptr<ui::test::EventGenerator> event_generator_;
AccountId account_id1_;
AccountId account_id2_;
ash::LoginManagerMixin login_mixin_{&mixin_host_};
};
// TODO(crbug.com/40890115): Flaky test.
IN_PROC_BROWSER_TEST_F(AppListSortLoginTalbetTest,
DISABLED_PRE_SwitchUnderTemporarySort) {
LoginUser(account_id1_);
// Because Account 1 is new, the reorder education nudge should show.
EXPECT_EQ(ash::AppListToastType::kReorderNudge,
app_list_test_api_.GetToastType());
// Reorder the app list.
ReorderAnimationEndState actual_state;
app_list_test_api_.ReorderByMouseClickAtToplevelAppsGridMenu(
ash::AppListSortOrder::kColor, MenuType::kAppListNonFolderItemMenu,
event_generator_.get(),
/*target_state=*/ReorderAnimationEndState::kCompleted, &actual_state);
EXPECT_EQ(ReorderAnimationEndState::kCompleted, actual_state);
// Verify that the reorder undo toast shows.
EXPECT_EQ(ash::AppListToastType::kReorderUndo,
app_list_test_api_.GetToastType());
}
// Verifies that the active account switch works as expected when the app list
// is under temporary sort.
//
// TODO(crbug.com/40890115): Flaky test.
IN_PROC_BROWSER_TEST_F(AppListSortLoginTalbetTest,
DISABLED_SwitchUnderTemporarySort) {
LoginUser(account_id1_);
// Reorder has been triggered in the pretest so the toast should not show.
EXPECT_EQ(ash::AppListToastType::kNone, app_list_test_api_.GetToastType());
// Switch to Account 2.
ash::UserAddingScreen::Get()->Start();
AddUser(account_id2_);
EXPECT_EQ(ash::AppListToastType::kReorderNudge,
app_list_test_api_.GetToastType());
// Reorder the app list and check that the undo toast shows.
ReorderAnimationEndState actual_state;
app_list_test_api_.ReorderByMouseClickAtToplevelAppsGridMenu(
ash::AppListSortOrder::kColor, MenuType::kAppListNonFolderItemMenu,
event_generator_.get(),
/*target_state=*/ReorderAnimationEndState::kCompleted, &actual_state);
EXPECT_EQ(ReorderAnimationEndState::kCompleted, actual_state);
EXPECT_EQ(ash::AppListToastType::kReorderUndo,
app_list_test_api_.GetToastType());
// Switch back to Account 1. Verify that the toast should not show.
user_manager::UserManager::Get()->SwitchActiveUser(account_id1_);
EXPECT_EQ(account_id1_,
user_manager::UserManager::Get()->GetActiveUser()->GetAccountId());
EXPECT_EQ(ash::AppListToastType::kNone, app_list_test_api_.GetToastType());
}