// 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.
#include "ash/app_list/model/app_list_model.h"
#include "chrome/browser/ash/app_list/app_list_model_updater.h"
#include "chrome/browser/ash/app_list/app_list_test_util.h"
#include "chrome/browser/ash/app_list/chrome_app_list_model_updater.h"
#include "chrome/browser/ash/app_list/test/app_list_syncable_service_test_base.h"
#include "chrome/browser/ash/app_list/test/test_app_list_controller.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_profile.h"
#include "components/crx_file/id_util.h"
#include "components/sync/test/fake_sync_change_processor.h"
#include "ui/display/test/test_screen.h"
namespace app_list {
using crx_file::id_util::GenerateId;
class TemporaryAppListSortTest : public test::AppListSyncableServiceTestBase {
public:
TemporaryAppListSortTest() = default;
~TemporaryAppListSortTest() override = default;
void SetUp() override {
AppListSyncableServiceTestBase::SetUp();
app_list_controller_ = std::make_unique<test::TestAppListController>();
GetModelUpdater()->SetActive(true);
content::RunAllTasksUntilIdle();
}
ChromeAppListModelUpdater* GetChromeModelUpdater() {
return static_cast<ChromeAppListModelUpdater*>(GetModelUpdater());
}
ash::AppListSortOrder GetTemporarySortOrder() {
return GetChromeModelUpdater()->GetTemporarySortOrderForTest();
}
// Returns the app list order stored as preference.
ash::AppListSortOrder GetSortOrderFromPrefs() {
return static_cast<ash::AppListSortOrder>(
profile()->GetPrefs()->GetInteger(prefs::kAppListPreferredOrder));
}
syncer::StringOrdinal GetPositionFromModelUpdater(const std::string& id) {
return GetModelUpdater()->FindItem(id)->position();
}
void Commit() {
static_cast<ChromeAppListModelUpdater*>(GetModelUpdater())
->EndTemporarySortAndTakeAction(
ChromeAppListModelUpdater::EndAction::kCommit);
}
bool IsUnderTemporarySort() {
return static_cast<ChromeAppListModelUpdater*>(GetModelUpdater())
->is_under_temporary_sort();
}
private:
display::test::TestScreen test_screen_{/*create_dispay=*/true,
/*register_screen=*/true};
std::unique_ptr<test::TestAppListController> app_list_controller_;
};
// Verifies that sorting by app names is case insensitive.
TEST_F(TemporaryAppListSortTest, SortIsCaseInsensitive) {
RemoveAllExistingItems();
// Add three apps.
const std::string kItemId1 = CreateNextAppId(GenerateId("app_id1"));
scoped_refptr<extensions::Extension> app1 =
MakeApp("aaa", kItemId1, extensions::Extension::NO_FLAGS);
InstallExtension(app1.get());
const std::string kItemId2 = CreateNextAppId(GenerateId("app_id2"));
scoped_refptr<extensions::Extension> app2 =
MakeApp("BBB", kItemId2, extensions::Extension::NO_FLAGS);
InstallExtension(app2.get());
const std::string kItemId3 = CreateNextAppId(GenerateId("app_id3"));
scoped_refptr<extensions::Extension> app3 =
MakeApp("ccc", kItemId3, extensions::Extension::NO_FLAGS);
InstallExtension(app3.get());
// Verify the default status. Note that when the order is kCustom, a new app
// should be placed at the front.
EXPECT_EQ(ash::AppListSortOrder::kCustom, GetSortOrderFromPrefs());
EXPECT_EQ(GetOrderedItemIdsFromModelUpdater(),
std::vector<std::string>({kItemId3, kItemId2, kItemId1}));
// Sort apps with name alphabetical order.
GetChromeModelUpdater()->RequestAppListSort(
ash::AppListSortOrder::kNameAlphabetical);
// The app positions stored in the model updater change, where the order of
// app names is case insensitive.
EXPECT_EQ(GetOrderedItemIdsFromModelUpdater(),
std::vector<std::string>({kItemId1, kItemId2, kItemId3}));
}
TEST_F(TemporaryAppListSortTest, AppInsertionInSortedAppListCaseInsensitive) {
RemoveAllExistingItems();
// Add three apps.
const std::string kItemId1 = CreateNextAppId(GenerateId("app_id1"));
scoped_refptr<extensions::Extension> app1 =
MakeApp("aaa", kItemId1, extensions::Extension::NO_FLAGS);
InstallExtension(app1.get());
const std::string kItemId2 = CreateNextAppId(GenerateId("app_id2"));
scoped_refptr<extensions::Extension> app2 =
MakeApp("BBB", kItemId2, extensions::Extension::NO_FLAGS);
InstallExtension(app2.get());
const std::string kItemId3 = CreateNextAppId(GenerateId("app_id3"));
scoped_refptr<extensions::Extension> app3 =
MakeApp("ccc", kItemId3, extensions::Extension::NO_FLAGS);
InstallExtension(app3.get());
// Verify the default status. Note that when the order is kCustom, a new app
// should be placed at the front.
EXPECT_EQ(ash::AppListSortOrder::kCustom, GetSortOrderFromPrefs());
EXPECT_EQ(GetOrderedItemIdsFromModelUpdater(),
std::vector<std::string>({kItemId3, kItemId2, kItemId1}));
// Sort apps with name alphabetical order.
GetChromeModelUpdater()->RequestAppListSort(
ash::AppListSortOrder::kNameAlphabetical);
// The app positions stored in the model updater change, where the order of
// app names is case insensitive.
EXPECT_EQ(GetOrderedItemIdsFromModelUpdater(),
std::vector<std::string>({kItemId1, kItemId2, kItemId3}));
// Install an additional app.
const std::string kItemId4 = GenerateId("app_id4");
scoped_refptr<extensions::Extension> app4 =
MakeApp("abc", kItemId4, extensions::Extension::NO_FLAGS);
InstallExtension(app4.get());
// The app positions stored in the model updater change, where the order of
// app names is case insensitive.
EXPECT_EQ(GetOrderedItemIdsFromModelUpdater(),
std::vector<std::string>({kItemId1, kItemId4, kItemId2, kItemId3}));
}
// Verifies sorting works as expected when the app list is under temporary sort.
TEST_F(TemporaryAppListSortTest, SortUponTemporaryOrder) {
RemoveAllExistingItems();
// Add three apps.
const std::string kItemId1 = CreateNextAppId(GenerateId("app_id1"));
scoped_refptr<extensions::Extension> app1 =
MakeApp("A", kItemId1, extensions::Extension::NO_FLAGS);
InstallExtension(app1.get());
const std::string kItemId2 = CreateNextAppId(GenerateId("app_id2"));
scoped_refptr<extensions::Extension> app2 =
MakeApp("B", kItemId2, extensions::Extension::NO_FLAGS);
InstallExtension(app2.get());
const std::string kItemId3 = CreateNextAppId(GenerateId("app_id3"));
scoped_refptr<extensions::Extension> app3 =
MakeApp("C", kItemId3, extensions::Extension::NO_FLAGS);
InstallExtension(app3.get());
// Verify the default status. Note that when the order is kCustom, a new app
// should be placed at the front.
EXPECT_EQ(ash::AppListSortOrder::kCustom, GetSortOrderFromPrefs());
EXPECT_EQ(GetOrderedItemIdsFromModelUpdater(),
std::vector<std::string>({kItemId3, kItemId2, kItemId1}));
// Sort apps with name alphabetical order.
GetChromeModelUpdater()->RequestAppListSort(
ash::AppListSortOrder::kNameAlphabetical);
// The permanent sort order does not change while the temporary order updates.
EXPECT_EQ(ash::AppListSortOrder::kCustom, GetSortOrderFromPrefs());
EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetTemporarySortOrder());
// The app positions stored in the model updater change.
EXPECT_EQ(GetOrderedItemIdsFromModelUpdater(),
std::vector<std::string>({kItemId1, kItemId2, kItemId3}));
// Permanent positions (i.e. the positions stored in sync data) do not change.
EXPECT_EQ(GetOrderedItemIdsFromSyncableService(),
std::vector<std::string>({kItemId3, kItemId2, kItemId1}));
// Sort again without exiting temporary sort.
GetChromeModelUpdater()->RequestAppListSort(
ash::AppListSortOrder::kNameAlphabetical);
// Verify sort orders and app positions.
EXPECT_EQ(ash::AppListSortOrder::kCustom, GetSortOrderFromPrefs());
EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetTemporarySortOrder());
EXPECT_EQ(GetOrderedItemIdsFromModelUpdater(),
std::vector<std::string>({kItemId1, kItemId2, kItemId3}));
EXPECT_EQ(GetOrderedItemIdsFromSyncableService(),
std::vector<std::string>({kItemId3, kItemId2, kItemId1}));
// Sort again without exiting temporary sort.
GetChromeModelUpdater()->RequestAppListSort(
ash::AppListSortOrder::kNameAlphabetical);
// Verify sort orders and app positions.
EXPECT_EQ(ash::AppListSortOrder::kCustom, GetSortOrderFromPrefs());
EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetTemporarySortOrder());
EXPECT_EQ(GetOrderedItemIdsFromModelUpdater(),
std::vector<std::string>({kItemId1, kItemId2, kItemId3}));
EXPECT_EQ(GetOrderedItemIdsFromSyncableService(),
std::vector<std::string>({kItemId3, kItemId2, kItemId1}));
EXPECT_TRUE(IsUnderTemporarySort());
}
// Verifies that committing name sort order works as expected.
TEST_F(TemporaryAppListSortTest, CommitNameOrder) {
RemoveAllExistingItems();
// Add three apps.
const std::string kItemId1 = CreateNextAppId(GenerateId("app_id1"));
scoped_refptr<extensions::Extension> app1 =
MakeApp("A", kItemId1, extensions::Extension::NO_FLAGS);
InstallExtension(app1.get());
const std::string kItemId2 = CreateNextAppId(GenerateId("app_id2"));
scoped_refptr<extensions::Extension> app2 =
MakeApp("B", kItemId2, extensions::Extension::NO_FLAGS);
InstallExtension(app2.get());
const std::string kItemId3 = CreateNextAppId(GenerateId("app_id3"));
scoped_refptr<extensions::Extension> app3 =
MakeApp("C", kItemId3, extensions::Extension::NO_FLAGS);
InstallExtension(app3.get());
// Sort with name alphabetical order.
GetChromeModelUpdater()->RequestAppListSort(
ash::AppListSortOrder::kNameAlphabetical);
// Verify that the permanent sort order and the permanent app positions do
// not change.
EXPECT_EQ(ash::AppListSortOrder::kCustom, GetSortOrderFromPrefs());
EXPECT_EQ(GetOrderedItemIdsFromSyncableService(),
std::vector<std::string>({kItemId3, kItemId2, kItemId1}));
// Commit the temporary sort order. Verify that permanent data update.
Commit();
EXPECT_FALSE(IsUnderTemporarySort());
EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
EXPECT_EQ(GetOrderedItemIdsFromSyncableService(),
std::vector<std::string>({kItemId1, kItemId2, kItemId3}));
}
// Verifies that moving an item on the app list that is under temporary sort
// works as expected.
TEST_F(TemporaryAppListSortTest, HandleMoveItem) {
RemoveAllExistingItems();
// Install four apps.
const std::string kItemId1 = CreateNextAppId(GenerateId("app_id1"));
scoped_refptr<extensions::Extension> app1 =
MakeApp("A", kItemId1, extensions::Extension::NO_FLAGS);
InstallExtension(app1.get());
const std::string kItemId2 = CreateNextAppId(GenerateId("app_id2"));
scoped_refptr<extensions::Extension> app2 =
MakeApp("B", kItemId2, extensions::Extension::NO_FLAGS);
InstallExtension(app2.get());
const std::string kItemId3 = CreateNextAppId(GenerateId("app_id3"));
scoped_refptr<extensions::Extension> app3 =
MakeApp("C", kItemId3, extensions::Extension::NO_FLAGS);
InstallExtension(app3.get());
const std::string kItemId4 = CreateNextAppId(GenerateId("app_id4"));
scoped_refptr<extensions::Extension> app4 =
MakeApp("D", kItemId4, extensions::Extension::NO_FLAGS);
InstallExtension(app4.get());
// Sort apps with name alphabetical order, commit the temporary order then
// verify the state.
ChromeAppListModelUpdater* model_updater = GetChromeModelUpdater();
model_updater->RequestAppListSort(ash::AppListSortOrder::kNameAlphabetical);
Commit();
EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
EXPECT_EQ(GetOrderedItemIdsFromSyncableService(),
std::vector<std::string>({kItemId1, kItemId2, kItemId3, kItemId4}));
// Sort with name reverse alphabetical order without committing.
model_updater->RequestAppListSort(
ash::AppListSortOrder::kNameReverseAlphabetical);
EXPECT_EQ(GetOrderedItemIdsFromModelUpdater(),
std::vector<std::string>({kItemId4, kItemId3, kItemId2, kItemId1}));
EXPECT_EQ(GetOrderedItemIdsFromSyncableService(),
std::vector<std::string>({kItemId1, kItemId2, kItemId3, kItemId4}));
// Move `app4` to the position between `app2` and `app3`.
const syncer::StringOrdinal target_position =
model_updater->FindItem(kItemId2)->position().CreateBetween(
model_updater->FindItem(kItemId3)->position());
model_updater->RequestPositionUpdate(
kItemId4, target_position, ash::RequestPositionUpdateReason::kMoveItem);
// Verify the following things:
// (1) The app list is not under temporary sort.
// (2) The permanent sort order is cleared.
// (3) The positions in both model updater and sync data are expected.
EXPECT_FALSE(IsUnderTemporarySort());
EXPECT_EQ(ash::AppListSortOrder::kCustom, GetSortOrderFromPrefs());
EXPECT_EQ(GetOrderedItemIdsFromModelUpdater(),
std::vector<std::string>({kItemId3, kItemId4, kItemId2, kItemId1}));
EXPECT_EQ(GetOrderedItemIdsFromSyncableService(),
std::vector<std::string>({kItemId3, kItemId4, kItemId2, kItemId1}));
}
// Verifies that moving an item within the app list resets the nominal app list
// sort order (if the app list is sorted at the time).
TEST_F(TemporaryAppListSortTest, MovingItemsResetsSortOrder) {
RemoveAllExistingItems();
std::vector<scoped_refptr<extensions::Extension>> apps;
for (int i = 0; i < 10; ++i) {
const std::string id = GenerateId(base::StringPrintf("app_id_%d", i));
const std::string name = base::StringPrintf("Item %d", i);
scoped_refptr<extensions::Extension> app =
MakeApp(name, id, extensions::Extension::NO_FLAGS);
apps.push_back(app);
InstallExtension(app.get());
}
// Sort with name alphabetical order.
GetChromeModelUpdater()->RequestAppListSort(
ash::AppListSortOrder::kNameAlphabetical);
Commit();
EXPECT_FALSE(IsUnderTemporarySort());
EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
EXPECT_EQ(GetOrderedNamesFromSyncableService(),
std::vector<std::string>({"Item 0", "Item 1", "Item 2", "Item 3",
"Item 4", "Item 5", "Item 6", "Item 7",
"Item 8", "Item 9"}));
// Move an item within the app list.
ChromeAppListModelUpdater* model_updater = GetChromeModelUpdater();
const syncer::StringOrdinal target_position =
model_updater->FindItem(apps[1]->id())
->position()
.CreateBetween(model_updater->FindItem(apps[2]->id())->position());
model_updater->RequestPositionUpdate(
apps[7]->id(), target_position,
ash::RequestPositionUpdateReason::kMoveItem);
// Verify that the app list is no longer considered sorted - new items are
// added to the first position within the app list.
EXPECT_FALSE(IsUnderTemporarySort());
EXPECT_EQ(ash::AppListSortOrder::kCustom, GetSortOrderFromPrefs());
EXPECT_EQ(GetOrderedNamesFromSyncableService(),
std::vector<std::string>({"Item 0", "Item 1", "Item 7", "Item 2",
"Item 3", "Item 4", "Item 5", "Item 6",
"Item 8", "Item 9"}));
scoped_refptr<extensions::Extension> new_app = MakeApp(
"Item 10", GenerateId("new_install"), extensions::Extension::NO_FLAGS);
InstallExtension(new_app.get());
EXPECT_EQ(ash::AppListSortOrder::kCustom, GetSortOrderFromPrefs());
EXPECT_EQ(GetOrderedNamesFromSyncableService(),
std::vector<std::string>({"Item 10", "Item 0", "Item 1", "Item 7",
"Item 2", "Item 3", "Item 4", "Item 5",
"Item 6", "Item 8", "Item 9"}));
}
// Verifies that moving an item from a folder to root apps grid resets the
// nominal app list sort order (if the app list is sorted at the time).
TEST_F(TemporaryAppListSortTest, ReparentingItemToRootResetsSortOrder) {
RemoveAllExistingItems();
std::vector<scoped_refptr<extensions::Extension>> apps;
for (int i = 0; i < 10; ++i) {
const std::string id = GenerateId(base::StringPrintf("app_id_%d", i));
const std::string name = base::StringPrintf("Item %d", i);
scoped_refptr<extensions::Extension> app =
MakeApp(name, id, extensions::Extension::NO_FLAGS);
apps.push_back(app);
InstallExtension(app.get());
}
// Create a folder that contains three items.
const std::string kFolderItemId = GenerateId("folder_id");
syncer::SyncDataList sync_list;
sync_list.push_back(CreateAppRemoteData(
kFolderItemId, "Folder", "",
syncer::StringOrdinal::CreateInitialOrdinal().ToInternalValue(), kUnset,
sync_pb::AppListSpecifics_AppListItemType_TYPE_FOLDER));
syncer::StringOrdinal child_position =
syncer::StringOrdinal::CreateInitialOrdinal();
sync_list.push_back(
CreateAppRemoteData(apps[1]->id(), "Item 1", kFolderItemId,
child_position.ToInternalValue(), kUnset));
child_position = child_position.CreateAfter();
sync_list.push_back(
CreateAppRemoteData(apps[2]->id(), "Item 2", kFolderItemId,
child_position.ToInternalValue(), kUnset));
child_position = child_position.CreateAfter();
sync_list.push_back(
CreateAppRemoteData(apps[3]->id(), "Item 3", kFolderItemId,
child_position.ToInternalValue(), kUnset));
app_list_syncable_service()->MergeDataAndStartSyncing(
syncer::APP_LIST, sync_list,
std::make_unique<syncer::FakeSyncChangeProcessor>());
content::RunAllTasksUntilIdle();
// Sort with name alphabetical order.
GetChromeModelUpdater()->RequestAppListSort(
ash::AppListSortOrder::kNameAlphabetical);
Commit();
EXPECT_FALSE(IsUnderTemporarySort());
EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
EXPECT_EQ(GetOrderedNamesFromSyncableService(),
std::vector<std::string>({"Folder", "Item 0", "Item 1", "Item 2",
"Item 3", "Item 4", "Item 5", "Item 6",
"Item 7", "Item 8", "Item 9"}));
// Move an from the folder to root apps grid.
ChromeAppListModelUpdater* model_updater = GetChromeModelUpdater();
const syncer::StringOrdinal target_position =
model_updater->FindItem(apps[6]->id())
->position()
.CreateBetween(model_updater->FindItem(apps[7]->id())->position());
model_updater->RequestMoveItemToRoot(apps[1]->id(), target_position);
// Verify that the app list is no longer considered sorted - new items are
// added to the first position within the app list.
EXPECT_FALSE(IsUnderTemporarySort());
EXPECT_EQ(ash::AppListSortOrder::kCustom, GetSortOrderFromPrefs());
EXPECT_EQ(GetOrderedNamesFromSyncableService(),
std::vector<std::string>({"Folder", "Item 0", "Item 2", "Item 3",
"Item 4", "Item 5", "Item 6", "Item 1",
"Item 7", "Item 8", "Item 9"}));
scoped_refptr<extensions::Extension> new_app = MakeApp(
"Item 10", GenerateId("new_install"), extensions::Extension::NO_FLAGS);
InstallExtension(new_app.get());
EXPECT_EQ(ash::AppListSortOrder::kCustom, GetSortOrderFromPrefs());
EXPECT_EQ(GetOrderedNamesFromSyncableService(),
std::vector<std::string>({"Item 10", "Folder", "Item 0", "Item 2",
"Item 3", "Item 4", "Item 5", "Item 6",
"Item 1", "Item 7", "Item 8", "Item 9"}));
}
// Verifies that merging two items to form a folder keeps the nominal app list
// sort order (if the app list is sorted at the time) and positions the new
// folder into sorted order.
TEST_F(TemporaryAppListSortTest, MergingItemsKeepsSortOrder) {
RemoveAllExistingItems();
std::vector<scoped_refptr<extensions::Extension>> apps;
for (int i = 0; i < 10; ++i) {
const std::string id = GenerateId(base::StringPrintf("app_id_%d", i));
const std::string name = base::StringPrintf("Item %d", i);
scoped_refptr<extensions::Extension> app =
MakeApp(name, id, extensions::Extension::NO_FLAGS);
apps.push_back(app);
InstallExtension(app.get());
}
// Sort with name alphabetical order.
GetChromeModelUpdater()->RequestAppListSort(
ash::AppListSortOrder::kNameAlphabetical);
Commit();
EXPECT_FALSE(IsUnderTemporarySort());
EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
EXPECT_EQ(GetOrderedNamesFromSyncableService(),
std::vector<std::string>({"Item 0", "Item 1", "Item 2", "Item 3",
"Item 4", "Item 5", "Item 6", "Item 7",
"Item 8", "Item 9"}));
// Merge two items into a folder.
ChromeAppListModelUpdater* model_updater = GetChromeModelUpdater();
const std::string folder_id =
model_updater->model_for_test()->MergeItems(apps[8]->id(), apps[9]->id());
// Verify that the app list is still considered sorted, and that new installs
// keep getting added in the sorted order.
EXPECT_FALSE(IsUnderTemporarySort());
EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
std::vector<std::string> ordered_names = GetOrderedNamesFromSyncableService();
EXPECT_EQ(ordered_names, std::vector<std::string>(
{"Item 0", "Item 1", "Item 2", "Item 3",
"Item 4", "Item 5", "Item 6", "Item 7",
"Item 8", "Item 9", "" /*"Unnamed" folder*/}));
scoped_refptr<extensions::Extension> new_app = MakeApp(
"Item 10", GenerateId("new_install"), extensions::Extension::NO_FLAGS);
InstallExtension(new_app.get());
EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
ordered_names = GetOrderedNamesFromSyncableService();
EXPECT_EQ(ordered_names,
std::vector<std::string>({"Item 0", "Item 1", "Item 10", "Item 2",
"Item 3", "Item 4", "Item 5", "Item 6",
"Item 7", "Item 8", "Item 9",
"" /*"Unnamed" folder*/}));
}
// Verifies that moving an item from a folder to root apps grid resets the
// nominal app list sort order (if the app list is sorted at the time).
TEST_F(TemporaryAppListSortTest, ReparentingItemToFolderDoesNotResetSortOrder) {
RemoveAllExistingItems();
std::vector<scoped_refptr<extensions::Extension>> apps;
for (int i = 0; i < 10; ++i) {
const std::string id = GenerateId(base::StringPrintf("app_id_%d", i));
const std::string name = base::StringPrintf("Item %d", i);
scoped_refptr<extensions::Extension> app =
MakeApp(name, id, extensions::Extension::NO_FLAGS);
apps.push_back(app);
InstallExtension(app.get());
}
// Create a folder that contains three items.
const std::string kFolderItemId = GenerateId("folder_id");
syncer::SyncDataList sync_list;
sync_list.push_back(CreateAppRemoteData(
kFolderItemId, "Folder", "",
syncer::StringOrdinal::CreateInitialOrdinal().ToInternalValue(), kUnset,
sync_pb::AppListSpecifics_AppListItemType_TYPE_FOLDER));
// Add three apps to the folder.
syncer::StringOrdinal child_position =
syncer::StringOrdinal::CreateInitialOrdinal();
sync_list.push_back(
CreateAppRemoteData(apps[1]->id(), "Item 1", kFolderItemId,
child_position.ToInternalValue(), kUnset));
child_position = child_position.CreateAfter();
sync_list.push_back(
CreateAppRemoteData(apps[2]->id(), "Item 2", kFolderItemId,
child_position.ToInternalValue(), kUnset));
child_position = child_position.CreateAfter();
sync_list.push_back(
CreateAppRemoteData(apps[3]->id(), "Item 3", kFolderItemId,
child_position.ToInternalValue(), kUnset));
app_list_syncable_service()->MergeDataAndStartSyncing(
syncer::APP_LIST, sync_list,
std::make_unique<syncer::FakeSyncChangeProcessor>());
content::RunAllTasksUntilIdle();
// Sort with name alphabetical order.
GetChromeModelUpdater()->RequestAppListSort(
ash::AppListSortOrder::kNameAlphabetical);
Commit();
EXPECT_FALSE(IsUnderTemporarySort());
EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
EXPECT_EQ(GetOrderedNamesFromSyncableService(),
std::vector<std::string>({"Folder", "Item 0", "Item 1", "Item 2",
"Item 3", "Item 4", "Item 5", "Item 6",
"Item 7", "Item 8", "Item 9"}));
// Move an from the folder to root apps grid.
ChromeAppListModelUpdater* model_updater = GetChromeModelUpdater();
model_updater->RequestMoveItemToFolder(apps[7]->id(), kFolderItemId);
// Verify that the app list is still considered sorted - new items are
// added to the app list to maintain sorted order.
EXPECT_FALSE(IsUnderTemporarySort());
EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
EXPECT_EQ(GetOrderedNamesFromSyncableService(),
std::vector<std::string>({"Folder", "Item 0", "Item 1", "Item 2",
"Item 3", "Item 4", "Item 5", "Item 6",
"Item 7", "Item 8", "Item 9"}));
scoped_refptr<extensions::Extension> new_app = MakeApp(
"Item 10", GenerateId("new_install"), extensions::Extension::NO_FLAGS);
InstallExtension(new_app.get());
EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
EXPECT_EQ(GetOrderedNamesFromSyncableService(),
std::vector<std::string>({"Folder", "Item 0", "Item 1", "Item 10",
"Item 2", "Item 3", "Item 4", "Item 5",
"Item 6", "Item 7", "Item 8", "Item 9"}));
}
// Verifies that reverting the temporary name sort order works as expected.
TEST_F(TemporaryAppListSortTest, RevertNameOrder) {
RemoveAllExistingItems();
// Install Three apps.
const std::string kItemId1 = CreateNextAppId(GenerateId("app_id1"));
scoped_refptr<extensions::Extension> app1 =
MakeApp("A", kItemId1, extensions::Extension::NO_FLAGS);
InstallExtension(app1.get());
const std::string kItemId2 = CreateNextAppId(GenerateId("app_id2"));
scoped_refptr<extensions::Extension> app2 =
MakeApp("B", kItemId2, extensions::Extension::NO_FLAGS);
InstallExtension(app2.get());
const std::string kItemId3 = CreateNextAppId(GenerateId("app_id3"));
scoped_refptr<extensions::Extension> app3 =
MakeApp("C", kItemId3, extensions::Extension::NO_FLAGS);
InstallExtension(app3.get());
// Sort apps with name alphabetical order, commit the temporary order then
// verify the state.
ChromeAppListModelUpdater* model_updater = GetChromeModelUpdater();
model_updater->RequestAppListSort(ash::AppListSortOrder::kNameAlphabetical);
Commit();
EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
EXPECT_EQ(GetOrderedItemIdsFromSyncableService(),
std::vector<std::string>({kItemId1, kItemId2, kItemId3}));
// Sort with name reverse alphabetical order without committing.
model_updater->RequestAppListSort(
ash::AppListSortOrder::kNameReverseAlphabetical);
EXPECT_EQ(GetOrderedItemIdsFromModelUpdater(),
std::vector<std::string>({kItemId3, kItemId2, kItemId1}));
EXPECT_EQ(GetOrderedItemIdsFromSyncableService(),
std::vector<std::string>({kItemId1, kItemId2, kItemId3}));
// Revert the temporary sort order.
model_updater->RequestAppListSortRevert();
// Verify the following things:
// (1) The app list is not under temporary sort.
// (2) The permanent order does not change.
// (3) The permanent item positions do not change.
// (4) The positions in the model updater are reset with the permanent
// positions.
EXPECT_FALSE(IsUnderTemporarySort());
EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
EXPECT_EQ(GetOrderedItemIdsFromModelUpdater(),
std::vector<std::string>({kItemId1, kItemId2, kItemId3}));
EXPECT_EQ(GetOrderedItemIdsFromSyncableService(),
std::vector<std::string>({kItemId1, kItemId2, kItemId3}));
}
// Verifies that the app list under temporary sort works as expected when the
// app list is hidden.
TEST_F(TemporaryAppListSortTest, AppListHidden) {
RemoveAllExistingItems();
// Install Three apps.
const std::string kItemId1 = CreateNextAppId(GenerateId("app_id1"));
scoped_refptr<extensions::Extension> app1 =
MakeApp("A", kItemId1, extensions::Extension::NO_FLAGS);
InstallExtension(app1.get());
const std::string kItemId2 = CreateNextAppId(GenerateId("app_id2"));
scoped_refptr<extensions::Extension> app2 =
MakeApp("B", kItemId2, extensions::Extension::NO_FLAGS);
InstallExtension(app2.get());
const std::string kItemId3 = CreateNextAppId(GenerateId("app_id3"));
scoped_refptr<extensions::Extension> app3 =
MakeApp("C", kItemId3, extensions::Extension::NO_FLAGS);
InstallExtension(app3.get());
// Sort with name alphabetical order.
ChromeAppListModelUpdater* model_updater = GetChromeModelUpdater();
model_updater->RequestAppListSort(ash::AppListSortOrder::kNameAlphabetical);
// Verify that the permanent sort order and the permanent app positions do
// not change.
EXPECT_EQ(ash::AppListSortOrder::kCustom, GetSortOrderFromPrefs());
EXPECT_EQ(GetOrderedItemIdsFromSyncableService(),
std::vector<std::string>({kItemId3, kItemId2, kItemId1}));
// Emulate that the app list is hidden.
model_updater->OnAppListHidden();
// Verify the following things:
// (1) The temporary sort ends, and
// (2) The sort order is committed, and
// (3) The item positions are committed.
EXPECT_FALSE(IsUnderTemporarySort());
EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
EXPECT_EQ(GetOrderedItemIdsFromSyncableService(),
std::vector<std::string>({kItemId1, kItemId2, kItemId3}));
}
// Verifies the temporary sort behavior when handling item move on a synced
// remote device.
TEST_F(TemporaryAppListSortTest, HandlePositionSyncUpdate) {
// Start syncing.
app_list_syncable_service()->MergeDataAndStartSyncing(
syncer::APP_LIST, syncer::SyncDataList(),
std::make_unique<syncer::FakeSyncChangeProcessor>());
content::RunAllTasksUntilIdle();
RemoveAllExistingItems();
// Install four apps.
const std::string kItemId1 = CreateNextAppId(GenerateId("app_id1"));
scoped_refptr<extensions::Extension> app1 =
MakeApp("A", kItemId1, extensions::Extension::NO_FLAGS);
InstallExtension(app1.get());
const std::string kItemId2 = CreateNextAppId(GenerateId("app_id2"));
scoped_refptr<extensions::Extension> app2 =
MakeApp("B", kItemId2, extensions::Extension::NO_FLAGS);
InstallExtension(app2.get());
const std::string kItemId3 = CreateNextAppId(GenerateId("app_id3"));
scoped_refptr<extensions::Extension> app3 =
MakeApp("C", kItemId3, extensions::Extension::NO_FLAGS);
InstallExtension(app3.get());
const std::string kItemId4 = CreateNextAppId(GenerateId("app_id4"));
scoped_refptr<extensions::Extension> app4 =
MakeApp("D", kItemId4, extensions::Extension::NO_FLAGS);
InstallExtension(app4.get());
// Sort with the name alphabetical order.
ChromeAppListModelUpdater* model_updater = GetChromeModelUpdater();
model_updater->RequestAppListSort(ash::AppListSortOrder::kNameAlphabetical);
Commit();
// Sort with the name reverse alphabetical order without committing.
model_updater->RequestAppListSort(
ash::AppListSortOrder::kNameReverseAlphabetical);
EXPECT_EQ(GetOrderedItemIdsFromSyncableService(),
std::vector<std::string>({kItemId1, kItemId2, kItemId3, kItemId4}));
EXPECT_EQ(GetOrderedItemIdsFromModelUpdater(),
std::vector<std::string>({kItemId4, kItemId3, kItemId2, kItemId1}));
// Emulate that `app4` is moved to the position between `app1` and `app2` on
// a remote device.
ChromeAppListItem* app4_item = model_updater->FindItem(kItemId4);
const syncer::StringOrdinal target_position =
GetPositionFromSyncData(kItemId1).CreateBetween(
GetPositionFromSyncData(kItemId2));
syncer::SyncChangeList change_list;
change_list.push_back(syncer::SyncChange(
FROM_HERE, syncer::SyncChange::ACTION_UPDATE,
CreateAppRemoteData(kItemId4, app4_item->name(), app4_item->folder_id(),
target_position.ToInternalValue(), "")));
app_list_syncable_service()->ProcessSyncChanges(base::Location(),
change_list);
content::RunAllTasksUntilIdle();
// Verify that the temporary sort is reverted.
EXPECT_FALSE(IsUnderTemporarySort());
EXPECT_EQ(GetOrderedItemIdsFromSyncableService(),
std::vector<std::string>({kItemId1, kItemId4, kItemId2, kItemId3}));
EXPECT_EQ(GetOrderedItemIdsFromModelUpdater(),
std::vector<std::string>({kItemId1, kItemId4, kItemId2, kItemId3}));
}
// Verifies that the app list under temporary sort works as expected when two
// items merge into a folder.
TEST_F(TemporaryAppListSortTest, HandleItemMerge) {
RemoveAllExistingItems();
// Install four apps.
const std::string kItemId1 = CreateNextAppId(GenerateId("app_id1"));
scoped_refptr<extensions::Extension> app1 =
MakeApp("A", kItemId1, extensions::Extension::NO_FLAGS);
InstallExtension(app1.get());
const std::string kItemId2 = CreateNextAppId(GenerateId("app_id2"));
scoped_refptr<extensions::Extension> app2 =
MakeApp("B", kItemId2, extensions::Extension::NO_FLAGS);
InstallExtension(app2.get());
const std::string kItemId3 = CreateNextAppId(GenerateId("app_id3"));
scoped_refptr<extensions::Extension> app3 =
MakeApp("C", kItemId3, extensions::Extension::NO_FLAGS);
InstallExtension(app3.get());
const std::string kItemId4 = CreateNextAppId(GenerateId("app_id4"));
scoped_refptr<extensions::Extension> app4 =
MakeApp("D", kItemId4, extensions::Extension::NO_FLAGS);
InstallExtension(app4.get());
// Sort with the name alphabetical order.
ChromeAppListModelUpdater* model_updater = GetChromeModelUpdater();
model_updater->RequestAppListSort(ash::AppListSortOrder::kNameAlphabetical);
Commit();
// Sort with the name reverse alphabetical order without committing. The
// permanent sort order and the permanent item positions should not change.
model_updater->RequestAppListSort(
ash::AppListSortOrder::kNameReverseAlphabetical);
EXPECT_EQ(GetOrderedItemIdsFromSyncableService(),
std::vector<std::string>({kItemId1, kItemId2, kItemId3, kItemId4}));
EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
// Emulate to merge two items into a folder.
syncer::StringOrdinal position =
model_updater->FindItem(kItemId4)->position().CreateBefore();
const std::string kFolderItemId = GenerateId("folder_id1");
const std::string folder_item_id =
model_updater->model_for_test()->MergeItems(kItemId4, kItemId3);
model_updater->RequestFolderRename(folder_item_id, "Folder1");
// Verify that:
// (1) Temporary sort ends.
// (2) Sort order is committed.
// (3) Local positions are committed.
EXPECT_FALSE(IsUnderTemporarySort());
EXPECT_EQ(ash::AppListSortOrder::kNameReverseAlphabetical,
GetSortOrderFromPrefs());
EXPECT_EQ(GetOrderedItemIdsFromSyncableService(),
std::vector<std::string>(
{folder_item_id, kItemId4, kItemId3, kItemId2, kItemId1}));
EXPECT_EQ(GetOrderedItemIdsFromModelUpdater(),
std::vector<std::string>(
{folder_item_id, kItemId4, kItemId3, kItemId2, kItemId1}));
}
// Verifies that the app list under temporary sort works as expected when a
// folder gets renamed.
TEST_F(TemporaryAppListSortTest, HandleFolderRename) {
RemoveAllExistingItems();
// Configure sunc data with a folder containing two apps.
const std::string kFolderItemId = "folder_id";
syncer::SyncDataList sync_list;
sync_list.push_back(CreateAppRemoteData(
kFolderItemId, "Folder", "",
syncer::StringOrdinal::CreateInitialOrdinal().ToInternalValue(), kUnset,
sync_pb::AppListSpecifics_AppListItemType_TYPE_FOLDER));
const std::string kItemId1 = GenerateId("app_id1");
const std::string kItemId2 = GenerateId("app_id2");
syncer::StringOrdinal child_position =
syncer::StringOrdinal::CreateInitialOrdinal();
sync_list.push_back(CreateAppRemoteData(
kItemId1, "A", kFolderItemId, child_position.ToInternalValue(), kUnset));
child_position = child_position.CreateAfter();
sync_list.push_back(CreateAppRemoteData(
kItemId2, "B", kFolderItemId, child_position.ToInternalValue(), kUnset));
app_list_syncable_service()->MergeDataAndStartSyncing(
syncer::APP_LIST, sync_list,
std::make_unique<syncer::FakeSyncChangeProcessor>());
content::RunAllTasksUntilIdle();
// Install four apps.
scoped_refptr<extensions::Extension> app1 =
MakeApp("A", kItemId1, extensions::Extension::NO_FLAGS);
InstallExtension(app1.get());
scoped_refptr<extensions::Extension> app2 =
MakeApp("B", kItemId2, extensions::Extension::NO_FLAGS);
InstallExtension(app2.get());
const std::string kItemId3 = CreateNextAppId(GenerateId("app_id3"));
scoped_refptr<extensions::Extension> app3 =
MakeApp("C", kItemId3, extensions::Extension::NO_FLAGS);
InstallExtension(app3.get());
const std::string kItemId4 = CreateNextAppId(GenerateId("app_id4"));
scoped_refptr<extensions::Extension> app4 =
MakeApp("D", kItemId4, extensions::Extension::NO_FLAGS);
InstallExtension(app4.get());
// Sort with the name alphabetical order.
ChromeAppListModelUpdater* model_updater = GetChromeModelUpdater();
model_updater->RequestAppListSort(ash::AppListSortOrder::kNameAlphabetical);
Commit();
// Sort with the name reverse alphabetical order without committing. The
// permanent sort order and the permanent item positions should not change.
model_updater->RequestAppListSort(
ash::AppListSortOrder::kNameReverseAlphabetical);
EXPECT_EQ(GetOrderedItemIdsFromSyncableService(),
std::vector<std::string>(
{kItemId1, kItemId2, kItemId3, kItemId4, kFolderItemId}));
EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
// Rename the test folder.
model_updater->RequestFolderRename(kFolderItemId, "A new folder name");
// Verify that:
// (1) Temporary sort ends.
// (2) Sort order is commited.
// (3) Local positions are committed.
EXPECT_FALSE(IsUnderTemporarySort());
EXPECT_EQ(ash::AppListSortOrder::kNameReverseAlphabetical,
GetSortOrderFromPrefs());
EXPECT_EQ(GetOrderedItemIdsFromSyncableService(),
std::vector<std::string>(
{kItemId4, kItemId3, kItemId2, kFolderItemId, kItemId1}));
EXPECT_EQ(GetOrderedItemIdsFromModelUpdater(),
std::vector<std::string>(
{kItemId4, kItemId3, kItemId2, kFolderItemId, kItemId1}));
}
// Verifies that the app list under temporary sort works as expected when moving
// an item to an existed folder.
TEST_F(TemporaryAppListSortTest, HandleMoveItemToFolder) {
RemoveAllExistingItems();
// Add one folder containing two apps.
// Emulate to merge two items into a folder.
const std::string kFolderItemId = GenerateId("folder_id");
ChromeAppListModelUpdater* model_updater = GetChromeModelUpdater();
std::unique_ptr<ChromeAppListItem> folder_item =
std::make_unique<ChromeAppListItem>(profile_.get(), kFolderItemId,
model_updater);
folder_item->SetChromeIsFolder(true);
ChromeAppListItem::TestApi(folder_item.get())
.SetPosition(syncer::StringOrdinal::CreateInitialOrdinal());
ChromeAppListItem::TestApi(folder_item.get()).SetName("Folder1");
app_list_syncable_service()->AddItem(std::move(folder_item));
const std::string kChildItemId1_1 = GenerateId("folder_child1");
const std::string kChildItemId1_2 = GenerateId("folder_child2");
syncer::SyncDataList sync_list;
sync_list.push_back(CreateAppRemoteData(
kFolderItemId, "Folder", "",
syncer::StringOrdinal::CreateInitialOrdinal().ToInternalValue(), kUnset,
sync_pb::AppListSpecifics_AppListItemType_TYPE_FOLDER));
syncer::StringOrdinal child_position =
syncer::StringOrdinal::CreateInitialOrdinal();
sync_list.push_back(CreateAppRemoteData(kChildItemId1_1, "D", kFolderItemId,
child_position.ToInternalValue(),
kUnset));
child_position = child_position.CreateAfter();
sync_list.push_back(CreateAppRemoteData(kChildItemId1_2, "E", kFolderItemId,
child_position.ToInternalValue(),
kUnset));
app_list_syncable_service()->MergeDataAndStartSyncing(
syncer::APP_LIST, sync_list,
std::make_unique<syncer::FakeSyncChangeProcessor>());
content::RunAllTasksUntilIdle();
// Install three apps.
const std::string kItemId1 = CreateNextAppId(GenerateId("app_id1"));
scoped_refptr<extensions::Extension> app1 =
MakeApp("A", kItemId1, extensions::Extension::NO_FLAGS);
InstallExtension(app1.get());
const std::string kItemId2 = CreateNextAppId(GenerateId("app_id2"));
scoped_refptr<extensions::Extension> app2 =
MakeApp("B", kItemId2, extensions::Extension::NO_FLAGS);
InstallExtension(app2.get());
const std::string kItemId3 = CreateNextAppId(GenerateId("app_id3"));
scoped_refptr<extensions::Extension> app3 =
MakeApp("C", kItemId3, extensions::Extension::NO_FLAGS);
InstallExtension(app3.get());
// Sort with the name alphabetical order and commit.
model_updater->RequestAppListSort(ash::AppListSortOrder::kNameAlphabetical);
Commit();
EXPECT_EQ(std::vector<std::string>({"A", "B", "C", "D", "E", "Folder"}),
GetOrderedNamesFromSyncableService());
// Sort with the name reverse alphabetical order without committing.
model_updater->RequestAppListSort(
ash::AppListSortOrder::kNameReverseAlphabetical);
EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
EXPECT_EQ(std::vector<std::string>({"A", "B", "C", "D", "E", "Folder"}),
GetOrderedNamesFromSyncableService());
// Move `app3` to the folder.
model_updater->RequestMoveItemToFolder(kItemId3, kFolderItemId);
// Verify that:
// (1) Temporary sort ends.
// (2) Sort order is committed.
EXPECT_FALSE(IsUnderTemporarySort());
EXPECT_EQ(ash::AppListSortOrder::kNameReverseAlphabetical,
GetSortOrderFromPrefs());
EXPECT_EQ(std::vector<std::string>({"Folder", "E", "D", "C", "B", "A"}),
GetOrderedNamesFromSyncableService());
}
// Verifies that the app list under temporary sort works as expected when moving
// an item from a folder to root apps grid.
TEST_F(TemporaryAppListSortTest, HandleMoveItemToRootGrid) {
RemoveAllExistingItems();
// Add one folder containing three apps.
const std::string kFolderItemId = GenerateId("folder_id");
syncer::SyncDataList sync_list;
sync_list.push_back(CreateAppRemoteData(
kFolderItemId, "Folder", "",
syncer::StringOrdinal::CreateInitialOrdinal().ToInternalValue(), kUnset,
sync_pb::AppListSpecifics_AppListItemType_TYPE_FOLDER));
const std::string kItemId1 = GenerateId("app_id1");
const std::string kItemId2 = GenerateId("app_id2");
const std::string kItemId3 = GenerateId("app_id3");
syncer::StringOrdinal child_position =
syncer::StringOrdinal::CreateInitialOrdinal();
sync_list.push_back(CreateAppRemoteData(
kItemId1, "A", kFolderItemId, child_position.ToInternalValue(), kUnset));
child_position = child_position.CreateAfter();
sync_list.push_back(CreateAppRemoteData(
kItemId2, "B", kFolderItemId, child_position.ToInternalValue(), kUnset));
child_position = child_position.CreateAfter();
sync_list.push_back(CreateAppRemoteData(
kItemId3, "C", kFolderItemId, child_position.ToInternalValue(), kUnset));
app_list_syncable_service()->MergeDataAndStartSyncing(
syncer::APP_LIST, sync_list,
std::make_unique<syncer::FakeSyncChangeProcessor>());
content::RunAllTasksUntilIdle();
// Install test apps that were added to the folder.
scoped_refptr<extensions::Extension> app1 =
MakeApp("A", kItemId1, extensions::Extension::NO_FLAGS);
InstallExtension(app1.get());
scoped_refptr<extensions::Extension> app2 =
MakeApp("B", kItemId2, extensions::Extension::NO_FLAGS);
InstallExtension(app2.get());
scoped_refptr<extensions::Extension> app3 =
MakeApp("C", kItemId3, extensions::Extension::NO_FLAGS);
InstallExtension(app3.get());
// Install an additional app.
const std::string kItemId4 = GenerateId("app_id4");
scoped_refptr<extensions::Extension> app4 =
MakeApp("G", kItemId4, extensions::Extension::NO_FLAGS);
InstallExtension(app4.get());
// Sort with the reverse alphabetical name order and commit.
ChromeAppListModelUpdater* model_updater = GetChromeModelUpdater();
model_updater->RequestAppListSort(
ash::AppListSortOrder::kNameReverseAlphabetical);
Commit();
EXPECT_EQ(std::vector<std::string>({"G", "Folder", "C", "B", "A"}),
GetOrderedNamesFromSyncableService());
// Sort with the name alphabetical order without committing.
model_updater->RequestAppListSort(ash::AppListSortOrder::kNameAlphabetical);
EXPECT_EQ(ash::AppListSortOrder::kNameReverseAlphabetical,
GetSortOrderFromPrefs());
EXPECT_EQ(std::vector<std::string>({"G", "Folder", "C", "B", "A"}),
GetOrderedNamesFromSyncableService());
// Move an folder item to root apps grid.
model_updater->RequestMoveItemToRoot(
kItemId1, model_updater->FindItem(kItemId4)->position().CreateAfter());
// Verify that:
// (1) Temporary sort ends.
// (2) Sort order pref reverts to custom.
EXPECT_FALSE(IsUnderTemporarySort());
EXPECT_EQ(ash::AppListSortOrder::kCustom, GetSortOrderFromPrefs());
EXPECT_EQ(std::vector<std::string>({"B", "C", "Folder", "G", "A"}),
GetOrderedNamesFromSyncableService());
}
// Verifies the temporary sorting behavior with local app installation.
TEST_F(TemporaryAppListSortTest, InstallAppLocally) {
RemoveAllExistingItems();
// Install three apps.
const std::string kItemId1 = CreateNextAppId(GenerateId("app_id1"));
scoped_refptr<extensions::Extension> app1 =
MakeApp("B", kItemId1, extensions::Extension::NO_FLAGS);
InstallExtension(app1.get());
const std::string kItemId2 = CreateNextAppId(GenerateId("app_id2"));
scoped_refptr<extensions::Extension> app2 =
MakeApp("C", kItemId2, extensions::Extension::NO_FLAGS);
InstallExtension(app2.get());
const std::string kItemId3 = CreateNextAppId(GenerateId("app_id3"));
scoped_refptr<extensions::Extension> app3 =
MakeApp("E", kItemId3, extensions::Extension::NO_FLAGS);
InstallExtension(app3.get());
// Sort with the name alphabetical order then commit the order.
ChromeAppListModelUpdater* model_updater = GetChromeModelUpdater();
model_updater->RequestAppListSort(ash::AppListSortOrder::kNameAlphabetical);
Commit();
// Sort with the name reverse alphabetical order without committing. The
// permanent sort order and the permanent item positions should not change.
model_updater->RequestAppListSort(
ash::AppListSortOrder::kNameReverseAlphabetical);
EXPECT_EQ(GetOrderedNamesFromSyncableService(),
std::vector<std::string>({"B", "C", "E"}));
EXPECT_EQ(GetOrderedNamesFromModelUpdater(),
std::vector<std::string>({"E", "C", "B"}));
EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
// Install the forth app.
const std::string kItemId4 = CreateNextAppId(GenerateId("app_id4"));
scoped_refptr<extensions::Extension> app4 =
MakeApp("A", kItemId4, extensions::Extension::NO_FLAGS);
InstallExtension(app4.get());
// Verify that the temporary sorting order is committed.
EXPECT_FALSE(IsUnderTemporarySort());
EXPECT_EQ(ash::AppListSortOrder::kNameReverseAlphabetical,
GetSortOrderFromPrefs());
EXPECT_EQ(GetOrderedNamesFromSyncableService(),
std::vector<std::string>({"E", "C", "B", "A"}));
EXPECT_EQ(GetOrderedNamesFromModelUpdater(),
std::vector<std::string>({"E", "C", "B", "A"}));
// Sort with the name alphabetical order without committing.
model_updater->RequestAppListSort(ash::AppListSortOrder::kNameAlphabetical);
EXPECT_EQ(GetOrderedNamesFromSyncableService(),
std::vector<std::string>({"E", "C", "B", "A"}));
EXPECT_EQ(GetOrderedNamesFromModelUpdater(),
std::vector<std::string>({"A", "B", "C", "E"}));
EXPECT_EQ(ash::AppListSortOrder::kNameReverseAlphabetical,
GetSortOrderFromPrefs());
// Install the fifth app.
const std::string kItemId5 = CreateNextAppId(GenerateId("app_id5"));
scoped_refptr<extensions::Extension> app5 =
MakeApp("D", kItemId5, extensions::Extension::NO_FLAGS);
InstallExtension(app5.get());
// Verify that the temporary sorting order is committed.
EXPECT_FALSE(IsUnderTemporarySort());
EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
EXPECT_EQ(GetOrderedNamesFromSyncableService(),
std::vector<std::string>({"A", "B", "C", "D", "E"}));
EXPECT_EQ(GetOrderedNamesFromModelUpdater(),
std::vector<std::string>({"A", "B", "C", "D", "E"}));
}
// Verifies the temporary sorting behavior with remote installation.
TEST_F(TemporaryAppListSortTest, InstallAppRemotely) {
// Start syncing.
app_list_syncable_service()->MergeDataAndStartSyncing(
syncer::APP_LIST, syncer::SyncDataList(),
std::make_unique<syncer::FakeSyncChangeProcessor>());
content::RunAllTasksUntilIdle();
RemoveAllExistingItems();
// Install three apps.
const std::string kItemId1 = CreateNextAppId(GenerateId("app_id1"));
scoped_refptr<extensions::Extension> app1 =
MakeApp("B", kItemId1, extensions::Extension::NO_FLAGS);
InstallExtension(app1.get());
const std::string kItemId2 = CreateNextAppId(GenerateId("app_id2"));
scoped_refptr<extensions::Extension> app2 =
MakeApp("C", kItemId2, extensions::Extension::NO_FLAGS);
InstallExtension(app2.get());
const std::string kItemId3 = CreateNextAppId(GenerateId("app_id3"));
scoped_refptr<extensions::Extension> app3 =
MakeApp("E", kItemId3, extensions::Extension::NO_FLAGS);
InstallExtension(app3.get());
// Sort with the name alphabetical order then commit the order.
ChromeAppListModelUpdater* model_updater = GetChromeModelUpdater();
model_updater->RequestAppListSort(ash::AppListSortOrder::kNameAlphabetical);
Commit();
// Sort with the name reverse alphabetical order without committing. The
// permanent sort order and the permanent item positions should not change.
model_updater->RequestAppListSort(
ash::AppListSortOrder::kNameReverseAlphabetical);
EXPECT_EQ(GetOrderedNamesFromSyncableService(),
std::vector<std::string>({"B", "C", "E"}));
EXPECT_EQ(GetOrderedNamesFromModelUpdater(),
std::vector<std::string>({"E", "C", "B"}));
EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
// Install an app remotely in the following steps:
// step 1: create a new sync data through sync service.
// step 2: install the new app that matches the data created in the step 1.
syncer::SyncChangeList change_list;
const std::string kItemId4 = CreateNextAppId(GenerateId("app_id4"));
const syncer::StringOrdinal target_position =
GetPositionFromSyncData(kItemId1).CreateBefore();
change_list.push_back(syncer::SyncChange(
FROM_HERE, syncer::SyncChange::ACTION_UPDATE,
CreateAppRemoteData(kItemId4, "A", std::string(),
target_position.ToInternalValue(), "")));
app_list_syncable_service()->ProcessSyncChanges(base::Location(),
change_list);
content::RunAllTasksUntilIdle();
scoped_refptr<extensions::Extension> app4 =
MakeApp("A", kItemId4, extensions::Extension::NO_FLAGS);
InstallExtension(app4.get());
// Verify that the app list is still under temporary sorting order.
EXPECT_TRUE(IsUnderTemporarySort());
EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
EXPECT_EQ(GetOrderedNamesFromSyncableService(),
std::vector<std::string>({"A", "B", "C", "E"}));
EXPECT_EQ(GetOrderedNamesFromModelUpdater(),
std::vector<std::string>({"E", "C", "B", "A"}));
// Revert the temporary sorting order. Verify that the new app is placed at
// the position that is specified by `change_list`.
model_updater->RequestAppListSortRevert();
EXPECT_FALSE(IsUnderTemporarySort());
EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
EXPECT_EQ(GetOrderedNamesFromSyncableService(),
std::vector<std::string>({"A", "B", "C", "E"}));
EXPECT_EQ(GetOrderedNamesFromModelUpdater(),
std::vector<std::string>({"A", "B", "C", "E"}));
}
// Verifies the temporary sorting behavior when an item is deleted due to the
// removal on a remote device.
TEST_F(TemporaryAppListSortTest, RemoveItemRemotely) {
// Start syncing.
app_list_syncable_service()->MergeDataAndStartSyncing(
syncer::APP_LIST, syncer::SyncDataList(),
std::make_unique<syncer::FakeSyncChangeProcessor>());
content::RunAllTasksUntilIdle();
RemoveAllExistingItems();
// Install four apps.
const std::string kItemId1 = CreateNextAppId(GenerateId("app_id1"));
scoped_refptr<extensions::Extension> app1 =
MakeApp("A", kItemId1, extensions::Extension::NO_FLAGS);
InstallExtension(app1.get());
const std::string kItemId2 = CreateNextAppId(GenerateId("app_id2"));
scoped_refptr<extensions::Extension> app2 =
MakeApp("B", kItemId2, extensions::Extension::NO_FLAGS);
InstallExtension(app2.get());
const std::string kItemId3 = CreateNextAppId(GenerateId("app_id3"));
scoped_refptr<extensions::Extension> app3 =
MakeApp("C", kItemId3, extensions::Extension::NO_FLAGS);
InstallExtension(app3.get());
const std::string kItemId4 = CreateNextAppId(GenerateId("app_id4"));
scoped_refptr<extensions::Extension> app4 =
MakeApp("D", kItemId4, extensions::Extension::NO_FLAGS);
InstallExtension(app4.get());
// Sort with the name alphabetical order then commit the order.
ChromeAppListModelUpdater* model_updater = GetChromeModelUpdater();
model_updater->RequestAppListSort(ash::AppListSortOrder::kNameAlphabetical);
Commit();
// Sort with the name reverse alphabetical order without committing. The
// permanent sort order and the permanent item positions should not change.
model_updater->RequestAppListSort(
ash::AppListSortOrder::kNameReverseAlphabetical);
EXPECT_EQ(GetOrderedNamesFromSyncableService(),
std::vector<std::string>({"A", "B", "C", "D"}));
EXPECT_EQ(GetOrderedNamesFromModelUpdater(),
std::vector<std::string>({"D", "C", "B", "A"}));
EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
// Delete `app4` remotely.
syncer::SyncChangeList change_list;
ChromeAppListItem* item_to_delete = model_updater->FindItem(kItemId4);
change_list.push_back(syncer::SyncChange(
FROM_HERE, syncer::SyncChange::ACTION_DELETE,
CreateAppRemoteData(
kItemId4, item_to_delete->name(), item_to_delete->folder_id(),
item_to_delete->position().ToInternalValue(), std::string())));
app_list_syncable_service()->ProcessSyncChanges(base::Location(),
change_list);
content::RunAllTasksUntilIdle();
// Verify that the app list is under temporary sorting.
EXPECT_EQ(GetOrderedNamesFromSyncableService(),
std::vector<std::string>({"A", "B", "C"}));
EXPECT_EQ(GetOrderedNamesFromModelUpdater(),
std::vector<std::string>({"C", "B", "A"}));
EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
// Revert the temporary sorting order.
model_updater->RequestAppListSortRevert();
EXPECT_FALSE(IsUnderTemporarySort());
EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
EXPECT_EQ(GetOrderedNamesFromSyncableService(),
std::vector<std::string>({"A", "B", "C"}));
EXPECT_EQ(GetOrderedNamesFromModelUpdater(),
std::vector<std::string>({"A", "B", "C"}));
}
// Verifies that ephemeral apps sorting moves all ephemeral items (apps and
// folders) to the front, in alphabetical, case insensitive order, followed by
// native items (apps and folders) also in alphabetical, case insensitive order.
TEST_F(TemporaryAppListSortTest, AlphabeticalEphemeralAppFirstSort) {
RemoveAllExistingItems();
ChromeAppListModelUpdater* model_updater = GetChromeModelUpdater();
std::vector<scoped_refptr<extensions::Extension>> apps;
for (int i = 0; i < 2; ++i) {
const std::string id =
GenerateId(base::StringPrintf("folder_app_id_%d", i));
const std::string name = base::StringPrintf("Folder Item %d", i);
scoped_refptr<extensions::Extension> app =
MakeApp(name, id, extensions::Extension::NO_FLAGS);
apps.push_back(app);
InstallExtension(app.get());
}
// Add a native folder with two items.
const std::string kFolderId1 = GenerateId("folder_id_1");
syncer::SyncDataList sync_list;
syncer::StringOrdinal child_position =
syncer::StringOrdinal::CreateInitialOrdinal();
sync_list.push_back(CreateAppRemoteData(
kFolderId1, "folder 1", "", child_position.ToInternalValue(), kUnset,
sync_pb::AppListSpecifics_AppListItemType_TYPE_FOLDER));
child_position = child_position.CreateAfter();
sync_list.push_back(
CreateAppRemoteData(apps[0]->id(), "Folder Item 0", kFolderId1,
child_position.ToInternalValue(), kUnset));
child_position = child_position.CreateAfter();
sync_list.push_back(
CreateAppRemoteData(apps[1]->id(), "Folder Item 1", kFolderId1,
child_position.ToInternalValue(), kUnset));
child_position = child_position.CreateAfter();
// Add two native apps.
const std::string kItemId1 = GenerateId("app_id1");
scoped_refptr<extensions::Extension> app1 =
MakeApp("app 1", kItemId1, extensions::Extension::NO_FLAGS);
InstallExtension(app1.get());
sync_list.push_back(CreateAppRemoteData(
kItemId1, "app 1", "", child_position.ToInternalValue(), kUnset));
child_position = child_position.CreateAfter();
const std::string kItemId2 = GenerateId("app_id2");
scoped_refptr<extensions::Extension> app2 =
MakeApp("App 2", kItemId2, extensions::Extension::NO_FLAGS);
InstallExtension(app2.get());
child_position = child_position.CreateAfter();
sync_list.push_back(CreateAppRemoteData(
kItemId2, "App 2", "", child_position.ToInternalValue(), kUnset));
child_position = child_position.CreateAfter();
// Add an ephemeral app and an ephemeral folder with two ephemeral apps
// inside.
std::unique_ptr<ChromeAppListItem> app3_item =
std::make_unique<ChromeAppListItem>(profile_.get(), GenerateId("app_id3"),
model_updater);
app3_item->SetIsEphemeral(true);
ChromeAppListItem::TestApi(app3_item.get()).SetPosition(child_position);
ChromeAppListItem::TestApi(app3_item.get()).SetName("app 3");
app_list_syncable_service()->AddItem(std::move(app3_item));
child_position = child_position.CreateAfter();
const std::string kFolderId2 = GenerateId("folder_id_2");
std::unique_ptr<ChromeAppListItem> folder2_item =
std::make_unique<ChromeAppListItem>(profile_.get(), kFolderId2,
model_updater);
folder2_item->SetChromeIsFolder(true);
folder2_item->SetIsEphemeral(true);
ChromeAppListItem::TestApi(folder2_item.get()).SetPosition(child_position);
ChromeAppListItem::TestApi(folder2_item.get()).SetName("folder 2");
app_list_syncable_service()->AddItem(std::move(folder2_item));
child_position = child_position.CreateAfter();
std::unique_ptr<ChromeAppListItem> app_folder2_item =
std::make_unique<ChromeAppListItem>(
profile_.get(), GenerateId("folder_app_id_2"), model_updater);
app_folder2_item->SetIsEphemeral(true);
app_folder2_item->SetChromeFolderId(kFolderId2);
ChromeAppListItem::TestApi(app_folder2_item.get())
.SetPosition(child_position);
ChromeAppListItem::TestApi(app_folder2_item.get()).SetName("Folder Item 2");
app_list_syncable_service()->AddItem(std::move(app_folder2_item));
child_position = child_position.CreateAfter();
std::unique_ptr<ChromeAppListItem> app_folder3_item =
std::make_unique<ChromeAppListItem>(
profile_.get(), GenerateId("folder_app_id_3"), model_updater);
app_folder3_item->SetIsEphemeral(true);
app_folder3_item->SetChromeFolderId(kFolderId2);
ChromeAppListItem::TestApi(app_folder3_item.get())
.SetPosition(child_position);
ChromeAppListItem::TestApi(app_folder3_item.get()).SetName("Folder Item 3");
app_list_syncable_service()->AddItem(std::move(app_folder3_item));
app_list_syncable_service()->MergeDataAndStartSyncing(
syncer::APP_LIST, sync_list,
std::make_unique<syncer::FakeSyncChangeProcessor>());
content::RunAllTasksUntilIdle();
// Verify the default order.
EXPECT_EQ(ash::AppListSortOrder::kCustom, GetSortOrderFromPrefs());
EXPECT_EQ(GetOrderedNamesFromSyncableService(),
std::vector<std::string>(
{"folder 1", "Folder Item 0", "Folder Item 1", "app 1", "App 2",
"app 3", "folder 2", "Folder Item 2", "Folder Item 3"}));
// Sort apps with ephemeral apps first order.
model_updater->RequestAppListSort(
ash::AppListSortOrder::kAlphabeticalEphemeralAppFirst);
// All ephemeral items (apps and folders) are sorted to the front, in
// alphabetical, case insensitive order, followed by native items (apps and
// folders) also in alphabetical, case insensitive order:
// Ephemeral items: [app 3, folder 2, Folder Item 2, Folder Item 3],
// Native items: [app 1, App 2, folder 1, Folder Item 0, Folder Item 1]
EXPECT_EQ(ash::AppListSortOrder::kCustom, GetSortOrderFromPrefs());
EXPECT_EQ(ash::AppListSortOrder::kAlphabeticalEphemeralAppFirst,
GetTemporarySortOrder());
EXPECT_EQ(GetOrderedNamesFromModelUpdater(),
std::vector<std::string>(
{"app 3", "folder 2", "Folder Item 2", "Folder Item 3", "app 1",
"App 2", "folder 1", "Folder Item 0", "Folder Item 1"}));
// Add a new ephemeral app.
const std::string kItemId6 = GenerateId("app_id6");
std::unique_ptr<ChromeAppListItem> kItemId6_item =
std::make_unique<ChromeAppListItem>(profile_.get(), kItemId6,
model_updater);
kItemId6_item->SetIsEphemeral(true);
ChromeAppListItem::TestApi(kItemId6_item.get()).SetName("App 4");
app_list_syncable_service()->AddItem(std::move(kItemId6_item));
// Verify that the app is added to the correct spot, with the other ephemeral
// items.
EXPECT_EQ(
GetOrderedNamesFromModelUpdater(),
std::vector<std::string>({"app 3", "App 4", "folder 2", "Folder Item 2",
"Folder Item 3", "app 1", "App 2", "folder 1",
"Folder Item 0", "Folder Item 1"}));
}
// The test class used to verify local uninstallation.
class TemporaryAppListSortLocalUninstallationTest
: public TemporaryAppListSortTest,
public testing::WithParamInterface<bool> {
public:
TemporaryAppListSortLocalUninstallationTest()
: TemporaryAppListSortTest(), uninstall_through_service_(GetParam()) {}
TemporaryAppListSortLocalUninstallationTest(
const TemporaryAppListSortLocalUninstallationTest&) = delete;
TemporaryAppListSortLocalUninstallationTest& operator=(
const TemporaryAppListSortLocalUninstallationTest&) = delete;
~TemporaryAppListSortLocalUninstallationTest() override = default;
protected:
// If true, an app should be uninstalled through the syncable service;
// otherwise, an app should be deleted by the model updater.
const bool uninstall_through_service_;
};
INSTANTIATE_TEST_SUITE_P(All,
TemporaryAppListSortLocalUninstallationTest,
testing::Bool());
// Verifies the temporary sorting behavior with local app uninstallation.
TEST_P(TemporaryAppListSortLocalUninstallationTest, Basics) {
RemoveAllExistingItems();
// Install four apps.
const std::string kItemId1 = CreateNextAppId(GenerateId("app_id1"));
scoped_refptr<extensions::Extension> app1 =
MakeApp("A", kItemId1, extensions::Extension::NO_FLAGS);
InstallExtension(app1.get());
const std::string kItemId2 = CreateNextAppId(GenerateId("app_id2"));
scoped_refptr<extensions::Extension> app2 =
MakeApp("B", kItemId2, extensions::Extension::NO_FLAGS);
InstallExtension(app2.get());
const std::string kItemId3 = CreateNextAppId(GenerateId("app_id3"));
scoped_refptr<extensions::Extension> app3 =
MakeApp("C", kItemId3, extensions::Extension::NO_FLAGS);
InstallExtension(app3.get());
const std::string kItemId4 = CreateNextAppId(GenerateId("app_id4"));
scoped_refptr<extensions::Extension> app4 =
MakeApp("D", kItemId4, extensions::Extension::NO_FLAGS);
InstallExtension(app4.get());
// Sort with the name alphabetical order then commit the order.
ChromeAppListModelUpdater* model_updater = GetChromeModelUpdater();
model_updater->RequestAppListSort(ash::AppListSortOrder::kNameAlphabetical);
Commit();
// Sort with the name reverse alphabetical order without committing. The
// permanent sort order and the permanent item positions should not change.
model_updater->RequestAppListSort(
ash::AppListSortOrder::kNameReverseAlphabetical);
EXPECT_EQ(GetOrderedNamesFromSyncableService(),
std::vector<std::string>({"A", "B", "C", "D"}));
EXPECT_EQ(GetOrderedNamesFromModelUpdater(),
std::vector<std::string>({"D", "C", "B", "A"}));
EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
// Trigger item removal from the local device by app list syncable service or
// the model updater depending on the test param.
if (uninstall_through_service_) {
app_list_syncable_service()->RemoveItem(kItemId4, /*is_uninstall=*/true);
} else {
// Default apps uninstallation could bypass app list syncable service.
// Therefore remove the item through `model_updater` to verify this
// scenario.
model_updater->RemoveItem(kItemId4, /*is_uninstall=*/true);
}
// Verify that the temporary order is committed.
if (uninstall_through_service_) {
EXPECT_EQ(GetOrderedNamesFromSyncableService(),
std::vector<std::string>({"C", "B", "A"}));
}
EXPECT_EQ(GetOrderedNamesFromModelUpdater(),
std::vector<std::string>({"C", "B", "A"}));
EXPECT_EQ(ash::AppListSortOrder::kNameReverseAlphabetical,
GetSortOrderFromPrefs());
}
} // namespace app_list