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.
5 #include "ui/app_list/app_list_model.h"
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"
24 class TestObserver
: public AppListModelObserver
{
27 : status_changed_count_(0),
32 ~TestObserver() override
{}
34 // AppListModelObserver
35 void OnAppListModelStatusChanged() override
{ ++status_changed_count_
; }
37 void OnAppListItemAdded(AppListItem
* item
) override
{ items_added_
++; }
39 void OnAppListItemWillBeDeleted(AppListItem
* item
) override
{
43 void OnAppListItemUpdated(AppListItem
* item
) override
{ items_updated_
++; }
45 int status_changed_count() const { return status_changed_count_
; }
46 size_t items_added() { return items_added_
; }
47 size_t items_removed() { return items_removed_
; }
48 size_t items_updated() { return items_updated_
; }
51 status_changed_count_
= 0;
58 int status_changed_count_
;
60 size_t items_removed_
;
61 size_t items_updated_
;
63 DISALLOW_COPY_AND_ASSIGN(TestObserver
);
68 class AppListModelTest
: public testing::Test
{
71 ~AppListModelTest() override
{}
73 // testing::Test overrides:
74 void SetUp() override
{ model_
.AddObserver(&observer_
); }
75 void TearDown() override
{ model_
.RemoveObserver(&observer_
); }
78 static bool ItemObservedByFolder(const AppListFolderItem
* folder
,
79 const AppListItem
* item
) {
80 return item
->observers_
.HasObserver(&folder
->folder_image());
83 std::string
GetItemListContents(AppListItemList
* item_list
) {
85 for (size_t i
= 0; i
< item_list
->item_count(); ++i
) {
88 s
+= item_list
->item_at(i
)->id();
93 std::string
GetModelContents() {
94 return GetItemListContents(model_
.top_level_item_list());
97 test::AppListTestModel model_
;
98 TestObserver observer_
;
101 DISALLOW_COPY_AND_ASSIGN(AppListModelTest
);
104 TEST_F(AppListModelTest
, SetStatus
) {
105 EXPECT_EQ(AppListModel::STATUS_NORMAL
, model_
.status());
106 model_
.SetStatus(AppListModel::STATUS_SYNCING
);
107 EXPECT_EQ(1, observer_
.status_changed_count());
108 EXPECT_EQ(AppListModel::STATUS_SYNCING
, model_
.status());
109 model_
.SetStatus(AppListModel::STATUS_NORMAL
);
110 EXPECT_EQ(2, observer_
.status_changed_count());
111 // Set the same status, no change is expected.
112 model_
.SetStatus(AppListModel::STATUS_NORMAL
);
113 EXPECT_EQ(2, observer_
.status_changed_count());
116 TEST_F(AppListModelTest
, AppsObserver
) {
117 const size_t num_apps
= 2;
118 model_
.PopulateApps(num_apps
);
119 EXPECT_EQ(num_apps
, observer_
.items_added());
122 TEST_F(AppListModelTest
, ModelGetItem
) {
123 const size_t num_apps
= 2;
124 model_
.PopulateApps(num_apps
);
125 AppListItem
* item0
= model_
.top_level_item_list()->item_at(0);
127 EXPECT_EQ(model_
.GetItemName(0), item0
->id());
128 AppListItem
* item1
= model_
.top_level_item_list()->item_at(1);
130 EXPECT_EQ(model_
.GetItemName(1), item1
->id());
133 TEST_F(AppListModelTest
, ModelFindItem
) {
134 const size_t num_apps
= 2;
135 model_
.PopulateApps(num_apps
);
136 std::string item_name0
= model_
.GetItemName(0);
137 AppListItem
* item0
= model_
.FindItem(item_name0
);
139 EXPECT_EQ(item_name0
, item0
->id());
140 std::string item_name1
= model_
.GetItemName(1);
141 AppListItem
* item1
= model_
.FindItem(item_name1
);
143 EXPECT_EQ(item_name1
, item1
->id());
146 TEST_F(AppListModelTest
, SetItemPosition
) {
147 const size_t num_apps
= 2;
148 model_
.PopulateApps(num_apps
);
149 // Adding another item will add it to the end.
150 model_
.CreateAndAddItem("Added Item 1");
151 ASSERT_EQ(num_apps
+ 1, model_
.top_level_item_list()->item_count());
152 EXPECT_EQ("Added Item 1",
153 model_
.top_level_item_list()->item_at(num_apps
)->id());
154 // Add an item between items 0 and 1.
155 AppListItem
* item0
= model_
.top_level_item_list()->item_at(0);
157 AppListItem
* item1
= model_
.top_level_item_list()->item_at(1);
159 AppListItem
* item2
= model_
.CreateItem("Added Item 2");
160 model_
.AddItem(item2
);
161 EXPECT_EQ("Item 0,Item 1,Added Item 1,Added Item 2", GetModelContents());
162 model_
.SetItemPosition(
163 item2
, item0
->position().CreateBetween(item1
->position()));
164 EXPECT_EQ(num_apps
+ 2, model_
.top_level_item_list()->item_count());
165 EXPECT_EQ(num_apps
+ 2, observer_
.items_added());
166 EXPECT_EQ("Item 0,Added Item 2,Item 1,Added Item 1", GetModelContents());
169 TEST_F(AppListModelTest
, ModelMoveItem
) {
170 const size_t num_apps
= 3;
171 model_
.PopulateApps(num_apps
);
172 // Adding another item will add it to the end.
173 model_
.CreateAndAddItem("Inserted Item");
174 ASSERT_EQ(num_apps
+ 1, model_
.top_level_item_list()->item_count());
175 // Move it to the position 1.
176 observer_
.ResetCounts();
177 model_
.top_level_item_list()->MoveItem(num_apps
, 1);
178 EXPECT_EQ(1u, observer_
.items_updated());
179 EXPECT_EQ("Item 0,Inserted Item,Item 1,Item 2", GetModelContents());
182 TEST_F(AppListModelTest
, ModelRemoveItem
) {
183 const size_t num_apps
= 4;
184 model_
.PopulateApps(num_apps
);
185 // Remove an item in the middle.
186 model_
.DeleteItem(model_
.GetItemName(1));
187 EXPECT_EQ(num_apps
- 1, model_
.top_level_item_list()->item_count());
188 EXPECT_EQ(1u, observer_
.items_removed());
189 EXPECT_EQ("Item 0,Item 2,Item 3", GetModelContents());
190 // Remove the first item in the list.
191 model_
.DeleteItem(model_
.GetItemName(0));
192 EXPECT_EQ(num_apps
- 2, model_
.top_level_item_list()->item_count());
193 EXPECT_EQ(2u, observer_
.items_removed());
194 EXPECT_EQ("Item 2,Item 3", GetModelContents());
195 // Remove the last item in the list.
196 model_
.DeleteItem(model_
.GetItemName(num_apps
- 1));
197 EXPECT_EQ(num_apps
- 3, model_
.top_level_item_list()->item_count());
198 EXPECT_EQ(3u, observer_
.items_removed());
199 EXPECT_EQ("Item 2", GetModelContents());
202 TEST_F(AppListModelTest
, AppOrder
) {
203 const size_t num_apps
= 5;
204 model_
.PopulateApps(num_apps
);
205 // Ensure order is preserved.
206 for (size_t i
= 1; i
< num_apps
; ++i
) {
208 model_
.top_level_item_list()->item_at(i
)->position().GreaterThan(
209 model_
.top_level_item_list()->item_at(i
- 1)->position()));
212 model_
.top_level_item_list()->MoveItem(num_apps
- 1, 1);
213 // Ensure order is preserved.
214 for (size_t i
= 1; i
< num_apps
; ++i
) {
216 model_
.top_level_item_list()->item_at(i
)->position().GreaterThan(
217 model_
.top_level_item_list()->item_at(i
- 1)->position()));
221 class AppListModelFolderTest
: public AppListModelTest
{
223 AppListModelFolderTest() {
224 model_
.SetFoldersEnabled(true);
226 ~AppListModelFolderTest() override
{}
228 // testing::Test overrides:
229 void SetUp() override
{ AppListModelTest::SetUp(); }
230 void TearDown() override
{ AppListModelTest::TearDown(); }
233 DISALLOW_COPY_AND_ASSIGN(AppListModelFolderTest
);
236 TEST_F(AppListModelFolderTest
, FolderItem
) {
237 AppListFolderItem
* folder
=
238 new AppListFolderItem("folder1", AppListFolderItem::FOLDER_TYPE_NORMAL
);
239 const size_t num_folder_apps
= 8;
240 const size_t num_observed_apps
= 4;
241 model_
.AddItem(folder
);
242 for (int i
= 0; static_cast<size_t>(i
) < num_folder_apps
; ++i
) {
243 std::string name
= model_
.GetItemName(i
);
244 model_
.AddItemToFolder(model_
.CreateItem(name
), folder
->id());
246 ASSERT_EQ(num_folder_apps
, folder
->item_list()->item_count());
247 // Check that items 0 and 3 are observed.
248 EXPECT_TRUE(ItemObservedByFolder(
249 folder
, folder
->item_list()->item_at(0)));
250 EXPECT_TRUE(ItemObservedByFolder(
251 folder
, folder
->item_list()->item_at(num_observed_apps
- 1)));
252 // Check that item 4 is not observed.
253 EXPECT_FALSE(ItemObservedByFolder(
254 folder
, folder
->item_list()->item_at(num_observed_apps
)));
255 folder
->item_list()->MoveItem(num_observed_apps
, 0);
256 // Confirm that everything was moved where expected.
257 EXPECT_EQ(model_
.GetItemName(num_observed_apps
),
258 folder
->item_list()->item_at(0)->id());
259 EXPECT_EQ(model_
.GetItemName(0),
260 folder
->item_list()->item_at(1)->id());
261 EXPECT_EQ(model_
.GetItemName(num_observed_apps
- 1),
262 folder
->item_list()->item_at(num_observed_apps
)->id());
263 // Check that items 0 and 3 are observed.
264 EXPECT_TRUE(ItemObservedByFolder(
265 folder
, folder
->item_list()->item_at(0)));
266 EXPECT_TRUE(ItemObservedByFolder(
267 folder
, folder
->item_list()->item_at(num_observed_apps
- 1)));
268 // Check that item 4 is not observed.
269 EXPECT_FALSE(ItemObservedByFolder(
270 folder
, folder
->item_list()->item_at(num_observed_apps
)));
273 TEST_F(AppListModelFolderTest
, MergeItems
) {
274 model_
.PopulateApps(3);
275 ASSERT_EQ(3u, model_
.top_level_item_list()->item_count());
276 AppListItem
* item0
= model_
.top_level_item_list()->item_at(0);
277 AppListItem
* item1
= model_
.top_level_item_list()->item_at(1);
278 AppListItem
* item2
= model_
.top_level_item_list()->item_at(2);
280 // Merge an item onto a non-existent target.
281 EXPECT_EQ(std::string(), model_
.MergeItems("nonexistent", item0
->id()));
282 ASSERT_EQ(3u, model_
.top_level_item_list()->item_count());
284 // Merge a non-existent item onto a target.
285 EXPECT_EQ(std::string(), model_
.MergeItems(item0
->id(), "nonexistent"));
286 ASSERT_EQ(3u, model_
.top_level_item_list()->item_count());
288 // Merge an item onto itself (should have no effect). This should not be
289 // possible, but there have been bugs in the past that made it possible (see
290 // http://crbug.com/415530), so it should be handled correctly.
291 EXPECT_EQ(std::string(), model_
.MergeItems(item0
->id(), item0
->id()));
292 ASSERT_EQ(3u, model_
.top_level_item_list()->item_count());
295 std::string folder1_id
= model_
.MergeItems(item0
->id(), item1
->id());
296 ASSERT_EQ(2u, model_
.top_level_item_list()->item_count()); // Folder + 1 item
297 AppListFolderItem
* folder1_item
= model_
.FindFolderItem(folder1_id
);
298 ASSERT_TRUE(folder1_item
);
299 EXPECT_EQ("Item 0,Item 1", GetItemListContents(folder1_item
->item_list()));
301 // Merge an item onto an item that is already in a folder (should have no
302 // effect). This should not be possible, but it should be handled correctly
303 // if it does happen.
304 EXPECT_EQ(std::string(), model_
.MergeItems(item1
->id(), item2
->id()));
305 ASSERT_EQ(2u, model_
.top_level_item_list()->item_count()); // Folder + 1 item
306 EXPECT_EQ("Item 0,Item 1", GetItemListContents(folder1_item
->item_list()));
308 // Merge an item from the new folder into the third item.
309 std::string folder2_id
= model_
.MergeItems(item2
->id(), item1
->id());
310 ASSERT_EQ(2u, model_
.top_level_item_list()->item_count()); // 2 folders
311 AppListFolderItem
* folder2_item
= model_
.FindFolderItem(folder2_id
);
312 EXPECT_EQ("Item 0", GetItemListContents(folder1_item
->item_list()));
313 EXPECT_EQ("Item 2,Item 1", GetItemListContents(folder2_item
->item_list()));
315 // Merge the remaining item to the new folder, ensure it is added to the end.
316 std::string folder_id
= model_
.MergeItems(folder2_id
, item0
->id());
317 EXPECT_EQ(folder2_id
, folder_id
);
318 EXPECT_EQ("Item 2,Item 1,Item 0",
319 GetItemListContents(folder2_item
->item_list()));
321 // The empty folder should be deleted.
322 folder1_item
= model_
.FindFolderItem(folder1_id
);
323 EXPECT_FALSE(folder1_item
);
324 EXPECT_EQ(1u, model_
.top_level_item_list()->item_count()); // 1 folder
327 TEST_F(AppListModelFolderTest
, AddItemToFolder
) {
328 AppListFolderItem
* folder
=
329 new AppListFolderItem("folder1", AppListFolderItem::FOLDER_TYPE_NORMAL
);
330 model_
.AddItem(folder
);
331 AppListItem
* item0
= new AppListItem("Item 0");
332 model_
.AddItemToFolder(item0
, folder
->id());
333 ASSERT_EQ(1u, model_
.top_level_item_list()->item_count());
334 AppListFolderItem
* folder_item
= model_
.FindFolderItem(folder
->id());
335 ASSERT_TRUE(folder_item
);
336 ASSERT_EQ(1u, folder_item
->item_list()->item_count());
337 EXPECT_EQ(item0
, folder_item
->item_list()->item_at(0));
338 EXPECT_EQ(folder
->id(), item0
->folder_id());
341 TEST_F(AppListModelFolderTest
, MoveItemToFolder
) {
342 AppListFolderItem
* folder
=
343 new AppListFolderItem("folder1", AppListFolderItem::FOLDER_TYPE_NORMAL
);
344 model_
.AddItem(folder
);
345 AppListItem
* item0
= new AppListItem("Item 0");
346 AppListItem
* item1
= new AppListItem("Item 1");
347 model_
.AddItem(item0
);
348 model_
.AddItem(item1
);
349 ASSERT_EQ(3u, model_
.top_level_item_list()->item_count());
350 // Move item0 and item1 to folder.
351 std::string folder_id
= folder
->id();
352 model_
.MoveItemToFolder(item0
, folder_id
);
353 model_
.MoveItemToFolder(item1
, folder_id
);
354 AppListFolderItem
* folder_item
= model_
.FindFolderItem(folder_id
);
355 ASSERT_TRUE(folder_item
);
356 EXPECT_EQ(folder_id
, item0
->folder_id());
357 EXPECT_EQ(folder_id
, item1
->folder_id());
358 EXPECT_EQ("Item 0,Item 1", GetItemListContents(folder_item
->item_list()));
359 // Move item0 out of folder.
360 model_
.MoveItemToFolder(item0
, "");
361 EXPECT_EQ("", item0
->folder_id());
362 folder_item
= model_
.FindFolderItem(folder_id
);
363 ASSERT_TRUE(folder_item
);
364 // Move item1 out of folder, folder should be deleted.
365 model_
.MoveItemToFolder(item1
, "");
366 EXPECT_EQ("", item1
->folder_id());
367 folder_item
= model_
.FindFolderItem(folder_id
);
368 EXPECT_FALSE(folder_item
);
371 TEST_F(AppListModelFolderTest
, MoveItemToFolderAt
) {
372 model_
.AddItem(new AppListItem("Item 0"));
373 model_
.AddItem(new AppListItem("Item 1"));
374 AppListFolderItem
* folder1
= static_cast<AppListFolderItem
*>(model_
.AddItem(
375 new AppListFolderItem("folder1", AppListFolderItem::FOLDER_TYPE_NORMAL
)));
376 model_
.AddItem(new AppListItem("Item 2"));
377 model_
.AddItem(new AppListItem("Item 3"));
378 ASSERT_EQ(5u, model_
.top_level_item_list()->item_count());
379 EXPECT_EQ("Item 0,Item 1,folder1,Item 2,Item 3", GetModelContents());
380 // Move Item 1 to folder1, then Item 2 before Item 1.
381 model_
.MoveItemToFolderAt(model_
.top_level_item_list()->item_at(1),
383 syncer::StringOrdinal());
384 EXPECT_EQ("Item 0,folder1,Item 2,Item 3", GetModelContents());
385 model_
.MoveItemToFolderAt(model_
.top_level_item_list()->item_at(2),
387 folder1
->item_list()->item_at(0)->position());
388 EXPECT_EQ("Item 2,Item 1", GetItemListContents(folder1
->item_list()));
389 EXPECT_EQ("Item 0,folder1,Item 3", GetModelContents());
390 // Move Item 2 out of folder to before folder.
391 model_
.MoveItemToFolderAt(
392 folder1
->item_list()->item_at(0), "", folder1
->position());
393 EXPECT_EQ("Item 0,Item 2,folder1,Item 3", GetModelContents());
394 // Move remaining folder item, (Item 1) out of folder to folder position.
395 ASSERT_EQ(1u, folder1
->item_list()->item_count());
396 model_
.MoveItemToFolderAt(
397 folder1
->item_list()->item_at(0), "", folder1
->position());
398 EXPECT_EQ("Item 0,Item 2,Item 1,Item 3", GetModelContents());
401 TEST_F(AppListModelFolderTest
, MoveItemFromFolderToFolder
) {
402 AppListFolderItem
* folder0
=
403 new AppListFolderItem("folder0", AppListFolderItem::FOLDER_TYPE_NORMAL
);
404 AppListFolderItem
* folder1
=
405 new AppListFolderItem("folder1", AppListFolderItem::FOLDER_TYPE_NORMAL
);
406 model_
.AddItem(folder0
);
407 model_
.AddItem(folder1
);
408 EXPECT_EQ("folder0,folder1", GetModelContents());
409 AppListItem
* item0
= new AppListItem("Item 0");
410 AppListItem
* item1
= new AppListItem("Item 1");
411 model_
.AddItemToFolder(item0
, folder0
->id());
412 model_
.AddItemToFolder(item1
, folder0
->id());
413 EXPECT_EQ(folder0
->id(), item0
->folder_id());
414 EXPECT_EQ(folder0
->id(), item1
->folder_id());
415 EXPECT_EQ("Item 0,Item 1", GetItemListContents(folder0
->item_list()));
417 // Move item0 from folder0 to folder1.
418 model_
.MoveItemToFolder(item0
, folder1
->id());
419 ASSERT_EQ(1u, folder0
->item_list()->item_count());
420 ASSERT_EQ(1u, folder1
->item_list()->item_count());
421 EXPECT_EQ(folder1
->id(), item0
->folder_id());
422 EXPECT_EQ("Item 1", GetItemListContents(folder0
->item_list()));
423 EXPECT_EQ("Item 0", GetItemListContents(folder1
->item_list()));
425 // Move item1 from folder0 to folder1. folder0 should get deleted.
426 model_
.MoveItemToFolder(item1
, folder1
->id());
427 ASSERT_EQ(1u, model_
.top_level_item_list()->item_count());
428 ASSERT_EQ(2u, folder1
->item_list()->item_count());
429 EXPECT_EQ(folder1
->id(), item1
->folder_id());
430 EXPECT_EQ("Item 0,Item 1", GetItemListContents(folder1
->item_list()));
432 // Move item1 to a non-existant folder2 which should get created.
433 model_
.MoveItemToFolder(item1
, "folder2");
434 ASSERT_EQ(2u, model_
.top_level_item_list()->item_count());
435 ASSERT_EQ(1u, folder1
->item_list()->item_count());
436 EXPECT_EQ("folder2", item1
->folder_id());
437 AppListFolderItem
* folder2
= model_
.FindFolderItem("folder2");
438 ASSERT_TRUE(folder2
);
441 TEST_F(AppListModelFolderTest
, FindItemInFolder
) {
442 AppListFolderItem
* folder
=
443 new AppListFolderItem("folder1", AppListFolderItem::FOLDER_TYPE_NORMAL
);
445 model_
.AddItem(folder
);
446 std::string folder_id
= folder
->id();
447 AppListItem
* item0
= new AppListItem("Item 0");
448 model_
.AddItemToFolder(item0
, folder_id
);
449 AppListItem
* found_item
= model_
.FindItem(item0
->id());
450 ASSERT_EQ(item0
, found_item
);
451 EXPECT_EQ(folder_id
, found_item
->folder_id());
454 TEST_F(AppListModelFolderTest
, OemFolder
) {
455 AppListFolderItem
* folder
=
456 new AppListFolderItem("folder1", AppListFolderItem::FOLDER_TYPE_OEM
);
457 model_
.AddItem(folder
);
458 std::string folder_id
= folder
->id();
460 // Should not be able to move to an OEM folder with MergeItems.
461 AppListItem
* item0
= new AppListItem("Item 0");
462 model_
.AddItem(item0
);
463 syncer::StringOrdinal item0_pos
= item0
->position();
464 std::string new_folder
= model_
.MergeItems(folder_id
, item0
->id());
465 EXPECT_EQ("", new_folder
);
466 EXPECT_EQ("", item0
->folder_id());
467 EXPECT_TRUE(item0
->position().Equals(item0_pos
));
469 // Should not be able to move from an OEM folder with MoveItemToFolderAt.
470 AppListItem
* item1
= new AppListItem("Item 1");
471 model_
.AddItemToFolder(item1
, folder_id
);
472 syncer::StringOrdinal item1_pos
= item1
->position();
473 bool move_res
= model_
.MoveItemToFolderAt(item1
, "", syncer::StringOrdinal());
474 EXPECT_FALSE(move_res
);
475 EXPECT_TRUE(item1
->position().Equals(item1_pos
));
478 TEST_F(AppListModelFolderTest
, DisableFolders
) {
479 // Set up a folder with two items and an OEM folder with one item.
480 AppListFolderItem
* folder
=
481 new AppListFolderItem("folder1", AppListFolderItem::FOLDER_TYPE_NORMAL
);
482 model_
.AddItem(folder
);
483 std::string folder_id
= folder
->id();
484 AppListItem
* item0
= new AppListItem("Item 0");
485 model_
.AddItemToFolder(item0
, folder_id
);
486 AppListItem
* item1
= new AppListItem("Item 1");
487 model_
.AddItemToFolder(item1
, folder_id
);
488 AppListFolderItem
* folder_item
= model_
.FindFolderItem(folder_id
);
489 ASSERT_TRUE(folder_item
);
490 EXPECT_EQ(2u, folder_item
->item_list()->item_count());
491 AppListFolderItem
* oem_folder
=
492 new AppListFolderItem("oem_folder", AppListFolderItem::FOLDER_TYPE_OEM
);
493 model_
.AddItem(oem_folder
);
494 AppListItem
* oem_item
= new AppListItem("OEM Item");
495 std::string oem_folder_id
= oem_folder
->id();
496 model_
.AddItemToFolder(oem_item
, oem_folder_id
);
497 EXPECT_EQ("folder1,oem_folder", GetModelContents());
499 // Disable folders. Ensure non-oem folder is removed.
500 model_
.SetFoldersEnabled(false);
501 ASSERT_FALSE(model_
.FindFolderItem(folder_id
));
502 ASSERT_TRUE(model_
.FindFolderItem(oem_folder_id
));
503 EXPECT_EQ("Item 0,Item 1,oem_folder", GetModelContents());
505 // Ensure folder creation fails.
506 EXPECT_EQ(std::string(), model_
.MergeItems(item0
->id(), item1
->id()));
509 } // namespace app_list