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