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