// Copyright 2014 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 "components/bookmarks/browser/bookmark_model.h" #include #include #include "base/base_paths.h" #include "base/command_line.h" #include "base/compiler_specific.h" #include "base/containers/hash_tables.h" #include "base/strings/string16.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/time/time.h" #include "components/bookmarks/browser/bookmark_model_observer.h" #include "components/bookmarks/browser/bookmark_utils.h" #include "components/bookmarks/test/bookmark_test_helpers.h" #include "components/bookmarks/test/test_bookmark_client.h" #include "components/favicon_base/favicon_callback.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/skia/include/core/SkBitmap.h" #include "ui/base/models/tree_node_iterator.h" #include "ui/base/models/tree_node_model.h" #include "ui/gfx/image/image.h" #include "url/gurl.h" using base::ASCIIToUTF16; using base::Time; using base::TimeDelta; namespace bookmarks { namespace { // Test cases used to test the removal of extra whitespace when adding // a new folder/bookmark or updating a title of a folder/bookmark. static struct { const std::string input_title; const std::string expected_title; } url_whitespace_test_cases[] = { {"foobar", "foobar"}, // Newlines. {"foo\nbar", "foo bar"}, {"foo\n\nbar", "foo bar"}, {"foo\n\n\nbar", "foo bar"}, {"foo\r\nbar", "foo bar"}, {"foo\r\n\r\nbar", "foo bar"}, {"\nfoo\nbar\n", "foo bar"}, // Spaces. {"foo bar", "foo bar"}, {" foo bar ", "foo bar"}, {" foo bar ", "foo bar"}, // Tabs. {"\tfoo\tbar\t", "foo bar"}, {"\tfoo bar\t", "foo bar"}, // Mixed cases. {"\tfoo\nbar\t", "foo bar"}, {"\tfoo\r\nbar\t", "foo bar"}, {" foo\tbar\n", "foo bar"}, {"\t foo \t bar \t", "foo bar"}, {"\n foo\r\n\tbar\n \t", "foo bar"}, }; // Test cases used to test the removal of extra whitespace when adding // a new folder/bookmark or updating a title of a folder/bookmark. static struct { const std::string input_title; const std::string expected_title; } title_whitespace_test_cases[] = { {"foobar", "foobar"}, // Newlines. {"foo\nbar", "foo bar"}, {"foo\n\nbar", "foo bar"}, {"foo\n\n\nbar", "foo bar"}, {"foo\r\nbar", "foo bar"}, {"foo\r\n\r\nbar", "foo bar"}, {"\nfoo\nbar\n", " foo bar "}, // Spaces. {"foo bar", "foo bar"}, {" foo bar ", " foo bar "}, {" foo bar ", " foo bar "}, // Tabs. {"\tfoo\tbar\t", " foo bar "}, {"\tfoo bar\t", " foo bar "}, // Mixed cases. {"\tfoo\nbar\t", " foo bar "}, {"\tfoo\r\nbar\t", " foo bar "}, {" foo\tbar\n", " foo bar "}, {"\t foo \t bar \t", " foo bar "}, {"\n foo\r\n\tbar\n \t", " foo bar "}, }; // Helper to get a mutable bookmark node. BookmarkNode* AsMutable(const BookmarkNode* node) { return const_cast(node); } void SwapDateAdded(BookmarkNode* n1, BookmarkNode* n2) { Time tmp = n1->date_added(); n1->set_date_added(n2->date_added()); n2->set_date_added(tmp); } // See comment in PopulateNodeFromString. using TestNode = ui::TreeNodeWithValue; // Does the work of PopulateNodeFromString. index gives the index of the current // element in description to process. void PopulateNodeImpl(const std::vector& 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(base::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. void PopulateNodeFromString(const std::string& description, TestNode* parent) { std::vector elements = base::SplitString( description, base::kWhitespaceASCII, base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); size_t index = 0; PopulateNodeImpl(elements, &index, parent); } // Populates the BookmarkNode with the children of parent. 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://" + base::UTF16ToASCII(child->GetTitle()))); } } } // 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); } else { // No need to check the URL, just the title is enough. ASSERT_TRUE(actual_child->is_url()); } } } void VerifyNoDuplicateIDs(BookmarkModel* model) { ui::TreeNodeIterator it(model->root_node()); base::hash_set ids; while (it.has_next()) ASSERT_TRUE(ids.insert(it.Next()->id()).second); } class BookmarkModelTest : public testing::Test, public BookmarkModelObserver { public: struct ObserverDetails { ObserverDetails() { Set(NULL, NULL, -1, -1); } void Set(const BookmarkNode* node1, const BookmarkNode* node2, int index1, int index2) { node1_ = node1; node2_ = node2; index1_ = index1; index2_ = index2; } void ExpectEquals(const BookmarkNode* node1, const BookmarkNode* node2, int index1, int index2) { EXPECT_EQ(node1_, node1); EXPECT_EQ(node2_, node2); EXPECT_EQ(index1_, index1); EXPECT_EQ(index2_, index2); } private: const BookmarkNode* node1_; const BookmarkNode* node2_; int index1_; int index2_; }; BookmarkModelTest() : model_(client_.CreateModel()) { model_->AddObserver(this); ClearCounts(); } void BookmarkModelLoaded(BookmarkModel* model, bool ids_reassigned) override { // We never load from the db, so that this should never get invoked. NOTREACHED(); } void BookmarkNodeMoved(BookmarkModel* model, const BookmarkNode* old_parent, int old_index, const BookmarkNode* new_parent, int new_index) override { ++moved_count_; observer_details_.Set(old_parent, new_parent, old_index, new_index); } void BookmarkNodeAdded(BookmarkModel* model, const BookmarkNode* parent, int index) override { ++added_count_; observer_details_.Set(parent, NULL, index, -1); } void OnWillRemoveBookmarks(BookmarkModel* model, const BookmarkNode* parent, int old_index, const BookmarkNode* node) override { ++before_remove_count_; } void BookmarkNodeRemoved(BookmarkModel* model, const BookmarkNode* parent, int old_index, const BookmarkNode* node, const std::set& removed_urls) override { ++removed_count_; observer_details_.Set(parent, NULL, old_index, -1); } void BookmarkNodeChanged(BookmarkModel* model, const BookmarkNode* node) override { ++changed_count_; observer_details_.Set(node, NULL, -1, -1); } void OnWillChangeBookmarkNode(BookmarkModel* model, const BookmarkNode* node) override { ++before_change_count_; } void BookmarkNodeChildrenReordered(BookmarkModel* model, const BookmarkNode* node) override { ++reordered_count_; } void OnWillReorderBookmarkNode(BookmarkModel* model, const BookmarkNode* node) override { ++before_reorder_count_; } void BookmarkNodeFaviconChanged(BookmarkModel* model, const BookmarkNode* node) override { // We never attempt to load favicons, so that this method never // gets invoked. } void ExtensiveBookmarkChangesBeginning(BookmarkModel* model) override { ++extensive_changes_beginning_count_; } void ExtensiveBookmarkChangesEnded(BookmarkModel* model) override { ++extensive_changes_ended_count_; } void BookmarkAllUserNodesRemoved( BookmarkModel* model, const std::set& removed_urls) override { ++all_bookmarks_removed_; } void OnWillRemoveAllUserBookmarks(BookmarkModel* model) override { ++before_remove_all_count_; } void ClearCounts() { added_count_ = moved_count_ = removed_count_ = changed_count_ = reordered_count_ = extensive_changes_beginning_count_ = extensive_changes_ended_count_ = all_bookmarks_removed_ = before_remove_count_ = before_change_count_ = before_reorder_count_ = before_remove_all_count_ = 0; } void AssertObserverCount(int added_count, int moved_count, int removed_count, int changed_count, int reordered_count, int before_remove_count, int before_change_count, int before_reorder_count, int before_remove_all_count) { EXPECT_EQ(added_count, added_count_); EXPECT_EQ(moved_count, moved_count_); EXPECT_EQ(removed_count, removed_count_); EXPECT_EQ(changed_count, changed_count_); EXPECT_EQ(reordered_count, reordered_count_); EXPECT_EQ(before_remove_count, before_remove_count_); EXPECT_EQ(before_change_count, before_change_count_); EXPECT_EQ(before_reorder_count, before_reorder_count_); EXPECT_EQ(before_remove_all_count, before_remove_all_count_); } void AssertExtensiveChangesObserverCount( int extensive_changes_beginning_count, int extensive_changes_ended_count) { EXPECT_EQ(extensive_changes_beginning_count, extensive_changes_beginning_count_); EXPECT_EQ(extensive_changes_ended_count, extensive_changes_ended_count_); } int AllNodesRemovedObserverCount() const { return all_bookmarks_removed_; } BookmarkPermanentNode* ReloadModelWithExtraNode() { BookmarkPermanentNode* extra_node = new BookmarkPermanentNode(100); BookmarkPermanentNodeList extra_nodes; extra_nodes.push_back(extra_node); client_.SetExtraNodesToLoad(extra_nodes.Pass()); model_->RemoveObserver(this); model_ = client_.CreateModel(); model_->AddObserver(this); ClearCounts(); if (model_->root_node()->GetIndexOf(extra_node) == -1) ADD_FAILURE(); return extra_node; } protected: TestBookmarkClient client_; scoped_ptr model_; ObserverDetails observer_details_; private: int added_count_; int moved_count_; int removed_count_; int changed_count_; int reordered_count_; int extensive_changes_beginning_count_; int extensive_changes_ended_count_; int all_bookmarks_removed_; int before_remove_count_; int before_change_count_; int before_reorder_count_; int before_remove_all_count_; DISALLOW_COPY_AND_ASSIGN(BookmarkModelTest); }; TEST_F(BookmarkModelTest, InitialState) { const BookmarkNode* bb_node = model_->bookmark_bar_node(); 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()); const BookmarkNode* mobile_node = model_->mobile_node(); ASSERT_TRUE(mobile_node != NULL); EXPECT_EQ(0, mobile_node->child_count()); EXPECT_EQ(BookmarkNode::MOBILE, mobile_node->type()); EXPECT_TRUE(bb_node->id() != other_node->id()); EXPECT_TRUE(bb_node->id() != mobile_node->id()); EXPECT_TRUE(other_node->id() != mobile_node->id()); } TEST_F(BookmarkModelTest, AddURL) { const BookmarkNode* root = model_->bookmark_bar_node(); const base::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, 0, 0, 0, 0); observer_details_.ExpectEquals(root, NULL, 0, -1); ASSERT_EQ(1, root->child_count()); ASSERT_EQ(title, new_node->GetTitle()); ASSERT_TRUE(url == new_node->url()); ASSERT_EQ(BookmarkNode::URL, new_node->type()); ASSERT_TRUE(new_node == model_->GetMostRecentlyAddedUserNodeForURL(url)); EXPECT_TRUE(new_node->id() != root->id() && new_node->id() != model_->other_node()->id() && new_node->id() != model_->mobile_node()->id()); } TEST_F(BookmarkModelTest, AddURLWithUnicodeTitle) { const BookmarkNode* root = model_->bookmark_bar_node(); const base::string16 title(base::WideToUTF16( L"\u767e\u5ea6\u4e00\u4e0b\uff0c\u4f60\u5c31\u77e5\u9053")); const GURL url("https://www.baidu.com/"); const BookmarkNode* new_node = model_->AddURL(root, 0, title, url); AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0); observer_details_.ExpectEquals(root, NULL, 0, -1); ASSERT_EQ(1, root->child_count()); ASSERT_EQ(title, new_node->GetTitle()); ASSERT_TRUE(url == new_node->url()); ASSERT_EQ(BookmarkNode::URL, new_node->type()); ASSERT_TRUE(new_node == model_->GetMostRecentlyAddedUserNodeForURL(url)); EXPECT_TRUE(new_node->id() != root->id() && new_node->id() != model_->other_node()->id() && new_node->id() != model_->mobile_node()->id()); } TEST_F(BookmarkModelTest, AddURLWithWhitespaceTitle) { for (size_t i = 0; i < arraysize(url_whitespace_test_cases); ++i) { const BookmarkNode* root = model_->bookmark_bar_node(); const base::string16 title( ASCIIToUTF16(url_whitespace_test_cases[i].input_title)); const GURL url("http://foo.com"); const BookmarkNode* new_node = model_->AddURL(root, i, title, url); int size = i + 1; EXPECT_EQ(size, root->child_count()); EXPECT_EQ(ASCIIToUTF16(url_whitespace_test_cases[i].expected_title), new_node->GetTitle()); EXPECT_EQ(BookmarkNode::URL, new_node->type()); } } TEST_F(BookmarkModelTest, AddURLWithCreationTimeAndMetaInfo) { const BookmarkNode* root = model_->bookmark_bar_node(); const base::string16 title(ASCIIToUTF16("foo")); const GURL url("http://foo.com"); const Time time = Time::Now() - TimeDelta::FromDays(1); BookmarkNode::MetaInfoMap meta_info; meta_info["foo"] = "bar"; const BookmarkNode* new_node = model_->AddURLWithCreationTimeAndMetaInfo( root, 0, title, url, time, &meta_info); AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0); observer_details_.ExpectEquals(root, NULL, 0, -1); ASSERT_EQ(1, root->child_count()); ASSERT_EQ(title, new_node->GetTitle()); ASSERT_TRUE(url == new_node->url()); ASSERT_EQ(BookmarkNode::URL, new_node->type()); ASSERT_EQ(time, new_node->date_added()); ASSERT_TRUE(new_node->GetMetaInfoMap()); ASSERT_EQ(meta_info, *new_node->GetMetaInfoMap()); ASSERT_TRUE(new_node == model_->GetMostRecentlyAddedUserNodeForURL(url)); EXPECT_TRUE(new_node->id() != root->id() && new_node->id() != model_->other_node()->id() && new_node->id() != model_->mobile_node()->id()); } TEST_F(BookmarkModelTest, AddURLToMobileBookmarks) { const BookmarkNode* root = model_->mobile_node(); const base::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, 0, 0, 0, 0); observer_details_.ExpectEquals(root, NULL, 0, -1); ASSERT_EQ(1, root->child_count()); ASSERT_EQ(title, new_node->GetTitle()); ASSERT_TRUE(url == new_node->url()); ASSERT_EQ(BookmarkNode::URL, new_node->type()); ASSERT_TRUE(new_node == model_->GetMostRecentlyAddedUserNodeForURL(url)); EXPECT_TRUE(new_node->id() != root->id() && new_node->id() != model_->other_node()->id() && new_node->id() != model_->mobile_node()->id()); } TEST_F(BookmarkModelTest, AddFolder) { const BookmarkNode* root = model_->bookmark_bar_node(); const base::string16 title(ASCIIToUTF16("foo")); const BookmarkNode* new_node = model_->AddFolder(root, 0, title); AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0); observer_details_.ExpectEquals(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() && new_node->id() != model_->mobile_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, 0, 0, 0, 0); observer_details_.ExpectEquals(root, NULL, 0, -1); } TEST_F(BookmarkModelTest, AddFolderWithWhitespaceTitle) { for (size_t i = 0; i < arraysize(title_whitespace_test_cases); ++i) { const BookmarkNode* root = model_->bookmark_bar_node(); const base::string16 title( ASCIIToUTF16(title_whitespace_test_cases[i].input_title)); const BookmarkNode* new_node = model_->AddFolder(root, i, title); int size = i + 1; EXPECT_EQ(size, root->child_count()); EXPECT_EQ(ASCIIToUTF16(title_whitespace_test_cases[i].expected_title), new_node->GetTitle()); EXPECT_EQ(BookmarkNode::FOLDER, new_node->type()); } } TEST_F(BookmarkModelTest, RemoveURL) { const BookmarkNode* root = model_->bookmark_bar_node(); const base::string16 title(ASCIIToUTF16("foo")); const GURL url("http://foo.com"); model_->AddURL(root, 0, title, url); ClearCounts(); model_->Remove(root->GetChild(0)); ASSERT_EQ(0, root->child_count()); AssertObserverCount(0, 0, 1, 0, 0, 1, 0, 0, 0); observer_details_.ExpectEquals(root, NULL, 0, -1); // Make sure there is no mapping for the URL. ASSERT_TRUE(model_->GetMostRecentlyAddedUserNodeForURL(url) == NULL); } TEST_F(BookmarkModelTest, RemoveFolder) { const BookmarkNode* root = model_->bookmark_bar_node(); const BookmarkNode* folder = model_->AddFolder(root, 0, ASCIIToUTF16("foo")); ClearCounts(); // Add a URL as a child. const base::string16 title(ASCIIToUTF16("foo")); const GURL url("http://foo.com"); model_->AddURL(folder, 0, title, url); ClearCounts(); // Now remove the folder. model_->Remove(root->GetChild(0)); ASSERT_EQ(0, root->child_count()); AssertObserverCount(0, 0, 1, 0, 0, 1, 0, 0, 0); observer_details_.ExpectEquals(root, NULL, 0, -1); // Make sure there is no mapping for the URL. ASSERT_TRUE(model_->GetMostRecentlyAddedUserNodeForURL(url) == NULL); } TEST_F(BookmarkModelTest, RemoveAllUserBookmarks) { const BookmarkNode* bookmark_bar_node = model_->bookmark_bar_node(); ClearCounts(); // Add a url to bookmark bar. base::string16 title(ASCIIToUTF16("foo")); GURL url("http://foo.com"); model_->AddURL(bookmark_bar_node, 0, title, url); // Add a folder with child URL. const BookmarkNode* folder = model_->AddFolder(bookmark_bar_node, 0, title); model_->AddURL(folder, 0, title, url); AssertObserverCount(3, 0, 0, 0, 0, 0, 0, 0, 0); ClearCounts(); model_->RemoveAllUserBookmarks(); EXPECT_EQ(0, bookmark_bar_node->child_count()); // No individual BookmarkNodeRemoved events are fired, so removed count // should be 0. AssertObserverCount(0, 0, 0, 0, 0, 0, 0, 0, 1); AssertExtensiveChangesObserverCount(1, 1); EXPECT_EQ(1, AllNodesRemovedObserverCount()); } TEST_F(BookmarkModelTest, SetTitle) { const BookmarkNode* root = model_->bookmark_bar_node(); base::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, 0, 1, 0, 0); observer_details_.ExpectEquals(node, NULL, -1, -1); EXPECT_EQ(title, node->GetTitle()); } TEST_F(BookmarkModelTest, SetTitleWithWhitespace) { for (size_t i = 0; i < arraysize(title_whitespace_test_cases); ++i) { const BookmarkNode* root = model_->bookmark_bar_node(); base::string16 title(ASCIIToUTF16("dummy")); const GURL url("http://foo.com"); const BookmarkNode* node = model_->AddURL(root, 0, title, url); title = ASCIIToUTF16(title_whitespace_test_cases[i].input_title); model_->SetTitle(node, title); EXPECT_EQ(ASCIIToUTF16(title_whitespace_test_cases[i].expected_title), node->GetTitle()); } } TEST_F(BookmarkModelTest, SetURL) { const BookmarkNode* root = model_->bookmark_bar_node(); const base::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, 0, 1, 0, 0); observer_details_.ExpectEquals(node, NULL, -1, -1); EXPECT_EQ(url, node->url()); } TEST_F(BookmarkModelTest, SetDateAdded) { const BookmarkNode* root = model_->bookmark_bar_node(); const base::string16 title(ASCIIToUTF16("foo")); GURL url("http://foo.com"); const BookmarkNode* node = model_->AddURL(root, 0, title, url); ClearCounts(); base::Time new_time = base::Time::Now() + base::TimeDelta::FromMinutes(20); model_->SetDateAdded(node, new_time); AssertObserverCount(0, 0, 0, 0, 0, 0, 0, 0, 0); EXPECT_EQ(new_time, node->date_added()); EXPECT_EQ(new_time, model_->bookmark_bar_node()->date_folder_modified()); } TEST_F(BookmarkModelTest, Move) { const BookmarkNode* root = model_->bookmark_bar_node(); const base::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, 0, 0, 0, 0); observer_details_.ExpectEquals(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->GetChild(0)); AssertObserverCount(0, 0, 1, 0, 0, 1, 0, 0, 0); observer_details_.ExpectEquals(root, NULL, 0, -1); EXPECT_TRUE(model_->GetMostRecentlyAddedUserNodeForURL(url) == NULL); EXPECT_EQ(0, root->child_count()); } TEST_F(BookmarkModelTest, NonMovingMoveCall) { const BookmarkNode* root = model_->bookmark_bar_node(); const base::string16 title(ASCIIToUTF16("foo")); const GURL url("http://foo.com"); const base::Time old_date(base::Time::Now() - base::TimeDelta::FromDays(1)); const BookmarkNode* node = model_->AddURL(root, 0, title, url); model_->SetDateFolderModified(root, old_date); // Since |node| is already at the index 0 of |root|, this is no-op. model_->Move(node, root, 0); // Check that the modification date is kept untouched. EXPECT_EQ(old_date, root->date_folder_modified()); } TEST_F(BookmarkModelTest, Copy) { const BookmarkNode* root = model_->bookmark_bar_node(); static const std::string model_string("a 1:[ b c ] d 2:[ e f g ] h "); test::AddNodesFromModelString(model_.get(), root, model_string); // Validate initial model. std::string actual_model_string = test::ModelStringFromNode(root); EXPECT_EQ(model_string, actual_model_string); // Copy 'd' to be after '1:b': URL item from bar to folder. const BookmarkNode* node_to_copy = root->GetChild(2); const BookmarkNode* destination = root->GetChild(1); model_->Copy(node_to_copy, destination, 1); actual_model_string = test::ModelStringFromNode(root); EXPECT_EQ("a 1:[ b d c ] d 2:[ e f g ] h ", actual_model_string); // Copy '1:d' to be after 'a': URL item from folder to bar. const BookmarkNode* folder = root->GetChild(1); node_to_copy = folder->GetChild(1); model_->Copy(node_to_copy, root, 1); actual_model_string = test::ModelStringFromNode(root); EXPECT_EQ("a d 1:[ b d c ] d 2:[ e f g ] h ", actual_model_string); // Copy '1' to be after '2:e': Folder from bar to folder. node_to_copy = root->GetChild(2); destination = root->GetChild(4); model_->Copy(node_to_copy, destination, 1); actual_model_string = test::ModelStringFromNode(root); EXPECT_EQ("a d 1:[ b d c ] d 2:[ e 1:[ b d c ] f g ] h ", actual_model_string); // Copy '2:1' to be after '2:f': Folder within same folder. folder = root->GetChild(4); node_to_copy = folder->GetChild(1); model_->Copy(node_to_copy, folder, 3); actual_model_string = test::ModelStringFromNode(root); EXPECT_EQ("a d 1:[ b d c ] d 2:[ e 1:[ b d c ] f 1:[ b d c ] g ] h ", actual_model_string); // Copy first 'd' to be after 'h': URL item within the bar. node_to_copy = root->GetChild(1); model_->Copy(node_to_copy, root, 6); actual_model_string = test::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 ", actual_model_string); // Copy '2' to be after 'a': Folder within the bar. node_to_copy = root->GetChild(4); model_->Copy(node_to_copy, root, 1); actual_model_string = test::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 ", actual_model_string); } // Tests that adding a URL to a folder updates the last modified time. TEST_F(BookmarkModelTest, ParentForNewNodes) { ASSERT_EQ(model_->bookmark_bar_node(), model_->GetParentForNewNodes()); const base::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()); } // Tests that adding a URL to a folder updates the last modified time. TEST_F(BookmarkModelTest, ParentForNewMobileNodes) { ASSERT_EQ(model_->bookmark_bar_node(), model_->GetParentForNewNodes()); const base::string16 title(ASCIIToUTF16("foo")); const GURL url("http://foo.com"); model_->AddURL(model_->mobile_node(), 0, title, url); ASSERT_EQ(model_->mobile_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 most_recent_folders = GetMostRecentlyModifiedUserFolders(model_.get(), 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()->GetChild(0)); most_recent_folders = GetMostRecentlyModifiedUserFolders(model_.get(), 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_->bookmark_bar_node(), 0, ASCIIToUTF16("blah"), GURL("http://foo.com/0"))); BookmarkNode* n2 = AsMutable(model_->AddURL(model_->bookmark_bar_node(), 1, ASCIIToUTF16("blah"), GURL("http://foo.com/1"))); BookmarkNode* n3 = AsMutable(model_->AddURL(model_->bookmark_bar_node(), 2, ASCIIToUTF16("blah"), GURL("http://foo.com/2"))); BookmarkNode* n4 = AsMutable(model_->AddURL(model_->bookmark_bar_node(), 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 recently_added; GetMostRecentlyAddedEntries(model_.get(), 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); GetMostRecentlyAddedEntries(model_.get(), 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 GetMostRecentlyAddedUserNodeForURL stays in sync. TEST_F(BookmarkModelTest, GetMostRecentlyAddedUserNodeForURL) { // 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_->bookmark_bar_node(), 0, ASCIIToUTF16("blah"), url)); BookmarkNode* n2 = AsMutable(model_->AddURL( model_->bookmark_bar_node(), 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_->GetMostRecentlyAddedUserNodeForURL(url)); // swap 1 and 2, then check again. SwapDateAdded(n1, n2); ASSERT_EQ(n2, model_->GetMostRecentlyAddedUserNodeForURL(url)); } // Makes sure GetBookmarks removes duplicates. TEST_F(BookmarkModelTest, GetBookmarksWithDups) { const GURL url("http://foo.com/0"); const base::string16 title(ASCIIToUTF16("blah")); model_->AddURL(model_->bookmark_bar_node(), 0, title, url); model_->AddURL(model_->bookmark_bar_node(), 1, title, url); std::vector bookmarks; model_->GetBookmarks(&bookmarks); ASSERT_EQ(1U, bookmarks.size()); EXPECT_EQ(url, bookmarks[0].url); EXPECT_EQ(title, bookmarks[0].title); model_->AddURL(model_->bookmark_bar_node(), 2, ASCIIToUTF16("Title2"), url); // Only one returned, even titles are different. bookmarks.clear(); model_->GetBookmarks(&bookmarks); EXPECT_EQ(1U, bookmarks.size()); } TEST_F(BookmarkModelTest, HasBookmarks) { const GURL url("http://foo.com/"); model_->AddURL(model_->bookmark_bar_node(), 0, ASCIIToUTF16("bar"), url); EXPECT_TRUE(model_->HasBookmarks()); } // http://crbug.com/450464 TEST_F(BookmarkModelTest, DISABLED_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_->bookmark_bar_node(); PopulateBookmarkNode(&bbn, model_.get(), parent); BookmarkNode* child1 = AsMutable(parent->GetChild(1)); child1->SetTitle(ASCIIToUTF16("a")); delete child1->Remove(child1->GetChild(0)); BookmarkNode* child3 = AsMutable(parent->GetChild(3)); child3->SetTitle(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, 0, 0, 1, 0); // 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")); } TEST_F(BookmarkModelTest, Reorder) { // Populate the bookmark bar node with nodes 'A', 'B', 'C' and 'D'. TestNode bbn; PopulateNodeFromString("A B C D", &bbn); BookmarkNode* parent = AsMutable(model_->bookmark_bar_node()); PopulateBookmarkNode(&bbn, model_.get(), parent); ClearCounts(); // Reorder bar node's bookmarks in reverse order. std::vector new_order; new_order.push_back(parent->GetChild(3)); new_order.push_back(parent->GetChild(2)); new_order.push_back(parent->GetChild(1)); new_order.push_back(parent->GetChild(0)); model_->ReorderChildren(parent, new_order); // Make sure we were notified. AssertObserverCount(0, 0, 0, 0, 1, 0, 0, 1, 0); // Make sure the order matches is correct (it should be reversed). ASSERT_EQ(4, parent->child_count()); EXPECT_EQ("D", base::UTF16ToASCII(parent->GetChild(0)->GetTitle())); EXPECT_EQ("C", base::UTF16ToASCII(parent->GetChild(1)->GetTitle())); EXPECT_EQ("B", base::UTF16ToASCII(parent->GetChild(2)->GetTitle())); EXPECT_EQ("A", base::UTF16ToASCII(parent->GetChild(3)->GetTitle())); } TEST_F(BookmarkModelTest, NodeVisibility) { // Mobile node invisible by default EXPECT_TRUE(model_->bookmark_bar_node()->IsVisible()); EXPECT_TRUE(model_->other_node()->IsVisible()); EXPECT_FALSE(model_->mobile_node()->IsVisible()); // Visibility of permanent node can only be changed if they are not // forced to be visible by the client. model_->SetPermanentNodeVisible(BookmarkNode::BOOKMARK_BAR, false); EXPECT_TRUE(model_->bookmark_bar_node()->IsVisible()); model_->SetPermanentNodeVisible(BookmarkNode::OTHER_NODE, false); EXPECT_TRUE(model_->other_node()->IsVisible()); model_->SetPermanentNodeVisible(BookmarkNode::MOBILE, true); EXPECT_TRUE(model_->mobile_node()->IsVisible()); model_->SetPermanentNodeVisible(BookmarkNode::MOBILE, false); EXPECT_FALSE(model_->mobile_node()->IsVisible()); // Arbitrary node should be visible TestNode bbn; PopulateNodeFromString("B", &bbn); const BookmarkNode* parent = model_->mobile_node(); PopulateBookmarkNode(&bbn, model_.get(), parent); EXPECT_TRUE(parent->GetChild(0)->IsVisible()); // Mobile folder should be visible now that it has a child. EXPECT_TRUE(model_->mobile_node()->IsVisible()); } TEST_F(BookmarkModelTest, MobileNodeVisibileWithChildren) { const BookmarkNode* root = model_->mobile_node(); const base::string16 title(ASCIIToUTF16("foo")); const GURL url("http://foo.com"); model_->AddURL(root, 0, title, url); EXPECT_TRUE(model_->mobile_node()->IsVisible()); } TEST_F(BookmarkModelTest, ExtensiveChangesObserver) { AssertExtensiveChangesObserverCount(0, 0); EXPECT_FALSE(model_->IsDoingExtensiveChanges()); model_->BeginExtensiveChanges(); EXPECT_TRUE(model_->IsDoingExtensiveChanges()); AssertExtensiveChangesObserverCount(1, 0); model_->EndExtensiveChanges(); EXPECT_FALSE(model_->IsDoingExtensiveChanges()); AssertExtensiveChangesObserverCount(1, 1); } TEST_F(BookmarkModelTest, MultipleExtensiveChangesObserver) { AssertExtensiveChangesObserverCount(0, 0); EXPECT_FALSE(model_->IsDoingExtensiveChanges()); model_->BeginExtensiveChanges(); EXPECT_TRUE(model_->IsDoingExtensiveChanges()); AssertExtensiveChangesObserverCount(1, 0); model_->BeginExtensiveChanges(); EXPECT_TRUE(model_->IsDoingExtensiveChanges()); AssertExtensiveChangesObserverCount(1, 0); model_->EndExtensiveChanges(); EXPECT_TRUE(model_->IsDoingExtensiveChanges()); AssertExtensiveChangesObserverCount(1, 0); model_->EndExtensiveChanges(); EXPECT_FALSE(model_->IsDoingExtensiveChanges()); AssertExtensiveChangesObserverCount(1, 1); } // Verifies that IsBookmarked is true if any bookmark matches the given URL, // and that IsBookmarkedByUser is true only if at least one of the matching // bookmarks can be edited by the user. TEST_F(BookmarkModelTest, IsBookmarked) { // Reload the model with an extra node that is not editable by the user. BookmarkPermanentNode* extra_node = ReloadModelWithExtraNode(); // "google.com" is a "user" bookmark. model_->AddURL(model_->other_node(), 0, base::ASCIIToUTF16("User"), GURL("http://google.com")); // "youtube.com" is not. model_->AddURL(extra_node, 0, base::ASCIIToUTF16("Extra"), GURL("http://youtube.com")); EXPECT_TRUE(model_->IsBookmarked(GURL("http://google.com"))); EXPECT_TRUE(model_->IsBookmarked(GURL("http://youtube.com"))); EXPECT_FALSE(model_->IsBookmarked(GURL("http://reddit.com"))); EXPECT_TRUE(IsBookmarkedByUser(model_.get(), GURL("http://google.com"))); EXPECT_FALSE(IsBookmarkedByUser(model_.get(), GURL("http://youtube.com"))); EXPECT_FALSE(IsBookmarkedByUser(model_.get(), GURL("http://reddit.com"))); } // Verifies that GetMostRecentlyAddedUserNodeForURL skips bookmarks that // are not owned by the user. TEST_F(BookmarkModelTest, GetMostRecentlyAddedUserNodeForURLSkipsManagedNodes) { // Reload the model with an extra node that is not editable by the user. BookmarkPermanentNode* extra_node = ReloadModelWithExtraNode(); const base::string16 title = base::ASCIIToUTF16("Title"); const BookmarkNode* user_parent = model_->other_node(); const BookmarkNode* managed_parent = extra_node; const GURL url("http://google.com"); // |url| is not bookmarked yet. EXPECT_TRUE(model_->GetMostRecentlyAddedUserNodeForURL(url) == NULL); // Having a managed node doesn't count. model_->AddURL(managed_parent, 0, title, url); EXPECT_TRUE(model_->GetMostRecentlyAddedUserNodeForURL(url) == NULL); // Now add a user node. const BookmarkNode* user = model_->AddURL(user_parent, 0, title, url); EXPECT_EQ(user, model_->GetMostRecentlyAddedUserNodeForURL(url)); // Having a more recent managed node doesn't count either. const BookmarkNode* managed = model_->AddURL(managed_parent, 0, title, url); EXPECT_GE(managed->date_added(), user->date_added()); EXPECT_EQ(user, model_->GetMostRecentlyAddedUserNodeForURL(url)); } TEST(BookmarkNodeTest, NodeMetaInfo) { GURL url; BookmarkNode node(url); EXPECT_FALSE(node.GetMetaInfoMap()); EXPECT_TRUE(node.SetMetaInfo("key1", "value1")); std::string out_value; EXPECT_TRUE(node.GetMetaInfo("key1", &out_value)); EXPECT_EQ("value1", out_value); EXPECT_FALSE(node.SetMetaInfo("key1", "value1")); EXPECT_FALSE(node.GetMetaInfo("key2.subkey1", &out_value)); EXPECT_TRUE(node.SetMetaInfo("key2.subkey1", "value2")); EXPECT_TRUE(node.GetMetaInfo("key2.subkey1", &out_value)); EXPECT_EQ("value2", out_value); EXPECT_FALSE(node.GetMetaInfo("key2.subkey2.leaf", &out_value)); EXPECT_TRUE(node.SetMetaInfo("key2.subkey2.leaf", "")); EXPECT_TRUE(node.GetMetaInfo("key2.subkey2.leaf", &out_value)); EXPECT_EQ("", out_value); EXPECT_TRUE(node.DeleteMetaInfo("key1")); EXPECT_TRUE(node.DeleteMetaInfo("key2.subkey1")); EXPECT_TRUE(node.DeleteMetaInfo("key2.subkey2.leaf")); EXPECT_FALSE(node.DeleteMetaInfo("key3")); EXPECT_FALSE(node.GetMetaInfo("key1", &out_value)); EXPECT_FALSE(node.GetMetaInfo("key2.subkey1", &out_value)); EXPECT_FALSE(node.GetMetaInfo("key2.subkey2", &out_value)); EXPECT_FALSE(node.GetMetaInfo("key2.subkey2.leaf", &out_value)); EXPECT_FALSE(node.GetMetaInfoMap()); } // Creates a set of nodes in the bookmark model, and checks that the loaded // structure is what we first created. TEST(BookmarkModelTest2, CreateAndRestore) { struct TestData { // Structure of the children of the bookmark model node. const std::string bbn_contents; // Structure of the children of the other node. const std::string other_contents; // Structure of the children of the synced node. const std::string mobile_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 ] ]"}, }; TestBookmarkClient client; scoped_ptr model; for (size_t i = 0; i < arraysize(data); ++i) { model = client.CreateModel(); TestNode bbn; PopulateNodeFromString(data[i].bbn_contents, &bbn); PopulateBookmarkNode(&bbn, model.get(), model->bookmark_bar_node()); TestNode other; PopulateNodeFromString(data[i].other_contents, &other); PopulateBookmarkNode(&other, model.get(), model->other_node()); TestNode mobile; PopulateNodeFromString(data[i].mobile_contents, &mobile); PopulateBookmarkNode(&mobile, model.get(), model->mobile_node()); VerifyModelMatchesNode(&bbn, model->bookmark_bar_node()); VerifyModelMatchesNode(&other, model->other_node()); VerifyModelMatchesNode(&mobile, model->mobile_node()); VerifyNoDuplicateIDs(model.get()); } } } // namespace class BookmarkModelFaviconTest : public testing::Test, public BookmarkModelObserver { public: BookmarkModelFaviconTest() : model_(client_.CreateModel()) { model_->AddObserver(this); } // Emulates the favicon getting asynchronously loaded. In production, the // favicon is asynchronously loaded when BookmarkModel::GetFavicon() is // called. void OnFaviconLoaded(BookmarkNode* node, const GURL& icon_url) { SkBitmap bitmap; bitmap.allocN32Pixels(16, 16); bitmap.eraseColor(SK_ColorBLUE); gfx::Image image = gfx::Image::CreateFrom1xBitmap(bitmap); favicon_base::FaviconImageResult image_result; image_result.image = image; image_result.icon_url = icon_url; model_->OnFaviconDataAvailable(node, favicon_base::IconType::FAVICON, image_result); } bool WasNodeUpdated(const BookmarkNode* node) { return std::find(updated_nodes_.begin(), updated_nodes_.end(), node) != updated_nodes_.end(); } void ClearUpdatedNodes() { updated_nodes_.clear(); } protected: void BookmarkModelLoaded(BookmarkModel* model, bool ids_reassigned) override { } void BookmarkNodeMoved(BookmarkModel* model, const BookmarkNode* old_parent, int old_index, const BookmarkNode* new_parent, int new_index) override {} void BookmarkNodeAdded(BookmarkModel* model, const BookmarkNode* parent, int index) override {} void BookmarkNodeRemoved(BookmarkModel* model, const BookmarkNode* parent, int old_index, const BookmarkNode* node, const std::set& removed_urls) override {} void BookmarkNodeChanged(BookmarkModel* model, const BookmarkNode* node) override {} void BookmarkNodeFaviconChanged(BookmarkModel* model, const BookmarkNode* node) override { updated_nodes_.push_back(node); } void BookmarkNodeChildrenReordered(BookmarkModel* model, const BookmarkNode* node) override {} void BookmarkAllUserNodesRemoved( BookmarkModel* model, const std::set& removed_urls) override { } TestBookmarkClient client_; scoped_ptr model_; std::vector updated_nodes_; private: DISALLOW_COPY_AND_ASSIGN(BookmarkModelFaviconTest); }; // Test that BookmarkModel::OnFaviconsChanged() sends a notification that the // favicon changed to each BookmarkNode which has either a matching page URL // (e.g. http://www.google.com) or a matching icon URL // (e.g. http://www.google.com/favicon.ico). TEST_F(BookmarkModelFaviconTest, FaviconsChangedObserver) { const BookmarkNode* root = model_->bookmark_bar_node(); base::string16 kTitle(ASCIIToUTF16("foo")); GURL kPageURL1("http://www.google.com"); GURL kPageURL2("http://www.google.ca"); GURL kPageURL3("http://www.amazon.com"); GURL kFaviconURL12("http://www.google.com/favicon.ico"); GURL kFaviconURL3("http://www.amazon.com/favicon.ico"); const BookmarkNode* node1 = model_->AddURL(root, 0, kTitle, kPageURL1); const BookmarkNode* node2 = model_->AddURL(root, 0, kTitle, kPageURL2); const BookmarkNode* node3 = model_->AddURL(root, 0, kTitle, kPageURL3); const BookmarkNode* node4 = model_->AddURL(root, 0, kTitle, kPageURL3); { OnFaviconLoaded(AsMutable(node1), kFaviconURL12); OnFaviconLoaded(AsMutable(node2), kFaviconURL12); OnFaviconLoaded(AsMutable(node3), kFaviconURL3); OnFaviconLoaded(AsMutable(node4), kFaviconURL3); ClearUpdatedNodes(); std::set changed_page_urls; changed_page_urls.insert(kPageURL2); changed_page_urls.insert(kPageURL3); model_->OnFaviconsChanged(changed_page_urls, GURL()); ASSERT_EQ(3u, updated_nodes_.size()); EXPECT_TRUE(WasNodeUpdated(node2)); EXPECT_TRUE(WasNodeUpdated(node3)); EXPECT_TRUE(WasNodeUpdated(node4)); } { // Reset the favicon data because BookmarkModel::OnFaviconsChanged() clears // the BookmarkNode's favicon data for all of the BookmarkNodes whose // favicon data changed. OnFaviconLoaded(AsMutable(node1), kFaviconURL12); OnFaviconLoaded(AsMutable(node2), kFaviconURL12); OnFaviconLoaded(AsMutable(node3), kFaviconURL3); OnFaviconLoaded(AsMutable(node4), kFaviconURL3); ClearUpdatedNodes(); model_->OnFaviconsChanged(std::set(), kFaviconURL12); ASSERT_EQ(2u, updated_nodes_.size()); EXPECT_TRUE(WasNodeUpdated(node1)); EXPECT_TRUE(WasNodeUpdated(node2)); } { OnFaviconLoaded(AsMutable(node1), kFaviconURL12); OnFaviconLoaded(AsMutable(node2), kFaviconURL12); OnFaviconLoaded(AsMutable(node3), kFaviconURL3); OnFaviconLoaded(AsMutable(node4), kFaviconURL3); ClearUpdatedNodes(); std::set changed_page_urls; changed_page_urls.insert(kPageURL1); model_->OnFaviconsChanged(changed_page_urls, kFaviconURL12); ASSERT_EQ(2u, updated_nodes_.size()); EXPECT_TRUE(WasNodeUpdated(node1)); EXPECT_TRUE(WasNodeUpdated(node2)); } } } // namespace bookmarks