// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ui/app_list/app_list_model.h" #include #include #include "base/command_line.h" #include "base/strings/utf_string_conversions.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/app_list/app_list_folder_item.h" #include "ui/app_list/app_list_item.h" #include "ui/app_list/app_list_model_observer.h" #include "ui/app_list/app_list_switches.h" #include "ui/app_list/test/app_list_test_model.h" #include "ui/base/models/list_model_observer.h" namespace app_list { namespace { class TestObserver : public AppListModelObserver { public: TestObserver() : status_changed_count_(0), items_added_(0), items_removed_(0), items_updated_(0) { } ~TestObserver() override {} // AppListModelObserver void OnAppListModelStatusChanged() override { ++status_changed_count_; } void OnAppListItemAdded(AppListItem* item) override { items_added_++; } void OnAppListItemWillBeDeleted(AppListItem* item) override { items_removed_++; } void OnAppListItemUpdated(AppListItem* item) override { items_updated_++; } int status_changed_count() const { return status_changed_count_; } size_t items_added() { return items_added_; } size_t items_removed() { return items_removed_; } size_t items_updated() { return items_updated_; } void ResetCounts() { status_changed_count_ = 0; items_added_ = 0; items_removed_ = 0; items_updated_ = 0; } private: int status_changed_count_; size_t items_added_; size_t items_removed_; size_t items_updated_; DISALLOW_COPY_AND_ASSIGN(TestObserver); }; } // namespace class AppListModelTest : public testing::Test { public: AppListModelTest() {} ~AppListModelTest() override {} // testing::Test overrides: void SetUp() override { model_.AddObserver(&observer_); } void TearDown() override { model_.RemoveObserver(&observer_); } protected: static bool ItemObservedByFolder(const AppListFolderItem* folder, const AppListItem* item) { return item->observers_.HasObserver(&folder->folder_image()); } std::string GetItemListContents(AppListItemList* item_list) { std::string s; for (size_t i = 0; i < item_list->item_count(); ++i) { if (i != 0) s += ","; s += item_list->item_at(i)->id(); } return s; } std::string GetModelContents() { return GetItemListContents(model_.top_level_item_list()); } test::AppListTestModel model_; TestObserver observer_; private: DISALLOW_COPY_AND_ASSIGN(AppListModelTest); }; TEST_F(AppListModelTest, SetStatus) { EXPECT_EQ(AppListModel::STATUS_NORMAL, model_.status()); model_.SetStatus(AppListModel::STATUS_SYNCING); EXPECT_EQ(1, observer_.status_changed_count()); EXPECT_EQ(AppListModel::STATUS_SYNCING, model_.status()); model_.SetStatus(AppListModel::STATUS_NORMAL); EXPECT_EQ(2, observer_.status_changed_count()); // Set the same status, no change is expected. model_.SetStatus(AppListModel::STATUS_NORMAL); EXPECT_EQ(2, observer_.status_changed_count()); } TEST_F(AppListModelTest, AppsObserver) { const size_t num_apps = 2; model_.PopulateApps(num_apps); EXPECT_EQ(num_apps, observer_.items_added()); } TEST_F(AppListModelTest, ModelGetItem) { const size_t num_apps = 2; model_.PopulateApps(num_apps); AppListItem* item0 = model_.top_level_item_list()->item_at(0); ASSERT_TRUE(item0); EXPECT_EQ(model_.GetItemName(0), item0->id()); AppListItem* item1 = model_.top_level_item_list()->item_at(1); ASSERT_TRUE(item1); EXPECT_EQ(model_.GetItemName(1), item1->id()); } TEST_F(AppListModelTest, ModelFindItem) { const size_t num_apps = 2; model_.PopulateApps(num_apps); std::string item_name0 = model_.GetItemName(0); AppListItem* item0 = model_.FindItem(item_name0); ASSERT_TRUE(item0); EXPECT_EQ(item_name0, item0->id()); std::string item_name1 = model_.GetItemName(1); AppListItem* item1 = model_.FindItem(item_name1); ASSERT_TRUE(item1); EXPECT_EQ(item_name1, item1->id()); } TEST_F(AppListModelTest, SetItemPosition) { const size_t num_apps = 2; model_.PopulateApps(num_apps); // Adding another item will add it to the end. model_.CreateAndAddItem("Added Item 1"); ASSERT_EQ(num_apps + 1, model_.top_level_item_list()->item_count()); EXPECT_EQ("Added Item 1", model_.top_level_item_list()->item_at(num_apps)->id()); // Add an item between items 0 and 1. AppListItem* item0 = model_.top_level_item_list()->item_at(0); ASSERT_TRUE(item0); AppListItem* item1 = model_.top_level_item_list()->item_at(1); ASSERT_TRUE(item1); AppListItem* item2 = model_.CreateItem("Added Item 2"); model_.AddItem(item2); EXPECT_EQ("Item 0,Item 1,Added Item 1,Added Item 2", GetModelContents()); model_.SetItemPosition( item2, item0->position().CreateBetween(item1->position())); EXPECT_EQ(num_apps + 2, model_.top_level_item_list()->item_count()); EXPECT_EQ(num_apps + 2, observer_.items_added()); EXPECT_EQ("Item 0,Added Item 2,Item 1,Added Item 1", GetModelContents()); } TEST_F(AppListModelTest, ModelMoveItem) { const size_t num_apps = 3; model_.PopulateApps(num_apps); // Adding another item will add it to the end. model_.CreateAndAddItem("Inserted Item"); ASSERT_EQ(num_apps + 1, model_.top_level_item_list()->item_count()); // Move it to the position 1. observer_.ResetCounts(); model_.top_level_item_list()->MoveItem(num_apps, 1); EXPECT_EQ(1u, observer_.items_updated()); EXPECT_EQ("Item 0,Inserted Item,Item 1,Item 2", GetModelContents()); } TEST_F(AppListModelTest, ModelRemoveItem) { const size_t num_apps = 4; model_.PopulateApps(num_apps); // Remove an item in the middle. model_.DeleteItem(model_.GetItemName(1)); EXPECT_EQ(num_apps - 1, model_.top_level_item_list()->item_count()); EXPECT_EQ(1u, observer_.items_removed()); EXPECT_EQ("Item 0,Item 2,Item 3", GetModelContents()); // Remove the first item in the list. model_.DeleteItem(model_.GetItemName(0)); EXPECT_EQ(num_apps - 2, model_.top_level_item_list()->item_count()); EXPECT_EQ(2u, observer_.items_removed()); EXPECT_EQ("Item 2,Item 3", GetModelContents()); // Remove the last item in the list. model_.DeleteItem(model_.GetItemName(num_apps - 1)); EXPECT_EQ(num_apps - 3, model_.top_level_item_list()->item_count()); EXPECT_EQ(3u, observer_.items_removed()); EXPECT_EQ("Item 2", GetModelContents()); } TEST_F(AppListModelTest, AppOrder) { const size_t num_apps = 5; model_.PopulateApps(num_apps); // Ensure order is preserved. for (size_t i = 1; i < num_apps; ++i) { EXPECT_TRUE( model_.top_level_item_list()->item_at(i)->position().GreaterThan( model_.top_level_item_list()->item_at(i - 1)->position())); } // Move an app model_.top_level_item_list()->MoveItem(num_apps - 1, 1); // Ensure order is preserved. for (size_t i = 1; i < num_apps; ++i) { EXPECT_TRUE( model_.top_level_item_list()->item_at(i)->position().GreaterThan( model_.top_level_item_list()->item_at(i - 1)->position())); } } class AppListModelFolderTest : public AppListModelTest { public: AppListModelFolderTest() { model_.SetFoldersEnabled(true); } ~AppListModelFolderTest() override {} // testing::Test overrides: void SetUp() override { AppListModelTest::SetUp(); } void TearDown() override { AppListModelTest::TearDown(); } private: DISALLOW_COPY_AND_ASSIGN(AppListModelFolderTest); }; TEST_F(AppListModelFolderTest, FolderItem) { AppListFolderItem* folder = new AppListFolderItem("folder1", AppListFolderItem::FOLDER_TYPE_NORMAL); const size_t num_folder_apps = 8; const size_t num_observed_apps = 4; model_.AddItem(folder); for (int i = 0; static_cast(i) < num_folder_apps; ++i) { std::string name = model_.GetItemName(i); model_.AddItemToFolder(model_.CreateItem(name), folder->id()); } ASSERT_EQ(num_folder_apps, folder->item_list()->item_count()); // Check that items 0 and 3 are observed. EXPECT_TRUE(ItemObservedByFolder( folder, folder->item_list()->item_at(0))); EXPECT_TRUE(ItemObservedByFolder( folder, folder->item_list()->item_at(num_observed_apps - 1))); // Check that item 4 is not observed. EXPECT_FALSE(ItemObservedByFolder( folder, folder->item_list()->item_at(num_observed_apps))); folder->item_list()->MoveItem(num_observed_apps, 0); // Confirm that everything was moved where expected. EXPECT_EQ(model_.GetItemName(num_observed_apps), folder->item_list()->item_at(0)->id()); EXPECT_EQ(model_.GetItemName(0), folder->item_list()->item_at(1)->id()); EXPECT_EQ(model_.GetItemName(num_observed_apps - 1), folder->item_list()->item_at(num_observed_apps)->id()); // Check that items 0 and 3 are observed. EXPECT_TRUE(ItemObservedByFolder( folder, folder->item_list()->item_at(0))); EXPECT_TRUE(ItemObservedByFolder( folder, folder->item_list()->item_at(num_observed_apps - 1))); // Check that item 4 is not observed. EXPECT_FALSE(ItemObservedByFolder( folder, folder->item_list()->item_at(num_observed_apps))); } TEST_F(AppListModelFolderTest, MergeItems) { model_.PopulateApps(3); ASSERT_EQ(3u, model_.top_level_item_list()->item_count()); AppListItem* item0 = model_.top_level_item_list()->item_at(0); AppListItem* item1 = model_.top_level_item_list()->item_at(1); AppListItem* item2 = model_.top_level_item_list()->item_at(2); // Merge an item onto a non-existent target. EXPECT_EQ(std::string(), model_.MergeItems("nonexistent", item0->id())); ASSERT_EQ(3u, model_.top_level_item_list()->item_count()); // Merge a non-existent item onto a target. EXPECT_EQ(std::string(), model_.MergeItems(item0->id(), "nonexistent")); ASSERT_EQ(3u, model_.top_level_item_list()->item_count()); // Merge an item onto itself (should have no effect). This should not be // possible, but there have been bugs in the past that made it possible (see // http://crbug.com/415530), so it should be handled correctly. EXPECT_EQ(std::string(), model_.MergeItems(item0->id(), item0->id())); ASSERT_EQ(3u, model_.top_level_item_list()->item_count()); // Merge two items. std::string folder1_id = model_.MergeItems(item0->id(), item1->id()); ASSERT_EQ(2u, model_.top_level_item_list()->item_count()); // Folder + 1 item AppListFolderItem* folder1_item = model_.FindFolderItem(folder1_id); ASSERT_TRUE(folder1_item); EXPECT_EQ("Item 0,Item 1", GetItemListContents(folder1_item->item_list())); // Merge an item onto an item that is already in a folder (should have no // effect). This should not be possible, but it should be handled correctly // if it does happen. EXPECT_EQ(std::string(), model_.MergeItems(item1->id(), item2->id())); ASSERT_EQ(2u, model_.top_level_item_list()->item_count()); // Folder + 1 item EXPECT_EQ("Item 0,Item 1", GetItemListContents(folder1_item->item_list())); // Merge an item from the new folder into the third item. std::string folder2_id = model_.MergeItems(item2->id(), item1->id()); ASSERT_EQ(2u, model_.top_level_item_list()->item_count()); // 2 folders AppListFolderItem* folder2_item = model_.FindFolderItem(folder2_id); EXPECT_EQ("Item 0", GetItemListContents(folder1_item->item_list())); EXPECT_EQ("Item 2,Item 1", GetItemListContents(folder2_item->item_list())); // Merge the remaining item to the new folder, ensure it is added to the end. std::string folder_id = model_.MergeItems(folder2_id, item0->id()); EXPECT_EQ(folder2_id, folder_id); EXPECT_EQ("Item 2,Item 1,Item 0", GetItemListContents(folder2_item->item_list())); // The empty folder should be deleted. folder1_item = model_.FindFolderItem(folder1_id); EXPECT_FALSE(folder1_item); EXPECT_EQ(1u, model_.top_level_item_list()->item_count()); // 1 folder } TEST_F(AppListModelFolderTest, AddItemToFolder) { AppListFolderItem* folder = new AppListFolderItem("folder1", AppListFolderItem::FOLDER_TYPE_NORMAL); model_.AddItem(folder); AppListItem* item0 = new AppListItem("Item 0"); model_.AddItemToFolder(item0, folder->id()); ASSERT_EQ(1u, model_.top_level_item_list()->item_count()); AppListFolderItem* folder_item = model_.FindFolderItem(folder->id()); ASSERT_TRUE(folder_item); ASSERT_EQ(1u, folder_item->item_list()->item_count()); EXPECT_EQ(item0, folder_item->item_list()->item_at(0)); EXPECT_EQ(folder->id(), item0->folder_id()); } TEST_F(AppListModelFolderTest, MoveItemToFolder) { AppListFolderItem* folder = new AppListFolderItem("folder1", AppListFolderItem::FOLDER_TYPE_NORMAL); model_.AddItem(folder); AppListItem* item0 = new AppListItem("Item 0"); AppListItem* item1 = new AppListItem("Item 1"); model_.AddItem(item0); model_.AddItem(item1); ASSERT_EQ(3u, model_.top_level_item_list()->item_count()); // Move item0 and item1 to folder. std::string folder_id = folder->id(); model_.MoveItemToFolder(item0, folder_id); model_.MoveItemToFolder(item1, folder_id); AppListFolderItem* folder_item = model_.FindFolderItem(folder_id); ASSERT_TRUE(folder_item); EXPECT_EQ(folder_id, item0->folder_id()); EXPECT_EQ(folder_id, item1->folder_id()); EXPECT_EQ("Item 0,Item 1", GetItemListContents(folder_item->item_list())); // Move item0 out of folder. model_.MoveItemToFolder(item0, ""); EXPECT_EQ("", item0->folder_id()); folder_item = model_.FindFolderItem(folder_id); ASSERT_TRUE(folder_item); // Move item1 out of folder, folder should be deleted. model_.MoveItemToFolder(item1, ""); EXPECT_EQ("", item1->folder_id()); folder_item = model_.FindFolderItem(folder_id); EXPECT_FALSE(folder_item); } TEST_F(AppListModelFolderTest, MoveItemToFolderAt) { model_.AddItem(new AppListItem("Item 0")); model_.AddItem(new AppListItem("Item 1")); AppListFolderItem* folder1 = static_cast(model_.AddItem( new AppListFolderItem("folder1", AppListFolderItem::FOLDER_TYPE_NORMAL))); model_.AddItem(new AppListItem("Item 2")); model_.AddItem(new AppListItem("Item 3")); ASSERT_EQ(5u, model_.top_level_item_list()->item_count()); EXPECT_EQ("Item 0,Item 1,folder1,Item 2,Item 3", GetModelContents()); // Move Item 1 to folder1, then Item 2 before Item 1. model_.MoveItemToFolderAt(model_.top_level_item_list()->item_at(1), folder1->id(), syncer::StringOrdinal()); EXPECT_EQ("Item 0,folder1,Item 2,Item 3", GetModelContents()); model_.MoveItemToFolderAt(model_.top_level_item_list()->item_at(2), folder1->id(), folder1->item_list()->item_at(0)->position()); EXPECT_EQ("Item 2,Item 1", GetItemListContents(folder1->item_list())); EXPECT_EQ("Item 0,folder1,Item 3", GetModelContents()); // Move Item 2 out of folder to before folder. model_.MoveItemToFolderAt( folder1->item_list()->item_at(0), "", folder1->position()); EXPECT_EQ("Item 0,Item 2,folder1,Item 3", GetModelContents()); // Move remaining folder item, (Item 1) out of folder to folder position. ASSERT_EQ(1u, folder1->item_list()->item_count()); model_.MoveItemToFolderAt( folder1->item_list()->item_at(0), "", folder1->position()); EXPECT_EQ("Item 0,Item 2,Item 1,Item 3", GetModelContents()); } TEST_F(AppListModelFolderTest, MoveItemFromFolderToFolder) { AppListFolderItem* folder0 = new AppListFolderItem("folder0", AppListFolderItem::FOLDER_TYPE_NORMAL); AppListFolderItem* folder1 = new AppListFolderItem("folder1", AppListFolderItem::FOLDER_TYPE_NORMAL); model_.AddItem(folder0); model_.AddItem(folder1); EXPECT_EQ("folder0,folder1", GetModelContents()); AppListItem* item0 = new AppListItem("Item 0"); AppListItem* item1 = new AppListItem("Item 1"); model_.AddItemToFolder(item0, folder0->id()); model_.AddItemToFolder(item1, folder0->id()); EXPECT_EQ(folder0->id(), item0->folder_id()); EXPECT_EQ(folder0->id(), item1->folder_id()); EXPECT_EQ("Item 0,Item 1", GetItemListContents(folder0->item_list())); // Move item0 from folder0 to folder1. model_.MoveItemToFolder(item0, folder1->id()); ASSERT_EQ(1u, folder0->item_list()->item_count()); ASSERT_EQ(1u, folder1->item_list()->item_count()); EXPECT_EQ(folder1->id(), item0->folder_id()); EXPECT_EQ("Item 1", GetItemListContents(folder0->item_list())); EXPECT_EQ("Item 0", GetItemListContents(folder1->item_list())); // Move item1 from folder0 to folder1. folder0 should get deleted. model_.MoveItemToFolder(item1, folder1->id()); ASSERT_EQ(1u, model_.top_level_item_list()->item_count()); ASSERT_EQ(2u, folder1->item_list()->item_count()); EXPECT_EQ(folder1->id(), item1->folder_id()); EXPECT_EQ("Item 0,Item 1", GetItemListContents(folder1->item_list())); // Move item1 to a non-existant folder2 which should get created. model_.MoveItemToFolder(item1, "folder2"); ASSERT_EQ(2u, model_.top_level_item_list()->item_count()); ASSERT_EQ(1u, folder1->item_list()->item_count()); EXPECT_EQ("folder2", item1->folder_id()); AppListFolderItem* folder2 = model_.FindFolderItem("folder2"); ASSERT_TRUE(folder2); } TEST_F(AppListModelFolderTest, UninstallFolderItems) { AppListItem* item0 = model_.CreateAndAddItem("Item 0"); AppListItem* item1 = model_.CreateAndAddItem("Item 1"); AppListItem* item2 = model_.CreateAndAddItem("Item 2"); AppListFolderItem* folder1 = static_cast(model_.AddItem( new AppListFolderItem("folder1", AppListFolderItem::FOLDER_TYPE_NORMAL))); EXPECT_EQ("Item 0,Item 1,Item 2,folder1", GetModelContents()); // Move all items to folder1. model_.MoveItemToFolderAt(item0, folder1->id(), item0->position()); model_.MoveItemToFolderAt(item1, folder1->id(), item1->position()); model_.MoveItemToFolderAt(item2, folder1->id(), item2->position()); EXPECT_EQ("Item 0,Item 1,Item 2", GetItemListContents(folder1->item_list())); EXPECT_EQ("folder1", GetModelContents()); // Delete Item 2 from folder. model_.DeleteUninstalledItem("Item 2"); EXPECT_EQ("Item 0,Item 1", GetItemListContents(folder1->item_list())); EXPECT_EQ("folder1", GetModelContents()); // Delete Item 1 from folder, should reparent Item 0 and delete folder1. model_.DeleteUninstalledItem("Item 1"); EXPECT_EQ(nullptr, model_.FindItem("folder1")); EXPECT_EQ("Item 0", GetModelContents()); } TEST_F(AppListModelFolderTest, UninstallSingleItemFolderItem) { AppListItem* item0 = model_.CreateAndAddItem("Item 0"); AppListFolderItem* folder1 = static_cast(model_.AddItem( new AppListFolderItem("folder1", AppListFolderItem::FOLDER_TYPE_NORMAL))); EXPECT_EQ("Item 0,folder1", GetModelContents()); // Move item0 to folder1. model_.MoveItemToFolderAt(item0, folder1->id(), item0->position()); EXPECT_EQ("Item 0", GetItemListContents(folder1->item_list())); EXPECT_EQ("folder1", GetModelContents()); // Delete only item from folder, folder should also be removed. model_.DeleteUninstalledItem("Item 0"); EXPECT_EQ(nullptr, model_.FindItem("folder1")); EXPECT_EQ("", GetModelContents()); } TEST_F(AppListModelFolderTest, FindItemInFolder) { AppListFolderItem* folder = new AppListFolderItem("folder1", AppListFolderItem::FOLDER_TYPE_NORMAL); EXPECT_TRUE(folder); model_.AddItem(folder); std::string folder_id = folder->id(); AppListItem* item0 = new AppListItem("Item 0"); model_.AddItemToFolder(item0, folder_id); AppListItem* found_item = model_.FindItem(item0->id()); ASSERT_EQ(item0, found_item); EXPECT_EQ(folder_id, found_item->folder_id()); } TEST_F(AppListModelFolderTest, OemFolder) { AppListFolderItem* folder = new AppListFolderItem("folder1", AppListFolderItem::FOLDER_TYPE_OEM); model_.AddItem(folder); std::string folder_id = folder->id(); // Should not be able to move to an OEM folder with MergeItems. AppListItem* item0 = new AppListItem("Item 0"); model_.AddItem(item0); syncer::StringOrdinal item0_pos = item0->position(); std::string new_folder = model_.MergeItems(folder_id, item0->id()); EXPECT_EQ("", new_folder); EXPECT_EQ("", item0->folder_id()); EXPECT_TRUE(item0->position().Equals(item0_pos)); // Should not be able to move from an OEM folder with MoveItemToFolderAt. AppListItem* item1 = new AppListItem("Item 1"); model_.AddItemToFolder(item1, folder_id); syncer::StringOrdinal item1_pos = item1->position(); bool move_res = model_.MoveItemToFolderAt(item1, "", syncer::StringOrdinal()); EXPECT_FALSE(move_res); EXPECT_TRUE(item1->position().Equals(item1_pos)); } TEST_F(AppListModelFolderTest, DisableFolders) { // Set up a folder with two items and an OEM folder with one item. AppListFolderItem* folder = new AppListFolderItem("folder1", AppListFolderItem::FOLDER_TYPE_NORMAL); model_.AddItem(folder); std::string folder_id = folder->id(); AppListItem* item0 = new AppListItem("Item 0"); model_.AddItemToFolder(item0, folder_id); AppListItem* item1 = new AppListItem("Item 1"); model_.AddItemToFolder(item1, folder_id); AppListFolderItem* folder_item = model_.FindFolderItem(folder_id); ASSERT_TRUE(folder_item); EXPECT_EQ(2u, folder_item->item_list()->item_count()); AppListFolderItem* oem_folder = new AppListFolderItem("oem_folder", AppListFolderItem::FOLDER_TYPE_OEM); model_.AddItem(oem_folder); AppListItem* oem_item = new AppListItem("OEM Item"); std::string oem_folder_id = oem_folder->id(); model_.AddItemToFolder(oem_item, oem_folder_id); EXPECT_EQ("folder1,oem_folder", GetModelContents()); // Disable folders. Ensure non-oem folder is removed. model_.SetFoldersEnabled(false); ASSERT_FALSE(model_.FindFolderItem(folder_id)); ASSERT_TRUE(model_.FindFolderItem(oem_folder_id)); EXPECT_EQ("Item 0,Item 1,oem_folder", GetModelContents()); // Ensure folder creation fails. EXPECT_EQ(std::string(), model_.MergeItems(item0->id(), item1->id())); } } // namespace app_list