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