1// Copyright 2014 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 "components/bookmarks/browser/bookmark_model.h"
6
7#include <set>
8#include <string>
9
10#include "base/base_paths.h"
11#include "base/basictypes.h"
12#include "base/command_line.h"
13#include "base/compiler_specific.h"
14#include "base/containers/hash_tables.h"
15#include "base/path_service.h"
16#include "base/strings/string16.h"
17#include "base/strings/string_number_conversions.h"
18#include "base/strings/string_split.h"
19#include "base/strings/string_util.h"
20#include "base/strings/utf_string_conversions.h"
21#include "base/time/time.h"
22#include "components/bookmarks/browser/bookmark_model_observer.h"
23#include "components/bookmarks/browser/bookmark_utils.h"
24#include "components/bookmarks/test/bookmark_test_helpers.h"
25#include "components/bookmarks/test/test_bookmark_client.h"
26#include "testing/gtest/include/gtest/gtest.h"
27#include "ui/base/models/tree_node_iterator.h"
28#include "ui/base/models/tree_node_model.h"
29#include "url/gurl.h"
30
31using base::ASCIIToUTF16;
32using base::Time;
33using base::TimeDelta;
34
35namespace bookmarks {
36namespace {
37
38// Test cases used to test the removal of extra whitespace when adding
39// a new folder/bookmark or updating a title of a folder/bookmark.
40static struct {
41  const std::string input_title;
42  const std::string expected_title;
43} url_whitespace_test_cases[] = {
44  {"foobar", "foobar"},
45  // Newlines.
46  {"foo\nbar", "foo bar"},
47  {"foo\n\nbar", "foo bar"},
48  {"foo\n\n\nbar", "foo bar"},
49  {"foo\r\nbar", "foo bar"},
50  {"foo\r\n\r\nbar", "foo bar"},
51  {"\nfoo\nbar\n", "foo bar"},
52  // Spaces.
53  {"foo  bar", "foo bar"},
54  {" foo bar ", "foo bar"},
55  {"  foo  bar  ", "foo bar"},
56  // Tabs.
57  {"\tfoo\tbar\t", "foo bar"},
58  {"\tfoo bar\t", "foo bar"},
59  // Mixed cases.
60  {"\tfoo\nbar\t", "foo bar"},
61  {"\tfoo\r\nbar\t", "foo bar"},
62  {"  foo\tbar\n", "foo bar"},
63  {"\t foo \t  bar  \t", "foo bar"},
64  {"\n foo\r\n\tbar\n \t", "foo bar"},
65};
66
67// Test cases used to test the removal of extra whitespace when adding
68// a new folder/bookmark or updating a title of a folder/bookmark.
69static struct {
70  const std::string input_title;
71  const std::string expected_title;
72} title_whitespace_test_cases[] = {
73  {"foobar", "foobar"},
74  // Newlines.
75  {"foo\nbar", "foo bar"},
76  {"foo\n\nbar", "foo  bar"},
77  {"foo\n\n\nbar", "foo   bar"},
78  {"foo\r\nbar", "foo  bar"},
79  {"foo\r\n\r\nbar", "foo    bar"},
80  {"\nfoo\nbar\n", " foo bar "},
81  // Spaces.
82  {"foo  bar", "foo  bar"},
83  {" foo bar ", " foo bar "},
84  {"  foo  bar  ", "  foo  bar  "},
85  // Tabs.
86  {"\tfoo\tbar\t", " foo bar "},
87  {"\tfoo bar\t", " foo bar "},
88  // Mixed cases.
89  {"\tfoo\nbar\t", " foo bar "},
90  {"\tfoo\r\nbar\t", " foo  bar "},
91  {"  foo\tbar\n", "  foo bar "},
92  {"\t foo \t  bar  \t", "  foo    bar   "},
93  {"\n foo\r\n\tbar\n \t", "  foo   bar   "},
94};
95
96// Helper to get a mutable bookmark node.
97BookmarkNode* AsMutable(const BookmarkNode* node) {
98  return const_cast<BookmarkNode*>(node);
99}
100
101void SwapDateAdded(BookmarkNode* n1, BookmarkNode* n2) {
102  Time tmp = n1->date_added();
103  n1->set_date_added(n2->date_added());
104  n2->set_date_added(tmp);
105}
106
107class BookmarkModelTest : public testing::Test,
108                          public BookmarkModelObserver {
109 public:
110  struct ObserverDetails {
111    ObserverDetails() {
112      Set(NULL, NULL, -1, -1);
113    }
114
115    void Set(const BookmarkNode* node1,
116             const BookmarkNode* node2,
117             int index1,
118             int index2) {
119      node1_ = node1;
120      node2_ = node2;
121      index1_ = index1;
122      index2_ = index2;
123    }
124
125    void ExpectEquals(const BookmarkNode* node1,
126                      const BookmarkNode* node2,
127                      int index1,
128                      int index2) {
129      EXPECT_EQ(node1_, node1);
130      EXPECT_EQ(node2_, node2);
131      EXPECT_EQ(index1_, index1);
132      EXPECT_EQ(index2_, index2);
133    }
134
135   private:
136    const BookmarkNode* node1_;
137    const BookmarkNode* node2_;
138    int index1_;
139    int index2_;
140  };
141
142  BookmarkModelTest() : model_(client_.CreateModel()) {
143    model_->AddObserver(this);
144    ClearCounts();
145  }
146
147  virtual void BookmarkModelLoaded(BookmarkModel* model,
148                                   bool ids_reassigned) OVERRIDE {
149    // We never load from the db, so that this should never get invoked.
150    NOTREACHED();
151  }
152
153  virtual void BookmarkNodeMoved(BookmarkModel* model,
154                                 const BookmarkNode* old_parent,
155                                 int old_index,
156                                 const BookmarkNode* new_parent,
157                                 int new_index) OVERRIDE {
158    ++moved_count_;
159    observer_details_.Set(old_parent, new_parent, old_index, new_index);
160  }
161
162  virtual void BookmarkNodeAdded(BookmarkModel* model,
163                                 const BookmarkNode* parent,
164                                 int index) OVERRIDE {
165    ++added_count_;
166    observer_details_.Set(parent, NULL, index, -1);
167  }
168
169  virtual void OnWillRemoveBookmarks(BookmarkModel* model,
170                                     const BookmarkNode* parent,
171                                     int old_index,
172                                     const BookmarkNode* node) OVERRIDE {
173    ++before_remove_count_;
174  }
175
176  virtual void BookmarkNodeRemoved(
177      BookmarkModel* model,
178      const BookmarkNode* parent,
179      int old_index,
180      const BookmarkNode* node,
181      const std::set<GURL>& removed_urls) OVERRIDE {
182    ++removed_count_;
183    observer_details_.Set(parent, NULL, old_index, -1);
184  }
185
186  virtual void BookmarkNodeChanged(BookmarkModel* model,
187                                   const BookmarkNode* node) OVERRIDE {
188    ++changed_count_;
189    observer_details_.Set(node, NULL, -1, -1);
190  }
191
192  virtual void OnWillChangeBookmarkNode(BookmarkModel* model,
193                                        const BookmarkNode* node) OVERRIDE {
194    ++before_change_count_;
195  }
196
197  virtual void BookmarkNodeChildrenReordered(
198      BookmarkModel* model,
199      const BookmarkNode* node) OVERRIDE {
200    ++reordered_count_;
201  }
202
203  virtual void OnWillReorderBookmarkNode(BookmarkModel* model,
204                                         const BookmarkNode* node) OVERRIDE {
205    ++before_reorder_count_;
206  }
207
208  virtual void BookmarkNodeFaviconChanged(BookmarkModel* model,
209                                          const BookmarkNode* node) OVERRIDE {
210    // We never attempt to load favicons, so that this method never
211    // gets invoked.
212  }
213
214  virtual void ExtensiveBookmarkChangesBeginning(
215      BookmarkModel* model) OVERRIDE {
216    ++extensive_changes_beginning_count_;
217  }
218
219  virtual void ExtensiveBookmarkChangesEnded(BookmarkModel* model) OVERRIDE {
220    ++extensive_changes_ended_count_;
221  }
222
223  virtual void BookmarkAllUserNodesRemoved(
224      BookmarkModel* model,
225      const std::set<GURL>& removed_urls) OVERRIDE {
226    ++all_bookmarks_removed_;
227  }
228
229  virtual void OnWillRemoveAllUserBookmarks(BookmarkModel* model) OVERRIDE {
230    ++before_remove_all_count_;
231  }
232
233  void ClearCounts() {
234    added_count_ = moved_count_ = removed_count_ = changed_count_ =
235        reordered_count_ = extensive_changes_beginning_count_ =
236        extensive_changes_ended_count_ = all_bookmarks_removed_ =
237        before_remove_count_ = before_change_count_ = before_reorder_count_ =
238        before_remove_all_count_ = 0;
239  }
240
241  void AssertObserverCount(int added_count,
242                           int moved_count,
243                           int removed_count,
244                           int changed_count,
245                           int reordered_count,
246                           int before_remove_count,
247                           int before_change_count,
248                           int before_reorder_count,
249                           int before_remove_all_count) {
250    EXPECT_EQ(added_count_, added_count);
251    EXPECT_EQ(moved_count_, moved_count);
252    EXPECT_EQ(removed_count_, removed_count);
253    EXPECT_EQ(changed_count_, changed_count);
254    EXPECT_EQ(reordered_count_, reordered_count);
255    EXPECT_EQ(before_remove_count_, before_remove_count);
256    EXPECT_EQ(before_change_count_, before_change_count);
257    EXPECT_EQ(before_reorder_count_, before_reorder_count);
258    EXPECT_EQ(before_remove_all_count_, before_remove_all_count);
259  }
260
261  void AssertExtensiveChangesObserverCount(
262      int extensive_changes_beginning_count,
263      int extensive_changes_ended_count) {
264    EXPECT_EQ(extensive_changes_beginning_count_,
265              extensive_changes_beginning_count);
266    EXPECT_EQ(extensive_changes_ended_count_, extensive_changes_ended_count);
267  }
268
269  int AllNodesRemovedObserverCount() const { return all_bookmarks_removed_; }
270
271  BookmarkPermanentNode* ReloadModelWithExtraNode() {
272    BookmarkPermanentNode* extra_node = new BookmarkPermanentNode(100);
273    bookmarks::BookmarkPermanentNodeList extra_nodes;
274    extra_nodes.push_back(extra_node);
275    client_.SetExtraNodesToLoad(extra_nodes.Pass());
276
277    model_->RemoveObserver(this);
278    model_ = client_.CreateModel();
279    model_->AddObserver(this);
280    ClearCounts();
281
282    if (model_->root_node()->GetIndexOf(extra_node) == -1)
283      ADD_FAILURE();
284
285    return extra_node;
286  }
287
288 protected:
289  TestBookmarkClient client_;
290  scoped_ptr<BookmarkModel> model_;
291  ObserverDetails observer_details_;
292
293 private:
294  int added_count_;
295  int moved_count_;
296  int removed_count_;
297  int changed_count_;
298  int reordered_count_;
299  int extensive_changes_beginning_count_;
300  int extensive_changes_ended_count_;
301  int all_bookmarks_removed_;
302  int before_remove_count_;
303  int before_change_count_;
304  int before_reorder_count_;
305  int before_remove_all_count_;
306
307  DISALLOW_COPY_AND_ASSIGN(BookmarkModelTest);
308};
309
310TEST_F(BookmarkModelTest, InitialState) {
311  const BookmarkNode* bb_node = model_->bookmark_bar_node();
312  ASSERT_TRUE(bb_node != NULL);
313  EXPECT_EQ(0, bb_node->child_count());
314  EXPECT_EQ(BookmarkNode::BOOKMARK_BAR, bb_node->type());
315
316  const BookmarkNode* other_node = model_->other_node();
317  ASSERT_TRUE(other_node != NULL);
318  EXPECT_EQ(0, other_node->child_count());
319  EXPECT_EQ(BookmarkNode::OTHER_NODE, other_node->type());
320
321  const BookmarkNode* mobile_node = model_->mobile_node();
322  ASSERT_TRUE(mobile_node != NULL);
323  EXPECT_EQ(0, mobile_node->child_count());
324  EXPECT_EQ(BookmarkNode::MOBILE, mobile_node->type());
325
326  EXPECT_TRUE(bb_node->id() != other_node->id());
327  EXPECT_TRUE(bb_node->id() != mobile_node->id());
328  EXPECT_TRUE(other_node->id() != mobile_node->id());
329}
330
331TEST_F(BookmarkModelTest, AddURL) {
332  const BookmarkNode* root = model_->bookmark_bar_node();
333  const base::string16 title(ASCIIToUTF16("foo"));
334  const GURL url("http://foo.com");
335
336  const BookmarkNode* new_node = model_->AddURL(root, 0, title, url);
337  AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0);
338  observer_details_.ExpectEquals(root, NULL, 0, -1);
339
340  ASSERT_EQ(1, root->child_count());
341  ASSERT_EQ(title, new_node->GetTitle());
342  ASSERT_TRUE(url == new_node->url());
343  ASSERT_EQ(BookmarkNode::URL, new_node->type());
344  ASSERT_TRUE(new_node == model_->GetMostRecentlyAddedUserNodeForURL(url));
345
346  EXPECT_TRUE(new_node->id() != root->id() &&
347              new_node->id() != model_->other_node()->id() &&
348              new_node->id() != model_->mobile_node()->id());
349}
350
351TEST_F(BookmarkModelTest, AddURLWithUnicodeTitle) {
352  const BookmarkNode* root = model_->bookmark_bar_node();
353  const base::string16 title(base::WideToUTF16(
354      L"\u767e\u5ea6\u4e00\u4e0b\uff0c\u4f60\u5c31\u77e5\u9053"));
355  const GURL url("https://www.baidu.com/");
356
357  const BookmarkNode* new_node = model_->AddURL(root, 0, title, url);
358  AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0);
359  observer_details_.ExpectEquals(root, NULL, 0, -1);
360
361  ASSERT_EQ(1, root->child_count());
362  ASSERT_EQ(title, new_node->GetTitle());
363  ASSERT_TRUE(url == new_node->url());
364  ASSERT_EQ(BookmarkNode::URL, new_node->type());
365  ASSERT_TRUE(new_node == model_->GetMostRecentlyAddedUserNodeForURL(url));
366
367  EXPECT_TRUE(new_node->id() != root->id() &&
368              new_node->id() != model_->other_node()->id() &&
369              new_node->id() != model_->mobile_node()->id());
370}
371
372TEST_F(BookmarkModelTest, AddURLWithWhitespaceTitle) {
373  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(url_whitespace_test_cases); ++i) {
374    const BookmarkNode* root = model_->bookmark_bar_node();
375    const base::string16 title(
376        ASCIIToUTF16(url_whitespace_test_cases[i].input_title));
377    const GURL url("http://foo.com");
378
379    const BookmarkNode* new_node = model_->AddURL(root, i, title, url);
380
381    int size = i + 1;
382    EXPECT_EQ(size, root->child_count());
383    EXPECT_EQ(ASCIIToUTF16(url_whitespace_test_cases[i].expected_title),
384              new_node->GetTitle());
385    EXPECT_EQ(BookmarkNode::URL, new_node->type());
386  }
387}
388
389TEST_F(BookmarkModelTest, AddURLWithCreationTimeAndMetaInfo) {
390  const BookmarkNode* root = model_->bookmark_bar_node();
391  const base::string16 title(ASCIIToUTF16("foo"));
392  const GURL url("http://foo.com");
393  const Time time = Time::Now() - TimeDelta::FromDays(1);
394  BookmarkNode::MetaInfoMap meta_info;
395  meta_info["foo"] = "bar";
396
397  const BookmarkNode* new_node = model_->AddURLWithCreationTimeAndMetaInfo(
398      root, 0, title, url, time, &meta_info);
399  AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0);
400  observer_details_.ExpectEquals(root, NULL, 0, -1);
401
402  ASSERT_EQ(1, root->child_count());
403  ASSERT_EQ(title, new_node->GetTitle());
404  ASSERT_TRUE(url == new_node->url());
405  ASSERT_EQ(BookmarkNode::URL, new_node->type());
406  ASSERT_EQ(time, new_node->date_added());
407  ASSERT_TRUE(new_node->GetMetaInfoMap());
408  ASSERT_EQ(meta_info, *new_node->GetMetaInfoMap());
409  ASSERT_TRUE(new_node == model_->GetMostRecentlyAddedUserNodeForURL(url));
410
411  EXPECT_TRUE(new_node->id() != root->id() &&
412              new_node->id() != model_->other_node()->id() &&
413              new_node->id() != model_->mobile_node()->id());
414}
415
416TEST_F(BookmarkModelTest, AddURLToMobileBookmarks) {
417  const BookmarkNode* root = model_->mobile_node();
418  const base::string16 title(ASCIIToUTF16("foo"));
419  const GURL url("http://foo.com");
420
421  const BookmarkNode* new_node = model_->AddURL(root, 0, title, url);
422  AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0);
423  observer_details_.ExpectEquals(root, NULL, 0, -1);
424
425  ASSERT_EQ(1, root->child_count());
426  ASSERT_EQ(title, new_node->GetTitle());
427  ASSERT_TRUE(url == new_node->url());
428  ASSERT_EQ(BookmarkNode::URL, new_node->type());
429  ASSERT_TRUE(new_node == model_->GetMostRecentlyAddedUserNodeForURL(url));
430
431  EXPECT_TRUE(new_node->id() != root->id() &&
432              new_node->id() != model_->other_node()->id() &&
433              new_node->id() != model_->mobile_node()->id());
434}
435
436TEST_F(BookmarkModelTest, AddFolder) {
437  const BookmarkNode* root = model_->bookmark_bar_node();
438  const base::string16 title(ASCIIToUTF16("foo"));
439
440  const BookmarkNode* new_node = model_->AddFolder(root, 0, title);
441  AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0);
442  observer_details_.ExpectEquals(root, NULL, 0, -1);
443
444  ASSERT_EQ(1, root->child_count());
445  ASSERT_EQ(title, new_node->GetTitle());
446  ASSERT_EQ(BookmarkNode::FOLDER, new_node->type());
447
448  EXPECT_TRUE(new_node->id() != root->id() &&
449              new_node->id() != model_->other_node()->id() &&
450              new_node->id() != model_->mobile_node()->id());
451
452  // Add another folder, just to make sure folder_ids are incremented correctly.
453  ClearCounts();
454  model_->AddFolder(root, 0, title);
455  AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0);
456  observer_details_.ExpectEquals(root, NULL, 0, -1);
457}
458
459TEST_F(BookmarkModelTest, AddFolderWithWhitespaceTitle) {
460  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(title_whitespace_test_cases); ++i) {
461    const BookmarkNode* root = model_->bookmark_bar_node();
462    const base::string16 title(
463        ASCIIToUTF16(title_whitespace_test_cases[i].input_title));
464
465    const BookmarkNode* new_node = model_->AddFolder(root, i, title);
466
467    int size = i + 1;
468    EXPECT_EQ(size, root->child_count());
469    EXPECT_EQ(ASCIIToUTF16(title_whitespace_test_cases[i].expected_title),
470              new_node->GetTitle());
471    EXPECT_EQ(BookmarkNode::FOLDER, new_node->type());
472  }
473}
474
475TEST_F(BookmarkModelTest, RemoveURL) {
476  const BookmarkNode* root = model_->bookmark_bar_node();
477  const base::string16 title(ASCIIToUTF16("foo"));
478  const GURL url("http://foo.com");
479  model_->AddURL(root, 0, title, url);
480  ClearCounts();
481
482  model_->Remove(root, 0);
483  ASSERT_EQ(0, root->child_count());
484  AssertObserverCount(0, 0, 1, 0, 0, 1, 0, 0, 0);
485  observer_details_.ExpectEquals(root, NULL, 0, -1);
486
487  // Make sure there is no mapping for the URL.
488  ASSERT_TRUE(model_->GetMostRecentlyAddedUserNodeForURL(url) == NULL);
489}
490
491TEST_F(BookmarkModelTest, RemoveFolder) {
492  const BookmarkNode* root = model_->bookmark_bar_node();
493  const BookmarkNode* folder = model_->AddFolder(root, 0, ASCIIToUTF16("foo"));
494
495  ClearCounts();
496
497  // Add a URL as a child.
498  const base::string16 title(ASCIIToUTF16("foo"));
499  const GURL url("http://foo.com");
500  model_->AddURL(folder, 0, title, url);
501
502  ClearCounts();
503
504  // Now remove the folder.
505  model_->Remove(root, 0);
506  ASSERT_EQ(0, root->child_count());
507  AssertObserverCount(0, 0, 1, 0, 0, 1, 0, 0, 0);
508  observer_details_.ExpectEquals(root, NULL, 0, -1);
509
510  // Make sure there is no mapping for the URL.
511  ASSERT_TRUE(model_->GetMostRecentlyAddedUserNodeForURL(url) == NULL);
512}
513
514TEST_F(BookmarkModelTest, RemoveAllUserBookmarks) {
515  const BookmarkNode* bookmark_bar_node = model_->bookmark_bar_node();
516
517  ClearCounts();
518
519  // Add a url to bookmark bar.
520  base::string16 title(ASCIIToUTF16("foo"));
521  GURL url("http://foo.com");
522  model_->AddURL(bookmark_bar_node, 0, title, url);
523
524  // Add a folder with child URL.
525  const BookmarkNode* folder = model_->AddFolder(bookmark_bar_node, 0, title);
526  model_->AddURL(folder, 0, title, url);
527
528  AssertObserverCount(3, 0, 0, 0, 0, 0, 0, 0, 0);
529  ClearCounts();
530
531  model_->RemoveAllUserBookmarks();
532
533  EXPECT_EQ(0, bookmark_bar_node->child_count());
534  // No individual BookmarkNodeRemoved events are fired, so removed count
535  // should be 0.
536  AssertObserverCount(0, 0, 0, 0, 0, 0, 0, 0, 1);
537  AssertExtensiveChangesObserverCount(1, 1);
538  EXPECT_EQ(1, AllNodesRemovedObserverCount());
539}
540
541TEST_F(BookmarkModelTest, SetTitle) {
542  const BookmarkNode* root = model_->bookmark_bar_node();
543  base::string16 title(ASCIIToUTF16("foo"));
544  const GURL url("http://foo.com");
545  const BookmarkNode* node = model_->AddURL(root, 0, title, url);
546
547  ClearCounts();
548
549  title = ASCIIToUTF16("foo2");
550  model_->SetTitle(node, title);
551  AssertObserverCount(0, 0, 0, 1, 0, 0, 1, 0, 0);
552  observer_details_.ExpectEquals(node, NULL, -1, -1);
553  EXPECT_EQ(title, node->GetTitle());
554}
555
556TEST_F(BookmarkModelTest, SetTitleWithWhitespace) {
557  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(title_whitespace_test_cases); ++i) {
558    const BookmarkNode* root = model_->bookmark_bar_node();
559    base::string16 title(ASCIIToUTF16("dummy"));
560    const GURL url("http://foo.com");
561    const BookmarkNode* node = model_->AddURL(root, 0, title, url);
562
563    title = ASCIIToUTF16(title_whitespace_test_cases[i].input_title);
564    model_->SetTitle(node, title);
565    EXPECT_EQ(ASCIIToUTF16(title_whitespace_test_cases[i].expected_title),
566              node->GetTitle());
567  }
568}
569
570TEST_F(BookmarkModelTest, SetURL) {
571  const BookmarkNode* root = model_->bookmark_bar_node();
572  const base::string16 title(ASCIIToUTF16("foo"));
573  GURL url("http://foo.com");
574  const BookmarkNode* node = model_->AddURL(root, 0, title, url);
575
576  ClearCounts();
577
578  url = GURL("http://foo2.com");
579  model_->SetURL(node, url);
580  AssertObserverCount(0, 0, 0, 1, 0, 0, 1, 0, 0);
581  observer_details_.ExpectEquals(node, NULL, -1, -1);
582  EXPECT_EQ(url, node->url());
583}
584
585TEST_F(BookmarkModelTest, SetDateAdded) {
586  const BookmarkNode* root = model_->bookmark_bar_node();
587  const base::string16 title(ASCIIToUTF16("foo"));
588  GURL url("http://foo.com");
589  const BookmarkNode* node = model_->AddURL(root, 0, title, url);
590
591  ClearCounts();
592
593  base::Time new_time = base::Time::Now() + base::TimeDelta::FromMinutes(20);
594  model_->SetDateAdded(node, new_time);
595  AssertObserverCount(0, 0, 0, 0, 0, 0, 0, 0, 0);
596  EXPECT_EQ(new_time, node->date_added());
597  EXPECT_EQ(new_time, model_->bookmark_bar_node()->date_folder_modified());
598}
599
600TEST_F(BookmarkModelTest, Move) {
601  const BookmarkNode* root = model_->bookmark_bar_node();
602  const base::string16 title(ASCIIToUTF16("foo"));
603  const GURL url("http://foo.com");
604  const BookmarkNode* node = model_->AddURL(root, 0, title, url);
605  const BookmarkNode* folder1 = model_->AddFolder(root, 0, ASCIIToUTF16("foo"));
606  ClearCounts();
607
608  model_->Move(node, folder1, 0);
609
610  AssertObserverCount(0, 1, 0, 0, 0, 0, 0, 0, 0);
611  observer_details_.ExpectEquals(root, folder1, 1, 0);
612  EXPECT_TRUE(folder1 == node->parent());
613  EXPECT_EQ(1, root->child_count());
614  EXPECT_EQ(folder1, root->GetChild(0));
615  EXPECT_EQ(1, folder1->child_count());
616  EXPECT_EQ(node, folder1->GetChild(0));
617
618  // And remove the folder.
619  ClearCounts();
620  model_->Remove(root, 0);
621  AssertObserverCount(0, 0, 1, 0, 0, 1, 0, 0, 0);
622  observer_details_.ExpectEquals(root, NULL, 0, -1);
623  EXPECT_TRUE(model_->GetMostRecentlyAddedUserNodeForURL(url) == NULL);
624  EXPECT_EQ(0, root->child_count());
625}
626
627TEST_F(BookmarkModelTest, NonMovingMoveCall) {
628  const BookmarkNode* root = model_->bookmark_bar_node();
629  const base::string16 title(ASCIIToUTF16("foo"));
630  const GURL url("http://foo.com");
631  const base::Time old_date(base::Time::Now() - base::TimeDelta::FromDays(1));
632
633  const BookmarkNode* node = model_->AddURL(root, 0, title, url);
634  model_->SetDateFolderModified(root, old_date);
635
636  // Since |node| is already at the index 0 of |root|, this is no-op.
637  model_->Move(node, root, 0);
638
639  // Check that the modification date is kept untouched.
640  EXPECT_EQ(old_date, root->date_folder_modified());
641}
642
643TEST_F(BookmarkModelTest, Copy) {
644  const BookmarkNode* root = model_->bookmark_bar_node();
645  static const std::string model_string("a 1:[ b c ] d 2:[ e f g ] h ");
646  test::AddNodesFromModelString(model_.get(), root, model_string);
647
648  // Validate initial model.
649  std::string actual_model_string = test::ModelStringFromNode(root);
650  EXPECT_EQ(model_string, actual_model_string);
651
652  // Copy 'd' to be after '1:b': URL item from bar to folder.
653  const BookmarkNode* node_to_copy = root->GetChild(2);
654  const BookmarkNode* destination = root->GetChild(1);
655  model_->Copy(node_to_copy, destination, 1);
656  actual_model_string = test::ModelStringFromNode(root);
657  EXPECT_EQ("a 1:[ b d c ] d 2:[ e f g ] h ", actual_model_string);
658
659  // Copy '1:d' to be after 'a': URL item from folder to bar.
660  const BookmarkNode* folder = root->GetChild(1);
661  node_to_copy = folder->GetChild(1);
662  model_->Copy(node_to_copy, root, 1);
663  actual_model_string = test::ModelStringFromNode(root);
664  EXPECT_EQ("a d 1:[ b d c ] d 2:[ e f g ] h ", actual_model_string);
665
666  // Copy '1' to be after '2:e': Folder from bar to folder.
667  node_to_copy = root->GetChild(2);
668  destination = root->GetChild(4);
669  model_->Copy(node_to_copy, destination, 1);
670  actual_model_string = test::ModelStringFromNode(root);
671  EXPECT_EQ("a d 1:[ b d c ] d 2:[ e 1:[ b d c ] f g ] h ",
672            actual_model_string);
673
674  // Copy '2:1' to be after '2:f': Folder within same folder.
675  folder = root->GetChild(4);
676  node_to_copy = folder->GetChild(1);
677  model_->Copy(node_to_copy, folder, 3);
678  actual_model_string = test::ModelStringFromNode(root);
679  EXPECT_EQ("a d 1:[ b d c ] d 2:[ e 1:[ b d c ] f 1:[ b d c ] g ] h ",
680            actual_model_string);
681
682  // Copy first 'd' to be after 'h': URL item within the bar.
683  node_to_copy = root->GetChild(1);
684  model_->Copy(node_to_copy, root, 6);
685  actual_model_string = test::ModelStringFromNode(root);
686  EXPECT_EQ("a d 1:[ b d c ] d 2:[ e 1:[ b d c ] f 1:[ b d c ] g ] h d ",
687            actual_model_string);
688
689  // Copy '2' to be after 'a': Folder within the bar.
690  node_to_copy = root->GetChild(4);
691  model_->Copy(node_to_copy, root, 1);
692  actual_model_string = test::ModelStringFromNode(root);
693  EXPECT_EQ("a 2:[ e 1:[ b d c ] f 1:[ b d c ] g ] d 1:[ b d c ] "
694            "d 2:[ e 1:[ b d c ] f 1:[ b d c ] g ] h d ",
695            actual_model_string);
696}
697
698// Tests that adding a URL to a folder updates the last modified time.
699TEST_F(BookmarkModelTest, ParentForNewNodes) {
700  ASSERT_EQ(model_->bookmark_bar_node(), model_->GetParentForNewNodes());
701
702  const base::string16 title(ASCIIToUTF16("foo"));
703  const GURL url("http://foo.com");
704
705  model_->AddURL(model_->other_node(), 0, title, url);
706  ASSERT_EQ(model_->other_node(), model_->GetParentForNewNodes());
707}
708
709// Tests that adding a URL to a folder updates the last modified time.
710TEST_F(BookmarkModelTest, ParentForNewMobileNodes) {
711  ASSERT_EQ(model_->bookmark_bar_node(), model_->GetParentForNewNodes());
712
713  const base::string16 title(ASCIIToUTF16("foo"));
714  const GURL url("http://foo.com");
715
716  model_->AddURL(model_->mobile_node(), 0, title, url);
717  ASSERT_EQ(model_->mobile_node(), model_->GetParentForNewNodes());
718}
719
720// Make sure recently modified stays in sync when adding a URL.
721TEST_F(BookmarkModelTest, MostRecentlyModifiedFolders) {
722  // Add a folder.
723  const BookmarkNode* folder =
724      model_->AddFolder(model_->other_node(), 0, ASCIIToUTF16("foo"));
725  // Add a URL to it.
726  model_->AddURL(folder, 0, ASCIIToUTF16("blah"), GURL("http://foo.com"));
727
728  // Make sure folder is in the most recently modified.
729  std::vector<const BookmarkNode*> most_recent_folders =
730      bookmarks::GetMostRecentlyModifiedUserFolders(model_.get(), 1);
731  ASSERT_EQ(1U, most_recent_folders.size());
732  ASSERT_EQ(folder, most_recent_folders[0]);
733
734  // Nuke the folder and do another fetch, making sure folder isn't in the
735  // returned list.
736  model_->Remove(folder->parent(), 0);
737  most_recent_folders =
738      bookmarks::GetMostRecentlyModifiedUserFolders(model_.get(), 1);
739  ASSERT_EQ(1U, most_recent_folders.size());
740  ASSERT_TRUE(most_recent_folders[0] != folder);
741}
742
743// Make sure MostRecentlyAddedEntries stays in sync.
744TEST_F(BookmarkModelTest, MostRecentlyAddedEntries) {
745  // Add a couple of nodes such that the following holds for the time of the
746  // nodes: n1 > n2 > n3 > n4.
747  Time base_time = Time::Now();
748  BookmarkNode* n1 = AsMutable(model_->AddURL(model_->bookmark_bar_node(),
749                                              0,
750                                              ASCIIToUTF16("blah"),
751                                              GURL("http://foo.com/0")));
752  BookmarkNode* n2 = AsMutable(model_->AddURL(model_->bookmark_bar_node(),
753                                              1,
754                                              ASCIIToUTF16("blah"),
755                                              GURL("http://foo.com/1")));
756  BookmarkNode* n3 = AsMutable(model_->AddURL(model_->bookmark_bar_node(),
757                                              2,
758                                              ASCIIToUTF16("blah"),
759                                              GURL("http://foo.com/2")));
760  BookmarkNode* n4 = AsMutable(model_->AddURL(model_->bookmark_bar_node(),
761                                              3,
762                                              ASCIIToUTF16("blah"),
763                                              GURL("http://foo.com/3")));
764  n1->set_date_added(base_time + TimeDelta::FromDays(4));
765  n2->set_date_added(base_time + TimeDelta::FromDays(3));
766  n3->set_date_added(base_time + TimeDelta::FromDays(2));
767  n4->set_date_added(base_time + TimeDelta::FromDays(1));
768
769  // Make sure order is honored.
770  std::vector<const BookmarkNode*> recently_added;
771  bookmarks::GetMostRecentlyAddedEntries(model_.get(), 2, &recently_added);
772  ASSERT_EQ(2U, recently_added.size());
773  ASSERT_TRUE(n1 == recently_added[0]);
774  ASSERT_TRUE(n2 == recently_added[1]);
775
776  // swap 1 and 2, then check again.
777  recently_added.clear();
778  SwapDateAdded(n1, n2);
779  bookmarks::GetMostRecentlyAddedEntries(model_.get(), 4, &recently_added);
780  ASSERT_EQ(4U, recently_added.size());
781  ASSERT_TRUE(n2 == recently_added[0]);
782  ASSERT_TRUE(n1 == recently_added[1]);
783  ASSERT_TRUE(n3 == recently_added[2]);
784  ASSERT_TRUE(n4 == recently_added[3]);
785}
786
787// Makes sure GetMostRecentlyAddedUserNodeForURL stays in sync.
788TEST_F(BookmarkModelTest, GetMostRecentlyAddedUserNodeForURL) {
789  // Add a couple of nodes such that the following holds for the time of the
790  // nodes: n1 > n2
791  Time base_time = Time::Now();
792  const GURL url("http://foo.com/0");
793  BookmarkNode* n1 = AsMutable(model_->AddURL(
794      model_->bookmark_bar_node(), 0, ASCIIToUTF16("blah"), url));
795  BookmarkNode* n2 = AsMutable(model_->AddURL(
796      model_->bookmark_bar_node(), 1, ASCIIToUTF16("blah"), url));
797  n1->set_date_added(base_time + TimeDelta::FromDays(4));
798  n2->set_date_added(base_time + TimeDelta::FromDays(3));
799
800  // Make sure order is honored.
801  ASSERT_EQ(n1, model_->GetMostRecentlyAddedUserNodeForURL(url));
802
803  // swap 1 and 2, then check again.
804  SwapDateAdded(n1, n2);
805  ASSERT_EQ(n2, model_->GetMostRecentlyAddedUserNodeForURL(url));
806}
807
808// Makes sure GetBookmarks removes duplicates.
809TEST_F(BookmarkModelTest, GetBookmarksWithDups) {
810  const GURL url("http://foo.com/0");
811  const base::string16 title(ASCIIToUTF16("blah"));
812  model_->AddURL(model_->bookmark_bar_node(), 0, title, url);
813  model_->AddURL(model_->bookmark_bar_node(), 1, title, url);
814
815  std::vector<BookmarkModel::URLAndTitle> bookmarks;
816  model_->GetBookmarks(&bookmarks);
817  ASSERT_EQ(1U, bookmarks.size());
818  EXPECT_EQ(url, bookmarks[0].url);
819  EXPECT_EQ(title, bookmarks[0].title);
820
821  model_->AddURL(model_->bookmark_bar_node(), 2, ASCIIToUTF16("Title2"), url);
822  // Only one returned, even titles are different.
823  bookmarks.clear();
824  model_->GetBookmarks(&bookmarks);
825  EXPECT_EQ(1U, bookmarks.size());
826}
827
828TEST_F(BookmarkModelTest, HasBookmarks) {
829  const GURL url("http://foo.com/");
830  model_->AddURL(model_->bookmark_bar_node(), 0, ASCIIToUTF16("bar"), url);
831
832  EXPECT_TRUE(model_->HasBookmarks());
833}
834
835// See comment in PopulateNodeFromString.
836typedef ui::TreeNodeWithValue<BookmarkNode::Type> TestNode;
837
838// Does the work of PopulateNodeFromString. index gives the index of the current
839// element in description to process.
840void PopulateNodeImpl(const std::vector<std::string>& description,
841                      size_t* index,
842                      TestNode* parent) {
843  while (*index < description.size()) {
844    const std::string& element = description[*index];
845    (*index)++;
846    if (element == "[") {
847      // Create a new folder and recurse to add all the children.
848      // Folders are given a unique named by way of an ever increasing integer
849      // value. The folders need not have a name, but one is assigned to help
850      // in debugging.
851      static int next_folder_id = 1;
852      TestNode* new_node =
853          new TestNode(base::IntToString16(next_folder_id++),
854                       BookmarkNode::FOLDER);
855      parent->Add(new_node, parent->child_count());
856      PopulateNodeImpl(description, index, new_node);
857    } else if (element == "]") {
858      // End the current folder.
859      return;
860    } else {
861      // Add a new URL.
862
863      // All tokens must be space separated. If there is a [ or ] in the name it
864      // likely means a space was forgotten.
865      DCHECK(element.find('[') == std::string::npos);
866      DCHECK(element.find(']') == std::string::npos);
867      parent->Add(new TestNode(base::UTF8ToUTF16(element), BookmarkNode::URL),
868                  parent->child_count());
869    }
870  }
871}
872
873// Creates and adds nodes to parent based on description. description consists
874// of the following tokens (all space separated):
875//   [ : creates a new USER_FOLDER node. All elements following the [ until the
876//       next balanced ] is encountered are added as children to the node.
877//   ] : closes the last folder created by [ so that any further nodes are added
878//       to the current folders parent.
879//   text: creates a new URL node.
880// For example, "a [b] c" creates the following nodes:
881//   a 1 c
882//     |
883//     b
884// In words: a node of type URL with the title a, followed by a folder node with
885// the title 1 having the single child of type url with name b, followed by
886// the url node with the title c.
887//
888// NOTE: each name must be unique, and folders are assigned a unique title by
889// way of an increasing integer.
890void PopulateNodeFromString(const std::string& description, TestNode* parent) {
891  std::vector<std::string> elements;
892  base::SplitStringAlongWhitespace(description, &elements);
893  size_t index = 0;
894  PopulateNodeImpl(elements, &index, parent);
895}
896
897// Populates the BookmarkNode with the children of parent.
898void PopulateBookmarkNode(TestNode* parent,
899                          BookmarkModel* model,
900                          const BookmarkNode* bb_node) {
901  for (int i = 0; i < parent->child_count(); ++i) {
902    TestNode* child = parent->GetChild(i);
903    if (child->value == BookmarkNode::FOLDER) {
904      const BookmarkNode* new_bb_node =
905          model->AddFolder(bb_node, i, child->GetTitle());
906      PopulateBookmarkNode(child, model, new_bb_node);
907    } else {
908      model->AddURL(bb_node, i, child->GetTitle(),
909          GURL("http://" + base::UTF16ToASCII(child->GetTitle())));
910    }
911  }
912}
913
914// Test class that creates a BookmarkModel with a real history backend.
915class BookmarkModelTestWithProfile : public testing::Test {
916 public:
917  BookmarkModelTestWithProfile() {}
918
919 protected:
920  // Verifies the contents of the bookmark bar node match the contents of the
921  // TestNode.
922  void VerifyModelMatchesNode(TestNode* expected, const BookmarkNode* actual) {
923    ASSERT_EQ(expected->child_count(), actual->child_count());
924    for (int i = 0; i < expected->child_count(); ++i) {
925      TestNode* expected_child = expected->GetChild(i);
926      const BookmarkNode* actual_child = actual->GetChild(i);
927      ASSERT_EQ(expected_child->GetTitle(), actual_child->GetTitle());
928      if (expected_child->value == BookmarkNode::FOLDER) {
929        ASSERT_TRUE(actual_child->type() == BookmarkNode::FOLDER);
930        // Recurse throught children.
931        VerifyModelMatchesNode(expected_child, actual_child);
932        if (HasFatalFailure())
933          return;
934      } else {
935        // No need to check the URL, just the title is enough.
936        ASSERT_TRUE(actual_child->is_url());
937      }
938    }
939  }
940
941  void VerifyNoDuplicateIDs(BookmarkModel* model) {
942    ui::TreeNodeIterator<const BookmarkNode> it(model->root_node());
943    base::hash_set<int64> ids;
944    while (it.has_next())
945      ASSERT_TRUE(ids.insert(it.Next()->id()).second);
946  }
947
948  TestBookmarkClient client_;
949  scoped_ptr<BookmarkModel> model_;
950};
951
952// Creates a set of nodes in the bookmark bar model, then recreates the
953// bookmark bar model which triggers loading from the db and checks the loaded
954// structure to make sure it is what we first created.
955TEST_F(BookmarkModelTestWithProfile, CreateAndRestore) {
956  struct TestData {
957    // Structure of the children of the bookmark bar model node.
958    const std::string bbn_contents;
959    // Structure of the children of the other node.
960    const std::string other_contents;
961    // Structure of the children of the synced node.
962    const std::string mobile_contents;
963  } data[] = {
964    // See PopulateNodeFromString for a description of these strings.
965    { "", "" },
966    { "a", "b" },
967    { "a [ b ]", "" },
968    { "", "[ b ] a [ c [ d e [ f ] ] ]" },
969    { "a [ b ]", "" },
970    { "a b c [ d e [ f ] ]", "g h i [ j k [ l ] ]"},
971  };
972  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(data); ++i) {
973    model_ = client_.CreateModel();
974
975    TestNode bbn;
976    PopulateNodeFromString(data[i].bbn_contents, &bbn);
977    PopulateBookmarkNode(&bbn, model_.get(), model_->bookmark_bar_node());
978
979    TestNode other;
980    PopulateNodeFromString(data[i].other_contents, &other);
981    PopulateBookmarkNode(&other, model_.get(), model_->other_node());
982
983    TestNode mobile;
984    PopulateNodeFromString(data[i].mobile_contents, &mobile);
985    PopulateBookmarkNode(&mobile, model_.get(), model_->mobile_node());
986
987    VerifyModelMatchesNode(&bbn, model_->bookmark_bar_node());
988    VerifyModelMatchesNode(&other, model_->other_node());
989    VerifyModelMatchesNode(&mobile, model_->mobile_node());
990    VerifyNoDuplicateIDs(model_.get());
991  }
992}
993
994TEST_F(BookmarkModelTest, Sort) {
995  // Populate the bookmark bar node with nodes for 'B', 'a', 'd' and 'C'.
996  // 'C' and 'a' are folders.
997  TestNode bbn;
998  PopulateNodeFromString("B [ a ] d [ a ]", &bbn);
999  const BookmarkNode* parent = model_->bookmark_bar_node();
1000  PopulateBookmarkNode(&bbn, model_.get(), parent);
1001
1002  BookmarkNode* child1 = AsMutable(parent->GetChild(1));
1003  child1->SetTitle(ASCIIToUTF16("a"));
1004  delete child1->Remove(child1->GetChild(0));
1005  BookmarkNode* child3 = AsMutable(parent->GetChild(3));
1006  child3->SetTitle(ASCIIToUTF16("C"));
1007  delete child3->Remove(child3->GetChild(0));
1008
1009  ClearCounts();
1010
1011  // Sort the children of the bookmark bar node.
1012  model_->SortChildren(parent);
1013
1014  // Make sure we were notified.
1015  AssertObserverCount(0, 0, 0, 0, 1, 0, 0, 1, 0);
1016
1017  // Make sure the order matches (remember, 'a' and 'C' are folders and
1018  // come first).
1019  EXPECT_EQ(parent->GetChild(0)->GetTitle(), ASCIIToUTF16("a"));
1020  EXPECT_EQ(parent->GetChild(1)->GetTitle(), ASCIIToUTF16("C"));
1021  EXPECT_EQ(parent->GetChild(2)->GetTitle(), ASCIIToUTF16("B"));
1022  EXPECT_EQ(parent->GetChild(3)->GetTitle(), ASCIIToUTF16("d"));
1023}
1024
1025TEST_F(BookmarkModelTest, Reorder) {
1026  // Populate the bookmark bar node with nodes 'A', 'B', 'C' and 'D'.
1027  TestNode bbn;
1028  PopulateNodeFromString("A B C D", &bbn);
1029  BookmarkNode* parent = AsMutable(model_->bookmark_bar_node());
1030  PopulateBookmarkNode(&bbn, model_.get(), parent);
1031
1032  ClearCounts();
1033
1034  // Reorder bar node's bookmarks in reverse order.
1035  std::vector<const BookmarkNode*> new_order;
1036  new_order.push_back(parent->GetChild(3));
1037  new_order.push_back(parent->GetChild(2));
1038  new_order.push_back(parent->GetChild(1));
1039  new_order.push_back(parent->GetChild(0));
1040  model_->ReorderChildren(parent, new_order);
1041
1042  // Make sure we were notified.
1043  AssertObserverCount(0, 0, 0, 0, 1, 0, 0, 1, 0);
1044
1045  // Make sure the order matches is correct (it should be reversed).
1046  ASSERT_EQ(4, parent->child_count());
1047  EXPECT_EQ("D", base::UTF16ToASCII(parent->GetChild(0)->GetTitle()));
1048  EXPECT_EQ("C", base::UTF16ToASCII(parent->GetChild(1)->GetTitle()));
1049  EXPECT_EQ("B", base::UTF16ToASCII(parent->GetChild(2)->GetTitle()));
1050  EXPECT_EQ("A", base::UTF16ToASCII(parent->GetChild(3)->GetTitle()));
1051}
1052
1053TEST_F(BookmarkModelTest, NodeVisibility) {
1054  // Mobile node invisible by default
1055  EXPECT_TRUE(model_->bookmark_bar_node()->IsVisible());
1056  EXPECT_TRUE(model_->other_node()->IsVisible());
1057  EXPECT_FALSE(model_->mobile_node()->IsVisible());
1058
1059  // Visibility of permanent node can only be changed if they are not
1060  // forced to be visible by the client.
1061  model_->SetPermanentNodeVisible(BookmarkNode::BOOKMARK_BAR, false);
1062  EXPECT_TRUE(model_->bookmark_bar_node()->IsVisible());
1063  model_->SetPermanentNodeVisible(BookmarkNode::OTHER_NODE, false);
1064  EXPECT_TRUE(model_->other_node()->IsVisible());
1065  model_->SetPermanentNodeVisible(BookmarkNode::MOBILE, true);
1066  EXPECT_TRUE(model_->mobile_node()->IsVisible());
1067  model_->SetPermanentNodeVisible(BookmarkNode::MOBILE, false);
1068  EXPECT_FALSE(model_->mobile_node()->IsVisible());
1069
1070  // Arbitrary node should be visible
1071  TestNode bbn;
1072  PopulateNodeFromString("B", &bbn);
1073  const BookmarkNode* parent = model_->mobile_node();
1074  PopulateBookmarkNode(&bbn, model_.get(), parent);
1075  EXPECT_TRUE(parent->GetChild(0)->IsVisible());
1076
1077  // Mobile folder should be visible now that it has a child.
1078  EXPECT_TRUE(model_->mobile_node()->IsVisible());
1079}
1080
1081TEST_F(BookmarkModelTest, MobileNodeVisibileWithChildren) {
1082  const BookmarkNode* root = model_->mobile_node();
1083  const base::string16 title(ASCIIToUTF16("foo"));
1084  const GURL url("http://foo.com");
1085
1086  model_->AddURL(root, 0, title, url);
1087  EXPECT_TRUE(model_->mobile_node()->IsVisible());
1088}
1089
1090TEST_F(BookmarkModelTest, ExtensiveChangesObserver) {
1091  AssertExtensiveChangesObserverCount(0, 0);
1092  EXPECT_FALSE(model_->IsDoingExtensiveChanges());
1093  model_->BeginExtensiveChanges();
1094  EXPECT_TRUE(model_->IsDoingExtensiveChanges());
1095  AssertExtensiveChangesObserverCount(1, 0);
1096  model_->EndExtensiveChanges();
1097  EXPECT_FALSE(model_->IsDoingExtensiveChanges());
1098  AssertExtensiveChangesObserverCount(1, 1);
1099}
1100
1101TEST_F(BookmarkModelTest, MultipleExtensiveChangesObserver) {
1102  AssertExtensiveChangesObserverCount(0, 0);
1103  EXPECT_FALSE(model_->IsDoingExtensiveChanges());
1104  model_->BeginExtensiveChanges();
1105  EXPECT_TRUE(model_->IsDoingExtensiveChanges());
1106  AssertExtensiveChangesObserverCount(1, 0);
1107  model_->BeginExtensiveChanges();
1108  EXPECT_TRUE(model_->IsDoingExtensiveChanges());
1109  AssertExtensiveChangesObserverCount(1, 0);
1110  model_->EndExtensiveChanges();
1111  EXPECT_TRUE(model_->IsDoingExtensiveChanges());
1112  AssertExtensiveChangesObserverCount(1, 0);
1113  model_->EndExtensiveChanges();
1114  EXPECT_FALSE(model_->IsDoingExtensiveChanges());
1115  AssertExtensiveChangesObserverCount(1, 1);
1116}
1117
1118// Verifies that IsBookmarked is true if any bookmark matches the given URL,
1119// and that IsBookmarkedByUser is true only if at least one of the matching
1120// bookmarks can be edited by the user.
1121TEST_F(BookmarkModelTest, IsBookmarked) {
1122  // Reload the model with an extra node that is not editable by the user.
1123  BookmarkPermanentNode* extra_node = ReloadModelWithExtraNode();
1124
1125  // "google.com" is a "user" bookmark.
1126  model_->AddURL(model_->other_node(), 0, base::ASCIIToUTF16("User"),
1127                 GURL("http://google.com"));
1128  // "youtube.com" is not.
1129  model_->AddURL(extra_node, 0, base::ASCIIToUTF16("Extra"),
1130                 GURL("http://youtube.com"));
1131
1132  EXPECT_TRUE(model_->IsBookmarked(GURL("http://google.com")));
1133  EXPECT_TRUE(model_->IsBookmarked(GURL("http://youtube.com")));
1134  EXPECT_FALSE(model_->IsBookmarked(GURL("http://reddit.com")));
1135
1136  EXPECT_TRUE(
1137      bookmarks::IsBookmarkedByUser(model_.get(), GURL("http://google.com")));
1138  EXPECT_FALSE(
1139      bookmarks::IsBookmarkedByUser(model_.get(), GURL("http://youtube.com")));
1140  EXPECT_FALSE(
1141      bookmarks::IsBookmarkedByUser(model_.get(), GURL("http://reddit.com")));
1142}
1143
1144// Verifies that GetMostRecentlyAddedUserNodeForURL skips bookmarks that
1145// are not owned by the user.
1146TEST_F(BookmarkModelTest, GetMostRecentlyAddedUserNodeForURLSkipsManagedNodes) {
1147  // Reload the model with an extra node that is not editable by the user.
1148  BookmarkPermanentNode* extra_node = ReloadModelWithExtraNode();
1149
1150  const base::string16 title = base::ASCIIToUTF16("Title");
1151  const BookmarkNode* user_parent = model_->other_node();
1152  const BookmarkNode* managed_parent = extra_node;
1153  const GURL url("http://google.com");
1154
1155  // |url| is not bookmarked yet.
1156  EXPECT_TRUE(model_->GetMostRecentlyAddedUserNodeForURL(url) == NULL);
1157
1158  // Having a managed node doesn't count.
1159  model_->AddURL(managed_parent, 0, title, url);
1160  EXPECT_TRUE(model_->GetMostRecentlyAddedUserNodeForURL(url) == NULL);
1161
1162  // Now add a user node.
1163  const BookmarkNode* user = model_->AddURL(user_parent, 0, title, url);
1164  EXPECT_EQ(user, model_->GetMostRecentlyAddedUserNodeForURL(url));
1165
1166  // Having a more recent managed node doesn't count either.
1167  const BookmarkNode* managed = model_->AddURL(managed_parent, 0, title, url);
1168  EXPECT_GE(managed->date_added(), user->date_added());
1169  EXPECT_EQ(user, model_->GetMostRecentlyAddedUserNodeForURL(url));
1170}
1171
1172TEST(BookmarkNodeTest, NodeMetaInfo) {
1173  GURL url;
1174  BookmarkNode node(url);
1175  EXPECT_FALSE(node.GetMetaInfoMap());
1176
1177  EXPECT_TRUE(node.SetMetaInfo("key1", "value1"));
1178  std::string out_value;
1179  EXPECT_TRUE(node.GetMetaInfo("key1", &out_value));
1180  EXPECT_EQ("value1", out_value);
1181  EXPECT_FALSE(node.SetMetaInfo("key1", "value1"));
1182
1183  EXPECT_FALSE(node.GetMetaInfo("key2.subkey1", &out_value));
1184  EXPECT_TRUE(node.SetMetaInfo("key2.subkey1", "value2"));
1185  EXPECT_TRUE(node.GetMetaInfo("key2.subkey1", &out_value));
1186  EXPECT_EQ("value2", out_value);
1187
1188  EXPECT_FALSE(node.GetMetaInfo("key2.subkey2.leaf", &out_value));
1189  EXPECT_TRUE(node.SetMetaInfo("key2.subkey2.leaf", ""));
1190  EXPECT_TRUE(node.GetMetaInfo("key2.subkey2.leaf", &out_value));
1191  EXPECT_EQ("", out_value);
1192
1193  EXPECT_TRUE(node.DeleteMetaInfo("key1"));
1194  EXPECT_TRUE(node.DeleteMetaInfo("key2.subkey1"));
1195  EXPECT_TRUE(node.DeleteMetaInfo("key2.subkey2.leaf"));
1196  EXPECT_FALSE(node.DeleteMetaInfo("key3"));
1197  EXPECT_FALSE(node.GetMetaInfo("key1", &out_value));
1198  EXPECT_FALSE(node.GetMetaInfo("key2.subkey1", &out_value));
1199  EXPECT_FALSE(node.GetMetaInfo("key2.subkey2", &out_value));
1200  EXPECT_FALSE(node.GetMetaInfo("key2.subkey2.leaf", &out_value));
1201  EXPECT_FALSE(node.GetMetaInfoMap());
1202}
1203
1204}  // namespace
1205}  // namespace bookmarks
1206