1 // Copyright 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "ui/app_list/app_list_model.h" 6 7 #include <map> 8 #include <string> 9 10 #include "base/command_line.h" 11 #include "base/strings/utf_string_conversions.h" 12 #include "testing/gtest/include/gtest/gtest.h" 13 #include "ui/app_list/app_list_folder_item.h" 14 #include "ui/app_list/app_list_item.h" 15 #include "ui/app_list/app_list_model_observer.h" 16 #include "ui/app_list/app_list_switches.h" 17 #include "ui/app_list/test/app_list_test_model.h" 18 #include "ui/base/models/list_model_observer.h" 19 20 namespace app_list { 21 22 namespace { 23 24 class TestObserver : public AppListModelObserver { 25 public: 26 TestObserver() 27 : status_changed_count_(0), 28 items_added_(0), 29 items_removed_(0), 30 items_updated_(0) { 31 } 32 virtual ~TestObserver() { 33 } 34 35 // AppListModelObserver 36 virtual void OnAppListModelStatusChanged() OVERRIDE { 37 ++status_changed_count_; 38 } 39 40 virtual void OnAppListItemAdded(AppListItem* item) OVERRIDE { 41 items_added_++; 42 } 43 44 virtual void OnAppListItemWillBeDeleted(AppListItem* item) OVERRIDE { 45 items_removed_++; 46 } 47 48 virtual void OnAppListItemUpdated(AppListItem* item) OVERRIDE { 49 items_updated_++; 50 } 51 52 int status_changed_count() const { return status_changed_count_; } 53 size_t items_added() { return items_added_; } 54 size_t items_removed() { return items_removed_; } 55 size_t items_updated() { return items_updated_; } 56 57 void ResetCounts() { 58 status_changed_count_ = 0; 59 items_added_ = 0; 60 items_removed_ = 0; 61 items_updated_ = 0; 62 } 63 64 private: 65 int status_changed_count_; 66 size_t items_added_; 67 size_t items_removed_; 68 size_t items_updated_; 69 70 DISALLOW_COPY_AND_ASSIGN(TestObserver); 71 }; 72 73 } // namespace 74 75 class AppListModelTest : public testing::Test { 76 public: 77 AppListModelTest() {} 78 virtual ~AppListModelTest() {} 79 80 // testing::Test overrides: 81 virtual void SetUp() OVERRIDE { 82 model_.AddObserver(&observer_); 83 } 84 virtual void TearDown() OVERRIDE { 85 model_.RemoveObserver(&observer_); 86 } 87 88 protected: 89 bool ItemObservedByFolder(AppListFolderItem* folder, 90 AppListItem* item) { 91 return item->observers_.HasObserver(folder); 92 } 93 94 std::string GetItemListContents(AppListItemList* item_list) { 95 std::string s; 96 for (size_t i = 0; i < item_list->item_count(); ++i) { 97 if (i != 0) 98 s += ","; 99 s += item_list->item_at(i)->id(); 100 } 101 return s; 102 } 103 104 std::string GetModelContents() { 105 return GetItemListContents(model_.top_level_item_list()); 106 } 107 108 test::AppListTestModel model_; 109 TestObserver observer_; 110 111 private: 112 DISALLOW_COPY_AND_ASSIGN(AppListModelTest); 113 }; 114 115 TEST_F(AppListModelTest, SetStatus) { 116 EXPECT_EQ(AppListModel::STATUS_NORMAL, model_.status()); 117 model_.SetStatus(AppListModel::STATUS_SYNCING); 118 EXPECT_EQ(1, observer_.status_changed_count()); 119 EXPECT_EQ(AppListModel::STATUS_SYNCING, model_.status()); 120 model_.SetStatus(AppListModel::STATUS_NORMAL); 121 EXPECT_EQ(2, observer_.status_changed_count()); 122 // Set the same status, no change is expected. 123 model_.SetStatus(AppListModel::STATUS_NORMAL); 124 EXPECT_EQ(2, observer_.status_changed_count()); 125 } 126 127 TEST_F(AppListModelTest, AppsObserver) { 128 const size_t num_apps = 2; 129 model_.PopulateApps(num_apps); 130 EXPECT_EQ(num_apps, observer_.items_added()); 131 } 132 133 TEST_F(AppListModelTest, ModelGetItem) { 134 const size_t num_apps = 2; 135 model_.PopulateApps(num_apps); 136 AppListItem* item0 = model_.top_level_item_list()->item_at(0); 137 ASSERT_TRUE(item0); 138 EXPECT_EQ(model_.GetItemName(0), item0->id()); 139 AppListItem* item1 = model_.top_level_item_list()->item_at(1); 140 ASSERT_TRUE(item1); 141 EXPECT_EQ(model_.GetItemName(1), item1->id()); 142 } 143 144 TEST_F(AppListModelTest, ModelFindItem) { 145 const size_t num_apps = 2; 146 model_.PopulateApps(num_apps); 147 std::string item_name0 = model_.GetItemName(0); 148 AppListItem* item0 = model_.FindItem(item_name0); 149 ASSERT_TRUE(item0); 150 EXPECT_EQ(item_name0, item0->id()); 151 std::string item_name1 = model_.GetItemName(1); 152 AppListItem* item1 = model_.FindItem(item_name1); 153 ASSERT_TRUE(item1); 154 EXPECT_EQ(item_name1, item1->id()); 155 } 156 157 TEST_F(AppListModelTest, SetItemPosition) { 158 const size_t num_apps = 2; 159 model_.PopulateApps(num_apps); 160 // Adding another item will add it to the end. 161 model_.CreateAndAddItem("Added Item 1"); 162 ASSERT_EQ(num_apps + 1, model_.top_level_item_list()->item_count()); 163 EXPECT_EQ("Added Item 1", 164 model_.top_level_item_list()->item_at(num_apps)->id()); 165 // Add an item between items 0 and 1. 166 AppListItem* item0 = model_.top_level_item_list()->item_at(0); 167 ASSERT_TRUE(item0); 168 AppListItem* item1 = model_.top_level_item_list()->item_at(1); 169 ASSERT_TRUE(item1); 170 AppListItem* item2 = model_.CreateItem("Added Item 2"); 171 model_.AddItem(item2); 172 EXPECT_EQ("Item 0,Item 1,Added Item 1,Added Item 2", GetModelContents()); 173 model_.SetItemPosition( 174 item2, item0->position().CreateBetween(item1->position())); 175 EXPECT_EQ(num_apps + 2, model_.top_level_item_list()->item_count()); 176 EXPECT_EQ(num_apps + 2, observer_.items_added()); 177 EXPECT_EQ("Item 0,Added Item 2,Item 1,Added Item 1", GetModelContents()); 178 } 179 180 TEST_F(AppListModelTest, ModelMoveItem) { 181 const size_t num_apps = 3; 182 model_.PopulateApps(num_apps); 183 // Adding another item will add it to the end. 184 model_.CreateAndAddItem("Inserted Item"); 185 ASSERT_EQ(num_apps + 1, model_.top_level_item_list()->item_count()); 186 // Move it to the position 1. 187 observer_.ResetCounts(); 188 model_.top_level_item_list()->MoveItem(num_apps, 1); 189 EXPECT_EQ(1u, observer_.items_updated()); 190 EXPECT_EQ("Item 0,Inserted Item,Item 1,Item 2", GetModelContents()); 191 } 192 193 TEST_F(AppListModelTest, ModelRemoveItem) { 194 const size_t num_apps = 4; 195 model_.PopulateApps(num_apps); 196 // Remove an item in the middle. 197 model_.DeleteItem(model_.GetItemName(1)); 198 EXPECT_EQ(num_apps - 1, model_.top_level_item_list()->item_count()); 199 EXPECT_EQ(1u, observer_.items_removed()); 200 EXPECT_EQ("Item 0,Item 2,Item 3", GetModelContents()); 201 // Remove the first item in the list. 202 model_.DeleteItem(model_.GetItemName(0)); 203 EXPECT_EQ(num_apps - 2, model_.top_level_item_list()->item_count()); 204 EXPECT_EQ(2u, observer_.items_removed()); 205 EXPECT_EQ("Item 2,Item 3", GetModelContents()); 206 // Remove the last item in the list. 207 model_.DeleteItem(model_.GetItemName(num_apps - 1)); 208 EXPECT_EQ(num_apps - 3, model_.top_level_item_list()->item_count()); 209 EXPECT_EQ(3u, observer_.items_removed()); 210 EXPECT_EQ("Item 2", GetModelContents()); 211 } 212 213 TEST_F(AppListModelTest, AppOrder) { 214 const size_t num_apps = 5; 215 model_.PopulateApps(num_apps); 216 // Ensure order is preserved. 217 for (size_t i = 1; i < num_apps; ++i) { 218 EXPECT_TRUE( 219 model_.top_level_item_list()->item_at(i)->position().GreaterThan( 220 model_.top_level_item_list()->item_at(i - 1)->position())); 221 } 222 // Move an app 223 model_.top_level_item_list()->MoveItem(num_apps - 1, 1); 224 // Ensure order is preserved. 225 for (size_t i = 1; i < num_apps; ++i) { 226 EXPECT_TRUE( 227 model_.top_level_item_list()->item_at(i)->position().GreaterThan( 228 model_.top_level_item_list()->item_at(i - 1)->position())); 229 } 230 } 231 232 class AppListModelFolderTest : public AppListModelTest { 233 public: 234 AppListModelFolderTest() { 235 model_.SetFoldersEnabled(true); 236 } 237 virtual ~AppListModelFolderTest() {} 238 239 // testing::Test overrides: 240 virtual void SetUp() OVERRIDE { 241 AppListModelTest::SetUp(); 242 } 243 virtual void TearDown() OVERRIDE { 244 AppListModelTest::TearDown(); 245 } 246 247 private: 248 DISALLOW_COPY_AND_ASSIGN(AppListModelFolderTest); 249 }; 250 251 TEST_F(AppListModelFolderTest, FolderItem) { 252 AppListFolderItem* folder = 253 new AppListFolderItem("folder1", AppListFolderItem::FOLDER_TYPE_NORMAL); 254 const size_t num_folder_apps = 8; 255 const size_t num_observed_apps = 4; 256 model_.AddItem(folder); 257 for (int i = 0; static_cast<size_t>(i) < num_folder_apps; ++i) { 258 std::string name = model_.GetItemName(i); 259 model_.AddItemToFolder(model_.CreateItem(name), folder->id()); 260 } 261 ASSERT_EQ(num_folder_apps, folder->item_list()->item_count()); 262 // Check that items 0 and 3 are observed. 263 EXPECT_TRUE(ItemObservedByFolder( 264 folder, folder->item_list()->item_at(0))); 265 EXPECT_TRUE(ItemObservedByFolder( 266 folder, folder->item_list()->item_at(num_observed_apps - 1))); 267 // Check that item 4 is not observed. 268 EXPECT_FALSE(ItemObservedByFolder( 269 folder, folder->item_list()->item_at(num_observed_apps))); 270 folder->item_list()->MoveItem(num_observed_apps, 0); 271 // Confirm that everything was moved where expected. 272 EXPECT_EQ(model_.GetItemName(num_observed_apps), 273 folder->item_list()->item_at(0)->id()); 274 EXPECT_EQ(model_.GetItemName(0), 275 folder->item_list()->item_at(1)->id()); 276 EXPECT_EQ(model_.GetItemName(num_observed_apps - 1), 277 folder->item_list()->item_at(num_observed_apps)->id()); 278 // Check that items 0 and 3 are observed. 279 EXPECT_TRUE(ItemObservedByFolder( 280 folder, folder->item_list()->item_at(0))); 281 EXPECT_TRUE(ItemObservedByFolder( 282 folder, folder->item_list()->item_at(num_observed_apps - 1))); 283 // Check that item 4 is not observed. 284 EXPECT_FALSE(ItemObservedByFolder( 285 folder, folder->item_list()->item_at(num_observed_apps))); 286 } 287 288 TEST_F(AppListModelFolderTest, MergeItems) { 289 model_.PopulateApps(3); 290 ASSERT_EQ(3u, model_.top_level_item_list()->item_count()); 291 AppListItem* item0 = model_.top_level_item_list()->item_at(0); 292 AppListItem* item1 = model_.top_level_item_list()->item_at(1); 293 AppListItem* item2 = model_.top_level_item_list()->item_at(2); 294 295 // Merge two items. 296 std::string folder1_id = model_.MergeItems(item0->id(), item1->id()); 297 ASSERT_EQ(2u, model_.top_level_item_list()->item_count()); // Folder + 1 item 298 AppListFolderItem* folder1_item = model_.FindFolderItem(folder1_id); 299 ASSERT_TRUE(folder1_item); 300 EXPECT_EQ("Item 0,Item 1", GetItemListContents(folder1_item->item_list())); 301 302 // Merge an item from the new folder into the third item. 303 std::string folder2_id = model_.MergeItems(item2->id(), item1->id()); 304 ASSERT_EQ(2u, model_.top_level_item_list()->item_count()); // 2 folders 305 AppListFolderItem* folder2_item = model_.FindFolderItem(folder2_id); 306 EXPECT_EQ("Item 0", GetItemListContents(folder1_item->item_list())); 307 EXPECT_EQ("Item 2,Item 1", GetItemListContents(folder2_item->item_list())); 308 309 // Merge the remaining item to the new folder, ensure it is added to the end. 310 std::string folder_id = model_.MergeItems(folder2_id, item0->id()); 311 EXPECT_EQ(folder2_id, folder_id); 312 EXPECT_EQ("Item 2,Item 1,Item 0", 313 GetItemListContents(folder2_item->item_list())); 314 315 // The empty folder should be deleted. 316 folder1_item = model_.FindFolderItem(folder1_id); 317 EXPECT_FALSE(folder1_item); 318 } 319 320 TEST_F(AppListModelFolderTest, AddItemToFolder) { 321 AppListFolderItem* folder = 322 new AppListFolderItem("folder1", AppListFolderItem::FOLDER_TYPE_NORMAL); 323 model_.AddItem(folder); 324 AppListItem* item0 = new AppListItem("Item 0"); 325 model_.AddItemToFolder(item0, folder->id()); 326 ASSERT_EQ(1u, model_.top_level_item_list()->item_count()); 327 AppListFolderItem* folder_item = model_.FindFolderItem(folder->id()); 328 ASSERT_TRUE(folder_item); 329 ASSERT_EQ(1u, folder_item->item_list()->item_count()); 330 EXPECT_EQ(item0, folder_item->item_list()->item_at(0)); 331 EXPECT_EQ(folder->id(), item0->folder_id()); 332 } 333 334 TEST_F(AppListModelFolderTest, MoveItemToFolder) { 335 AppListFolderItem* folder = 336 new AppListFolderItem("folder1", AppListFolderItem::FOLDER_TYPE_NORMAL); 337 model_.AddItem(folder); 338 AppListItem* item0 = new AppListItem("Item 0"); 339 AppListItem* item1 = new AppListItem("Item 1"); 340 model_.AddItem(item0); 341 model_.AddItem(item1); 342 ASSERT_EQ(3u, model_.top_level_item_list()->item_count()); 343 // Move item0 and item1 to folder. 344 std::string folder_id = folder->id(); 345 model_.MoveItemToFolder(item0, folder_id); 346 model_.MoveItemToFolder(item1, folder_id); 347 AppListFolderItem* folder_item = model_.FindFolderItem(folder_id); 348 ASSERT_TRUE(folder_item); 349 EXPECT_EQ(folder_id, item0->folder_id()); 350 EXPECT_EQ(folder_id, item1->folder_id()); 351 EXPECT_EQ("Item 0,Item 1", GetItemListContents(folder_item->item_list())); 352 // Move item0 out of folder. 353 model_.MoveItemToFolder(item0, ""); 354 EXPECT_EQ("", item0->folder_id()); 355 folder_item = model_.FindFolderItem(folder_id); 356 ASSERT_TRUE(folder_item); 357 // Move item1 out of folder, folder should be deleted. 358 model_.MoveItemToFolder(item1, ""); 359 EXPECT_EQ("", item1->folder_id()); 360 folder_item = model_.FindFolderItem(folder_id); 361 EXPECT_FALSE(folder_item); 362 } 363 364 TEST_F(AppListModelFolderTest, MoveItemToFolderAt) { 365 model_.AddItem(new AppListItem("Item 0")); 366 model_.AddItem(new AppListItem("Item 1")); 367 AppListFolderItem* folder1 = static_cast<AppListFolderItem*>(model_.AddItem( 368 new AppListFolderItem("folder1", AppListFolderItem::FOLDER_TYPE_NORMAL))); 369 model_.AddItem(new AppListItem("Item 2")); 370 model_.AddItem(new AppListItem("Item 3")); 371 ASSERT_EQ(5u, model_.top_level_item_list()->item_count()); 372 EXPECT_EQ("Item 0,Item 1,folder1,Item 2,Item 3", GetModelContents()); 373 // Move Item 1 to folder1, then Item 2 before Item 1. 374 model_.MoveItemToFolderAt(model_.top_level_item_list()->item_at(1), 375 folder1->id(), 376 syncer::StringOrdinal()); 377 EXPECT_EQ("Item 0,folder1,Item 2,Item 3", GetModelContents()); 378 model_.MoveItemToFolderAt(model_.top_level_item_list()->item_at(2), 379 folder1->id(), 380 folder1->item_list()->item_at(0)->position()); 381 EXPECT_EQ("Item 2,Item 1", GetItemListContents(folder1->item_list())); 382 EXPECT_EQ("Item 0,folder1,Item 3", GetModelContents()); 383 // Move Item 2 out of folder to before folder. 384 model_.MoveItemToFolderAt( 385 folder1->item_list()->item_at(0), "", folder1->position()); 386 EXPECT_EQ("Item 0,Item 2,folder1,Item 3", GetModelContents()); 387 // Move remaining folder item, (Item 1) out of folder to folder position. 388 ASSERT_EQ(1u, folder1->item_list()->item_count()); 389 model_.MoveItemToFolderAt( 390 folder1->item_list()->item_at(0), "", folder1->position()); 391 EXPECT_EQ("Item 0,Item 2,Item 1,Item 3", GetModelContents()); 392 } 393 394 TEST_F(AppListModelFolderTest, MoveItemFromFolderToFolder) { 395 AppListFolderItem* folder0 = 396 new AppListFolderItem("folder0", AppListFolderItem::FOLDER_TYPE_NORMAL); 397 AppListFolderItem* folder1 = 398 new AppListFolderItem("folder1", AppListFolderItem::FOLDER_TYPE_NORMAL); 399 model_.AddItem(folder0); 400 model_.AddItem(folder1); 401 EXPECT_EQ("folder0,folder1", GetModelContents()); 402 AppListItem* item0 = new AppListItem("Item 0"); 403 AppListItem* item1 = new AppListItem("Item 1"); 404 model_.AddItemToFolder(item0, folder0->id()); 405 model_.AddItemToFolder(item1, folder0->id()); 406 EXPECT_EQ(folder0->id(), item0->folder_id()); 407 EXPECT_EQ(folder0->id(), item1->folder_id()); 408 EXPECT_EQ("Item 0,Item 1", GetItemListContents(folder0->item_list())); 409 410 // Move item0 from folder0 to folder1. 411 model_.MoveItemToFolder(item0, folder1->id()); 412 ASSERT_EQ(1u, folder0->item_list()->item_count()); 413 ASSERT_EQ(1u, folder1->item_list()->item_count()); 414 EXPECT_EQ(folder1->id(), item0->folder_id()); 415 EXPECT_EQ("Item 1", GetItemListContents(folder0->item_list())); 416 EXPECT_EQ("Item 0", GetItemListContents(folder1->item_list())); 417 418 // Move item1 from folder0 to folder1. folder0 should get deleted. 419 model_.MoveItemToFolder(item1, folder1->id()); 420 ASSERT_EQ(1u, model_.top_level_item_list()->item_count()); 421 ASSERT_EQ(2u, folder1->item_list()->item_count()); 422 EXPECT_EQ(folder1->id(), item1->folder_id()); 423 EXPECT_EQ("Item 0,Item 1", GetItemListContents(folder1->item_list())); 424 425 // Move item1 to a non-existant folder2 which should get created. 426 model_.MoveItemToFolder(item1, "folder2"); 427 ASSERT_EQ(2u, model_.top_level_item_list()->item_count()); 428 ASSERT_EQ(1u, folder1->item_list()->item_count()); 429 EXPECT_EQ("folder2", item1->folder_id()); 430 AppListFolderItem* folder2 = model_.FindFolderItem("folder2"); 431 ASSERT_TRUE(folder2); 432 } 433 434 TEST_F(AppListModelFolderTest, FindItemInFolder) { 435 AppListFolderItem* folder = 436 new AppListFolderItem("folder1", AppListFolderItem::FOLDER_TYPE_NORMAL); 437 EXPECT_TRUE(folder); 438 model_.AddItem(folder); 439 std::string folder_id = folder->id(); 440 AppListItem* item0 = new AppListItem("Item 0"); 441 model_.AddItemToFolder(item0, folder_id); 442 AppListItem* found_item = model_.FindItem(item0->id()); 443 ASSERT_EQ(item0, found_item); 444 EXPECT_EQ(folder_id, found_item->folder_id()); 445 } 446 447 TEST_F(AppListModelFolderTest, OemFolder) { 448 AppListFolderItem* folder = 449 new AppListFolderItem("folder1", AppListFolderItem::FOLDER_TYPE_OEM); 450 model_.AddItem(folder); 451 std::string folder_id = folder->id(); 452 453 // Should not be able to move to an OEM folder with MergeItems. 454 AppListItem* item0 = new AppListItem("Item 0"); 455 model_.AddItem(item0); 456 syncer::StringOrdinal item0_pos = item0->position(); 457 std::string new_folder = model_.MergeItems(folder_id, item0->id()); 458 EXPECT_EQ("", new_folder); 459 EXPECT_EQ("", item0->folder_id()); 460 EXPECT_TRUE(item0->position().Equals(item0_pos)); 461 462 // Should not be able to move from an OEM folder with MoveItemToFolderAt. 463 AppListItem* item1 = new AppListItem("Item 1"); 464 model_.AddItemToFolder(item1, folder_id); 465 syncer::StringOrdinal item1_pos = item1->position(); 466 bool move_res = model_.MoveItemToFolderAt(item1, "", syncer::StringOrdinal()); 467 EXPECT_FALSE(move_res); 468 EXPECT_TRUE(item1->position().Equals(item1_pos)); 469 } 470 471 TEST_F(AppListModelFolderTest, DisableFolders) { 472 // Set up a folder with two items and an OEM folder with one item. 473 AppListFolderItem* folder = 474 new AppListFolderItem("folder1", AppListFolderItem::FOLDER_TYPE_NORMAL); 475 model_.AddItem(folder); 476 std::string folder_id = folder->id(); 477 AppListItem* item0 = new AppListItem("Item 0"); 478 model_.AddItemToFolder(item0, folder_id); 479 AppListItem* item1 = new AppListItem("Item 1"); 480 model_.AddItemToFolder(item1, folder_id); 481 AppListFolderItem* folder_item = model_.FindFolderItem(folder_id); 482 ASSERT_TRUE(folder_item); 483 EXPECT_EQ(2u, folder_item->item_list()->item_count()); 484 AppListFolderItem* oem_folder = 485 new AppListFolderItem("oem_folder", AppListFolderItem::FOLDER_TYPE_OEM); 486 model_.AddItem(oem_folder); 487 AppListItem* oem_item = new AppListItem("OEM Item"); 488 std::string oem_folder_id = oem_folder->id(); 489 model_.AddItemToFolder(oem_item, oem_folder_id); 490 EXPECT_EQ("folder1,oem_folder", GetModelContents()); 491 492 // Disable folders. Ensure non-oem folder is removed. 493 model_.SetFoldersEnabled(false); 494 ASSERT_FALSE(model_.FindFolderItem(folder_id)); 495 ASSERT_TRUE(model_.FindFolderItem(oem_folder_id)); 496 EXPECT_EQ("Item 0,Item 1,oem_folder", GetModelContents()); 497 498 // Ensure folder creation fails. 499 EXPECT_EQ(std::string(), model_.MergeItems(item0->id(), item1->id())); 500 } 501 502 } // namespace app_list 503