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