shelf_model_unittest.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  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. #include "ash/public/cpp/shelf_model.h"
  5. #include <memory>
  6. #include <set>
  7. #include <string>
  8. #include "ash/public/cpp/shelf_model_observer.h"
  9. #include "ash/public/cpp/test/test_shelf_item_delegate.h"
  10. #include "base/strings/stringprintf.h"
  11. #include "testing/gtest/include/gtest/gtest.h"
  12. namespace ash {
  13. namespace {
  14. // ShelfModelObserver implementation that tracks what message are invoked.
  15. class TestShelfModelObserver : public ShelfModelObserver {
  16. public:
  17. TestShelfModelObserver() = default;
  18. TestShelfModelObserver(const TestShelfModelObserver&) = delete;
  19. TestShelfModelObserver& operator=(const TestShelfModelObserver&) = delete;
  20. // Returns a string description of the changes that have occurred since this
  21. // was last invoked. Resets state to initial state.
  22. std::string StateStringAndClear() {
  23. std::string result;
  24. AddToResult("added=%d", added_count_, &result);
  25. AddToResult("removed=%d", removed_count_, &result);
  26. AddToResult("changed=%d", changed_count_, &result);
  27. AddToResult("moved=%d", moved_count_, &result);
  28. AddToResult("delegate_changed=%d", delegate_changed_count_, &result);
  29. added_count_ = removed_count_ = changed_count_ = moved_count_ =
  30. delegate_changed_count_ = 0;
  31. return result;
  32. }
  33. // ShelfModelObserver overrides:
  34. void ShelfItemAdded(int) override { added_count_++; }
  35. void ShelfItemRemoved(int, const ShelfItem&) override { removed_count_++; }
  36. void ShelfItemChanged(int, const ShelfItem&) override { changed_count_++; }
  37. void ShelfItemMoved(int, int) override { moved_count_++; }
  38. void ShelfItemDelegateChanged(const ShelfID&,
  39. ShelfItemDelegate*,
  40. ShelfItemDelegate*) override {
  41. delegate_changed_count_++;
  42. }
  43. private:
  44. void AddToResult(const std::string& format, int count, std::string* result) {
  45. if (!count)
  46. return;
  47. if (!result->empty())
  48. *result += " ";
  49. *result += base::StringPrintf(format.c_str(), count);
  50. }
  51. int added_count_ = 0;
  52. int removed_count_ = 0;
  53. int changed_count_ = 0;
  54. int moved_count_ = 0;
  55. int delegate_changed_count_ = 0;
  56. };
  57. } // namespace
  58. class ShelfModelTest : public testing::Test {
  59. public:
  60. ShelfModelTest() = default;
  61. ShelfModelTest(const ShelfModelTest&) = delete;
  62. ShelfModelTest& operator=(const ShelfModelTest&) = delete;
  63. ~ShelfModelTest() override = default;
  64. void SetUp() override {
  65. model_ = std::make_unique<ShelfModel>();
  66. observer_ = std::make_unique<TestShelfModelObserver>();
  67. model_->AddObserver(observer_.get());
  68. }
  69. void TearDown() override {
  70. observer_.reset();
  71. model_.reset();
  72. }
  73. // Helper function for simplifying adding items to the shelf.
  74. int Add(const ShelfItem& item) {
  75. return model_->Add(item, std::make_unique<TestShelfItemDelegate>(item.id));
  76. }
  77. int AddAt(int index, const ShelfItem& item) {
  78. return model_->AddAt(index, item,
  79. std::make_unique<TestShelfItemDelegate>(item.id));
  80. }
  81. std::unique_ptr<ShelfModel> model_;
  82. std::unique_ptr<TestShelfModelObserver> observer_;
  83. };
  84. TEST_F(ShelfModelTest, BasicAssertions) {
  85. // Add an item.
  86. ShelfItem item1;
  87. item1.id = ShelfID("item1");
  88. item1.type = TYPE_PINNED_APP;
  89. int index = Add(item1);
  90. EXPECT_EQ(1, model_->item_count());
  91. EXPECT_LE(0, model_->ItemIndexByID(item1.id));
  92. EXPECT_TRUE(model_->ItemByID(item1.id));
  93. EXPECT_EQ("added=1", observer_->StateStringAndClear());
  94. // Change to a platform app item.
  95. item1.type = TYPE_APP;
  96. model_->Set(index, item1);
  97. EXPECT_EQ(item1.id, model_->items()[index].id);
  98. EXPECT_LE(0, model_->ItemIndexByID(item1.id));
  99. EXPECT_TRUE(model_->ItemByID(item1.id));
  100. EXPECT_EQ("changed=1", observer_->StateStringAndClear());
  101. EXPECT_EQ(TYPE_APP, model_->items()[index].type);
  102. // Remove the item.
  103. model_->RemoveItemAt(index);
  104. EXPECT_EQ(0, model_->item_count());
  105. EXPECT_EQ(-1, model_->ItemIndexByID(item1.id));
  106. EXPECT_FALSE(model_->ItemByID(item1.id));
  107. EXPECT_EQ("removed=1", observer_->StateStringAndClear());
  108. // Add an app item.
  109. ShelfItem item2;
  110. item2.id = ShelfID("item2");
  111. item2.type = TYPE_PINNED_APP;
  112. index = Add(item2);
  113. EXPECT_EQ(1, model_->item_count());
  114. EXPECT_LE(0, model_->ItemIndexByID(item2.id));
  115. EXPECT_TRUE(model_->ItemByID(item2.id));
  116. EXPECT_EQ("added=1", observer_->StateStringAndClear());
  117. // Change the item type.
  118. item2.type = TYPE_APP;
  119. model_->Set(index, item2);
  120. EXPECT_LE(0, model_->ItemIndexByID(item2.id));
  121. EXPECT_TRUE(model_->ItemByID(item2.id));
  122. EXPECT_EQ("changed=1", observer_->StateStringAndClear());
  123. EXPECT_EQ(TYPE_APP, model_->items()[index].type);
  124. // Add another item.
  125. ShelfItem item3;
  126. item3.id = ShelfID("item3");
  127. item3.type = TYPE_PINNED_APP;
  128. Add(item3);
  129. EXPECT_EQ(2, model_->item_count());
  130. EXPECT_LE(0, model_->ItemIndexByID(item3.id));
  131. EXPECT_TRUE(model_->ItemByID(item3.id));
  132. EXPECT_EQ("added=1", observer_->StateStringAndClear());
  133. // Move the second to the first.
  134. model_->Move(1, 0);
  135. EXPECT_EQ("moved=1", observer_->StateStringAndClear());
  136. // And back.
  137. model_->Move(0, 1);
  138. EXPECT_EQ("moved=1", observer_->StateStringAndClear());
  139. // Verifies all the items get unique ids.
  140. std::set<ShelfID> ids;
  141. for (int i = 0; i < model_->item_count(); ++i)
  142. ids.insert(model_->items()[i].id);
  143. EXPECT_EQ(model_->item_count(), static_cast<int>(ids.size()));
  144. }
  145. // Assertions around where items are added.
  146. TEST_F(ShelfModelTest, AddIndices) {
  147. // Insert a browser shortcut, like Chrome does, it should be added at index 0.
  148. ShelfItem browser_shortcut;
  149. browser_shortcut.id = ShelfID("browser");
  150. browser_shortcut.type = TYPE_BROWSER_SHORTCUT;
  151. EXPECT_EQ(0, Add(browser_shortcut));
  152. // App items should be after the browser shortcut.
  153. ShelfItem item;
  154. item.type = TYPE_APP;
  155. item.id = ShelfID("id1");
  156. int platform_app_index1 = Add(item);
  157. EXPECT_EQ(1, platform_app_index1);
  158. // Add another platform app item, it should follow first.
  159. item.id = ShelfID("id2");
  160. int platform_app_index2 = Add(item);
  161. EXPECT_EQ(2, platform_app_index2);
  162. // TYPE_PINNED_APP priority is higher than TYPE_APP but same as
  163. // TYPE_BROWSER_SHORTCUT. So TYPE_PINNED_APP is located after
  164. // TYPE_BROWSER_SHORTCUT.
  165. item.type = TYPE_PINNED_APP;
  166. item.id = ShelfID("id3");
  167. int app_shortcut_index1 = Add(item);
  168. EXPECT_EQ(1, app_shortcut_index1);
  169. item.type = TYPE_PINNED_APP;
  170. item.id = ShelfID("id4");
  171. int app_shortcut_index2 = Add(item);
  172. EXPECT_EQ(2, app_shortcut_index2);
  173. // Check that AddAt() figures out the correct indexes for app
  174. // shortcuts. TYPE_PINNED_APP and TYPE_BROWSER_SHORTCUT has the same weight.
  175. // So TYPE_PINNED_APP is located at index 0. And, TYPE_BROWSER_SHORTCUT is
  176. // located at index 1.
  177. item.type = TYPE_PINNED_APP;
  178. item.id = ShelfID("id5");
  179. int app_shortcut_index3 = AddAt(0, item);
  180. EXPECT_EQ(0, app_shortcut_index3);
  181. item.type = TYPE_PINNED_APP;
  182. item.id = ShelfID("id6");
  183. int app_shortcut_index4 = AddAt(5, item);
  184. EXPECT_EQ(4, app_shortcut_index4);
  185. item.type = TYPE_PINNED_APP;
  186. item.id = ShelfID("id7");
  187. int app_shortcut_index5 = AddAt(1, item);
  188. EXPECT_EQ(1, app_shortcut_index5);
  189. // Check that AddAt() figures out the correct indexes for apps.
  190. item.type = TYPE_APP;
  191. item.id = ShelfID("id8");
  192. int platform_app_index3 = AddAt(2, item);
  193. EXPECT_EQ(6, platform_app_index3);
  194. item.type = TYPE_APP;
  195. item.id = ShelfID("id9");
  196. int platform_app_index4 = AddAt(6, item);
  197. EXPECT_EQ(6, platform_app_index4);
  198. EXPECT_EQ(TYPE_BROWSER_SHORTCUT, model_->items()[2].type);
  199. // TYPE_UNPINNED_BROWSER_SHORTCUT icons should behave similar to
  200. // unpinned apps.
  201. item.type = TYPE_UNPINNED_BROWSER_SHORTCUT;
  202. item.id = ShelfID("unpinned_browser");
  203. int unpinned_browser_index = AddAt(2, item);
  204. EXPECT_EQ(6, unpinned_browser_index);
  205. }
  206. // Test that the indexes for the running applications are properly determined.
  207. TEST_F(ShelfModelTest, FirstRunningAppIndex) {
  208. // Insert the browser shortcut at index 0 and check that the running
  209. // application index would be behind it.
  210. ShelfItem item;
  211. item.id = ShelfID("browser");
  212. item.type = TYPE_BROWSER_SHORTCUT;
  213. EXPECT_EQ(0, Add(item));
  214. EXPECT_EQ(1, model_->FirstRunningAppIndex());
  215. // Insert an application shortcut and make sure that the running application
  216. // index would be behind it.
  217. item.type = TYPE_PINNED_APP;
  218. item.id = ShelfID("pinned app");
  219. EXPECT_EQ(1, Add(item));
  220. EXPECT_EQ(2, model_->FirstRunningAppIndex());
  221. // Insert a two app items and check the first running app index.
  222. item.type = TYPE_APP;
  223. item.id = ShelfID("app1");
  224. EXPECT_EQ(2, Add(item));
  225. EXPECT_EQ(2, model_->FirstRunningAppIndex());
  226. item.id = ShelfID("app2");
  227. EXPECT_EQ(3, Add(item));
  228. EXPECT_EQ(2, model_->FirstRunningAppIndex());
  229. item.type = TYPE_UNPINNED_BROWSER_SHORTCUT;
  230. item.id = ShelfID("unpinned browser");
  231. EXPECT_EQ(4, Add(item));
  232. EXPECT_EQ(2, model_->FirstRunningAppIndex());
  233. }
  234. // Test item reordering on type/weight (eg. pinning) changes. crbug.com/248769.
  235. TEST_F(ShelfModelTest, ReorderOnTypeChanges) {
  236. // Add three pinned items.
  237. ShelfItem item1;
  238. item1.type = TYPE_PINNED_APP;
  239. item1.id = ShelfID("id1");
  240. int app1_index = Add(item1);
  241. EXPECT_EQ(0, app1_index);
  242. ShelfItem item2;
  243. item2.type = TYPE_PINNED_APP;
  244. item2.id = ShelfID("id2");
  245. int app2_index = Add(item2);
  246. EXPECT_EQ(1, app2_index);
  247. ShelfItem item3;
  248. item3.type = TYPE_PINNED_APP;
  249. item3.id = ShelfID("id3");
  250. int app3_index = Add(item3);
  251. EXPECT_EQ(2, app3_index);
  252. // Unpinning an item moves it behind the shortcuts.
  253. EXPECT_EQ(item3.id, model_->items()[2].id);
  254. item2.type = TYPE_APP;
  255. model_->Set(app2_index, item2);
  256. EXPECT_EQ(item2.id, model_->items()[2].id);
  257. }
  258. // Test getting the index of ShelfIDs as a check for item presence.
  259. TEST_F(ShelfModelTest, ItemIndexByID) {
  260. // Expect empty and unknown ids to return the invalid index -1.
  261. EXPECT_EQ(-1, model_->ItemIndexByID(ShelfID()));
  262. EXPECT_EQ(-1, model_->ItemIndexByID(ShelfID("foo")));
  263. EXPECT_EQ(-1, model_->ItemIndexByID(ShelfID("foo", "bar")));
  264. // Add an item and expect to get a valid index for its id.
  265. ShelfItem item1;
  266. item1.type = TYPE_PINNED_APP;
  267. item1.id = ShelfID("app_id1", "launch_id1");
  268. const int index1 = Add(item1);
  269. EXPECT_EQ(index1, model_->ItemIndexByID(item1.id));
  270. // Add another item and expect to get another valid index for its id.
  271. ShelfItem item2;
  272. item2.type = TYPE_APP;
  273. item2.id = ShelfID("app_id2", "launch_id2");
  274. const int index2 = Add(item2);
  275. EXPECT_EQ(index2, model_->ItemIndexByID(item2.id));
  276. // Removing the first item should yield an invalid index for that item.
  277. model_->RemoveItemAt(index1);
  278. EXPECT_EQ(-1, model_->ItemIndexByID(item1.id));
  279. // The index of the second item should be decremented, but still valid.
  280. EXPECT_EQ(index2 - 1, model_->ItemIndexByID(item2.id));
  281. EXPECT_LE(0, model_->ItemIndexByID(item2.id));
  282. }
  283. // Test pinning and unpinning a closed app, and checking if it is pinned.
  284. TEST_F(ShelfModelTest, ClosedAppPinning) {
  285. const std::string app_id("app_id");
  286. // Check the initial state.
  287. EXPECT_FALSE(model_->IsAppPinned(app_id));
  288. EXPECT_EQ(0, model_->item_count());
  289. // Pinning a previously unknown app should add an item.
  290. ShelfItem item;
  291. item.id = ShelfID(app_id);
  292. item.type = TYPE_PINNED_APP;
  293. model_->Add(item, std::make_unique<TestShelfItemDelegate>(item.id));
  294. EXPECT_TRUE(model_->IsAppPinned(app_id));
  295. EXPECT_EQ(1, model_->item_count());
  296. EXPECT_EQ(TYPE_PINNED_APP, model_->items()[0].type);
  297. EXPECT_EQ(app_id, model_->items()[0].id.app_id);
  298. // Pinning the same app id again should have no change.
  299. model_->PinExistingItemWithID(app_id);
  300. EXPECT_TRUE(model_->IsAppPinned(app_id));
  301. EXPECT_EQ(1, model_->item_count());
  302. EXPECT_EQ(TYPE_PINNED_APP, model_->items()[0].type);
  303. EXPECT_EQ(app_id, model_->items()[0].id.app_id);
  304. // Unpinning the app should remove the item.
  305. model_->UnpinAppWithID(app_id);
  306. EXPECT_FALSE(model_->IsAppPinned(app_id));
  307. EXPECT_EQ(0, model_->item_count());
  308. // Unpinning the same app id again should have no change.
  309. model_->UnpinAppWithID(app_id);
  310. EXPECT_FALSE(model_->IsAppPinned(app_id));
  311. EXPECT_EQ(0, model_->item_count());
  312. }
  313. // Test pinning and unpinning a running app, and checking if it is pinned.
  314. TEST_F(ShelfModelTest, RunningAppPinning) {
  315. const std::string app_id("app_id");
  316. // Check the initial state.
  317. EXPECT_FALSE(model_->IsAppPinned(app_id));
  318. EXPECT_EQ(0, model_->item_count());
  319. // Add an example running app.
  320. ShelfItem item;
  321. item.type = TYPE_APP;
  322. item.status = STATUS_RUNNING;
  323. item.id = ShelfID(app_id);
  324. const int index = Add(item);
  325. // The item should be added but not pinned.
  326. EXPECT_FALSE(model_->IsAppPinned(app_id));
  327. EXPECT_EQ(1, model_->item_count());
  328. EXPECT_EQ(TYPE_APP, model_->items()[index].type);
  329. EXPECT_EQ(item.id, model_->items()[index].id);
  330. // Pinning the item should just change its type.
  331. model_->PinExistingItemWithID(app_id);
  332. EXPECT_TRUE(model_->IsAppPinned(app_id));
  333. EXPECT_EQ(1, model_->item_count());
  334. EXPECT_EQ(TYPE_PINNED_APP, model_->items()[index].type);
  335. EXPECT_EQ(item.id, model_->items()[index].id);
  336. // Pinning the same app id again should have no change.
  337. model_->PinExistingItemWithID(app_id);
  338. EXPECT_TRUE(model_->IsAppPinned(app_id));
  339. EXPECT_EQ(1, model_->item_count());
  340. EXPECT_EQ(TYPE_PINNED_APP, model_->items()[index].type);
  341. EXPECT_EQ(item.id, model_->items()[index].id);
  342. // Unpinning the app should leave the item unpinnned but running.
  343. model_->UnpinAppWithID(app_id);
  344. EXPECT_FALSE(model_->IsAppPinned(app_id));
  345. EXPECT_EQ(1, model_->item_count());
  346. EXPECT_EQ(TYPE_APP, model_->items()[index].type);
  347. EXPECT_EQ(item.id, model_->items()[index].id);
  348. // Unpinning the same app id again should have no change.
  349. model_->UnpinAppWithID(app_id);
  350. EXPECT_FALSE(model_->IsAppPinned(app_id));
  351. EXPECT_EQ(1, model_->item_count());
  352. EXPECT_EQ(TYPE_APP, model_->items()[index].type);
  353. EXPECT_EQ(item.id, model_->items()[index].id);
  354. }
  355. // Tests that apps are updated properly when notifications are added or removed.
  356. TEST_F(ShelfModelTest, AddRemoveNotification) {
  357. const std::string app_id("app_id");
  358. // Add an example running app.
  359. ShelfItem item;
  360. item.type = TYPE_APP;
  361. item.status = STATUS_RUNNING;
  362. item.id = ShelfID(app_id);
  363. const int index = Add(item);
  364. EXPECT_FALSE(model_->items()[index].has_notification);
  365. // Update to add a notification for the app.
  366. model_->UpdateItemNotification(app_id, true /* has_badge */);
  367. EXPECT_TRUE(model_->items()[index].has_notification);
  368. // Update to remove the notification for the app.
  369. model_->UpdateItemNotification(app_id, false /* has_badge */);
  370. EXPECT_FALSE(model_->items()[index].has_notification);
  371. }
  372. // Test that RemoveItemAndTakeShelfItemDelegate has the same effect as
  373. // RemoveItemAt and returns the correct delegate.
  374. TEST_F(ShelfModelTest, RemoveItemAndTakeShelfItemDelegate) {
  375. // Add an item.
  376. ShelfItem item1;
  377. item1.id = ShelfID("item1");
  378. item1.type = TYPE_PINNED_APP;
  379. Add(item1);
  380. EXPECT_EQ(1, model_->item_count());
  381. EXPECT_LE(0, model_->ItemIndexByID(item1.id));
  382. EXPECT_TRUE(model_->ItemByID(item1.id));
  383. EXPECT_EQ("added=1", observer_->StateStringAndClear());
  384. // Set item delegate.
  385. auto* delegate = new TestShelfItemDelegate(item1.id);
  386. model_->ReplaceShelfItemDelegate(
  387. item1.id, std::unique_ptr<ShelfItemDelegate>(delegate));
  388. EXPECT_EQ("delegate_changed=1", observer_->StateStringAndClear());
  389. // Remove the item.
  390. auto taken_delegate = model_->RemoveItemAndTakeShelfItemDelegate(item1.id);
  391. EXPECT_EQ(0, model_->item_count());
  392. EXPECT_EQ(-1, model_->ItemIndexByID(item1.id));
  393. EXPECT_FALSE(model_->ItemByID(item1.id));
  394. EXPECT_EQ("removed=1", observer_->StateStringAndClear());
  395. EXPECT_EQ(delegate, taken_delegate.get());
  396. }
  397. } // namespace ash