bookmark_model_unittest.cc revision c407dc5cd9bdc5668497f21b26b09d988ab439de
1// Copyright (c) 2006-2008 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 <set>
6
7#include "app/tree_node_iterator.h"
8#include "app/tree_node_model.h"
9#include "base/hash_tables.h"
10#include "base/string_util.h"
11#include "chrome/browser/bookmarks/bookmark_codec.h"
12#include "chrome/browser/bookmarks/bookmark_model.h"
13#include "chrome/browser/bookmarks/bookmark_utils.h"
14#include "chrome/browser/chrome_thread.h"
15#include "chrome/browser/history/history_notifications.h"
16#include "chrome/common/chrome_constants.h"
17#include "chrome/common/chrome_paths.h"
18#include "chrome/common/notification_registrar.h"
19#include "chrome/common/notification_service.h"
20#include "chrome/test/model_test_utils.h"
21#include "chrome/test/testing_profile.h"
22#include "testing/gtest/include/gtest/gtest.h"
23
24using base::Time;
25using base::TimeDelta;
26
27namespace {
28
29// Helper to get a mutable bookmark node.
30static BookmarkNode* AsMutable(const BookmarkNode* node) {
31  return const_cast<BookmarkNode*>(node);
32}
33
34void SwapDateAdded(BookmarkNode* n1, BookmarkNode* n2) {
35  Time tmp = n1->date_added();
36  n1->set_date_added(n2->date_added());
37  n2->set_date_added(tmp);
38}
39
40}  // anonymous namespace
41
42class BookmarkModelTest : public testing::Test, public BookmarkModelObserver {
43 public:
44  struct ObserverDetails {
45    ObserverDetails() {
46      Set(NULL, NULL, -1, -1);
47    }
48
49    void Set(const BookmarkNode* node1,
50             const BookmarkNode* node2,
51             int index1,
52             int index2) {
53      this->node1 = node1;
54      this->node2 = node2;
55      this->index1 = index1;
56      this->index2 = index2;
57    }
58
59    void AssertEquals(const BookmarkNode* node1,
60                      const BookmarkNode* node2,
61                      int index1,
62                      int index2) {
63      ASSERT_TRUE(this->node1 == node1);
64      ASSERT_TRUE(this->node2 == node2);
65      ASSERT_EQ(index1, this->index1);
66      ASSERT_EQ(index2, this->index2);
67    }
68
69    const BookmarkNode* node1;
70    const BookmarkNode* node2;
71    int index1;
72    int index2;
73  };
74
75  BookmarkModelTest() : model(NULL) {
76    model.AddObserver(this);
77    ClearCounts();
78  }
79
80
81  void Loaded(BookmarkModel* model) {
82    // We never load from the db, so that this should never get invoked.
83    NOTREACHED();
84  }
85
86  virtual void BookmarkNodeMoved(BookmarkModel* model,
87                                 const BookmarkNode* old_parent,
88                                 int old_index,
89                                 const BookmarkNode* new_parent,
90                                 int new_index) {
91    moved_count++;
92    observer_details.Set(old_parent, new_parent, old_index, new_index);
93  }
94
95  virtual void BookmarkNodeAdded(BookmarkModel* model,
96                                 const BookmarkNode* parent,
97                                 int index) {
98    added_count++;
99    observer_details.Set(parent, NULL, index, -1);
100  }
101
102  virtual void BookmarkNodeRemoved(BookmarkModel* model,
103                                   const BookmarkNode* parent,
104                                   int old_index,
105                                   const BookmarkNode* node) {
106    removed_count++;
107    observer_details.Set(parent, NULL, old_index, -1);
108  }
109
110  virtual void BookmarkNodeChanged(BookmarkModel* model,
111                                   const BookmarkNode* node) {
112    changed_count++;
113    observer_details.Set(node, NULL, -1, -1);
114  }
115
116  virtual void BookmarkNodeChildrenReordered(BookmarkModel* model,
117                                             const BookmarkNode* node) {
118    reordered_count_++;
119  }
120
121  virtual void BookmarkNodeFavIconLoaded(BookmarkModel* model,
122                                         const BookmarkNode* node) {
123    // We never attempt to load favicons, so that this method never
124    // gets invoked.
125  }
126
127  void ClearCounts() {
128    reordered_count_ = moved_count = added_count = removed_count =
129        changed_count = 0;
130  }
131
132  void AssertObserverCount(int added_count,
133                           int moved_count,
134                           int removed_count,
135                           int changed_count,
136                           int reordered_count) {
137    ASSERT_EQ(added_count, this->added_count);
138    ASSERT_EQ(moved_count, this->moved_count);
139    ASSERT_EQ(removed_count, this->removed_count);
140    ASSERT_EQ(changed_count, this->changed_count);
141    ASSERT_EQ(reordered_count, reordered_count_);
142  }
143
144  BookmarkModel model;
145
146  int moved_count;
147
148  int added_count;
149
150  int removed_count;
151
152  int changed_count;
153
154  int reordered_count_;
155
156  ObserverDetails observer_details;
157};
158
159TEST_F(BookmarkModelTest, InitialState) {
160  const BookmarkNode* bb_node = model.GetBookmarkBarNode();
161  ASSERT_TRUE(bb_node != NULL);
162  EXPECT_EQ(0, bb_node->GetChildCount());
163  EXPECT_EQ(BookmarkNode::BOOKMARK_BAR, bb_node->type());
164
165  const BookmarkNode* other_node = model.other_node();
166  ASSERT_TRUE(other_node != NULL);
167  EXPECT_EQ(0, other_node->GetChildCount());
168  EXPECT_EQ(BookmarkNode::OTHER_NODE, other_node->type());
169
170  EXPECT_TRUE(bb_node->id() != other_node->id());
171}
172
173TEST_F(BookmarkModelTest, AddURL) {
174  const BookmarkNode* root = model.GetBookmarkBarNode();
175  const std::wstring title(L"foo");
176  const GURL url("http://foo.com");
177
178  const BookmarkNode* new_node = model.AddURL(root, 0, title, url);
179  AssertObserverCount(1, 0, 0, 0, 0);
180  observer_details.AssertEquals(root, NULL, 0, -1);
181
182  ASSERT_EQ(1, root->GetChildCount());
183  ASSERT_EQ(title, new_node->GetTitle());
184  ASSERT_TRUE(url == new_node->GetURL());
185  ASSERT_EQ(BookmarkNode::URL, new_node->type());
186  ASSERT_TRUE(new_node == model.GetMostRecentlyAddedNodeForURL(url));
187
188  EXPECT_TRUE(new_node->id() != root->id() &&
189              new_node->id() != model.other_node()->id());
190}
191
192TEST_F(BookmarkModelTest, AddGroup) {
193  const BookmarkNode* root = model.GetBookmarkBarNode();
194  const std::wstring title(L"foo");
195
196  const BookmarkNode* new_node = model.AddGroup(root, 0, title);
197  AssertObserverCount(1, 0, 0, 0, 0);
198  observer_details.AssertEquals(root, NULL, 0, -1);
199
200  ASSERT_EQ(1, root->GetChildCount());
201  ASSERT_EQ(title, new_node->GetTitle());
202  ASSERT_EQ(BookmarkNode::FOLDER, new_node->type());
203
204  EXPECT_TRUE(new_node->id() != root->id() &&
205              new_node->id() != model.other_node()->id());
206
207  // Add another group, just to make sure group_ids are incremented correctly.
208  ClearCounts();
209  model.AddGroup(root, 0, title);
210  AssertObserverCount(1, 0, 0, 0, 0);
211  observer_details.AssertEquals(root, NULL, 0, -1);
212}
213
214TEST_F(BookmarkModelTest, RemoveURL) {
215  const BookmarkNode* root = model.GetBookmarkBarNode();
216  const std::wstring title(L"foo");
217  const GURL url("http://foo.com");
218  model.AddURL(root, 0, title, url);
219  ClearCounts();
220
221  model.Remove(root, 0);
222  ASSERT_EQ(0, root->GetChildCount());
223  AssertObserverCount(0, 0, 1, 0, 0);
224  observer_details.AssertEquals(root, NULL, 0, -1);
225
226  // Make sure there is no mapping for the URL.
227  ASSERT_TRUE(model.GetMostRecentlyAddedNodeForURL(url) == NULL);
228}
229
230TEST_F(BookmarkModelTest, RemoveGroup) {
231  const BookmarkNode* root = model.GetBookmarkBarNode();
232  const BookmarkNode* group = model.AddGroup(root, 0, L"foo");
233
234  ClearCounts();
235
236  // Add a URL as a child.
237  const std::wstring title(L"foo");
238  const GURL url("http://foo.com");
239  model.AddURL(group, 0, title, url);
240
241  ClearCounts();
242
243  // Now remove the group.
244  model.Remove(root, 0);
245  ASSERT_EQ(0, root->GetChildCount());
246  AssertObserverCount(0, 0, 1, 0, 0);
247  observer_details.AssertEquals(root, NULL, 0, -1);
248
249  // Make sure there is no mapping for the URL.
250  ASSERT_TRUE(model.GetMostRecentlyAddedNodeForURL(url) == NULL);
251}
252
253TEST_F(BookmarkModelTest, SetTitle) {
254  const BookmarkNode* root = model.GetBookmarkBarNode();
255  std::wstring title(L"foo");
256  const GURL url("http://foo.com");
257  const BookmarkNode* node = model.AddURL(root, 0, title, url);
258
259  ClearCounts();
260
261  title = L"foo2";
262  model.SetTitle(node, title);
263  AssertObserverCount(0, 0, 0, 1, 0);
264  observer_details.AssertEquals(node, NULL, -1, -1);
265  EXPECT_EQ(title, node->GetTitle());
266}
267
268TEST_F(BookmarkModelTest, SetURL) {
269  const BookmarkNode* root = model.GetBookmarkBarNode();
270  const std::wstring title(L"foo");
271  GURL url("http://foo.com");
272  const BookmarkNode* node = model.AddURL(root, 0, title, url);
273
274  ClearCounts();
275
276  url = GURL("http://foo2.com");
277  model.SetURL(node, url);
278  AssertObserverCount(0, 0, 0, 1, 0);
279  observer_details.AssertEquals(node, NULL, -1, -1);
280  EXPECT_EQ(url, node->GetURL());
281}
282
283TEST_F(BookmarkModelTest, Move) {
284  const BookmarkNode* root = model.GetBookmarkBarNode();
285  std::wstring title(L"foo");
286  const GURL url("http://foo.com");
287  const BookmarkNode* node = model.AddURL(root, 0, title, url);
288  const BookmarkNode* group1 = model.AddGroup(root, 0, L"foo");
289  ClearCounts();
290
291  model.Move(node, group1, 0);
292
293  AssertObserverCount(0, 1, 0, 0, 0);
294  observer_details.AssertEquals(root, group1, 1, 0);
295  EXPECT_TRUE(group1 == node->GetParent());
296  EXPECT_EQ(1, root->GetChildCount());
297  EXPECT_EQ(group1, root->GetChild(0));
298  EXPECT_EQ(1, group1->GetChildCount());
299  EXPECT_EQ(node, group1->GetChild(0));
300
301  // And remove the group.
302  ClearCounts();
303  model.Remove(root, 0);
304  AssertObserverCount(0, 0, 1, 0, 0);
305  observer_details.AssertEquals(root, NULL, 0, -1);
306  EXPECT_TRUE(model.GetMostRecentlyAddedNodeForURL(url) == NULL);
307  EXPECT_EQ(0, root->GetChildCount());
308}
309
310TEST_F(BookmarkModelTest, Copy) {
311  const BookmarkNode* root = model.GetBookmarkBarNode();
312  static const std::wstring model_string(L"a 1:[ b c ] d 2:[ e f g ] h ");
313  model_test_utils::AddNodesFromModelString(model, root, model_string);
314
315  // Validate initial model.
316  std::wstring actualModelString = model_test_utils::ModelStringFromNode(root);
317  EXPECT_EQ(model_string, actualModelString);
318
319  // Copy 'd' to be after '1:b': URL item from bar to folder.
320  const BookmarkNode* nodeToCopy = root->GetChild(2);
321  const BookmarkNode* destination = root->GetChild(1);
322  model.Copy(nodeToCopy, destination, 1);
323  actualModelString = model_test_utils::ModelStringFromNode(root);
324  EXPECT_EQ(L"a 1:[ b d c ] d 2:[ e f g ] h ", actualModelString);
325
326  // Copy '1:d' to be after 'a': URL item from folder to bar.
327  const BookmarkNode* group = root->GetChild(1);
328  nodeToCopy = group->GetChild(1);
329  model.Copy(nodeToCopy, root, 1);
330  actualModelString = model_test_utils::ModelStringFromNode(root);
331  EXPECT_EQ(L"a d 1:[ b d c ] d 2:[ e f g ] h ", actualModelString);
332
333  // Copy '1' to be after '2:e': Folder from bar to folder.
334  nodeToCopy = root->GetChild(2);
335  destination = root->GetChild(4);
336  model.Copy(nodeToCopy, destination, 1);
337  actualModelString = model_test_utils::ModelStringFromNode(root);
338  EXPECT_EQ(L"a d 1:[ b d c ] d 2:[ e 1:[ b d c ] f g ] h ", actualModelString);
339
340  // Copy '2:1' to be after '2:f': Folder within same folder.
341  group = root->GetChild(4);
342  nodeToCopy = group->GetChild(1);
343  model.Copy(nodeToCopy, group, 3);
344  actualModelString = model_test_utils::ModelStringFromNode(root);
345  EXPECT_EQ(L"a d 1:[ b d c ] d 2:[ e 1:[ b d c ] f 1:[ b d c ] g ] h ",
346            actualModelString);
347
348  // Copy first 'd' to be after 'h': URL item within the bar.
349  nodeToCopy = root->GetChild(1);
350  model.Copy(nodeToCopy, root, 6);
351  actualModelString = model_test_utils::ModelStringFromNode(root);
352  EXPECT_EQ(L"a d 1:[ b d c ] d 2:[ e 1:[ b d c ] f 1:[ b d c ] g ] h d ",
353            actualModelString);
354
355  // Copy '2' to be after 'a': Folder within the bar.
356  nodeToCopy = root->GetChild(4);
357  model.Copy(nodeToCopy, root, 1);
358  actualModelString = model_test_utils::ModelStringFromNode(root);
359  EXPECT_EQ(L"a 2:[ e 1:[ b d c ] f 1:[ b d c ] g ] d 1:[ b d c ] "
360            L"d 2:[ e 1:[ b d c ] f 1:[ b d c ] g ] h d ",
361            actualModelString);
362}
363
364// Tests that adding a URL to a folder updates the last modified time.
365TEST_F(BookmarkModelTest, ParentForNewNodes) {
366  ASSERT_EQ(model.GetBookmarkBarNode(), model.GetParentForNewNodes());
367
368  const std::wstring title(L"foo");
369  const GURL url("http://foo.com");
370
371  model.AddURL(model.other_node(), 0, title, url);
372  ASSERT_EQ(model.other_node(), model.GetParentForNewNodes());
373}
374
375// Make sure recently modified stays in sync when adding a URL.
376TEST_F(BookmarkModelTest, MostRecentlyModifiedGroups) {
377  // Add a group.
378  const BookmarkNode* group = model.AddGroup(model.other_node(), 0, L"foo");
379  // Add a URL to it.
380  model.AddURL(group, 0, L"blah", GURL("http://foo.com"));
381
382  // Make sure group is in the most recently modified.
383  std::vector<const BookmarkNode*> most_recent_groups =
384      bookmark_utils::GetMostRecentlyModifiedGroups(&model, 1);
385  ASSERT_EQ(1U, most_recent_groups.size());
386  ASSERT_EQ(group, most_recent_groups[0]);
387
388  // Nuke the group and do another fetch, making sure group isn't in the
389  // returned list.
390  model.Remove(group->GetParent(), 0);
391  most_recent_groups =
392      bookmark_utils::GetMostRecentlyModifiedGroups(&model, 1);
393  ASSERT_EQ(1U, most_recent_groups.size());
394  ASSERT_TRUE(most_recent_groups[0] != group);
395}
396
397// Make sure MostRecentlyAddedEntries stays in sync.
398TEST_F(BookmarkModelTest, MostRecentlyAddedEntries) {
399  // Add a couple of nodes such that the following holds for the time of the
400  // nodes: n1 > n2 > n3 > n4.
401  Time base_time = Time::Now();
402  BookmarkNode* n1 = AsMutable(model.AddURL(model.GetBookmarkBarNode(),
403                                  0,
404                                  L"blah",
405                                  GURL("http://foo.com/0")));
406  BookmarkNode* n2 = AsMutable(model.AddURL(model.GetBookmarkBarNode(),
407                                  1,
408                                  L"blah",
409                                  GURL("http://foo.com/1")));
410  BookmarkNode* n3 = AsMutable(model.AddURL(model.GetBookmarkBarNode(),
411                                  2,
412                                  L"blah",
413                                  GURL("http://foo.com/2")));
414  BookmarkNode* n4 = AsMutable(model.AddURL(model.GetBookmarkBarNode(),
415                                  3,
416                                  L"blah",
417                                  GURL("http://foo.com/3")));
418  n1->set_date_added(base_time + TimeDelta::FromDays(4));
419  n2->set_date_added(base_time + TimeDelta::FromDays(3));
420  n3->set_date_added(base_time + TimeDelta::FromDays(2));
421  n4->set_date_added(base_time + TimeDelta::FromDays(1));
422
423  // Make sure order is honored.
424  std::vector<const BookmarkNode*> recently_added;
425  bookmark_utils::GetMostRecentlyAddedEntries(&model, 2, &recently_added);
426  ASSERT_EQ(2U, recently_added.size());
427  ASSERT_TRUE(n1 == recently_added[0]);
428  ASSERT_TRUE(n2 == recently_added[1]);
429
430  // swap 1 and 2, then check again.
431  recently_added.clear();
432  SwapDateAdded(n1, n2);
433  bookmark_utils::GetMostRecentlyAddedEntries(&model, 4, &recently_added);
434  ASSERT_EQ(4U, recently_added.size());
435  ASSERT_TRUE(n2 == recently_added[0]);
436  ASSERT_TRUE(n1 == recently_added[1]);
437  ASSERT_TRUE(n3 == recently_added[2]);
438  ASSERT_TRUE(n4 == recently_added[3]);
439}
440
441// Makes sure GetMostRecentlyAddedNodeForURL stays in sync.
442TEST_F(BookmarkModelTest, GetMostRecentlyAddedNodeForURL) {
443  // Add a couple of nodes such that the following holds for the time of the
444  // nodes: n1 > n2
445  Time base_time = Time::Now();
446  const GURL url("http://foo.com/0");
447  BookmarkNode* n1 = AsMutable(model.AddURL(
448      model.GetBookmarkBarNode(), 0, L"blah", url));
449  BookmarkNode* n2 = AsMutable(model.AddURL(
450      model.GetBookmarkBarNode(), 1, L"blah", url));
451  n1->set_date_added(base_time + TimeDelta::FromDays(4));
452  n2->set_date_added(base_time + TimeDelta::FromDays(3));
453
454  // Make sure order is honored.
455  ASSERT_EQ(n1, model.GetMostRecentlyAddedNodeForURL(url));
456
457  // swap 1 and 2, then check again.
458  SwapDateAdded(n1, n2);
459  ASSERT_EQ(n2, model.GetMostRecentlyAddedNodeForURL(url));
460}
461
462// Makes sure GetBookmarks removes duplicates.
463TEST_F(BookmarkModelTest, GetBookmarksWithDups) {
464  const GURL url("http://foo.com/0");
465  model.AddURL(model.GetBookmarkBarNode(), 0, L"blah", url);
466  model.AddURL(model.GetBookmarkBarNode(), 1, L"blah", url);
467
468  std::vector<GURL> urls;
469  model.GetBookmarks(&urls);
470  EXPECT_EQ(1U, urls.size());
471  ASSERT_TRUE(urls[0] == url);
472}
473
474namespace {
475
476// NotificationObserver implementation used in verifying we've received the
477// NOTIFY_URLS_STARRED method correctly.
478class StarredListener : public NotificationObserver {
479 public:
480  StarredListener() : notification_count_(0), details_(false) {
481    registrar_.Add(this, NotificationType::URLS_STARRED, Source<Profile>(NULL));
482  }
483
484  virtual void Observe(NotificationType type,
485                       const NotificationSource& source,
486                       const NotificationDetails& details) {
487    if (type == NotificationType::URLS_STARRED) {
488      notification_count_++;
489      details_ = *(Details<history::URLsStarredDetails>(details).ptr());
490    }
491  }
492
493  // Number of times NOTIFY_URLS_STARRED has been observed.
494  int notification_count_;
495
496  // Details from the last NOTIFY_URLS_STARRED.
497  history::URLsStarredDetails details_;
498
499 private:
500  NotificationRegistrar registrar_;
501
502  DISALLOW_COPY_AND_ASSIGN(StarredListener);
503};
504
505}  // namespace
506
507// Makes sure NOTIFY_URLS_STARRED is sent correctly.
508TEST_F(BookmarkModelTest, NotifyURLsStarred) {
509  StarredListener listener;
510  const GURL url("http://foo.com/0");
511  const BookmarkNode* n1 = model.AddURL(
512      model.GetBookmarkBarNode(), 0, L"blah", url);
513
514  // Starred notification should be sent.
515  EXPECT_EQ(1, listener.notification_count_);
516  ASSERT_TRUE(listener.details_.starred);
517  ASSERT_EQ(1U, listener.details_.changed_urls.size());
518  EXPECT_TRUE(url == *(listener.details_.changed_urls.begin()));
519  listener.notification_count_ = 0;
520  listener.details_.changed_urls.clear();
521
522  // Add another bookmark for the same URL. This should not send any
523  // notification.
524  const BookmarkNode* n2 = model.AddURL(
525      model.GetBookmarkBarNode(), 1, L"blah", url);
526
527  EXPECT_EQ(0, listener.notification_count_);
528
529  // Remove n2.
530  model.Remove(n2->GetParent(), 1);
531  n2 = NULL;
532
533  // Shouldn't have received any notification as n1 still exists with the same
534  // URL.
535  EXPECT_EQ(0, listener.notification_count_);
536
537  EXPECT_TRUE(model.GetMostRecentlyAddedNodeForURL(url) == n1);
538
539  // Remove n1.
540  model.Remove(n1->GetParent(), 0);
541
542  // Now we should get the notification.
543  EXPECT_EQ(1, listener.notification_count_);
544  ASSERT_FALSE(listener.details_.starred);
545  ASSERT_EQ(1U, listener.details_.changed_urls.size());
546  EXPECT_TRUE(url == *(listener.details_.changed_urls.begin()));
547}
548
549namespace {
550
551// See comment in PopulateNodeFromString.
552typedef TreeNodeWithValue<BookmarkNode::Type> TestNode;
553
554// Does the work of PopulateNodeFromString. index gives the index of the current
555// element in description to process.
556static void PopulateNodeImpl(const std::vector<std::wstring>& description,
557                             size_t* index,
558                             TestNode* parent) {
559  while (*index < description.size()) {
560    const std::wstring& element = description[*index];
561    (*index)++;
562    if (element == L"[") {
563      // Create a new group and recurse to add all the children.
564      // Groups are given a unique named by way of an ever increasing integer
565      // value. The groups need not have a name, but one is assigned to help
566      // in debugging.
567      static int next_group_id = 1;
568      TestNode* new_node =
569          new TestNode(IntToWString(next_group_id++),
570                       BookmarkNode::FOLDER);
571      parent->Add(parent->GetChildCount(), new_node);
572      PopulateNodeImpl(description, index, new_node);
573    } else if (element == L"]") {
574      // End the current group.
575      return;
576    } else {
577      // Add a new URL.
578
579      // All tokens must be space separated. If there is a [ or ] in the name it
580      // likely means a space was forgotten.
581      DCHECK(element.find('[') == std::string::npos);
582      DCHECK(element.find(']') == std::string::npos);
583      parent->Add(parent->GetChildCount(),
584                  new TestNode(element, BookmarkNode::URL));
585    }
586  }
587}
588
589// Creates and adds nodes to parent based on description. description consists
590// of the following tokens (all space separated):
591//   [ : creates a new USER_GROUP node. All elements following the [ until the
592//       next balanced ] is encountered are added as children to the node.
593//   ] : closes the last group created by [ so that any further nodes are added
594//       to the current groups parent.
595//   text: creates a new URL node.
596// For example, "a [b] c" creates the following nodes:
597//   a 1 c
598//     |
599//     b
600// In words: a node of type URL with the title a, followed by a group node with
601// the title 1 having the single child of type url with name b, followed by
602// the url node with the title c.
603//
604// NOTE: each name must be unique, and groups are assigned a unique title by way
605// of an increasing integer.
606static void PopulateNodeFromString(const std::wstring& description,
607                                   TestNode* parent) {
608  std::vector<std::wstring> elements;
609  size_t index = 0;
610  SplitStringAlongWhitespace(description, &elements);
611  PopulateNodeImpl(elements, &index, parent);
612}
613
614// Populates the BookmarkNode with the children of parent.
615static void PopulateBookmarkNode(TestNode* parent,
616                                 BookmarkModel* model,
617                                 const BookmarkNode* bb_node) {
618  for (int i = 0; i < parent->GetChildCount(); ++i) {
619    TestNode* child = parent->GetChild(i);
620    if (child->value == BookmarkNode::FOLDER) {
621      const BookmarkNode* new_bb_node =
622          model->AddGroup(bb_node, i, child->GetTitle());
623      PopulateBookmarkNode(child, model, new_bb_node);
624    } else {
625      model->AddURL(bb_node, i, child->GetTitle(),
626                    GURL("http://" + WideToASCII(child->GetTitle())));
627    }
628  }
629}
630
631}  // namespace
632
633// Test class that creates a BookmarkModel with a real history backend.
634class BookmarkModelTestWithProfile : public testing::Test,
635                                     public BookmarkModelObserver {
636 public:
637  BookmarkModelTestWithProfile()
638      : ui_thread_(ChromeThread::UI, &message_loop_),
639        file_thread_(ChromeThread::FILE, &message_loop_) {}
640
641  virtual void SetUp() {
642  }
643
644  virtual void TearDown() {
645    profile_.reset(NULL);
646  }
647
648  // The profile.
649  scoped_ptr<TestingProfile> profile_;
650
651 protected:
652  // Verifies the contents of the bookmark bar node match the contents of the
653  // TestNode.
654  void VerifyModelMatchesNode(TestNode* expected, const BookmarkNode* actual) {
655    ASSERT_EQ(expected->GetChildCount(), actual->GetChildCount());
656    for (int i = 0; i < expected->GetChildCount(); ++i) {
657      TestNode* expected_child = expected->GetChild(i);
658      const BookmarkNode* actual_child = actual->GetChild(i);
659      ASSERT_EQ(expected_child->GetTitle(), actual_child->GetTitle());
660      if (expected_child->value == BookmarkNode::FOLDER) {
661        ASSERT_TRUE(actual_child->type() == BookmarkNode::FOLDER);
662        // Recurse throught children.
663        VerifyModelMatchesNode(expected_child, actual_child);
664        if (HasFatalFailure())
665          return;
666      } else {
667        // No need to check the URL, just the title is enough.
668        ASSERT_TRUE(actual_child->type() == BookmarkNode::URL);
669      }
670    }
671  }
672
673  void VerifyNoDuplicateIDs(BookmarkModel* model) {
674    TreeNodeIterator<const BookmarkNode> it(model->root_node());
675    base::hash_set<int64> ids;
676    while (it.has_next())
677      ASSERT_TRUE(ids.insert(it.Next()->id()).second);
678  }
679
680  void BlockTillBookmarkModelLoaded() {
681    bb_model_ = profile_->GetBookmarkModel();
682    if (!bb_model_->IsLoaded())
683      BlockTillLoaded(bb_model_);
684    else
685      bb_model_->AddObserver(this);
686  }
687
688  // Destroys the current profile, creates a new one and creates the history
689  // service.
690  void RecreateProfile() {
691    // Need to shutdown the old one before creating a new one.
692    profile_.reset(NULL);
693    profile_.reset(new TestingProfile());
694    profile_->CreateHistoryService(true, false);
695  }
696
697  BookmarkModel* bb_model_;
698
699 private:
700  // Blocks until the BookmarkModel has finished loading.
701  void BlockTillLoaded(BookmarkModel* model) {
702    model->AddObserver(this);
703    MessageLoop::current()->Run();
704  }
705
706  // BookmarkModelObserver methods.
707  virtual void Loaded(BookmarkModel* model) {
708    // Balances the call in BlockTillLoaded.
709    MessageLoop::current()->Quit();
710  }
711  virtual void BookmarkNodeMoved(BookmarkModel* model,
712                                 const BookmarkNode* old_parent,
713                                 int old_index,
714                                 const BookmarkNode* new_parent,
715                                 int new_index) {}
716  virtual void BookmarkNodeAdded(BookmarkModel* model,
717                                 const BookmarkNode* parent,
718                                 int index) {}
719  virtual void BookmarkNodeRemoved(BookmarkModel* model,
720                                   const BookmarkNode* parent,
721                                   int old_index,
722                                   const BookmarkNode* node) {}
723  virtual void BookmarkNodeChanged(BookmarkModel* model,
724                                   const BookmarkNode* node) {}
725  virtual void BookmarkNodeChildrenReordered(BookmarkModel* model,
726                                             const BookmarkNode* node) {}
727  virtual void BookmarkNodeFavIconLoaded(BookmarkModel* model,
728                                         const BookmarkNode* node) {}
729
730  MessageLoopForUI message_loop_;
731  ChromeThread ui_thread_;
732  ChromeThread file_thread_;
733};
734
735// Creates a set of nodes in the bookmark bar model, then recreates the
736// bookmark bar model which triggers loading from the db and checks the loaded
737// structure to make sure it is what we first created.
738TEST_F(BookmarkModelTestWithProfile, CreateAndRestore) {
739  struct TestData {
740    // Structure of the children of the bookmark bar model node.
741    const std::wstring bbn_contents;
742    // Structure of the children of the other node.
743    const std::wstring other_contents;
744  } data[] = {
745    // See PopulateNodeFromString for a description of these strings.
746    { L"", L"" },
747    { L"a", L"b" },
748    { L"a [ b ]", L"" },
749    { L"", L"[ b ] a [ c [ d e [ f ] ] ]" },
750    { L"a [ b ]", L"" },
751    { L"a b c [ d e [ f ] ]", L"g h i [ j k [ l ] ]"},
752  };
753  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(data); ++i) {
754    // Recreate the profile. We need to reset with NULL first so that the last
755    // HistoryService releases the locks on the files it creates and we can
756    // delete them.
757    profile_.reset(NULL);
758    profile_.reset(new TestingProfile());
759    profile_->CreateBookmarkModel(true);
760    profile_->CreateHistoryService(true, false);
761    BlockTillBookmarkModelLoaded();
762
763    TestNode bbn;
764    PopulateNodeFromString(data[i].bbn_contents, &bbn);
765    PopulateBookmarkNode(&bbn, bb_model_, bb_model_->GetBookmarkBarNode());
766
767    TestNode other;
768    PopulateNodeFromString(data[i].other_contents, &other);
769    PopulateBookmarkNode(&other, bb_model_, bb_model_->other_node());
770
771    profile_->CreateBookmarkModel(false);
772    BlockTillBookmarkModelLoaded();
773
774    VerifyModelMatchesNode(&bbn, bb_model_->GetBookmarkBarNode());
775    VerifyModelMatchesNode(&other, bb_model_->other_node());
776    VerifyNoDuplicateIDs(bb_model_);
777  }
778}
779
780// Test class that creates a BookmarkModel with a real history backend.
781class BookmarkModelTestWithProfile2 : public BookmarkModelTestWithProfile {
782 public:
783  virtual void SetUp() {
784    profile_.reset(new TestingProfile());
785  }
786
787 protected:
788  // Verifies the state of the model matches that of the state in the saved
789  // history file.
790  void VerifyExpectedState() {
791    // Here's the structure we expect:
792    // bbn
793    //   www.google.com - Google
794    //   F1
795    //     http://www.google.com/intl/en/ads/ - Google Advertising
796    //     F11
797    //       http://www.google.com/services/ - Google Business Solutions
798    // other
799    //   OF1
800    //   http://www.google.com/intl/en/about.html - About Google
801    const BookmarkNode* bbn = bb_model_->GetBookmarkBarNode();
802    ASSERT_EQ(2, bbn->GetChildCount());
803
804    const BookmarkNode* child = bbn->GetChild(0);
805    ASSERT_EQ(BookmarkNode::URL, child->type());
806    ASSERT_EQ(L"Google", child->GetTitle());
807    ASSERT_TRUE(child->GetURL() == GURL("http://www.google.com"));
808
809    child = bbn->GetChild(1);
810    ASSERT_TRUE(child->is_folder());
811    ASSERT_EQ(L"F1", child->GetTitle());
812    ASSERT_EQ(2, child->GetChildCount());
813
814    const BookmarkNode* parent = child;
815    child = parent->GetChild(0);
816    ASSERT_EQ(BookmarkNode::URL, child->type());
817    ASSERT_EQ(L"Google Advertising", child->GetTitle());
818    ASSERT_TRUE(child->GetURL() == GURL("http://www.google.com/intl/en/ads/"));
819
820    child = parent->GetChild(1);
821    ASSERT_TRUE(child->is_folder());
822    ASSERT_EQ(L"F11", child->GetTitle());
823    ASSERT_EQ(1, child->GetChildCount());
824
825    parent = child;
826    child = parent->GetChild(0);
827    ASSERT_EQ(BookmarkNode::URL, child->type());
828    ASSERT_EQ(L"Google Business Solutions", child->GetTitle());
829    ASSERT_TRUE(child->GetURL() == GURL("http://www.google.com/services/"));
830
831    parent = bb_model_->other_node();
832    ASSERT_EQ(2, parent->GetChildCount());
833
834    child = parent->GetChild(0);
835    ASSERT_TRUE(child->is_folder());
836    ASSERT_EQ(L"OF1", child->GetTitle());
837    ASSERT_EQ(0, child->GetChildCount());
838
839    child = parent->GetChild(1);
840    ASSERT_EQ(BookmarkNode::URL, child->type());
841    ASSERT_EQ(L"About Google", child->GetTitle());
842    ASSERT_TRUE(child->GetURL() ==
843                GURL("http://www.google.com/intl/en/about.html"));
844
845    ASSERT_TRUE(bb_model_->IsBookmarked(GURL("http://www.google.com")));
846  }
847
848  void VerifyUniqueIDs() {
849    std::set<int64> ids;
850    bool has_unique = true;
851    VerifyUniqueIDImpl(bb_model_->GetBookmarkBarNode(), &ids, &has_unique);
852    VerifyUniqueIDImpl(bb_model_->other_node(), &ids, &has_unique);
853    ASSERT_TRUE(has_unique);
854  }
855
856 private:
857  void VerifyUniqueIDImpl(const BookmarkNode* node,
858                          std::set<int64>* ids,
859                          bool* has_unique) {
860    if (!*has_unique)
861      return;
862    if (ids->count(node->id()) != 0) {
863      *has_unique = false;
864      return;
865    }
866    ids->insert(node->id());
867    for (int i = 0; i < node->GetChildCount(); ++i) {
868      VerifyUniqueIDImpl(node->GetChild(i), ids, has_unique);
869      if (!*has_unique)
870        return;
871    }
872  }
873};
874
875// Tests migrating bookmarks from db into file. This copies an old history db
876// file containing bookmarks and make sure they are loaded correctly and
877// persisted correctly.
878TEST_F(BookmarkModelTestWithProfile2, MigrateFromDBToFileTest) {
879  // Copy db file over that contains starred table.
880  FilePath old_history_path;
881  PathService::Get(chrome::DIR_TEST_DATA, &old_history_path);
882  old_history_path = old_history_path.AppendASCII("bookmarks");
883  old_history_path = old_history_path.AppendASCII("History_with_starred");
884  FilePath new_history_path = profile_->GetPath();
885  file_util::Delete(new_history_path, true);
886  file_util::CreateDirectory(new_history_path);
887  FilePath new_history_file = new_history_path.Append(
888      chrome::kHistoryFilename);
889  ASSERT_TRUE(file_util::CopyFile(old_history_path, new_history_file));
890
891  // Create the history service making sure it doesn't blow away the file we
892  // just copied.
893  profile_->CreateHistoryService(false, false);
894  profile_->CreateBookmarkModel(true);
895  BlockTillBookmarkModelLoaded();
896
897  // Make sure we loaded OK.
898  VerifyExpectedState();
899  if (HasFatalFailure())
900    return;
901
902  // Make sure the ids are unique.
903  VerifyUniqueIDs();
904  if (HasFatalFailure())
905    return;
906
907  // Create again. This time we shouldn't load from history at all.
908  profile_->CreateBookmarkModel(false);
909  BlockTillBookmarkModelLoaded();
910
911  // Make sure we loaded OK.
912  VerifyExpectedState();
913  if (HasFatalFailure())
914    return;
915
916  VerifyUniqueIDs();
917  if (HasFatalFailure())
918    return;
919
920  // Recreate the history service (with a clean db). Do this just to make sure
921  // we're loading correctly from the bookmarks file.
922  profile_->CreateHistoryService(true, false);
923  profile_->CreateBookmarkModel(false);
924  BlockTillBookmarkModelLoaded();
925  VerifyExpectedState();
926  VerifyUniqueIDs();
927}
928
929// Simple test that removes a bookmark. This test exercises the code paths in
930// History that block till bookmark bar model is loaded.
931TEST_F(BookmarkModelTestWithProfile2, RemoveNotification) {
932  profile_->CreateHistoryService(false, false);
933  profile_->CreateBookmarkModel(true);
934  BlockTillBookmarkModelLoaded();
935
936  // Add a URL.
937  GURL url("http://www.google.com");
938  bb_model_->SetURLStarred(url, std::wstring(), true);
939
940  profile_->GetHistoryService(Profile::EXPLICIT_ACCESS)->AddPage(
941      url, NULL, 1, GURL(), PageTransition::TYPED,
942      history::RedirectList(), false);
943
944  // This won't actually delete the URL, rather it'll empty out the visits.
945  // This triggers blocking on the BookmarkModel.
946  profile_->GetHistoryService(Profile::EXPLICIT_ACCESS)->DeleteURL(url);
947}
948
949TEST_F(BookmarkModelTest, Sort) {
950  // Populate the bookmark bar node with nodes for 'B', 'a', 'd' and 'C'.
951  // 'C' and 'a' are folders.
952  TestNode bbn;
953  PopulateNodeFromString(L"B [ a ] d [ a ]", &bbn);
954  const BookmarkNode* parent = model.GetBookmarkBarNode();
955  PopulateBookmarkNode(&bbn, &model, parent);
956
957  BookmarkNode* child1 = AsMutable(parent->GetChild(1));
958  child1->SetTitle(L"a");
959  delete child1->Remove(0);
960  BookmarkNode* child3 = AsMutable(parent->GetChild(3));
961  child3->SetTitle(L"C");
962  delete child3->Remove(0);
963
964  ClearCounts();
965
966  // Sort the children of the bookmark bar node.
967  model.SortChildren(parent);
968
969  // Make sure we were notified.
970  AssertObserverCount(0, 0, 0, 0, 1);
971
972  // Make sure the order matches (remember, 'a' and 'C' are folders and
973  // come first).
974  EXPECT_TRUE(parent->GetChild(0)->GetTitle() == L"a");
975  EXPECT_TRUE(parent->GetChild(1)->GetTitle() == L"C");
976  EXPECT_TRUE(parent->GetChild(2)->GetTitle() == L"B");
977  EXPECT_TRUE(parent->GetChild(3)->GetTitle() == L"d");
978}
979