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
20namespace app_list {
21
22namespace {
23
24class 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
75class 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
115TEST_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
127TEST_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
133TEST_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
144TEST_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
157TEST_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
180TEST_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
193TEST_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
213TEST_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
232class 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
251TEST_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
288TEST_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
320TEST_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
334TEST_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
364TEST_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
394TEST_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
434TEST_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
447TEST_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
471TEST_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