bookmark_model_observer_impl_unittest.cc 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977
  1. // Copyright 2018 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 "components/sync_bookmarks/bookmark_model_observer_impl.h"
  5. #include <algorithm>
  6. #include <list>
  7. #include <map>
  8. #include <memory>
  9. #include <utility>
  10. #include <vector>
  11. #include "base/callback_helpers.h"
  12. #include "base/strings/utf_string_conversions.h"
  13. #include "base/test/mock_callback.h"
  14. #include "components/bookmarks/browser/bookmark_model.h"
  15. #include "components/bookmarks/test/test_bookmark_client.h"
  16. #include "components/favicon_base/favicon_types.h"
  17. #include "components/sync/base/time.h"
  18. #include "components/sync/base/unique_position.h"
  19. #include "components/sync/protocol/bookmark_specifics.pb.h"
  20. #include "components/sync/protocol/entity_metadata.pb.h"
  21. #include "components/sync/protocol/entity_specifics.pb.h"
  22. #include "components/sync/protocol/model_type_state.pb.h"
  23. #include "components/sync_bookmarks/bookmark_specifics_conversions.h"
  24. #include "components/sync_bookmarks/synced_bookmark_tracker.h"
  25. #include "components/sync_bookmarks/synced_bookmark_tracker_entity.h"
  26. #include "components/undo/bookmark_undo_service.h"
  27. #include "testing/gmock/include/gmock/gmock.h"
  28. #include "testing/gtest/include/gtest/gtest.h"
  29. #include "third_party/skia/include/core/SkBitmap.h"
  30. #include "ui/gfx/image/image.h"
  31. namespace sync_bookmarks {
  32. namespace {
  33. using testing::ElementsAre;
  34. using testing::Eq;
  35. using testing::IsEmpty;
  36. using testing::IsNull;
  37. using testing::Ne;
  38. using testing::NiceMock;
  39. using testing::NotNull;
  40. using testing::SizeIs;
  41. using testing::UnorderedElementsAre;
  42. const char kBookmarkBarId[] = "bookmark_bar_id";
  43. const char kBookmarkBarTag[] = "bookmark_bar";
  44. const char kOtherBookmarksId[] = "other_bookmarks_id";
  45. const char kOtherBookmarksTag[] = "other_bookmarks";
  46. const char kMobileBookmarksId[] = "synced_bookmarks_id";
  47. const char kMobileBookmarksTag[] = "synced_bookmarks";
  48. // Matches |arg| of type SyncedBookmarkTrackerEntity*.
  49. MATCHER_P(HasBookmarkNode, node, "") {
  50. return arg->bookmark_node() == node;
  51. }
  52. // Returns a single-color 16x16 image using |color|.
  53. gfx::Image CreateTestImage(SkColor color) {
  54. SkBitmap bitmap;
  55. bitmap.allocN32Pixels(16, 16);
  56. bitmap.eraseColor(color);
  57. return gfx::Image::CreateFrom1xBitmap(bitmap);
  58. }
  59. class BookmarkModelObserverImplTest : public testing::Test {
  60. public:
  61. BookmarkModelObserverImplTest()
  62. : bookmark_tracker_(
  63. SyncedBookmarkTracker::CreateEmpty(sync_pb::ModelTypeState())),
  64. observer_(nudge_for_commit_closure_.Get(),
  65. /*on_bookmark_model_being_deleted_closure=*/base::DoNothing(),
  66. bookmark_tracker_.get()),
  67. bookmark_model_(bookmarks::TestBookmarkClient::CreateModel()) {
  68. bookmark_model_->AddObserver(&observer_);
  69. sync_pb::EntitySpecifics specifics;
  70. specifics.mutable_bookmark()->set_legacy_canonicalized_title(
  71. kBookmarkBarTag);
  72. bookmark_tracker_->Add(
  73. /*bookmark_node=*/bookmark_model()->bookmark_bar_node(),
  74. /*sync_id=*/kBookmarkBarId,
  75. /*server_version=*/0, /*creation_time=*/base::Time::Now(), specifics);
  76. specifics.mutable_bookmark()->set_legacy_canonicalized_title(
  77. kOtherBookmarksTag);
  78. bookmark_tracker_->Add(
  79. /*bookmark_node=*/bookmark_model()->other_node(),
  80. /*sync_id=*/kOtherBookmarksId,
  81. /*server_version=*/0, /*creation_time=*/base::Time::Now(), specifics);
  82. specifics.mutable_bookmark()->set_legacy_canonicalized_title(
  83. kMobileBookmarksTag);
  84. bookmark_tracker_->Add(
  85. /*bookmark_node=*/bookmark_model()->mobile_node(),
  86. /*sync_id=*/kMobileBookmarksId,
  87. /*server_version=*/0, /*creation_time=*/base::Time::Now(), specifics);
  88. }
  89. ~BookmarkModelObserverImplTest() override {
  90. bookmark_model_->RemoveObserver(&observer_);
  91. }
  92. void SimulateCommitResponseForAllLocalChanges() {
  93. for (const SyncedBookmarkTrackerEntity* entity :
  94. bookmark_tracker()->GetEntitiesWithLocalChanges()) {
  95. const std::string id = entity->metadata().server_id();
  96. // Don't simulate change in id for simplicity.
  97. bookmark_tracker()->UpdateUponCommitResponse(
  98. entity, id,
  99. /*server_version=*/1,
  100. /*acked_sequence_number=*/entity->metadata().sequence_number());
  101. }
  102. }
  103. syncer::UniquePosition PositionOf(
  104. const bookmarks::BookmarkNode* bookmark_node) {
  105. const SyncedBookmarkTrackerEntity* entity =
  106. bookmark_tracker()->GetEntityForBookmarkNode(bookmark_node);
  107. return syncer::UniquePosition::FromProto(
  108. entity->metadata().unique_position());
  109. }
  110. std::vector<const bookmarks::BookmarkNode*> GenerateBookmarkNodes(
  111. size_t num_bookmarks) {
  112. const std::string kTitle = "title";
  113. const std::string kUrl = "http://www.url.com";
  114. const bookmarks::BookmarkNode* bookmark_bar_node =
  115. bookmark_model()->bookmark_bar_node();
  116. std::vector<const bookmarks::BookmarkNode*> nodes;
  117. for (size_t i = 0; i < num_bookmarks; ++i) {
  118. nodes.push_back(bookmark_model()->AddURL(
  119. /*parent=*/bookmark_bar_node, /*index=*/i, base::UTF8ToUTF16(kTitle),
  120. GURL(kUrl)));
  121. }
  122. // Verify number of entities local changes. Should be the same as number of
  123. // new nodes.
  124. DCHECK_EQ(bookmark_tracker()->GetEntitiesWithLocalChanges().size(),
  125. num_bookmarks);
  126. // All bookmarks should be tracked now (including permanent nodes).
  127. DCHECK_EQ(bookmark_tracker()->TrackedEntitiesCountForTest(),
  128. 3 + num_bookmarks);
  129. return nodes;
  130. }
  131. bookmarks::BookmarkModel* bookmark_model() { return bookmark_model_.get(); }
  132. SyncedBookmarkTracker* bookmark_tracker() { return bookmark_tracker_.get(); }
  133. BookmarkModelObserverImpl* observer() { return &observer_; }
  134. base::MockCallback<base::RepeatingClosure>* nudge_for_commit_closure() {
  135. return &nudge_for_commit_closure_;
  136. }
  137. bookmarks::TestBookmarkClient* bookmark_client() {
  138. return static_cast<bookmarks::TestBookmarkClient*>(
  139. bookmark_model_->client());
  140. }
  141. private:
  142. NiceMock<base::MockCallback<base::RepeatingClosure>>
  143. nudge_for_commit_closure_;
  144. std::unique_ptr<SyncedBookmarkTracker> bookmark_tracker_;
  145. BookmarkModelObserverImpl observer_;
  146. std::unique_ptr<bookmarks::BookmarkModel> bookmark_model_;
  147. };
  148. TEST_F(BookmarkModelObserverImplTest,
  149. BookmarkAddedShouldPutInTheTrackerAndNudgeForCommit) {
  150. const std::string kTitle = "title";
  151. const std::string kUrl = "http://www.url.com";
  152. EXPECT_CALL(*nudge_for_commit_closure(), Run());
  153. const bookmarks::BookmarkNode* bookmark_bar_node =
  154. bookmark_model()->bookmark_bar_node();
  155. const bookmarks::BookmarkNode* bookmark_node = bookmark_model()->AddURL(
  156. /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle),
  157. GURL(kUrl));
  158. EXPECT_THAT(bookmark_tracker()->TrackedEntitiesCountForTest(), 4U);
  159. std::vector<const SyncedBookmarkTrackerEntity*> local_changes =
  160. bookmark_tracker()->GetEntitiesWithLocalChanges();
  161. ASSERT_THAT(local_changes.size(), 1U);
  162. EXPECT_THAT(local_changes[0]->bookmark_node(), Eq(bookmark_node));
  163. EXPECT_THAT(local_changes[0]->metadata().server_id(),
  164. Eq(bookmark_node->guid().AsLowercaseString()));
  165. }
  166. TEST_F(BookmarkModelObserverImplTest,
  167. BookmarkChangedShouldUpdateTheTrackerAndNudgeForCommit) {
  168. const std::string kTitle1 = "title1";
  169. const std::string kUrl1 = "http://www.url1.com";
  170. const std::string kNewUrl1 = "http://www.new-url1.com";
  171. const std::string kTitle2 = "title2";
  172. const std::string kUrl2 = "http://www.url2.com";
  173. const std::string kNewTitle2 = "new_title2";
  174. const bookmarks::BookmarkNode* bookmark_bar_node =
  175. bookmark_model()->bookmark_bar_node();
  176. const bookmarks::BookmarkNode* bookmark_node1 = bookmark_model()->AddURL(
  177. /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle1),
  178. GURL(kUrl1));
  179. const bookmarks::BookmarkNode* bookmark_node2 = bookmark_model()->AddURL(
  180. /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle2),
  181. GURL(kUrl2));
  182. // Both bookmarks should be tracked now.
  183. ASSERT_THAT(bookmark_tracker()->TrackedEntitiesCountForTest(), 5U);
  184. // There should be two local changes now for both entities.
  185. ASSERT_THAT(bookmark_tracker()->GetEntitiesWithLocalChanges().size(), 2U);
  186. SimulateCommitResponseForAllLocalChanges();
  187. // There should be no local changes now.
  188. ASSERT_TRUE(bookmark_tracker()->GetEntitiesWithLocalChanges().empty());
  189. // Now update the title of the 2nd node.
  190. EXPECT_CALL(*nudge_for_commit_closure(), Run());
  191. bookmark_model()->SetTitle(bookmark_node2, base::UTF8ToUTF16(kNewTitle2));
  192. // Node 2 should be in the local changes list.
  193. EXPECT_THAT(bookmark_tracker()->GetEntitiesWithLocalChanges(),
  194. ElementsAre(HasBookmarkNode(bookmark_node2)));
  195. // Now update the url of the 1st node.
  196. EXPECT_CALL(*nudge_for_commit_closure(), Run());
  197. bookmark_model()->SetURL(bookmark_node1, GURL(kNewUrl1));
  198. // Node 1 and 2 should be in the local changes list.
  199. EXPECT_THAT(bookmark_tracker()->GetEntitiesWithLocalChanges(),
  200. UnorderedElementsAre(HasBookmarkNode(bookmark_node1),
  201. HasBookmarkNode(bookmark_node2)));
  202. // Now update metainfo of the 1st node.
  203. EXPECT_CALL(*nudge_for_commit_closure(), Run());
  204. bookmark_model()->SetNodeMetaInfo(bookmark_node1, "key", "value");
  205. }
  206. TEST_F(BookmarkModelObserverImplTest,
  207. BookmarkMovedShouldUpdateTheTrackerAndNudgeForCommit) {
  208. // Build this structure:
  209. // bookmark_bar
  210. // |- folder1
  211. // |- bookmark1
  212. const GURL kUrl("http://www.url1.com");
  213. const bookmarks::BookmarkNode* bookmark_bar_node =
  214. bookmark_model()->bookmark_bar_node();
  215. const bookmarks::BookmarkNode* folder1_node = bookmark_model()->AddFolder(
  216. /*parent=*/bookmark_bar_node, /*index=*/0, u"folder1");
  217. const bookmarks::BookmarkNode* bookmark1_node = bookmark_model()->AddURL(
  218. /*parent=*/folder1_node, /*index=*/0, u"bookmark1", kUrl);
  219. // Verify number of entities local changes. Should be the same as number of
  220. // new nodes.
  221. ASSERT_THAT(bookmark_tracker()->GetEntitiesWithLocalChanges().size(), 2U);
  222. // All bookmarks should be tracked now.
  223. ASSERT_THAT(bookmark_tracker()->TrackedEntitiesCountForTest(), 5U);
  224. SimulateCommitResponseForAllLocalChanges();
  225. // There should be no local changes now.
  226. ASSERT_TRUE(bookmark_tracker()->GetEntitiesWithLocalChanges().empty());
  227. // Now change it to this structure.
  228. // Build this structure:
  229. // bookmark_bar
  230. // |- bookmark1
  231. // |- folder1
  232. EXPECT_CALL(*nudge_for_commit_closure(), Run());
  233. bookmark_model()->Move(bookmark1_node, bookmark_bar_node, 0);
  234. EXPECT_TRUE(PositionOf(bookmark1_node).LessThan(PositionOf(folder1_node)));
  235. }
  236. TEST_F(BookmarkModelObserverImplTest,
  237. ReorderChildrenShouldUpdateTheTrackerAndNudgeForCommit) {
  238. std::vector<const bookmarks::BookmarkNode*> nodes =
  239. GenerateBookmarkNodes(/*num_bookmarks=*/4);
  240. SimulateCommitResponseForAllLocalChanges();
  241. // Reorder it to be (2 bookmarks have been moved):
  242. // bookmark_bar
  243. // |- node1
  244. // |- node3
  245. // |- node0
  246. // |- node2
  247. bookmark_model()->ReorderChildren(bookmark_model()->bookmark_bar_node(),
  248. {nodes[1], nodes[3], nodes[0], nodes[2]});
  249. EXPECT_TRUE(PositionOf(nodes[1]).LessThan(PositionOf(nodes[3])));
  250. EXPECT_TRUE(PositionOf(nodes[3]).LessThan(PositionOf(nodes[0])));
  251. EXPECT_TRUE(PositionOf(nodes[0]).LessThan(PositionOf(nodes[2])));
  252. // Only 2 moved nodes should have local changes to commit.
  253. EXPECT_THAT(bookmark_tracker()->GetEntitiesWithLocalChanges(), SizeIs(2));
  254. }
  255. TEST_F(BookmarkModelObserverImplTest,
  256. ShouldReorderChildrenAndUpdateOnlyMovedToRightBookmark) {
  257. std::vector<const bookmarks::BookmarkNode*> nodes =
  258. GenerateBookmarkNodes(/*num_bookmarks=*/4);
  259. SimulateCommitResponseForAllLocalChanges();
  260. // Reorder it to be:
  261. // bookmark_bar
  262. // |- node1
  263. // |- node2
  264. // |- node0 (moved)
  265. // |- node3
  266. bookmark_model()->ReorderChildren(bookmark_model()->bookmark_bar_node(),
  267. {nodes[1], nodes[2], nodes[0], nodes[3]});
  268. EXPECT_TRUE(PositionOf(nodes[1]).LessThan(PositionOf(nodes[2])));
  269. EXPECT_TRUE(PositionOf(nodes[2]).LessThan(PositionOf(nodes[0])));
  270. EXPECT_TRUE(PositionOf(nodes[0]).LessThan(PositionOf(nodes[3])));
  271. // Only one moved node should have local changes to commit.
  272. EXPECT_THAT(bookmark_tracker()->GetEntitiesWithLocalChanges(),
  273. UnorderedElementsAre(HasBookmarkNode(nodes[0])));
  274. }
  275. TEST_F(BookmarkModelObserverImplTest,
  276. ShouldReorderChildrenAndUpdateOnlyMovedToLeftBookmark) {
  277. std::vector<const bookmarks::BookmarkNode*> nodes =
  278. GenerateBookmarkNodes(/*num_bookmarks=*/4);
  279. SimulateCommitResponseForAllLocalChanges();
  280. // Reorder it to be:
  281. // bookmark_bar
  282. // |- node0
  283. // |- node3 (moved)
  284. // |- node1
  285. // |- node2
  286. bookmark_model()->ReorderChildren(bookmark_model()->bookmark_bar_node(),
  287. {nodes[0], nodes[3], nodes[1], nodes[2]});
  288. EXPECT_TRUE(PositionOf(nodes[0]).LessThan(PositionOf(nodes[3])));
  289. EXPECT_TRUE(PositionOf(nodes[3]).LessThan(PositionOf(nodes[1])));
  290. EXPECT_TRUE(PositionOf(nodes[1]).LessThan(PositionOf(nodes[2])));
  291. // Only one moved node should have local changes to commit.
  292. EXPECT_THAT(bookmark_tracker()->GetEntitiesWithLocalChanges(),
  293. UnorderedElementsAre(HasBookmarkNode(nodes[3])));
  294. }
  295. TEST_F(BookmarkModelObserverImplTest,
  296. ShouldReorderWhenBookmarkMovedToLastPosition) {
  297. std::vector<const bookmarks::BookmarkNode*> nodes =
  298. GenerateBookmarkNodes(/*num_bookmarks=*/4);
  299. SimulateCommitResponseForAllLocalChanges();
  300. // Reorder it to be:
  301. // bookmark_bar
  302. // |- node1
  303. // |- node2
  304. // |- node3
  305. // |- node0 (moved)
  306. bookmark_model()->ReorderChildren(bookmark_model()->bookmark_bar_node(),
  307. {nodes[1], nodes[2], nodes[3], nodes[0]});
  308. EXPECT_TRUE(PositionOf(nodes[1]).LessThan(PositionOf(nodes[2])));
  309. EXPECT_TRUE(PositionOf(nodes[2]).LessThan(PositionOf(nodes[3])));
  310. EXPECT_TRUE(PositionOf(nodes[3]).LessThan(PositionOf(nodes[0])));
  311. // Only one moved node should have local changes to commit.
  312. EXPECT_THAT(bookmark_tracker()->GetEntitiesWithLocalChanges(),
  313. UnorderedElementsAre(HasBookmarkNode(nodes[0])));
  314. }
  315. TEST_F(BookmarkModelObserverImplTest,
  316. ShouldReorderWhenBookmarkMovedToFirstPosition) {
  317. std::vector<const bookmarks::BookmarkNode*> nodes =
  318. GenerateBookmarkNodes(/*num_bookmarks=*/4);
  319. SimulateCommitResponseForAllLocalChanges();
  320. // Reorder it to be:
  321. // bookmark_bar
  322. // |- node3 (moved)
  323. // |- node0
  324. // |- node1
  325. // |- node2
  326. bookmark_model()->ReorderChildren(bookmark_model()->bookmark_bar_node(),
  327. {nodes[3], nodes[0], nodes[1], nodes[2]});
  328. EXPECT_TRUE(PositionOf(nodes[3]).LessThan(PositionOf(nodes[0])));
  329. EXPECT_TRUE(PositionOf(nodes[0]).LessThan(PositionOf(nodes[1])));
  330. EXPECT_TRUE(PositionOf(nodes[1]).LessThan(PositionOf(nodes[2])));
  331. // Only one moved node should have local changes to commit.
  332. EXPECT_THAT(bookmark_tracker()->GetEntitiesWithLocalChanges(),
  333. UnorderedElementsAre(HasBookmarkNode(nodes[3])));
  334. }
  335. TEST_F(BookmarkModelObserverImplTest, ShouldReorderWhenAllBookmarksReversed) {
  336. // In this case almost all the bookmarks should be updated apart from only one
  337. // bookmark.
  338. std::vector<const bookmarks::BookmarkNode*> nodes =
  339. GenerateBookmarkNodes(/*num_bookmarks=*/4);
  340. SimulateCommitResponseForAllLocalChanges();
  341. // Reorder it to be (all nodes are moved):
  342. // bookmark_bar
  343. // |- node3
  344. // |- node2
  345. // |- node1
  346. // |- node0
  347. bookmark_model()->ReorderChildren(bookmark_model()->bookmark_bar_node(),
  348. {nodes[3], nodes[2], nodes[1], nodes[0]});
  349. EXPECT_TRUE(PositionOf(nodes[3]).LessThan(PositionOf(nodes[2])));
  350. EXPECT_TRUE(PositionOf(nodes[2]).LessThan(PositionOf(nodes[1])));
  351. EXPECT_TRUE(PositionOf(nodes[1]).LessThan(PositionOf(nodes[0])));
  352. // Do not verify which nodes exactly have been updated, it depends on the
  353. // implementation and any of the nodes may become a base node to calculate
  354. // relative positions of all other nodes.
  355. EXPECT_THAT(bookmark_tracker()->GetEntitiesWithLocalChanges(), SizeIs(3));
  356. }
  357. TEST_F(BookmarkModelObserverImplTest,
  358. ShouldNotReorderIfAllBookmarksStillOrdered) {
  359. std::vector<const bookmarks::BookmarkNode*> nodes =
  360. GenerateBookmarkNodes(/*num_bookmarks=*/4);
  361. SimulateCommitResponseForAllLocalChanges();
  362. // Keep the original order.
  363. bookmark_model()->ReorderChildren(bookmark_model()->bookmark_bar_node(),
  364. {nodes[0], nodes[1], nodes[2], nodes[3]});
  365. EXPECT_TRUE(PositionOf(nodes[0]).LessThan(PositionOf(nodes[1])));
  366. EXPECT_TRUE(PositionOf(nodes[1]).LessThan(PositionOf(nodes[2])));
  367. EXPECT_TRUE(PositionOf(nodes[2]).LessThan(PositionOf(nodes[3])));
  368. // The bookmarks remain in the same order, nothing to commit.
  369. EXPECT_THAT(bookmark_tracker()->GetEntitiesWithLocalChanges(), IsEmpty());
  370. }
  371. TEST_F(BookmarkModelObserverImplTest,
  372. BookmarkRemovalShouldUpdateTheTrackerAndNudgeForCommit) {
  373. // Build this structure:
  374. // bookmark_bar
  375. // |- folder1
  376. // |- bookmark1
  377. // |- folder2
  378. // |- bookmark2
  379. // |- bookmark3
  380. // and then delete folder2.
  381. const GURL kUrl("http://www.url1.com");
  382. const bookmarks::BookmarkNode* bookmark_bar_node =
  383. bookmark_model()->bookmark_bar_node();
  384. const bookmarks::BookmarkNode* folder1_node = bookmark_model()->AddFolder(
  385. /*parent=*/bookmark_bar_node, /*index=*/0, u"folder1");
  386. const bookmarks::BookmarkNode* bookmark1_node = bookmark_model()->AddURL(
  387. /*parent=*/folder1_node, /*index=*/0, u"bookmark1", kUrl);
  388. const bookmarks::BookmarkNode* folder2_node = bookmark_model()->AddFolder(
  389. /*parent=*/folder1_node, /*index=*/1, u"folder2");
  390. const bookmarks::BookmarkNode* bookmark2_node = bookmark_model()->AddURL(
  391. /*parent=*/folder2_node, /*index=*/0, u"bookmark2", kUrl);
  392. const bookmarks::BookmarkNode* bookmark3_node = bookmark_model()->AddURL(
  393. /*parent=*/folder2_node, /*index=*/1, u"bookmark3", kUrl);
  394. // All bookmarks should be tracked now.
  395. ASSERT_THAT(bookmark_tracker()->TrackedEntitiesCountForTest(), 8U);
  396. SimulateCommitResponseForAllLocalChanges();
  397. // There should be no local changes now.
  398. ASSERT_TRUE(bookmark_tracker()->GetEntitiesWithLocalChanges().empty());
  399. const SyncedBookmarkTrackerEntity* folder2_entity =
  400. bookmark_tracker()->GetEntityForBookmarkNode(folder2_node);
  401. const SyncedBookmarkTrackerEntity* bookmark2_entity =
  402. bookmark_tracker()->GetEntityForBookmarkNode(bookmark2_node);
  403. const SyncedBookmarkTrackerEntity* bookmark3_entity =
  404. bookmark_tracker()->GetEntityForBookmarkNode(bookmark3_node);
  405. ASSERT_FALSE(folder2_entity->metadata().is_deleted());
  406. ASSERT_FALSE(bookmark2_entity->metadata().is_deleted());
  407. ASSERT_FALSE(bookmark3_entity->metadata().is_deleted());
  408. const std::string& folder2_entity_id = folder2_entity->metadata().server_id();
  409. const std::string& bookmark2_entity_id =
  410. bookmark2_entity->metadata().server_id();
  411. const std::string& bookmark3_entity_id =
  412. bookmark3_entity->metadata().server_id();
  413. // Delete folder2.
  414. EXPECT_CALL(*nudge_for_commit_closure(), Run());
  415. bookmark_model()->Remove(folder2_node);
  416. // folder2, bookmark2, and bookmark3 should be marked deleted.
  417. EXPECT_TRUE(bookmark_tracker()
  418. ->GetEntityForSyncId(folder2_entity_id)
  419. ->metadata()
  420. .is_deleted());
  421. EXPECT_TRUE(bookmark_tracker()
  422. ->GetEntityForSyncId(bookmark2_entity_id)
  423. ->metadata()
  424. .is_deleted());
  425. EXPECT_TRUE(bookmark_tracker()
  426. ->GetEntityForSyncId(bookmark3_entity_id)
  427. ->metadata()
  428. .is_deleted());
  429. // folder2, bookmark2, and bookmark3 should be in the local changes to be
  430. // committed and folder2 deletion should be the last one (after all children
  431. // deletions).
  432. EXPECT_THAT(
  433. bookmark_tracker()->GetEntitiesWithLocalChanges(),
  434. ElementsAre(bookmark_tracker()->GetEntityForSyncId(bookmark2_entity_id),
  435. bookmark_tracker()->GetEntityForSyncId(bookmark3_entity_id),
  436. bookmark_tracker()->GetEntityForSyncId(folder2_entity_id)));
  437. // folder1 and bookmark1 are still tracked.
  438. EXPECT_TRUE(bookmark_tracker()->GetEntityForBookmarkNode(folder1_node));
  439. EXPECT_TRUE(bookmark_tracker()->GetEntityForBookmarkNode(bookmark1_node));
  440. }
  441. TEST_F(BookmarkModelObserverImplTest,
  442. BookmarkCreationAndRemovalShouldRequireTwoCommitResponsesBeforeRemoval) {
  443. const bookmarks::BookmarkNode* bookmark_bar_node =
  444. bookmark_model()->bookmark_bar_node();
  445. const bookmarks::BookmarkNode* folder_node = bookmark_model()->AddFolder(
  446. /*parent=*/bookmark_bar_node, /*index=*/0, u"folder");
  447. // Node should be tracked now.
  448. ASSERT_THAT(bookmark_tracker()->TrackedEntitiesCountForTest(), 4U);
  449. const SyncedBookmarkTrackerEntity* entity =
  450. bookmark_tracker()->GetEntityForBookmarkNode(folder_node);
  451. const std::string id = entity->metadata().server_id();
  452. ASSERT_THAT(bookmark_tracker()->GetEntitiesWithLocalChanges().size(), 1U);
  453. bookmark_tracker()->MarkCommitMayHaveStarted(entity);
  454. // Remove the folder.
  455. bookmark_model()->Remove(folder_node);
  456. // Simulate a commit response for the first commit request (the creation).
  457. // Don't simulate change in id for simplicity.
  458. bookmark_tracker()->UpdateUponCommitResponse(entity, id,
  459. /*server_version=*/1,
  460. /*acked_sequence_number=*/1);
  461. // There should still be one local change (the deletion).
  462. EXPECT_THAT(bookmark_tracker()->GetEntitiesWithLocalChanges().size(), 1U);
  463. // Entity is still tracked.
  464. EXPECT_THAT(bookmark_tracker()->TrackedEntitiesCountForTest(), 4U);
  465. // Commit the deletion.
  466. bookmark_tracker()->UpdateUponCommitResponse(entity, id,
  467. /*server_version=*/2,
  468. /*acked_sequence_number=*/2);
  469. // Entity should have been dropped.
  470. EXPECT_THAT(bookmark_tracker()->TrackedEntitiesCountForTest(), 3U);
  471. }
  472. TEST_F(BookmarkModelObserverImplTest,
  473. BookmarkCreationAndRemovalBeforeCommitRequestShouldBeRemovedDirectly) {
  474. const bookmarks::BookmarkNode* bookmark_bar_node =
  475. bookmark_model()->bookmark_bar_node();
  476. const bookmarks::BookmarkNode* folder_node = bookmark_model()->AddFolder(
  477. /*parent=*/bookmark_bar_node, /*index=*/0, u"folder");
  478. // Node should be tracked now.
  479. ASSERT_THAT(bookmark_tracker()->TrackedEntitiesCountForTest(), 4U);
  480. const std::string id = bookmark_tracker()
  481. ->GetEntityForBookmarkNode(folder_node)
  482. ->metadata()
  483. .server_id();
  484. ASSERT_THAT(bookmark_tracker()->GetEntitiesWithLocalChanges().size(), 1U);
  485. // Remove the folder.
  486. bookmark_model()->Remove(folder_node);
  487. // Entity should have been dropped.
  488. EXPECT_THAT(bookmark_tracker()->TrackedEntitiesCountForTest(), 3U);
  489. }
  490. TEST_F(BookmarkModelObserverImplTest, ShouldPositionSiblings) {
  491. const std::string kTitle = "title";
  492. const std::string kUrl = "http://www.url.com";
  493. // Build this structure:
  494. // bookmark_bar
  495. // |- node1
  496. // |- node2
  497. // Expectation:
  498. // p1 < p2
  499. const bookmarks::BookmarkNode* bookmark_bar_node =
  500. bookmark_model()->bookmark_bar_node();
  501. const bookmarks::BookmarkNode* bookmark_node1 = bookmark_model()->AddURL(
  502. /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle),
  503. GURL(kUrl));
  504. const bookmarks::BookmarkNode* bookmark_node2 = bookmark_model()->AddURL(
  505. /*parent=*/bookmark_bar_node, /*index=*/1, base::UTF8ToUTF16(kTitle),
  506. GURL(kUrl));
  507. EXPECT_TRUE(PositionOf(bookmark_node1).LessThan(PositionOf(bookmark_node2)));
  508. // Now insert node3 at index 1 to build this structure:
  509. // bookmark_bar
  510. // |- node1
  511. // |- node3
  512. // |- node2
  513. // Expectation:
  514. // p1 < p2 (still holds)
  515. // p1 < p3
  516. // p3 < p2
  517. const bookmarks::BookmarkNode* bookmark_node3 = bookmark_model()->AddURL(
  518. /*parent=*/bookmark_bar_node, /*index=*/1, base::UTF8ToUTF16(kTitle),
  519. GURL(kUrl));
  520. EXPECT_THAT(bookmark_tracker()->TrackedEntitiesCountForTest(), Eq(6U));
  521. EXPECT_TRUE(PositionOf(bookmark_node1).LessThan(PositionOf(bookmark_node2)));
  522. EXPECT_TRUE(PositionOf(bookmark_node1).LessThan(PositionOf(bookmark_node3)));
  523. EXPECT_TRUE(PositionOf(bookmark_node3).LessThan(PositionOf(bookmark_node2)));
  524. }
  525. TEST_F(BookmarkModelObserverImplTest, ShouldNotSyncUnsyncableBookmarks) {
  526. auto client = std::make_unique<bookmarks::TestBookmarkClient>();
  527. bookmarks::BookmarkNode* managed_node = client->EnableManagedNode();
  528. std::unique_ptr<bookmarks::BookmarkModel> model =
  529. bookmarks::TestBookmarkClient::CreateModelWithClient(std::move(client));
  530. std::unique_ptr<SyncedBookmarkTracker> bookmark_tracker =
  531. SyncedBookmarkTracker::CreateEmpty(sync_pb::ModelTypeState());
  532. sync_pb::EntitySpecifics specifics;
  533. specifics.mutable_bookmark()->set_legacy_canonicalized_title(kBookmarkBarTag);
  534. *specifics.mutable_bookmark()->mutable_unique_position() =
  535. syncer::UniquePosition::InitialPosition(
  536. syncer::UniquePosition::RandomSuffix())
  537. .ToProto();
  538. bookmark_tracker->Add(
  539. /*bookmark_node=*/model->bookmark_bar_node(),
  540. /*sync_id=*/kBookmarkBarId,
  541. /*server_version=*/0, /*creation_time=*/base::Time::Now(), specifics);
  542. specifics.mutable_bookmark()->set_legacy_canonicalized_title(
  543. kOtherBookmarksTag);
  544. bookmark_tracker->Add(
  545. /*bookmark_node=*/model->other_node(),
  546. /*sync_id=*/kOtherBookmarksId,
  547. /*server_version=*/0, /*creation_time=*/base::Time::Now(), specifics);
  548. specifics.mutable_bookmark()->set_legacy_canonicalized_title(
  549. kMobileBookmarksTag);
  550. bookmark_tracker->Add(
  551. /*bookmark_node=*/model->mobile_node(),
  552. /*sync_id=*/kMobileBookmarksId,
  553. /*server_version=*/0, /*creation_time=*/base::Time::Now(), specifics);
  554. BookmarkModelObserverImpl observer(
  555. nudge_for_commit_closure()->Get(),
  556. /*on_bookmark_model_being_deleted_closure=*/base::DoNothing(),
  557. bookmark_tracker.get());
  558. model->AddObserver(&observer);
  559. EXPECT_CALL(*nudge_for_commit_closure(), Run()).Times(0);
  560. // In the TestBookmarkClient, descendants of managed nodes shouldn't be
  561. // synced.
  562. const bookmarks::BookmarkNode* unsyncable_node =
  563. model->AddURL(/*parent=*/managed_node, /*index=*/0, u"Title",
  564. GURL("http://www.url.com"));
  565. // Only permanent folders should be tracked.
  566. EXPECT_THAT(bookmark_tracker->TrackedEntitiesCountForTest(), 3U);
  567. EXPECT_CALL(*nudge_for_commit_closure(), Run()).Times(0);
  568. // In the TestBookmarkClient, descendants of managed nodes shouldn't be
  569. // synced.
  570. model->SetTitle(unsyncable_node, u"NewTitle");
  571. // Only permanent folders should be tracked.
  572. EXPECT_THAT(bookmark_tracker->TrackedEntitiesCountForTest(), 3U);
  573. EXPECT_CALL(*nudge_for_commit_closure(), Run()).Times(0);
  574. // In the TestBookmarkClient, descendants of managed nodes shouldn't be
  575. // synced.
  576. model->Remove(unsyncable_node);
  577. // Only permanent folders should be tracked.
  578. EXPECT_THAT(bookmark_tracker->TrackedEntitiesCountForTest(), 3U);
  579. model->RemoveObserver(&observer);
  580. }
  581. TEST_F(BookmarkModelObserverImplTest, ShouldAddChildrenInArbitraryOrder) {
  582. std::unique_ptr<SyncedBookmarkTracker> bookmark_tracker =
  583. SyncedBookmarkTracker::CreateEmpty(sync_pb::ModelTypeState());
  584. BookmarkModelObserverImpl observer(
  585. /*nudge_for_commit_closure=*/base::DoNothing(),
  586. /*on_bookmark_model_being_deleted_closure=*/base::DoNothing(),
  587. bookmark_tracker.get());
  588. const bookmarks::BookmarkNode* bookmark_bar_node =
  589. bookmark_model()->bookmark_bar_node();
  590. // Add the bookmark bar to the tracker.
  591. sync_pb::EntitySpecifics specifics;
  592. specifics.mutable_bookmark()->set_legacy_canonicalized_title(kBookmarkBarTag);
  593. *specifics.mutable_bookmark()->mutable_unique_position() =
  594. syncer::UniquePosition::InitialPosition(
  595. syncer::UniquePosition::RandomSuffix())
  596. .ToProto();
  597. bookmark_tracker->Add(
  598. /*bookmark_node=*/bookmark_model()->bookmark_bar_node(),
  599. /*sync_id=*/kBookmarkBarId,
  600. /*server_version=*/0, /*creation_time=*/base::Time::Now(), specifics);
  601. // Build this structure:
  602. // bookmark_bar
  603. // |- folder0
  604. // |- folder1
  605. // |- folder2
  606. // |- folder3
  607. // |- folder4
  608. const bookmarks::BookmarkNode* nodes[5];
  609. for (size_t i = 0; i < 5; i++) {
  610. nodes[i] = bookmark_model()->AddFolder(
  611. /*parent=*/bookmark_bar_node, /*index=*/i,
  612. base::UTF8ToUTF16("folder" + std::to_string(i)));
  613. }
  614. // Now simulate calling the observer as if the nodes are added in that order.
  615. // 4,0,2,3,1.
  616. observer.BookmarkNodeAdded(bookmark_model(), bookmark_bar_node, 4);
  617. observer.BookmarkNodeAdded(bookmark_model(), bookmark_bar_node, 0);
  618. observer.BookmarkNodeAdded(bookmark_model(), bookmark_bar_node, 2);
  619. observer.BookmarkNodeAdded(bookmark_model(), bookmark_bar_node, 3);
  620. observer.BookmarkNodeAdded(bookmark_model(), bookmark_bar_node, 1);
  621. ASSERT_THAT(bookmark_tracker->TrackedEntitiesCountForTest(), 6U);
  622. // Check that position information match the children order.
  623. EXPECT_TRUE(PositionOf(nodes[0]).LessThan(PositionOf(nodes[1])));
  624. EXPECT_TRUE(PositionOf(nodes[1]).LessThan(PositionOf(nodes[2])));
  625. EXPECT_TRUE(PositionOf(nodes[2]).LessThan(PositionOf(nodes[3])));
  626. EXPECT_TRUE(PositionOf(nodes[3]).LessThan(PositionOf(nodes[4])));
  627. }
  628. TEST_F(BookmarkModelObserverImplTest,
  629. ShouldCallOnBookmarkModelBeingDeletedClosure) {
  630. std::unique_ptr<SyncedBookmarkTracker> bookmark_tracker =
  631. SyncedBookmarkTracker::CreateEmpty(sync_pb::ModelTypeState());
  632. NiceMock<base::MockCallback<base::OnceClosure>>
  633. on_bookmark_model_being_deleted_closure_mock;
  634. BookmarkModelObserverImpl observer(
  635. /*nudge_for_commit_closure=*/base::DoNothing(),
  636. on_bookmark_model_being_deleted_closure_mock.Get(),
  637. bookmark_tracker.get());
  638. EXPECT_CALL(on_bookmark_model_being_deleted_closure_mock, Run());
  639. observer.BookmarkModelBeingDeleted(/*model=*/nullptr);
  640. }
  641. TEST_F(BookmarkModelObserverImplTest, ShouldNotIssueCommitUponFaviconLoad) {
  642. const GURL kBookmarkUrl("http://www.url.com");
  643. const GURL kIconUrl("http://www.url.com/favicon.ico");
  644. const SkColor kColor = SK_ColorRED;
  645. const bookmarks::BookmarkNode* bookmark_bar_node =
  646. bookmark_model()->bookmark_bar_node();
  647. const bookmarks::BookmarkNode* bookmark_node = bookmark_model()->AddURL(
  648. /*parent=*/bookmark_bar_node, /*index=*/0, u"title", kBookmarkUrl);
  649. ASSERT_TRUE(bookmark_client()->SimulateFaviconLoaded(
  650. kBookmarkUrl, kIconUrl, CreateTestImage(kColor)));
  651. SimulateCommitResponseForAllLocalChanges();
  652. ASSERT_THAT(bookmark_tracker()->GetEntitiesWithLocalChanges(), IsEmpty());
  653. const SyncedBookmarkTrackerEntity* entity =
  654. bookmark_tracker()->GetEntityForBookmarkNode(bookmark_node);
  655. ASSERT_THAT(entity, NotNull());
  656. ASSERT_TRUE(entity->metadata().has_bookmark_favicon_hash());
  657. const uint32_t initial_favicon_hash =
  658. entity->metadata().bookmark_favicon_hash();
  659. // Clear the specifics hash (as if the proto definition would have changed).
  660. // This is needed because otherwise the commit is trivially optimized away
  661. // (i.e. literally nothing changed).
  662. bookmark_tracker()->ClearSpecificsHashForTest(entity);
  663. // Mimic the very same favicon being loaded again (similar to a startup
  664. // scenario). Note that OnFaviconsChanged() needs no icon URL to invalidate
  665. // the favicon of a bookmark.
  666. EXPECT_CALL(*nudge_for_commit_closure(), Run()).Times(0);
  667. bookmark_model()->OnFaviconsChanged(/*page_urls=*/{kBookmarkUrl},
  668. /*icon_url=*/GURL());
  669. ASSERT_TRUE(bookmark_node->is_favicon_loading());
  670. ASSERT_TRUE(bookmark_client()->SimulateFaviconLoaded(
  671. kBookmarkUrl, kIconUrl, CreateTestImage(kColor)));
  672. EXPECT_TRUE(entity->metadata().has_bookmark_favicon_hash());
  673. EXPECT_THAT(entity->metadata().bookmark_favicon_hash(),
  674. Eq(initial_favicon_hash));
  675. EXPECT_THAT(bookmark_tracker()->GetEntitiesWithLocalChanges(), IsEmpty());
  676. }
  677. TEST_F(BookmarkModelObserverImplTest, ShouldCommitLocalFaviconChange) {
  678. const GURL kBookmarkUrl("http://www.url.com");
  679. const GURL kInitialIconUrl("http://www.url.com/initial.ico");
  680. const GURL kFinalIconUrl("http://www.url.com/final.ico");
  681. const bookmarks::BookmarkNode* bookmark_bar_node =
  682. bookmark_model()->bookmark_bar_node();
  683. const bookmarks::BookmarkNode* bookmark_node = bookmark_model()->AddURL(
  684. /*parent=*/bookmark_bar_node, /*index=*/0, u"title", kBookmarkUrl);
  685. ASSERT_TRUE(bookmark_node->is_favicon_loading());
  686. ASSERT_TRUE(bookmark_client()->SimulateFaviconLoaded(
  687. kBookmarkUrl, kInitialIconUrl, CreateTestImage(SK_ColorRED)));
  688. SimulateCommitResponseForAllLocalChanges();
  689. ASSERT_THAT(bookmark_tracker()->GetEntitiesWithLocalChanges(), IsEmpty());
  690. const SyncedBookmarkTrackerEntity* entity =
  691. bookmark_tracker()->GetEntityForBookmarkNode(bookmark_node);
  692. ASSERT_THAT(entity, NotNull());
  693. ASSERT_TRUE(entity->metadata().has_bookmark_favicon_hash());
  694. const uint32_t initial_favicon_hash =
  695. entity->metadata().bookmark_favicon_hash();
  696. // A favicon change should trigger a commit nudge once the favicon loads, but
  697. // not earlier. Note that OnFaviconsChanged() needs no icon URL to invalidate
  698. // the favicon of a bookmark.
  699. EXPECT_CALL(*nudge_for_commit_closure(), Run()).Times(0);
  700. bookmark_model()->OnFaviconsChanged(/*page_urls=*/{kBookmarkUrl},
  701. /*icon_url=*/GURL());
  702. ASSERT_TRUE(bookmark_node->is_favicon_loading());
  703. EXPECT_CALL(*nudge_for_commit_closure(), Run());
  704. ASSERT_TRUE(bookmark_client()->SimulateFaviconLoaded(
  705. kBookmarkUrl, kFinalIconUrl, CreateTestImage(SK_ColorBLUE)));
  706. EXPECT_TRUE(entity->metadata().has_bookmark_favicon_hash());
  707. EXPECT_THAT(entity->metadata().bookmark_favicon_hash(),
  708. Ne(initial_favicon_hash));
  709. EXPECT_THAT(bookmark_tracker()->GetEntitiesWithLocalChanges(),
  710. ElementsAre(HasBookmarkNode(bookmark_node)));
  711. }
  712. TEST_F(BookmarkModelObserverImplTest,
  713. ShouldNudgeForCommitOnFaviconLoadAfterRestart) {
  714. const GURL kBookmarkUrl("http://www.url.com");
  715. const GURL kIconUrl("http://www.url.com/favicon.ico");
  716. const SkColor kColor = SK_ColorRED;
  717. // Simulate work after restart. Add a new bookmark to a model and its
  718. // specifics to the tracker without loading favicon.
  719. bookmark_model()->RemoveObserver(observer());
  720. // Add a new node with specifics and mark it unsynced.
  721. const bookmarks::BookmarkNode* bookmark_bar_node =
  722. bookmark_model()->bookmark_bar_node();
  723. const bookmarks::BookmarkNode* bookmark_node = bookmark_model()->AddURL(
  724. /*parent=*/bookmark_bar_node, /*index=*/0, u"title", kBookmarkUrl);
  725. sync_pb::EntitySpecifics specifics = CreateSpecificsFromBookmarkNode(
  726. bookmark_node, bookmark_model(),
  727. syncer::UniquePosition::InitialPosition(
  728. syncer::UniquePosition::RandomSuffix())
  729. .ToProto(),
  730. /*force_favicon_load=*/false);
  731. const gfx::Image favicon_image = CreateTestImage(kColor);
  732. scoped_refptr<base::RefCountedMemory> favicon_bytes =
  733. favicon_image.As1xPNGBytes();
  734. specifics.mutable_bookmark()->set_favicon(favicon_bytes->front(),
  735. favicon_bytes->size());
  736. specifics.mutable_bookmark()->set_icon_url(kIconUrl.spec());
  737. *specifics.mutable_bookmark()->mutable_unique_position() =
  738. syncer::UniquePosition::InitialPosition(
  739. syncer::UniquePosition::RandomSuffix())
  740. .ToProto();
  741. const SyncedBookmarkTrackerEntity* entity = bookmark_tracker()->Add(
  742. bookmark_node, "id", /*server_version=*/1, base::Time::Now(), specifics);
  743. bookmark_tracker()->IncrementSequenceNumber(entity);
  744. // Restore state.
  745. bookmark_model()->AddObserver(observer());
  746. // Currently there is the unsynced |entity| which has no loaded favicon.
  747. ASSERT_FALSE(bookmark_node->is_favicon_loaded());
  748. ASSERT_TRUE(entity->IsUnsynced());
  749. EXPECT_CALL(*nudge_for_commit_closure(), Run());
  750. bookmark_model()->GetFavicon(bookmark_node);
  751. ASSERT_TRUE(bookmark_client()->SimulateFaviconLoaded(
  752. kBookmarkUrl, kIconUrl, CreateTestImage(SK_ColorRED)));
  753. }
  754. TEST_F(BookmarkModelObserverImplTest,
  755. ShouldAddRestoredBookmarkWhenTombstoneCommitMayHaveStarted) {
  756. const bookmarks::BookmarkNode* bookmark_bar_node =
  757. bookmark_model()->bookmark_bar_node();
  758. const bookmarks::BookmarkNode* folder =
  759. bookmark_model()->AddFolder(bookmark_bar_node, 0, u"Title");
  760. const syncer::ClientTagHash folder_client_tag_hash =
  761. SyncedBookmarkTracker::GetClientTagHashFromGUID(folder->guid());
  762. // Check that the bookmark was added by observer.
  763. const SyncedBookmarkTrackerEntity* folder_entity =
  764. bookmark_tracker()->GetEntityForBookmarkNode(folder);
  765. ASSERT_THAT(folder_entity, NotNull());
  766. ASSERT_TRUE(folder_entity->IsUnsynced());
  767. SimulateCommitResponseForAllLocalChanges();
  768. ASSERT_FALSE(folder_entity->IsUnsynced());
  769. // Now delete the entity and restore it with the same bookmark node.
  770. BookmarkUndoService undo_service;
  771. undo_service.Start(bookmark_model());
  772. bookmark_model()->Remove(folder);
  773. // The removed bookmark must be saved in the undo service.
  774. ASSERT_EQ(undo_service.undo_manager()->undo_count(), 1u);
  775. ASSERT_THAT(bookmark_tracker()->GetEntityForBookmarkNode(folder), IsNull());
  776. // Check that the entity is a tombstone now.
  777. const std::vector<const SyncedBookmarkTrackerEntity*> local_changes =
  778. bookmark_tracker()->GetEntitiesWithLocalChanges();
  779. ASSERT_THAT(local_changes, ElementsAre(folder_entity));
  780. ASSERT_TRUE(folder_entity->metadata().is_deleted());
  781. ASSERT_EQ(
  782. bookmark_tracker()->GetEntityForClientTagHash(folder_client_tag_hash),
  783. folder_entity);
  784. // Restore the removed bookmark.
  785. undo_service.undo_manager()->Undo();
  786. undo_service.Shutdown();
  787. EXPECT_EQ(folder_entity,
  788. bookmark_tracker()->GetEntityForBookmarkNode(folder));
  789. EXPECT_EQ(
  790. bookmark_tracker()->GetEntityForClientTagHash(folder_client_tag_hash),
  791. folder_entity);
  792. EXPECT_TRUE(folder_entity->IsUnsynced());
  793. EXPECT_FALSE(folder_entity->metadata().is_deleted());
  794. EXPECT_EQ(folder_entity->bookmark_node(), folder);
  795. }
  796. // Tests that the bookmark entity will be committed if its favicon is deleted.
  797. TEST_F(BookmarkModelObserverImplTest, ShouldCommitOnDeleteFavicon) {
  798. const GURL kBookmarkUrl("http://www.url.com");
  799. const GURL kIconUrl("http://www.url.com/favicon.ico");
  800. // Add a new node with specifics.
  801. const bookmarks::BookmarkNode* bookmark_bar_node =
  802. bookmark_model()->bookmark_bar_node();
  803. const bookmarks::BookmarkNode* bookmark_node = bookmark_model()->AddURL(
  804. /*parent=*/bookmark_bar_node, /*index=*/0, u"title", kBookmarkUrl);
  805. ASSERT_TRUE(bookmark_node->is_favicon_loading());
  806. ASSERT_TRUE(bookmark_client()->SimulateFaviconLoaded(
  807. kBookmarkUrl, kIconUrl, CreateTestImage(SK_ColorRED)));
  808. const SyncedBookmarkTrackerEntity* entity =
  809. bookmark_tracker()->GetEntityForBookmarkNode(bookmark_node);
  810. ASSERT_THAT(entity, NotNull());
  811. ASSERT_TRUE(entity->IsUnsynced());
  812. SimulateCommitResponseForAllLocalChanges();
  813. ASSERT_FALSE(bookmark_tracker()->HasLocalChanges());
  814. // Delete favicon and check that its deletion is committed.
  815. bookmark_model()->OnFaviconsChanged({kBookmarkUrl}, GURL());
  816. ASSERT_TRUE(bookmark_node->is_favicon_loading());
  817. ASSERT_TRUE(bookmark_client()->SimulateEmptyFaviconLoaded(kBookmarkUrl));
  818. EXPECT_TRUE(entity->IsUnsynced());
  819. }
  820. } // namespace
  821. } // namespace sync_bookmarks