| // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <set> |
| #include <string> |
| |
| #include "base/base_paths.h" |
| #include "base/file_util.h" |
| #include "base/hash_tables.h" |
| #include "base/path_service.h" |
| #include "base/string16.h" |
| #include "base/string_number_conversions.h" |
| #include "base/string_split.h" |
| #include "base/string_util.h" |
| #include "base/utf_string_conversions.h" |
| #include "chrome/browser/bookmarks/bookmark_codec.h" |
| #include "chrome/browser/bookmarks/bookmark_model.h" |
| #include "chrome/browser/bookmarks/bookmark_utils.h" |
| #include "chrome/browser/history/history_notifications.h" |
| #include "chrome/common/chrome_constants.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "chrome/test/model_test_utils.h" |
| #include "chrome/test/testing_browser_process_test.h" |
| #include "chrome/test/testing_profile.h" |
| #include "content/browser/browser_thread.h" |
| #include "content/common/notification_details.h" |
| #include "content/common/notification_registrar.h" |
| #include "content/common/notification_source.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/base/models/tree_node_iterator.h" |
| #include "ui/base/models/tree_node_model.h" |
| |
| using base::Time; |
| using base::TimeDelta; |
| |
| namespace { |
| |
| // Helper to get a mutable bookmark node. |
| static BookmarkNode* AsMutable(const BookmarkNode* node) { |
| return const_cast<BookmarkNode*>(node); |
| } |
| |
| void SwapDateAdded(BookmarkNode* n1, BookmarkNode* n2) { |
| Time tmp = n1->date_added(); |
| n1->set_date_added(n2->date_added()); |
| n2->set_date_added(tmp); |
| } |
| |
| } // anonymous namespace |
| |
| class BookmarkModelTest : public TestingBrowserProcessTest, |
| public BookmarkModelObserver { |
| public: |
| struct ObserverDetails { |
| ObserverDetails() { |
| Set(NULL, NULL, -1, -1); |
| } |
| |
| void Set(const BookmarkNode* node1, |
| const BookmarkNode* node2, |
| int index1, |
| int index2) { |
| this->node1 = node1; |
| this->node2 = node2; |
| this->index1 = index1; |
| this->index2 = index2; |
| } |
| |
| void AssertEquals(const BookmarkNode* node1, |
| const BookmarkNode* node2, |
| int index1, |
| int index2) { |
| ASSERT_TRUE(this->node1 == node1); |
| ASSERT_TRUE(this->node2 == node2); |
| ASSERT_EQ(index1, this->index1); |
| ASSERT_EQ(index2, this->index2); |
| } |
| |
| const BookmarkNode* node1; |
| const BookmarkNode* node2; |
| int index1; |
| int index2; |
| }; |
| |
| BookmarkModelTest() : model(NULL) { |
| model.AddObserver(this); |
| ClearCounts(); |
| } |
| |
| |
| void Loaded(BookmarkModel* model) { |
| // We never load from the db, so that this should never get invoked. |
| NOTREACHED(); |
| } |
| |
| virtual void BookmarkNodeMoved(BookmarkModel* model, |
| const BookmarkNode* old_parent, |
| int old_index, |
| const BookmarkNode* new_parent, |
| int new_index) { |
| moved_count++; |
| observer_details.Set(old_parent, new_parent, old_index, new_index); |
| } |
| |
| virtual void BookmarkNodeAdded(BookmarkModel* model, |
| const BookmarkNode* parent, |
| int index) { |
| added_count++; |
| observer_details.Set(parent, NULL, index, -1); |
| } |
| |
| virtual void BookmarkNodeRemoved(BookmarkModel* model, |
| const BookmarkNode* parent, |
| int old_index, |
| const BookmarkNode* node) { |
| removed_count++; |
| observer_details.Set(parent, NULL, old_index, -1); |
| } |
| |
| virtual void BookmarkNodeChanged(BookmarkModel* model, |
| const BookmarkNode* node) { |
| changed_count++; |
| observer_details.Set(node, NULL, -1, -1); |
| } |
| |
| virtual void BookmarkNodeChildrenReordered(BookmarkModel* model, |
| const BookmarkNode* node) { |
| reordered_count_++; |
| } |
| |
| virtual void BookmarkNodeFaviconLoaded(BookmarkModel* model, |
| const BookmarkNode* node) { |
| // We never attempt to load favicons, so that this method never |
| // gets invoked. |
| } |
| |
| void ClearCounts() { |
| reordered_count_ = moved_count = added_count = removed_count = |
| changed_count = 0; |
| } |
| |
| void AssertObserverCount(int added_count, |
| int moved_count, |
| int removed_count, |
| int changed_count, |
| int reordered_count) { |
| ASSERT_EQ(added_count, this->added_count); |
| ASSERT_EQ(moved_count, this->moved_count); |
| ASSERT_EQ(removed_count, this->removed_count); |
| ASSERT_EQ(changed_count, this->changed_count); |
| ASSERT_EQ(reordered_count, reordered_count_); |
| } |
| |
| BookmarkModel model; |
| |
| int moved_count; |
| |
| int added_count; |
| |
| int removed_count; |
| |
| int changed_count; |
| |
| int reordered_count_; |
| |
| ObserverDetails observer_details; |
| }; |
| |
| TEST_F(BookmarkModelTest, InitialState) { |
| const BookmarkNode* bb_node = model.GetBookmarkBarNode(); |
| ASSERT_TRUE(bb_node != NULL); |
| EXPECT_EQ(0, bb_node->child_count()); |
| EXPECT_EQ(BookmarkNode::BOOKMARK_BAR, bb_node->type()); |
| |
| const BookmarkNode* other_node = model.other_node(); |
| ASSERT_TRUE(other_node != NULL); |
| EXPECT_EQ(0, other_node->child_count()); |
| EXPECT_EQ(BookmarkNode::OTHER_NODE, other_node->type()); |
| |
| EXPECT_TRUE(bb_node->id() != other_node->id()); |
| } |
| |
| TEST_F(BookmarkModelTest, AddURL) { |
| const BookmarkNode* root = model.GetBookmarkBarNode(); |
| const string16 title(ASCIIToUTF16("foo")); |
| const GURL url("http://foo.com"); |
| |
| const BookmarkNode* new_node = model.AddURL(root, 0, title, url); |
| AssertObserverCount(1, 0, 0, 0, 0); |
| observer_details.AssertEquals(root, NULL, 0, -1); |
| |
| ASSERT_EQ(1, root->child_count()); |
| ASSERT_EQ(title, new_node->GetTitle()); |
| ASSERT_TRUE(url == new_node->GetURL()); |
| ASSERT_EQ(BookmarkNode::URL, new_node->type()); |
| ASSERT_TRUE(new_node == model.GetMostRecentlyAddedNodeForURL(url)); |
| |
| EXPECT_TRUE(new_node->id() != root->id() && |
| new_node->id() != model.other_node()->id()); |
| } |
| |
| TEST_F(BookmarkModelTest, AddFolder) { |
| const BookmarkNode* root = model.GetBookmarkBarNode(); |
| const string16 title(ASCIIToUTF16("foo")); |
| |
| const BookmarkNode* new_node = model.AddFolder(root, 0, title); |
| AssertObserverCount(1, 0, 0, 0, 0); |
| observer_details.AssertEquals(root, NULL, 0, -1); |
| |
| ASSERT_EQ(1, root->child_count()); |
| ASSERT_EQ(title, new_node->GetTitle()); |
| ASSERT_EQ(BookmarkNode::FOLDER, new_node->type()); |
| |
| EXPECT_TRUE(new_node->id() != root->id() && |
| new_node->id() != model.other_node()->id()); |
| |
| // Add another folder, just to make sure folder_ids are incremented correctly. |
| ClearCounts(); |
| model.AddFolder(root, 0, title); |
| AssertObserverCount(1, 0, 0, 0, 0); |
| observer_details.AssertEquals(root, NULL, 0, -1); |
| } |
| |
| TEST_F(BookmarkModelTest, RemoveURL) { |
| const BookmarkNode* root = model.GetBookmarkBarNode(); |
| const string16 title(ASCIIToUTF16("foo")); |
| const GURL url("http://foo.com"); |
| model.AddURL(root, 0, title, url); |
| ClearCounts(); |
| |
| model.Remove(root, 0); |
| ASSERT_EQ(0, root->child_count()); |
| AssertObserverCount(0, 0, 1, 0, 0); |
| observer_details.AssertEquals(root, NULL, 0, -1); |
| |
| // Make sure there is no mapping for the URL. |
| ASSERT_TRUE(model.GetMostRecentlyAddedNodeForURL(url) == NULL); |
| } |
| |
| TEST_F(BookmarkModelTest, RemoveFolder) { |
| const BookmarkNode* root = model.GetBookmarkBarNode(); |
| const BookmarkNode* folder = model.AddFolder(root, 0, ASCIIToUTF16("foo")); |
| |
| ClearCounts(); |
| |
| // Add a URL as a child. |
| const string16 title(ASCIIToUTF16("foo")); |
| const GURL url("http://foo.com"); |
| model.AddURL(folder, 0, title, url); |
| |
| ClearCounts(); |
| |
| // Now remove the folder. |
| model.Remove(root, 0); |
| ASSERT_EQ(0, root->child_count()); |
| AssertObserverCount(0, 0, 1, 0, 0); |
| observer_details.AssertEquals(root, NULL, 0, -1); |
| |
| // Make sure there is no mapping for the URL. |
| ASSERT_TRUE(model.GetMostRecentlyAddedNodeForURL(url) == NULL); |
| } |
| |
| TEST_F(BookmarkModelTest, SetTitle) { |
| const BookmarkNode* root = model.GetBookmarkBarNode(); |
| string16 title(ASCIIToUTF16("foo")); |
| const GURL url("http://foo.com"); |
| const BookmarkNode* node = model.AddURL(root, 0, title, url); |
| |
| ClearCounts(); |
| |
| title = ASCIIToUTF16("foo2"); |
| model.SetTitle(node, title); |
| AssertObserverCount(0, 0, 0, 1, 0); |
| observer_details.AssertEquals(node, NULL, -1, -1); |
| EXPECT_EQ(title, node->GetTitle()); |
| } |
| |
| TEST_F(BookmarkModelTest, SetURL) { |
| const BookmarkNode* root = model.GetBookmarkBarNode(); |
| const string16 title(ASCIIToUTF16("foo")); |
| GURL url("http://foo.com"); |
| const BookmarkNode* node = model.AddURL(root, 0, title, url); |
| |
| ClearCounts(); |
| |
| url = GURL("http://foo2.com"); |
| model.SetURL(node, url); |
| AssertObserverCount(0, 0, 0, 1, 0); |
| observer_details.AssertEquals(node, NULL, -1, -1); |
| EXPECT_EQ(url, node->GetURL()); |
| } |
| |
| TEST_F(BookmarkModelTest, Move) { |
| const BookmarkNode* root = model.GetBookmarkBarNode(); |
| const string16 title(ASCIIToUTF16("foo")); |
| const GURL url("http://foo.com"); |
| const BookmarkNode* node = model.AddURL(root, 0, title, url); |
| const BookmarkNode* folder1 = model.AddFolder(root, 0, ASCIIToUTF16("foo")); |
| ClearCounts(); |
| |
| model.Move(node, folder1, 0); |
| |
| AssertObserverCount(0, 1, 0, 0, 0); |
| observer_details.AssertEquals(root, folder1, 1, 0); |
| EXPECT_TRUE(folder1 == node->parent()); |
| EXPECT_EQ(1, root->child_count()); |
| EXPECT_EQ(folder1, root->GetChild(0)); |
| EXPECT_EQ(1, folder1->child_count()); |
| EXPECT_EQ(node, folder1->GetChild(0)); |
| |
| // And remove the folder. |
| ClearCounts(); |
| model.Remove(root, 0); |
| AssertObserverCount(0, 0, 1, 0, 0); |
| observer_details.AssertEquals(root, NULL, 0, -1); |
| EXPECT_TRUE(model.GetMostRecentlyAddedNodeForURL(url) == NULL); |
| EXPECT_EQ(0, root->child_count()); |
| } |
| |
| TEST_F(BookmarkModelTest, Copy) { |
| const BookmarkNode* root = model.GetBookmarkBarNode(); |
| static const std::string model_string("a 1:[ b c ] d 2:[ e f g ] h "); |
| model_test_utils::AddNodesFromModelString(model, root, model_string); |
| |
| // Validate initial model. |
| std::string actualModelString = model_test_utils::ModelStringFromNode(root); |
| EXPECT_EQ(model_string, actualModelString); |
| |
| // Copy 'd' to be after '1:b': URL item from bar to folder. |
| const BookmarkNode* nodeToCopy = root->GetChild(2); |
| const BookmarkNode* destination = root->GetChild(1); |
| model.Copy(nodeToCopy, destination, 1); |
| actualModelString = model_test_utils::ModelStringFromNode(root); |
| EXPECT_EQ("a 1:[ b d c ] d 2:[ e f g ] h ", actualModelString); |
| |
| // Copy '1:d' to be after 'a': URL item from folder to bar. |
| const BookmarkNode* folder = root->GetChild(1); |
| nodeToCopy = folder->GetChild(1); |
| model.Copy(nodeToCopy, root, 1); |
| actualModelString = model_test_utils::ModelStringFromNode(root); |
| EXPECT_EQ("a d 1:[ b d c ] d 2:[ e f g ] h ", actualModelString); |
| |
| // Copy '1' to be after '2:e': Folder from bar to folder. |
| nodeToCopy = root->GetChild(2); |
| destination = root->GetChild(4); |
| model.Copy(nodeToCopy, destination, 1); |
| actualModelString = model_test_utils::ModelStringFromNode(root); |
| EXPECT_EQ("a d 1:[ b d c ] d 2:[ e 1:[ b d c ] f g ] h ", actualModelString); |
| |
| // Copy '2:1' to be after '2:f': Folder within same folder. |
| folder = root->GetChild(4); |
| nodeToCopy = folder->GetChild(1); |
| model.Copy(nodeToCopy, folder, 3); |
| actualModelString = model_test_utils::ModelStringFromNode(root); |
| EXPECT_EQ("a d 1:[ b d c ] d 2:[ e 1:[ b d c ] f 1:[ b d c ] g ] h ", |
| actualModelString); |
| |
| // Copy first 'd' to be after 'h': URL item within the bar. |
| nodeToCopy = root->GetChild(1); |
| model.Copy(nodeToCopy, root, 6); |
| actualModelString = model_test_utils::ModelStringFromNode(root); |
| EXPECT_EQ("a d 1:[ b d c ] d 2:[ e 1:[ b d c ] f 1:[ b d c ] g ] h d ", |
| actualModelString); |
| |
| // Copy '2' to be after 'a': Folder within the bar. |
| nodeToCopy = root->GetChild(4); |
| model.Copy(nodeToCopy, root, 1); |
| actualModelString = model_test_utils::ModelStringFromNode(root); |
| EXPECT_EQ("a 2:[ e 1:[ b d c ] f 1:[ b d c ] g ] d 1:[ b d c ] " |
| "d 2:[ e 1:[ b d c ] f 1:[ b d c ] g ] h d ", |
| actualModelString); |
| } |
| |
| // Tests that adding a URL to a folder updates the last modified time. |
| TEST_F(BookmarkModelTest, ParentForNewNodes) { |
| ASSERT_EQ(model.GetBookmarkBarNode(), model.GetParentForNewNodes()); |
| |
| const string16 title(ASCIIToUTF16("foo")); |
| const GURL url("http://foo.com"); |
| |
| model.AddURL(model.other_node(), 0, title, url); |
| ASSERT_EQ(model.other_node(), model.GetParentForNewNodes()); |
| } |
| |
| // Make sure recently modified stays in sync when adding a URL. |
| TEST_F(BookmarkModelTest, MostRecentlyModifiedFolders) { |
| // Add a folder. |
| const BookmarkNode* folder = model.AddFolder(model.other_node(), 0, |
| ASCIIToUTF16("foo")); |
| // Add a URL to it. |
| model.AddURL(folder, 0, ASCIIToUTF16("blah"), GURL("http://foo.com")); |
| |
| // Make sure folder is in the most recently modified. |
| std::vector<const BookmarkNode*> most_recent_folders = |
| bookmark_utils::GetMostRecentlyModifiedFolders(&model, 1); |
| ASSERT_EQ(1U, most_recent_folders.size()); |
| ASSERT_EQ(folder, most_recent_folders[0]); |
| |
| // Nuke the folder and do another fetch, making sure folder isn't in the |
| // returned list. |
| model.Remove(folder->parent(), 0); |
| most_recent_folders = |
| bookmark_utils::GetMostRecentlyModifiedFolders(&model, 1); |
| ASSERT_EQ(1U, most_recent_folders.size()); |
| ASSERT_TRUE(most_recent_folders[0] != folder); |
| } |
| |
| // Make sure MostRecentlyAddedEntries stays in sync. |
| TEST_F(BookmarkModelTest, MostRecentlyAddedEntries) { |
| // Add a couple of nodes such that the following holds for the time of the |
| // nodes: n1 > n2 > n3 > n4. |
| Time base_time = Time::Now(); |
| BookmarkNode* n1 = AsMutable(model.AddURL(model.GetBookmarkBarNode(), |
| 0, |
| ASCIIToUTF16("blah"), |
| GURL("http://foo.com/0"))); |
| BookmarkNode* n2 = AsMutable(model.AddURL(model.GetBookmarkBarNode(), |
| 1, |
| ASCIIToUTF16("blah"), |
| GURL("http://foo.com/1"))); |
| BookmarkNode* n3 = AsMutable(model.AddURL(model.GetBookmarkBarNode(), |
| 2, |
| ASCIIToUTF16("blah"), |
| GURL("http://foo.com/2"))); |
| BookmarkNode* n4 = AsMutable(model.AddURL(model.GetBookmarkBarNode(), |
| 3, |
| ASCIIToUTF16("blah"), |
| GURL("http://foo.com/3"))); |
| n1->set_date_added(base_time + TimeDelta::FromDays(4)); |
| n2->set_date_added(base_time + TimeDelta::FromDays(3)); |
| n3->set_date_added(base_time + TimeDelta::FromDays(2)); |
| n4->set_date_added(base_time + TimeDelta::FromDays(1)); |
| |
| // Make sure order is honored. |
| std::vector<const BookmarkNode*> recently_added; |
| bookmark_utils::GetMostRecentlyAddedEntries(&model, 2, &recently_added); |
| ASSERT_EQ(2U, recently_added.size()); |
| ASSERT_TRUE(n1 == recently_added[0]); |
| ASSERT_TRUE(n2 == recently_added[1]); |
| |
| // swap 1 and 2, then check again. |
| recently_added.clear(); |
| SwapDateAdded(n1, n2); |
| bookmark_utils::GetMostRecentlyAddedEntries(&model, 4, &recently_added); |
| ASSERT_EQ(4U, recently_added.size()); |
| ASSERT_TRUE(n2 == recently_added[0]); |
| ASSERT_TRUE(n1 == recently_added[1]); |
| ASSERT_TRUE(n3 == recently_added[2]); |
| ASSERT_TRUE(n4 == recently_added[3]); |
| } |
| |
| // Makes sure GetMostRecentlyAddedNodeForURL stays in sync. |
| TEST_F(BookmarkModelTest, GetMostRecentlyAddedNodeForURL) { |
| // Add a couple of nodes such that the following holds for the time of the |
| // nodes: n1 > n2 |
| Time base_time = Time::Now(); |
| const GURL url("http://foo.com/0"); |
| BookmarkNode* n1 = AsMutable(model.AddURL( |
| model.GetBookmarkBarNode(), 0, ASCIIToUTF16("blah"), url)); |
| BookmarkNode* n2 = AsMutable(model.AddURL( |
| model.GetBookmarkBarNode(), 1, ASCIIToUTF16("blah"), url)); |
| n1->set_date_added(base_time + TimeDelta::FromDays(4)); |
| n2->set_date_added(base_time + TimeDelta::FromDays(3)); |
| |
| // Make sure order is honored. |
| ASSERT_EQ(n1, model.GetMostRecentlyAddedNodeForURL(url)); |
| |
| // swap 1 and 2, then check again. |
| SwapDateAdded(n1, n2); |
| ASSERT_EQ(n2, model.GetMostRecentlyAddedNodeForURL(url)); |
| } |
| |
| // Makes sure GetBookmarks removes duplicates. |
| TEST_F(BookmarkModelTest, GetBookmarksWithDups) { |
| const GURL url("http://foo.com/0"); |
| model.AddURL(model.GetBookmarkBarNode(), 0, ASCIIToUTF16("blah"), url); |
| model.AddURL(model.GetBookmarkBarNode(), 1, ASCIIToUTF16("blah"), url); |
| |
| std::vector<GURL> urls; |
| model.GetBookmarks(&urls); |
| EXPECT_EQ(1U, urls.size()); |
| ASSERT_TRUE(urls[0] == url); |
| } |
| |
| TEST_F(BookmarkModelTest, HasBookmarks) { |
| const GURL url("http://foo.com/"); |
| model.AddURL(model.GetBookmarkBarNode(), 0, ASCIIToUTF16("bar"), url); |
| |
| EXPECT_TRUE(model.HasBookmarks()); |
| } |
| |
| namespace { |
| |
| // NotificationObserver implementation used in verifying we've received the |
| // NOTIFY_URLS_STARRED method correctly. |
| class StarredListener : public NotificationObserver { |
| public: |
| StarredListener() : notification_count_(0), details_(false) { |
| registrar_.Add(this, NotificationType::URLS_STARRED, Source<Profile>(NULL)); |
| } |
| |
| virtual void Observe(NotificationType type, |
| const NotificationSource& source, |
| const NotificationDetails& details) { |
| if (type == NotificationType::URLS_STARRED) { |
| notification_count_++; |
| details_ = *(Details<history::URLsStarredDetails>(details).ptr()); |
| } |
| } |
| |
| // Number of times NOTIFY_URLS_STARRED has been observed. |
| int notification_count_; |
| |
| // Details from the last NOTIFY_URLS_STARRED. |
| history::URLsStarredDetails details_; |
| |
| private: |
| NotificationRegistrar registrar_; |
| |
| DISALLOW_COPY_AND_ASSIGN(StarredListener); |
| }; |
| |
| } // namespace |
| |
| // Makes sure NOTIFY_URLS_STARRED is sent correctly. |
| TEST_F(BookmarkModelTest, NotifyURLsStarred) { |
| StarredListener listener; |
| const GURL url("http://foo.com/0"); |
| const BookmarkNode* n1 = model.AddURL( |
| model.GetBookmarkBarNode(), 0, ASCIIToUTF16("blah"), url); |
| |
| // Starred notification should be sent. |
| EXPECT_EQ(1, listener.notification_count_); |
| ASSERT_TRUE(listener.details_.starred); |
| ASSERT_EQ(1U, listener.details_.changed_urls.size()); |
| EXPECT_TRUE(url == *(listener.details_.changed_urls.begin())); |
| listener.notification_count_ = 0; |
| listener.details_.changed_urls.clear(); |
| |
| // Add another bookmark for the same URL. This should not send any |
| // notification. |
| const BookmarkNode* n2 = model.AddURL( |
| model.GetBookmarkBarNode(), 1, ASCIIToUTF16("blah"), url); |
| |
| EXPECT_EQ(0, listener.notification_count_); |
| |
| // Remove n2. |
| model.Remove(n2->parent(), 1); |
| n2 = NULL; |
| |
| // Shouldn't have received any notification as n1 still exists with the same |
| // URL. |
| EXPECT_EQ(0, listener.notification_count_); |
| |
| EXPECT_TRUE(model.GetMostRecentlyAddedNodeForURL(url) == n1); |
| |
| // Remove n1. |
| model.Remove(n1->parent(), 0); |
| |
| // Now we should get the notification. |
| EXPECT_EQ(1, listener.notification_count_); |
| ASSERT_FALSE(listener.details_.starred); |
| ASSERT_EQ(1U, listener.details_.changed_urls.size()); |
| EXPECT_TRUE(url == *(listener.details_.changed_urls.begin())); |
| } |
| |
| namespace { |
| |
| // See comment in PopulateNodeFromString. |
| typedef ui::TreeNodeWithValue<BookmarkNode::Type> TestNode; |
| |
| // Does the work of PopulateNodeFromString. index gives the index of the current |
| // element in description to process. |
| static void PopulateNodeImpl(const std::vector<std::string>& description, |
| size_t* index, |
| TestNode* parent) { |
| while (*index < description.size()) { |
| const std::string& element = description[*index]; |
| (*index)++; |
| if (element == "[") { |
| // Create a new folder and recurse to add all the children. |
| // Folders are given a unique named by way of an ever increasing integer |
| // value. The folders need not have a name, but one is assigned to help |
| // in debugging. |
| static int next_folder_id = 1; |
| TestNode* new_node = |
| new TestNode(base::IntToString16(next_folder_id++), |
| BookmarkNode::FOLDER); |
| parent->Add(new_node, parent->child_count()); |
| PopulateNodeImpl(description, index, new_node); |
| } else if (element == "]") { |
| // End the current folder. |
| return; |
| } else { |
| // Add a new URL. |
| |
| // All tokens must be space separated. If there is a [ or ] in the name it |
| // likely means a space was forgotten. |
| DCHECK(element.find('[') == std::string::npos); |
| DCHECK(element.find(']') == std::string::npos); |
| parent->Add(new TestNode(UTF8ToUTF16(element), BookmarkNode::URL), |
| parent->child_count()); |
| } |
| } |
| } |
| |
| // Creates and adds nodes to parent based on description. description consists |
| // of the following tokens (all space separated): |
| // [ : creates a new USER_FOLDER node. All elements following the [ until the |
| // next balanced ] is encountered are added as children to the node. |
| // ] : closes the last folder created by [ so that any further nodes are added |
| // to the current folders parent. |
| // text: creates a new URL node. |
| // For example, "a [b] c" creates the following nodes: |
| // a 1 c |
| // | |
| // b |
| // In words: a node of type URL with the title a, followed by a folder node with |
| // the title 1 having the single child of type url with name b, followed by |
| // the url node with the title c. |
| // |
| // NOTE: each name must be unique, and folders are assigned a unique title by |
| // way of an increasing integer. |
| static void PopulateNodeFromString(const std::string& description, |
| TestNode* parent) { |
| std::vector<std::string> elements; |
| size_t index = 0; |
| base::SplitStringAlongWhitespace(description, &elements); |
| PopulateNodeImpl(elements, &index, parent); |
| } |
| |
| // Populates the BookmarkNode with the children of parent. |
| static void PopulateBookmarkNode(TestNode* parent, |
| BookmarkModel* model, |
| const BookmarkNode* bb_node) { |
| for (int i = 0; i < parent->child_count(); ++i) { |
| TestNode* child = parent->GetChild(i); |
| if (child->value == BookmarkNode::FOLDER) { |
| const BookmarkNode* new_bb_node = |
| model->AddFolder(bb_node, i, child->GetTitle()); |
| PopulateBookmarkNode(child, model, new_bb_node); |
| } else { |
| model->AddURL(bb_node, i, child->GetTitle(), |
| GURL("http://" + UTF16ToASCII(child->GetTitle()))); |
| } |
| } |
| } |
| |
| } // namespace |
| |
| // Test class that creates a BookmarkModel with a real history backend. |
| class BookmarkModelTestWithProfile : public TestingBrowserProcessTest, |
| public BookmarkModelObserver { |
| public: |
| BookmarkModelTestWithProfile() |
| : ui_thread_(BrowserThread::UI, &message_loop_), |
| file_thread_(BrowserThread::FILE, &message_loop_) {} |
| |
| virtual void SetUp() { |
| } |
| |
| virtual void TearDown() { |
| profile_.reset(NULL); |
| } |
| |
| // The profile. |
| scoped_ptr<TestingProfile> profile_; |
| |
| protected: |
| // Verifies the contents of the bookmark bar node match the contents of the |
| // TestNode. |
| void VerifyModelMatchesNode(TestNode* expected, const BookmarkNode* actual) { |
| ASSERT_EQ(expected->child_count(), actual->child_count()); |
| for (int i = 0; i < expected->child_count(); ++i) { |
| TestNode* expected_child = expected->GetChild(i); |
| const BookmarkNode* actual_child = actual->GetChild(i); |
| ASSERT_EQ(expected_child->GetTitle(), actual_child->GetTitle()); |
| if (expected_child->value == BookmarkNode::FOLDER) { |
| ASSERT_TRUE(actual_child->type() == BookmarkNode::FOLDER); |
| // Recurse throught children. |
| VerifyModelMatchesNode(expected_child, actual_child); |
| if (HasFatalFailure()) |
| return; |
| } else { |
| // No need to check the URL, just the title is enough. |
| ASSERT_TRUE(actual_child->type() == BookmarkNode::URL); |
| } |
| } |
| } |
| |
| void VerifyNoDuplicateIDs(BookmarkModel* model) { |
| ui::TreeNodeIterator<const BookmarkNode> it(model->root_node()); |
| base::hash_set<int64> ids; |
| while (it.has_next()) |
| ASSERT_TRUE(ids.insert(it.Next()->id()).second); |
| } |
| |
| void BlockTillBookmarkModelLoaded() { |
| bb_model_ = profile_->GetBookmarkModel(); |
| if (!bb_model_->IsLoaded()) |
| BlockTillLoaded(bb_model_); |
| else |
| bb_model_->AddObserver(this); |
| } |
| |
| // Destroys the current profile, creates a new one and creates the history |
| // service. |
| void RecreateProfile() { |
| // Need to shutdown the old one before creating a new one. |
| profile_.reset(NULL); |
| profile_.reset(new TestingProfile()); |
| profile_->CreateHistoryService(true, false); |
| } |
| |
| BookmarkModel* bb_model_; |
| |
| private: |
| // Blocks until the BookmarkModel has finished loading. |
| void BlockTillLoaded(BookmarkModel* model) { |
| model->AddObserver(this); |
| MessageLoop::current()->Run(); |
| } |
| |
| // BookmarkModelObserver methods. |
| virtual void Loaded(BookmarkModel* model) { |
| // Balances the call in BlockTillLoaded. |
| MessageLoop::current()->Quit(); |
| } |
| virtual void BookmarkNodeMoved(BookmarkModel* model, |
| const BookmarkNode* old_parent, |
| int old_index, |
| const BookmarkNode* new_parent, |
| int new_index) {} |
| virtual void BookmarkNodeAdded(BookmarkModel* model, |
| const BookmarkNode* parent, |
| int index) {} |
| virtual void BookmarkNodeRemoved(BookmarkModel* model, |
| const BookmarkNode* parent, |
| int old_index, |
| const BookmarkNode* node) {} |
| virtual void BookmarkNodeChanged(BookmarkModel* model, |
| const BookmarkNode* node) {} |
| virtual void BookmarkNodeChildrenReordered(BookmarkModel* model, |
| const BookmarkNode* node) {} |
| virtual void BookmarkNodeFaviconLoaded(BookmarkModel* model, |
| const BookmarkNode* node) {} |
| |
| MessageLoopForUI message_loop_; |
| BrowserThread ui_thread_; |
| BrowserThread file_thread_; |
| }; |
| |
| // Creates a set of nodes in the bookmark bar model, then recreates the |
| // bookmark bar model which triggers loading from the db and checks the loaded |
| // structure to make sure it is what we first created. |
| TEST_F(BookmarkModelTestWithProfile, CreateAndRestore) { |
| struct TestData { |
| // Structure of the children of the bookmark bar model node. |
| const std::string bbn_contents; |
| // Structure of the children of the other node. |
| const std::string other_contents; |
| } data[] = { |
| // See PopulateNodeFromString for a description of these strings. |
| { "", "" }, |
| { "a", "b" }, |
| { "a [ b ]", "" }, |
| { "", "[ b ] a [ c [ d e [ f ] ] ]" }, |
| { "a [ b ]", "" }, |
| { "a b c [ d e [ f ] ]", "g h i [ j k [ l ] ]"}, |
| }; |
| for (size_t i = 0; i < ARRAYSIZE_UNSAFE(data); ++i) { |
| // Recreate the profile. We need to reset with NULL first so that the last |
| // HistoryService releases the locks on the files it creates and we can |
| // delete them. |
| profile_.reset(NULL); |
| profile_.reset(new TestingProfile()); |
| profile_->CreateBookmarkModel(true); |
| profile_->CreateHistoryService(true, false); |
| BlockTillBookmarkModelLoaded(); |
| |
| TestNode bbn; |
| PopulateNodeFromString(data[i].bbn_contents, &bbn); |
| PopulateBookmarkNode(&bbn, bb_model_, bb_model_->GetBookmarkBarNode()); |
| |
| TestNode other; |
| PopulateNodeFromString(data[i].other_contents, &other); |
| PopulateBookmarkNode(&other, bb_model_, bb_model_->other_node()); |
| |
| profile_->CreateBookmarkModel(false); |
| BlockTillBookmarkModelLoaded(); |
| |
| VerifyModelMatchesNode(&bbn, bb_model_->GetBookmarkBarNode()); |
| VerifyModelMatchesNode(&other, bb_model_->other_node()); |
| VerifyNoDuplicateIDs(bb_model_); |
| } |
| } |
| |
| // Test class that creates a BookmarkModel with a real history backend. |
| class BookmarkModelTestWithProfile2 : public BookmarkModelTestWithProfile { |
| public: |
| virtual void SetUp() { |
| profile_.reset(new TestingProfile()); |
| } |
| |
| protected: |
| // Verifies the state of the model matches that of the state in the saved |
| // history file. |
| void VerifyExpectedState() { |
| // Here's the structure we expect: |
| // bbn |
| // www.google.com - Google |
| // F1 |
| // http://www.google.com/intl/en/ads/ - Google Advertising |
| // F11 |
| // http://www.google.com/services/ - Google Business Solutions |
| // other |
| // OF1 |
| // http://www.google.com/intl/en/about.html - About Google |
| const BookmarkNode* bbn = bb_model_->GetBookmarkBarNode(); |
| ASSERT_EQ(2, bbn->child_count()); |
| |
| const BookmarkNode* child = bbn->GetChild(0); |
| ASSERT_EQ(BookmarkNode::URL, child->type()); |
| ASSERT_EQ(ASCIIToUTF16("Google"), child->GetTitle()); |
| ASSERT_TRUE(child->GetURL() == GURL("http://www.google.com")); |
| |
| child = bbn->GetChild(1); |
| ASSERT_TRUE(child->is_folder()); |
| ASSERT_EQ(ASCIIToUTF16("F1"), child->GetTitle()); |
| ASSERT_EQ(2, child->child_count()); |
| |
| const BookmarkNode* parent = child; |
| child = parent->GetChild(0); |
| ASSERT_EQ(BookmarkNode::URL, child->type()); |
| ASSERT_EQ(ASCIIToUTF16("Google Advertising"), child->GetTitle()); |
| ASSERT_TRUE(child->GetURL() == GURL("http://www.google.com/intl/en/ads/")); |
| |
| child = parent->GetChild(1); |
| ASSERT_TRUE(child->is_folder()); |
| ASSERT_EQ(ASCIIToUTF16("F11"), child->GetTitle()); |
| ASSERT_EQ(1, child->child_count()); |
| |
| parent = child; |
| child = parent->GetChild(0); |
| ASSERT_EQ(BookmarkNode::URL, child->type()); |
| ASSERT_EQ(ASCIIToUTF16("Google Business Solutions"), child->GetTitle()); |
| ASSERT_TRUE(child->GetURL() == GURL("http://www.google.com/services/")); |
| |
| parent = bb_model_->other_node(); |
| ASSERT_EQ(2, parent->child_count()); |
| |
| child = parent->GetChild(0); |
| ASSERT_TRUE(child->is_folder()); |
| ASSERT_EQ(ASCIIToUTF16("OF1"), child->GetTitle()); |
| ASSERT_EQ(0, child->child_count()); |
| |
| child = parent->GetChild(1); |
| ASSERT_EQ(BookmarkNode::URL, child->type()); |
| ASSERT_EQ(ASCIIToUTF16("About Google"), child->GetTitle()); |
| ASSERT_TRUE(child->GetURL() == |
| GURL("http://www.google.com/intl/en/about.html")); |
| |
| ASSERT_TRUE(bb_model_->IsBookmarked(GURL("http://www.google.com"))); |
| } |
| |
| void VerifyUniqueIDs() { |
| std::set<int64> ids; |
| bool has_unique = true; |
| VerifyUniqueIDImpl(bb_model_->GetBookmarkBarNode(), &ids, &has_unique); |
| VerifyUniqueIDImpl(bb_model_->other_node(), &ids, &has_unique); |
| ASSERT_TRUE(has_unique); |
| } |
| |
| private: |
| void VerifyUniqueIDImpl(const BookmarkNode* node, |
| std::set<int64>* ids, |
| bool* has_unique) { |
| if (!*has_unique) |
| return; |
| if (ids->count(node->id()) != 0) { |
| *has_unique = false; |
| return; |
| } |
| ids->insert(node->id()); |
| for (int i = 0; i < node->child_count(); ++i) { |
| VerifyUniqueIDImpl(node->GetChild(i), ids, has_unique); |
| if (!*has_unique) |
| return; |
| } |
| } |
| }; |
| |
| // Tests migrating bookmarks from db into file. This copies an old history db |
| // file containing bookmarks and make sure they are loaded correctly and |
| // persisted correctly. |
| TEST_F(BookmarkModelTestWithProfile2, MigrateFromDBToFileTest) { |
| // Copy db file over that contains starred table. |
| FilePath old_history_path; |
| PathService::Get(chrome::DIR_TEST_DATA, &old_history_path); |
| old_history_path = old_history_path.AppendASCII("bookmarks"); |
| old_history_path = old_history_path.AppendASCII("History_with_starred"); |
| FilePath new_history_path = profile_->GetPath(); |
| file_util::Delete(new_history_path, true); |
| file_util::CreateDirectory(new_history_path); |
| FilePath new_history_file = new_history_path.Append( |
| chrome::kHistoryFilename); |
| ASSERT_TRUE(file_util::CopyFile(old_history_path, new_history_file)); |
| |
| // Create the history service making sure it doesn't blow away the file we |
| // just copied. |
| profile_->CreateHistoryService(false, false); |
| profile_->CreateBookmarkModel(true); |
| BlockTillBookmarkModelLoaded(); |
| |
| // Make sure we loaded OK. |
| VerifyExpectedState(); |
| if (HasFatalFailure()) |
| return; |
| |
| // Make sure the ids are unique. |
| VerifyUniqueIDs(); |
| if (HasFatalFailure()) |
| return; |
| |
| // Create again. This time we shouldn't load from history at all. |
| profile_->CreateBookmarkModel(false); |
| BlockTillBookmarkModelLoaded(); |
| |
| // Make sure we loaded OK. |
| VerifyExpectedState(); |
| if (HasFatalFailure()) |
| return; |
| |
| VerifyUniqueIDs(); |
| if (HasFatalFailure()) |
| return; |
| |
| // Recreate the history service (with a clean db). Do this just to make sure |
| // we're loading correctly from the bookmarks file. |
| profile_->CreateHistoryService(true, false); |
| profile_->CreateBookmarkModel(false); |
| BlockTillBookmarkModelLoaded(); |
| VerifyExpectedState(); |
| VerifyUniqueIDs(); |
| } |
| |
| // Simple test that removes a bookmark. This test exercises the code paths in |
| // History that block till bookmark bar model is loaded. |
| TEST_F(BookmarkModelTestWithProfile2, RemoveNotification) { |
| profile_->CreateHistoryService(false, false); |
| profile_->CreateBookmarkModel(true); |
| BlockTillBookmarkModelLoaded(); |
| |
| // Add a URL. |
| GURL url("http://www.google.com"); |
| bb_model_->SetURLStarred(url, string16(), true); |
| |
| profile_->GetHistoryService(Profile::EXPLICIT_ACCESS)->AddPage( |
| url, NULL, 1, GURL(), PageTransition::TYPED, |
| history::RedirectList(), history::SOURCE_BROWSED, false); |
| |
| // This won't actually delete the URL, rather it'll empty out the visits. |
| // This triggers blocking on the BookmarkModel. |
| profile_->GetHistoryService(Profile::EXPLICIT_ACCESS)->DeleteURL(url); |
| } |
| |
| TEST_F(BookmarkModelTest, Sort) { |
| // Populate the bookmark bar node with nodes for 'B', 'a', 'd' and 'C'. |
| // 'C' and 'a' are folders. |
| TestNode bbn; |
| PopulateNodeFromString("B [ a ] d [ a ]", &bbn); |
| const BookmarkNode* parent = model.GetBookmarkBarNode(); |
| PopulateBookmarkNode(&bbn, &model, parent); |
| |
| BookmarkNode* child1 = AsMutable(parent->GetChild(1)); |
| child1->set_title(ASCIIToUTF16("a")); |
| delete child1->Remove(child1->GetChild(0)); |
| BookmarkNode* child3 = AsMutable(parent->GetChild(3)); |
| child3->set_title(ASCIIToUTF16("C")); |
| delete child3->Remove(child3->GetChild(0)); |
| |
| ClearCounts(); |
| |
| // Sort the children of the bookmark bar node. |
| model.SortChildren(parent); |
| |
| // Make sure we were notified. |
| AssertObserverCount(0, 0, 0, 0, 1); |
| |
| // Make sure the order matches (remember, 'a' and 'C' are folders and |
| // come first). |
| EXPECT_EQ(parent->GetChild(0)->GetTitle(), ASCIIToUTF16("a")); |
| EXPECT_EQ(parent->GetChild(1)->GetTitle(), ASCIIToUTF16("C")); |
| EXPECT_EQ(parent->GetChild(2)->GetTitle(), ASCIIToUTF16("B")); |
| EXPECT_EQ(parent->GetChild(3)->GetTitle(), ASCIIToUTF16("d")); |
| } |