blob: e04cd6731f6964d09693269721c9b889b028020f [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/sync_bookmarks/local_bookmark_to_account_merger.h"
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include "base/test/scoped_feature_list.h"
#include "base/uuid.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/browser/bookmark_node.h"
#include "components/bookmarks/browser/bookmark_test_util.h"
#include "components/bookmarks/test/mock_bookmark_model_observer.h"
#include "components/bookmarks/test/test_bookmark_client.h"
#include "components/bookmarks/test/test_matchers.h"
#include "components/signin/public/base/signin_switches.h"
#include "components/sync_bookmarks/switches.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace sync_bookmarks {
namespace {
using bookmarks::test::IsFolder;
using bookmarks::test::IsFolderWithUuid;
using bookmarks::test::IsUrlBookmark;
using bookmarks::test::IsUrlBookmarkWithUuid;
using testing::ElementsAre;
using testing::IsEmpty;
using testing::Ne;
// Test class to build bookmark URLs conveniently and compactly in tests.
class UrlBuilder {
public:
UrlBuilder(const std::u16string& title, const GURL& url)
: title_(title), url_(url) {}
UrlBuilder(const UrlBuilder&) = default;
~UrlBuilder() = default;
UrlBuilder& SetUuid(const base::Uuid& uuid) {
uuid_ = uuid;
return *this;
}
void Build(bookmarks::BookmarkModel* model,
const bookmarks::BookmarkNode* parent) const {
model->AddURL(parent, parent->children().size(), title_, url_,
/*meta_info=*/nullptr, /*creation_time=*/std::nullopt, uuid_);
}
private:
const std::u16string title_;
const GURL url_;
std::optional<base::Uuid> uuid_;
};
// Test class to build bookmark folders conveniently and compactly in tests.
class FolderBuilder {
public:
using FolderOrUrl = std::variant<FolderBuilder, UrlBuilder>;
static void AddChildTo(bookmarks::BookmarkModel* model,
const bookmarks::BookmarkNode* parent,
const FolderOrUrl& folder_or_url) {
if (std::holds_alternative<UrlBuilder>(folder_or_url)) {
std::get<UrlBuilder>(folder_or_url).Build(model, parent);
} else {
CHECK(std::holds_alternative<FolderBuilder>(folder_or_url));
std::get<FolderBuilder>(folder_or_url).Build(model, parent);
}
}
static void AddChildrenTo(bookmarks::BookmarkModel* model,
const bookmarks::BookmarkNode* parent,
const std::vector<FolderOrUrl>& children) {
for (const FolderOrUrl& folder_or_url : children) {
AddChildTo(model, parent, folder_or_url);
}
}
explicit FolderBuilder(const std::u16string& title) : title_(title) {}
FolderBuilder(const FolderBuilder&) = default;
~FolderBuilder() = default;
FolderBuilder& SetChildren(std::vector<FolderOrUrl> children) {
children_ = std::move(children);
return *this;
}
FolderBuilder& SetUuid(const base::Uuid& uuid) {
uuid_ = uuid;
return *this;
}
void Build(bookmarks::BookmarkModel* model,
const bookmarks::BookmarkNode* parent) const {
const bookmarks::BookmarkNode* folder = model->AddFolder(
parent, parent->children().size(), title_,
/*meta_info=*/nullptr, /*creation_time=*/std::nullopt, uuid_);
AddChildrenTo(model, folder, children_);
}
private:
const std::u16string title_;
std::vector<FolderOrUrl> children_;
std::optional<base::Uuid> uuid_;
};
class LocalBookmarkToAccountMergerTest : public testing::Test {
protected:
LocalBookmarkToAccountMergerTest() {
model_->AddObserver(&observer_);
model_->CreateAccountPermanentFolders();
}
~LocalBookmarkToAccountMergerTest() override {
model_->RemoveObserver(&observer_);
}
void AddLocalNodes(
const std::vector<FolderBuilder::FolderOrUrl>& children_of_bookmark_bar,
const std::vector<FolderBuilder::FolderOrUrl>& children_of_mobile_node =
{},
const std::vector<FolderBuilder::FolderOrUrl>& children_of_other_node =
{}) {
FolderBuilder::AddChildrenTo(model_.get(), model_->bookmark_bar_node(),
children_of_bookmark_bar);
FolderBuilder::AddChildrenTo(model_.get(), model_->mobile_node(),
children_of_mobile_node);
FolderBuilder::AddChildrenTo(model_.get(), model_->other_node(),
children_of_other_node);
}
void AddAccountNodes(
const std::vector<FolderBuilder::FolderOrUrl>& children_of_bookmark_bar) {
FolderBuilder::AddChildrenTo(model_.get(),
model_->account_bookmark_bar_node(),
children_of_bookmark_bar);
}
base::test::ScopedFeatureList feature_list_{
switches::kSyncEnableBookmarksInTransportMode};
const std::unique_ptr<bookmarks::BookmarkModel> model_ =
bookmarks::TestBookmarkClient::CreateModel();
testing::NiceMock<bookmarks::MockBookmarkModelObserver> observer_;
};
TEST_F(LocalBookmarkToAccountMergerTest,
ShouldUploadLocalNodesIfNoAccountNodes) {
const std::u16string kFolder1Title = u"folder1";
const std::u16string kFolder2Title = u"folder2";
const std::u16string kUrl1Title = u"url1";
const std::u16string kUrl2Title = u"url2";
const std::u16string kUrl3Title = u"url3";
const std::u16string kUrl4Title = u"url4";
const GURL kUrl1("http://www.url1.com/");
const GURL kUrl2("http://www.url2.com/");
const GURL kUrl3("http://www.url3.com/");
const GURL kUrl4("http://www.url4.com/");
// -------- Local bookmarks --------
// bookmark_bar
// |- folder 1
// |- url1(http://www.url1.com)
// |- url2(http://www.url2.com)
// |- folder 2
// |- url3(http://www.url3.com)
// |- url4(http://www.url4.com)
AddLocalNodes({FolderBuilder(kFolder1Title)
.SetChildren({UrlBuilder(kUrl1Title, kUrl1),
UrlBuilder(kUrl2Title, kUrl2)}),
FolderBuilder(kFolder2Title)
.SetChildren({UrlBuilder(kUrl3Title, kUrl3),
UrlBuilder(kUrl4Title, kUrl4)})});
// -------- Account bookmarks --------
// bookmark_bar
AddAccountNodes({});
// -------- The expected merge outcome --------
// Same as the local model described above.
EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
EXPECT_CALL(observer_, BookmarkNodeMoved).Times(2);
EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(0);
EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);
LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();
EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
EXPECT_THAT(
model_->account_bookmark_bar_node()->children(),
ElementsAre(IsFolder(kFolder1Title,
ElementsAre(IsUrlBookmark(kUrl1Title, kUrl1),
IsUrlBookmark(kUrl2Title, kUrl2))),
IsFolder(kFolder2Title,
ElementsAre(IsUrlBookmark(kUrl3Title, kUrl3),
IsUrlBookmark(kUrl4Title, kUrl4)))));
}
TEST_F(LocalBookmarkToAccountMergerTest,
ShouldUploadLocalNodesUnderAllPermanentNodes) {
const std::u16string kUrl1Title = u"url1";
const std::u16string kUrl2Title = u"url2";
const std::u16string kUrl3Title = u"url3";
const GURL kUrl1("http://www.url1.com/");
const GURL kUrl2("http://www.url2.com/");
const GURL kUrl3("http://www.url3.com/");
// -------- Local bookmarks --------
// bookmark_bar
// |- url1(http://www.url1.com)
// mobile_node
// |- url2(http://www.url2.com)
// other_node
// |- url3(http://www.url3.com)
AddLocalNodes(/*children_of_bookmark_bar=*/{UrlBuilder(kUrl1Title, kUrl1)},
/*children_of_mobile_node=*/{UrlBuilder(kUrl2Title, kUrl2)},
/*children_of_other_node=*/{UrlBuilder(kUrl3Title, kUrl3)});
// -------- Account bookmarks --------
// bookmark_bar
// mobile_node
// other_node
AddAccountNodes({});
// -------- The expected merge outcome --------
// Same as the local model described above.
EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
EXPECT_CALL(observer_, BookmarkNodeMoved).Times(3);
EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(0);
EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);
LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();
EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
EXPECT_THAT(model_->mobile_node()->children(), IsEmpty());
EXPECT_THAT(model_->other_node()->children(), IsEmpty());
EXPECT_THAT(model_->account_bookmark_bar_node()->children(),
ElementsAre(IsUrlBookmark(kUrl1Title, kUrl1)));
EXPECT_THAT(model_->account_mobile_node()->children(),
ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2)));
EXPECT_THAT(model_->account_other_node()->children(),
ElementsAre(IsUrlBookmark(kUrl3Title, kUrl3)));
}
TEST_F(LocalBookmarkToAccountMergerTest, ShouldIgnoreManagedNodes) {
const std::u16string kUrl1Title = u"url1";
const std::u16string kUrl2Title = u"url2";
const GURL kUrl1("http://www.url1.com/");
const GURL kUrl2("http://www.url2.com/");
auto local_client = std::make_unique<bookmarks::TestBookmarkClient>();
bookmarks::BookmarkNode* managed_node = local_client->EnableManagedNode();
std::unique_ptr<bookmarks::BookmarkModel> model =
bookmarks::TestBookmarkClient::CreateModelWithClient(
std::move(local_client));
model->CreateAccountPermanentFolders();
// -------- Local bookmarks --------
// bookmark_bar
// |- url1(http://www.url1.com)
// managed_bookmarks
// |- url2(http://www.url2.com)
FolderBuilder::AddChildrenTo(model.get(), model->bookmark_bar_node(),
{UrlBuilder(kUrl1Title, kUrl1)});
FolderBuilder::AddChildrenTo(model.get(), managed_node,
{UrlBuilder(kUrl2Title, kUrl2)});
ASSERT_THAT(managed_node->children(),
ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2)));
// -------- Account bookmarks --------
// bookmark_bar
FolderBuilder::AddChildrenTo(model.get(), model->account_bookmark_bar_node(),
{});
// -------- The expected merge outcome --------
// bookmark_bar
// |- url1(http://www.url1.com)
//
LocalBookmarkToAccountMerger(model.get()).MoveAndMergeAllNodes();
ASSERT_THAT(model->bookmark_bar_node()->children(), IsEmpty());
ASSERT_THAT(model->account_bookmark_bar_node()->children(),
ElementsAre(IsUrlBookmark(kUrl1Title, kUrl1)));
// Managed nodes should be excluded from the merge and be left unmodified.
ASSERT_THAT(managed_node->children(),
ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2)));
EXPECT_THAT(model->GetNodesByURL(kUrl2),
ElementsAre(managed_node->children()[0].get()));
}
TEST_F(LocalBookmarkToAccountMergerTest, ShouldUploadLocalUuid) {
const std::u16string kUrl1Title = u"url1";
const GURL kUrl1("http://www.url1.com/");
const base::Uuid kUrl1Uuid = base::Uuid::GenerateRandomV4();
// -------- Local bookmarks --------
// bookmark_bar
// | - bookmark(kUuid/kLocalTitle)
AddLocalNodes({UrlBuilder(kUrl1Title, kUrl1).SetUuid(kUrl1Uuid)});
// -------- Account bookmarks --------
// bookmark_bar
AddAccountNodes({});
// -------- The expected merge outcome --------
// Same as the local model described above, including the UUID.
EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
EXPECT_CALL(observer_, BookmarkNodeMoved).Times(1);
EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(0);
EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);
LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();
EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
EXPECT_THAT(model_->account_bookmark_bar_node()->children(),
ElementsAre(IsUrlBookmarkWithUuid(kUrl1Title, kUrl1, kUrl1Uuid)));
}
TEST_F(LocalBookmarkToAccountMergerTest, ShouldDeduplicateBySemantics) {
const std::u16string kFolder1Title = u"folder1";
const std::u16string kUrl1Title = u"url1";
const std::u16string kUrl2Title = u"url2";
const std::u16string kUrl3Title = u"url3";
const GURL kUrl1("http://www.url1.com/");
const GURL kUrl2("http://www.url2.com/");
const GURL kUrl3("http://www.url3.com/");
// -------- Local bookmarks --------
// bookmark_bar
// |- folder 1
// |- url1(http://www.url1.com)
// |- url2(http://www.url2.com)
AddLocalNodes({FolderBuilder(kFolder1Title)
.SetChildren({UrlBuilder(kUrl1Title, kUrl1),
UrlBuilder(kUrl2Title, kUrl2)})});
// -------- Account bookmarks --------
// bookmark_bar
// |- folder 1
// |- url2(http://www.url2.com)
// |- url3(http://www.url3.com)
AddAccountNodes({FolderBuilder(kFolder1Title)
.SetChildren({UrlBuilder(kUrl2Title, kUrl2),
UrlBuilder(kUrl3Title, kUrl3)})});
// -------- The expected merge outcome --------
// bookmark_bar
// |- folder 1
// |- url2(http://www.url2.com)
// |- url3(http://www.url3.com)
// |- url1(http://www.url1.com)
EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
EXPECT_CALL(observer_, BookmarkNodeMoved).Times(1);
EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(2);
EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);
LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();
EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
EXPECT_THAT(
model_->account_bookmark_bar_node()->children(),
ElementsAre(IsFolder(kFolder1Title,
ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2),
IsUrlBookmark(kUrl3Title, kUrl3),
IsUrlBookmark(kUrl1Title, kUrl1)))));
}
TEST_F(LocalBookmarkToAccountMergerTest,
ShouldDeduplicateBySemanticsWhenSelected) {
const std::u16string kFolder1Title = u"folder1";
const std::u16string kUrl1Title = u"url1";
const std::u16string kUrl2Title = u"url2";
const std::u16string kUrl3Title = u"url3";
const GURL kUrl1("http://www.url1.com/");
const GURL kUrl2("http://www.url2.com/");
const GURL kUrl3("http://www.url3.com/");
// -------- Local bookmarks --------
// bookmark_bar
// |- url1(http://www.url1.com)
// |- url2(http://www.url2.com)
AddLocalNodes({UrlBuilder(kUrl1Title, kUrl1), UrlBuilder(kUrl2Title, kUrl2)});
// -------- Account bookmarks --------
// bookmark_bar
// |- url2(http://www.url2.com)
// |- url3(http://www.url3.com)
AddAccountNodes(
{UrlBuilder(kUrl2Title, kUrl2), UrlBuilder(kUrl3Title, kUrl3)});
// -------- The expected merge outcome --------
// Move(url2)
//
// ---- Local bookmarks ----
// |- url1(http://www.url1.com)
//
// ---- Account bookmarks ----
// bookmark_bar
// |- url2(http://www.url2.com)
// |- url3(http://www.url3.com)
EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
EXPECT_CALL(observer_, BookmarkNodeMoved).Times(0);
EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(1);
EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);
LocalBookmarkToAccountMerger(model_.get())
.MoveAndMergeSpecificSubtrees(
{model_->bookmark_bar_node()->children()[1]->id()});
EXPECT_THAT(model_->bookmark_bar_node()->children(),
ElementsAre(IsUrlBookmark(kUrl1Title, kUrl1)));
EXPECT_THAT(model_->account_bookmark_bar_node()->children(),
ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2),
IsUrlBookmark(kUrl3Title, kUrl3)));
}
TEST_F(LocalBookmarkToAccountMergerTest, ShouldNotDeduplicateIfDifferentUrls) {
const std::u16string kFolder1Title = u"folder1";
const std::u16string kFolder2Title = u"folder2";
const std::u16string kFolder3Title = u"folder3";
const std::u16string kFolder4Title = u"folder4";
const std::u16string kUrl1Title = u"url1";
const std::u16string kUrl2Title = u"url2";
const std::u16string kUrl3Title = u"url3";
const std::u16string kUrl4Title = u"url4";
const GURL kUrl1("http://www.url1.com/");
const GURL kUrl2("http://www.url2.com/");
const GURL kUrl3("http://www.url3.com/");
const GURL kUrl4("http://www.url4.com/");
const GURL kAnotherUrl2("http://www.another-url2.com/");
// -------- Local bookmarks --------
// bookmark_bar
// |- folder 1
// |- url1(http://www.url1.com)
// |- url2(http://www.url2.com)
// |- folder 2
// |- url3(http://www.url3.com)
// |- url4(http://www.url4.com)
// |- folder 3
AddLocalNodes({FolderBuilder(kFolder1Title)
.SetChildren({UrlBuilder(kUrl1Title, kUrl1),
UrlBuilder(kUrl2Title, kUrl2)}),
FolderBuilder(kFolder2Title)
.SetChildren({UrlBuilder(kUrl3Title, kUrl3),
UrlBuilder(kUrl4Title, kUrl4)}),
FolderBuilder(kFolder3Title)});
// -------- Account bookmarks --------
// bookmark_bar
// |- folder 1
// |- url1(http://www.url1.com)
// |- url2(http://www.another-url2.com)
// |- folder 4
// |- url3(http://www.url3.com)
// |- url4(http://www.url4.com)
AddAccountNodes({FolderBuilder(kFolder1Title)
.SetChildren({UrlBuilder(kUrl1Title, kUrl1),
UrlBuilder(kUrl2Title, kAnotherUrl2)}),
FolderBuilder(kFolder4Title)
.SetChildren({UrlBuilder(kUrl3Title, kUrl3),
UrlBuilder(kUrl4Title, kUrl4)})});
// -------- The expected merge outcome --------
// bookmark_bar
// |- folder 1
// |- url1(http://www.url1.com)
// |- url2(http://www.another-url2.com)
// |- url2(http://www.url2.com)
// |- folder 4
// |- url3(http://www.url3.com)
// |- url4(http://www.url4.com)
// |- folder 2
// |- url3(http://www.url3.com)
// |- url4(http://www.url4.com)
// |- folder 3
EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
EXPECT_CALL(observer_, BookmarkNodeMoved).Times(3);
EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(2);
EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);
LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();
EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
EXPECT_THAT(
model_->account_bookmark_bar_node()->children(),
ElementsAre(IsFolder(kFolder1Title,
ElementsAre(IsUrlBookmark(kUrl1Title, kUrl1),
IsUrlBookmark(kUrl2Title, kAnotherUrl2),
IsUrlBookmark(kUrl2Title, kUrl2))),
IsFolder(kFolder4Title,
ElementsAre(IsUrlBookmark(kUrl3Title, kUrl3),
IsUrlBookmark(kUrl4Title, kUrl4))),
IsFolder(kFolder2Title,
ElementsAre(IsUrlBookmark(kUrl3Title, kUrl3),
IsUrlBookmark(kUrl4Title, kUrl4))),
IsFolder(kFolder3Title, IsEmpty())));
}
// This tests that truncated titles produced by legacy clients are properly
// matched.
TEST_F(LocalBookmarkToAccountMergerTest,
ShouldDedupLocalAndAccountNodesWhenAccountHasLegacyTruncatedTitle) {
const std::u16string kLocalLongTitle(300, 'A');
const std::u16string kAccountTruncatedTitle(255, 'A');
// -------- Local bookmarks --------
AddLocalNodes({FolderBuilder(kLocalLongTitle)});
// -------- Account bookmarks --------
AddAccountNodes({FolderBuilder(kAccountTruncatedTitle)});
// -------- The expected merge outcome --------
EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
EXPECT_CALL(observer_, BookmarkNodeMoved).Times(0);
EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(1);
EXPECT_CALL(observer_, BookmarkNodeChanged).Times(1);
LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();
EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
// Both titles should have matched against each other.
EXPECT_THAT(model_->account_bookmark_bar_node()->children(),
ElementsAre(IsFolder(kLocalLongTitle, IsEmpty())));
}
// This test checks that local node with truncated title will merge with account
// node which has full title.
TEST_F(LocalBookmarkToAccountMergerTest,
ShouldMergeLocalAndAccountNodesWhenLocalHasLegacyTruncatedTitle) {
const std::u16string kAccountFullTitle(300, 'A');
const std::u16string kLocalTruncatedTitle(255, 'A');
// -------- Local bookmarks --------
AddLocalNodes({FolderBuilder(kLocalTruncatedTitle)});
// -------- Account bookmarks --------
AddAccountNodes({FolderBuilder(kAccountFullTitle)});
// -------- The expected merge outcome --------
EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
EXPECT_CALL(observer_, BookmarkNodeMoved).Times(0);
EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(1);
EXPECT_CALL(observer_, BookmarkNodeChanged).Times(1);
LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();
EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
// Both titles should have matched against each other. Although the local
// title is truncated, for simplicity of the algorithm and considering how
// rare this scenario is, the local one wins.
EXPECT_THAT(model_->account_bookmark_bar_node()->children(),
ElementsAre(IsFolder(kLocalTruncatedTitle, IsEmpty())));
}
TEST_F(LocalBookmarkToAccountMergerTest, ShouldDeduplicateBookmarkByUuid) {
const std::u16string kLocalTitle = u"Title 1";
const std::u16string kAccountTitle = u"Title 2";
const GURL kUrl("http://www.foo.com/");
const base::Uuid kUuid = base::Uuid::GenerateRandomV4();
// -------- Local bookmarks --------
// bookmark_bar
// | - bookmark(kUuid/kLocalTitle)
AddLocalNodes({UrlBuilder(kLocalTitle, kUrl).SetUuid(kUuid)});
// -------- Account bookmarks --------
// bookmark_bar
// | - bookmark(kUuid/kAccountTitle)
AddAccountNodes({UrlBuilder(kAccountTitle, kUrl).SetUuid(kUuid)});
// -------- The expected merge outcome --------
// bookmark_bar
// |- bookmark(kUuid/kLocalTitle)
EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
EXPECT_CALL(observer_, BookmarkNodeMoved).Times(0);
EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(1);
EXPECT_CALL(observer_, BookmarkNodeChanged).Times(1);
LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();
EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
EXPECT_THAT(model_->account_bookmark_bar_node()->children(),
ElementsAre(IsUrlBookmarkWithUuid(kLocalTitle, kUrl, kUuid)));
}
TEST_F(LocalBookmarkToAccountMergerTest,
ShouldDeduplicateBookmarkByUuidWhenSelected) {
const std::u16string kLocalTitle = u"Title 1";
const std::u16string kAccountTitle = u"Title 2";
const GURL kUrl("http://www.foo.com/");
const base::Uuid kUuid = base::Uuid::GenerateRandomV4();
// -------- Local bookmarks --------
// bookmark_bar
// | - bookmark(kUuid/kLocalTitle)
AddLocalNodes({UrlBuilder(kLocalTitle, kUrl).SetUuid(kUuid)});
// -------- Account bookmarks --------
// bookmark_bar
// | - bookmark(kUuid/kAccountTitle)
AddAccountNodes({UrlBuilder(kAccountTitle, kUrl).SetUuid(kUuid)});
// -------- The expected merge outcome --------
// bookmark_bar
// |- bookmark(kUuid/kLocalTitle)
EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
EXPECT_CALL(observer_, BookmarkNodeMoved).Times(0);
EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(1);
EXPECT_CALL(observer_, BookmarkNodeChanged).Times(1);
LocalBookmarkToAccountMerger(model_.get())
.MoveAndMergeSpecificSubtrees(
{model_->bookmark_bar_node()->children()[0]->id()});
EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
EXPECT_THAT(model_->account_bookmark_bar_node()->children(),
ElementsAre(IsUrlBookmarkWithUuid(kLocalTitle, kUrl, kUuid)));
}
TEST_F(LocalBookmarkToAccountMergerTest,
ShouldDeduplicateBySemanticsAfterParentMatchedByUuid) {
const std::u16string kFolder1Title = u"folder1";
const std::u16string kFolder2Title = u"folder2";
const std::u16string kFolder3Title = u"folder3";
const std::u16string kUrl1Title = u"url1";
const std::u16string kUrl2Title = u"url2";
const std::u16string kUrl3Title = u"url3";
const GURL kUrl1("http://www.url1.com/");
const GURL kUrl2("http://www.url2.com/");
const GURL kUrl3("http://www.url3.com/");
const base::Uuid kFolder2Uuid = base::Uuid::GenerateRandomV4();
// -------- Local bookmarks --------
// bookmark_bar
// | - folder 1 (kFolder1Title)
// | - folder 2 (kFolder2Uuid/kFolder2Title)
// |- url1(http://www.url1.com)
// |- url2(http://www.url2.com)
AddLocalNodes({FolderBuilder(kFolder1Title),
FolderBuilder(kFolder2Title)
.SetUuid(kFolder2Uuid)
.SetChildren({UrlBuilder(kUrl1Title, kUrl1),
UrlBuilder(kUrl2Title, kUrl2)})});
// -------- Account bookmarks --------
// bookmark_bar
// | - folder 3 (kFolder3Title)
// | - folder 2 (kFolder2Uuid/kFolder2Title)
// |- url2(http://www.url1.com)
// |- url3(http://www.url3.com)
AddAccountNodes(
{FolderBuilder(kFolder3Title)
.SetChildren({FolderBuilder(kFolder2Title)
.SetUuid(kFolder2Uuid)
.SetChildren({UrlBuilder(kUrl2Title, kUrl2),
UrlBuilder(kUrl3Title, kUrl3)})})});
// -------- The expected merge outcome --------
// bookmark_bar
// | - folder 3 (kFolder3Title)
// | - folder 2 (kFolder2Uuid/kTitle2)
// |- url2(http://www.url2.com)
// |- url3(http://www.url3.com)
// |- url1(http://www.url1.com)
// | - folder 1 (kTitle1)
EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
EXPECT_CALL(observer_, BookmarkNodeMoved).Times(2);
EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(2);
EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);
LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();
EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
EXPECT_THAT(
model_->account_bookmark_bar_node()->children(),
ElementsAre(IsFolder(kFolder3Title,
ElementsAre(IsFolder(
kFolder2Title,
ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2),
IsUrlBookmark(kUrl3Title, kUrl3),
IsUrlBookmark(kUrl1Title, kUrl1))))),
IsFolder(kFolder1Title, IsEmpty())));
}
TEST_F(LocalBookmarkToAccountMergerTest,
ShouldDeduplicateBySemanticsAfterNestedParentMatchedByUuid) {
const std::u16string kFolder1Title = u"folder1";
const std::u16string kFolder2Title = u"folder2";
const std::u16string kUrl1Title = u"url1";
const std::u16string kUrl2Title = u"url2";
const std::u16string kUrl3Title = u"url3";
const GURL kUrl1("http://www.url1.com/");
const GURL kUrl2("http://www.url2.com/");
const GURL kUrl3("http://www.url3.com/");
const base::Uuid kFolder2Uuid = base::Uuid::GenerateRandomV4();
// -------- Local bookmarks --------
// bookmark_bar
// | - folder 1 (kFolder1Title)
// | - folder 2 (kFolder2Uuid/kFolder2Title)
// |- url1(http://www.url1.com)
// |- url2(http://www.url2.com)
AddLocalNodes(
{FolderBuilder(kFolder1Title)
.SetChildren({FolderBuilder(kFolder2Title)
.SetUuid(kFolder2Uuid)
.SetChildren({UrlBuilder(kUrl1Title, kUrl1),
UrlBuilder(kUrl2Title, kUrl2)})})});
// -------- Account bookmarks --------
// bookmark_bar
// | - folder 2 (kFolder2Uuid/kFolder2Title)
// |- url2(http://www.url2.com)
// |- url3(http://www.url3.com)
AddAccountNodes({FolderBuilder(kFolder2Title)
.SetUuid(kFolder2Uuid)
.SetChildren({UrlBuilder(kUrl2Title, kUrl2),
UrlBuilder(kUrl3Title, kUrl3)})});
// -------- The expected merge outcome --------
// bookmark_bar
// | - folder 2 (kFolder2Uuid/kTitle2)
// |- url2(http://www.url2.com)
// |- url3(http://www.url3.com)
// |- url1(http://www.url1.com)
// | - folder 1 (kTitle1)
EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
EXPECT_CALL(observer_, BookmarkNodeMoved).Times(2);
EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(2);
EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);
LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();
EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
EXPECT_THAT(
model_->account_bookmark_bar_node()->children(),
ElementsAre(IsFolder(kFolder2Title,
ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2),
IsUrlBookmark(kUrl3Title, kUrl3),
IsUrlBookmark(kUrl1Title, kUrl1))),
IsFolder(kFolder1Title, IsEmpty())));
}
TEST_F(LocalBookmarkToAccountMergerTest,
ShouldDeduplicateBySemanticsAfterTwoConsecutiveAncestorsMatchedByUuid) {
const std::u16string kFolder1Title = u"folder1";
const std::u16string kFolder2Title = u"folder2";
const std::u16string kFolder3Title = u"folder3";
const std::u16string kUrl1Title = u"url1";
const std::u16string kUrl2Title = u"url2";
const std::u16string kUrl3Title = u"url3";
const GURL kUrl1("http://www.url1.com/");
const GURL kUrl2("http://www.url2.com/");
const GURL kUrl3("http://www.url3.com/");
const base::Uuid kFolder2Uuid = base::Uuid::GenerateRandomV4();
const base::Uuid kFolder3Uuid = base::Uuid::GenerateRandomV4();
// -------- Local bookmarks --------
// bookmark_bar
// | - folder 1 (kFolder1Title)
// | - folder 2 (kFolder2Uuid/kFolder2Title)
// | - folder 3 (kFolder3Uuid/kFolder3Title)
// |- url1(http://www.url1.com)
// |- url2(http://www.url2.com)
AddLocalNodes(
{FolderBuilder(kFolder1Title)
.SetChildren(
{FolderBuilder(kFolder2Title)
.SetUuid(kFolder2Uuid)
.SetChildren(
{FolderBuilder(kFolder3Title)
.SetUuid(kFolder3Uuid)
.SetChildren(
{UrlBuilder(kUrl1Title, kUrl1),
UrlBuilder(kUrl2Title, kUrl2)})})})});
// -------- Account bookmarks --------
// bookmark_bar
// | - folder 2 (kFolder2Uuid/kFolder2Title)
// | - folder 3 (kFolder3Uuid/kFolder3Title)
// |- url2(http://www.url2.com)
// |- url3(http://www.url3.com)
AddAccountNodes(
{FolderBuilder(kFolder2Title)
.SetUuid(kFolder2Uuid)
.SetChildren({FolderBuilder(kFolder3Title)
.SetUuid(kFolder3Uuid)
.SetChildren({UrlBuilder(kUrl2Title, kUrl2),
UrlBuilder(kUrl3Title, kUrl3)})})});
// -------- The expected merge outcome --------
// bookmark_bar
// | - folder 2 (kFolder2Uuid/kTitle2)
// | - folder 3 (kFolder3Uuid/kTitle3)
// |- url2(http://www.url2.com)
// |- url3(http://www.url3.com)
// |- url1(http://www.url1.com)
// | - folder 1 (kTitle1)
EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
EXPECT_CALL(observer_, BookmarkNodeMoved).Times(2);
EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(3);
EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);
LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();
EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
EXPECT_THAT(
model_->account_bookmark_bar_node()->children(),
ElementsAre(IsFolder(kFolder2Title,
ElementsAre(IsFolder(
kFolder3Title,
ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2),
IsUrlBookmark(kUrl3Title, kUrl3),
IsUrlBookmark(kUrl1Title, kUrl1))))),
IsFolder(kFolder1Title, IsEmpty())));
}
TEST_F(
LocalBookmarkToAccountMergerTest,
ShouldDeduplicateBySemanticsAfterTwoNonConsecutiveAncestorsMatchedByUuid) {
const std::u16string kFolder1Title = u"folder1";
const std::u16string kFolder2Title = u"folder2";
const std::u16string kFolder3Title = u"folder3";
const std::u16string kFolder4Title = u"folder4";
const std::u16string kFolder5Title = u"folder5";
const std::u16string kUrl1Title = u"url1";
const std::u16string kUrl2Title = u"url2";
const std::u16string kUrl3Title = u"url3";
const GURL kUrl1("http://www.url1.com/");
const GURL kUrl2("http://www.url2.com/");
const GURL kUrl3("http://www.url3.com/");
const base::Uuid kFolder2Uuid = base::Uuid::GenerateRandomV4();
const base::Uuid kFolder5Uuid = base::Uuid::GenerateRandomV4();
// -------- Local bookmarks --------
// bookmark_bar
// | - folder 1 (kFolder1Title)
// | - folder 2 (kFolder2Uuid/kFolder2Title)
// | - folder 3 (kFolder3Title)
// | - folder 4 (kFolder4Title)
// | - folder 5 (kFolder5Uuid/kFolder5Title)
// |- url1(http://www.url1.com)
// |- url2(http://www.url2.com)
AddLocalNodes(
{FolderBuilder(kFolder1Title)
.SetChildren(
{FolderBuilder(kFolder2Title)
.SetUuid(kFolder2Uuid)
.SetChildren(
{FolderBuilder(kFolder3Title)
.SetChildren(
{FolderBuilder(kFolder4Title)
.SetChildren(
{FolderBuilder(kFolder5Title)
.SetUuid(kFolder5Uuid)
.SetChildren(
{UrlBuilder(kUrl1Title,
kUrl1),
UrlBuilder(
kUrl2Title,
kUrl2)})})})})})});
// -------- Account bookmarks --------
// bookmark_bar
// | - folder 2 (kFolder2Uuid/kFolder2Title)
// | - folder 5 (kFolder5Uuid/kFolder5Title)
// |- url2(http://www.url2.com)
// |- url3(http://www.url3.com)
AddAccountNodes({FolderBuilder(kFolder2Title).SetUuid(kFolder2Uuid),
FolderBuilder(kFolder5Title)
.SetUuid(kFolder5Uuid)
.SetChildren({UrlBuilder(kUrl2Title, kUrl2),
UrlBuilder(kUrl3Title, kUrl3)})});
// -------- The expected merge outcome --------
// bookmark_bar
// | - folder 2 (kFolder2Uuid/kTitle2)
// | - folder 3 (kTitle3)
// | - folder 4 (kTitle4)
// | - folder 5 (kFolder5Uuid/kFolder5Title)
// |- url2(http://www.url2.com)
// |- url3(http://www.url3.com)
// |- url1(http://www.url1.com)
// | - folder 1 (kTitle1)
EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
EXPECT_CALL(observer_, BookmarkNodeMoved).Times(3);
EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(3);
EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);
LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();
EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
EXPECT_THAT(
model_->account_bookmark_bar_node()->children(),
ElementsAre(IsFolder(kFolder2Title,
ElementsAre(IsFolder(
kFolder3Title, ElementsAre(IsFolder(
kFolder4Title, IsEmpty()))))),
IsFolder(kFolder5Title,
ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2),
IsUrlBookmark(kUrl3Title, kUrl3),
IsUrlBookmark(kUrl1Title, kUrl1))),
IsFolder(kFolder1Title, IsEmpty())));
}
TEST_F(LocalBookmarkToAccountMergerTest,
ShouldMergeBookmarkByUuidDespiteDifferentParent) {
const std::u16string kFolderTitle = u"Folder Title";
const std::u16string kLocalTitle = u"Title 1";
const std::u16string kAccountTitle = u"Title 2";
const GURL kUrl("http://www.foo.com/");
const base::Uuid kUuid = base::Uuid::GenerateRandomV4();
// -------- Local bookmarks --------
// bookmark_bar
// |- bookmark(kUuid/kLocalTitle)
AddLocalNodes({UrlBuilder(kLocalTitle, kUrl).SetUuid(kUuid)});
// -------- Account bookmarks --------
// bookmark_bar
// | - folder
// | - bookmark(kUuid/kAccountTitle)
AddAccountNodes(
{FolderBuilder(kFolderTitle)
.SetChildren({UrlBuilder(kAccountTitle, kUrl).SetUuid(kUuid)})});
// -------- The merged account nodes --------
// bookmark_bar
// | - folder
// | - bookmark(kUuid/kLocalTitle)
EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
EXPECT_CALL(observer_, BookmarkNodeMoved).Times(0);
EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(1);
EXPECT_CALL(observer_, BookmarkNodeChanged).Times(1);
LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();
EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
EXPECT_THAT(
model_->account_bookmark_bar_node()->children(),
ElementsAre(IsFolder(kFolderTitle, ElementsAre(IsUrlBookmarkWithUuid(
kLocalTitle, kUrl, kUuid)))));
}
TEST_F(LocalBookmarkToAccountMergerTest,
ShouldNotMergeBySemanticsIfDifferentParent) {
const std::u16string kFolder1Title = u"folder1";
const std::u16string kFolder2Title = u"folder2";
const std::u16string kUrl1Title = u"url1";
const std::u16string kUrl2Title = u"url2";
const GURL kUrl1("http://www.url1.com/");
const GURL kUrl2("http://www.url2.com/");
// -------- Local bookmarks --------
// bookmark_bar
// |- folder 1
// |- folder 2
// |- url1(http://www.url1.com)
AddLocalNodes(
{FolderBuilder(kFolder1Title)
.SetChildren({FolderBuilder(kFolder2Title)
.SetChildren({UrlBuilder(kUrl1Title, kUrl1)})})});
// -------- Account bookmarks --------
// bookmark_bar
// |- folder 2
// |- url2(http://www.url2.com)
AddAccountNodes({FolderBuilder(kFolder2Title)
.SetChildren({UrlBuilder(kUrl2Title, kUrl2)})});
// -------- The merged account nodes --------
// bookmark_bar
// |- folder 2
// |- url2(http://www.url2.com)
// |- folder 1
// |- folder 2
// |- url1(http://www.url1.com)
EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
EXPECT_CALL(observer_, BookmarkNodeMoved).Times(1);
EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(0);
EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);
LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();
EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
EXPECT_THAT(
model_->account_bookmark_bar_node()->children(),
ElementsAre(IsFolder(kFolder2Title,
ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2))),
IsFolder(kFolder1Title,
ElementsAre(IsFolder(kFolder2Title,
ElementsAre(IsUrlBookmark(
kUrl1Title, kUrl1)))))));
}
TEST_F(LocalBookmarkToAccountMergerTest,
ShouldMergeFolderByUuidAndNotSemantics) {
const std::u16string kTitle1 = u"Title 1";
const std::u16string kTitle2 = u"Title 2";
const GURL kUrl("http://www.foo.com/");
const base::Uuid kUuid1 = base::Uuid::GenerateRandomV4();
const base::Uuid kUuid2 = base::Uuid::GenerateRandomV4();
// -------- Local bookmarks --------
// bookmark_bar
// | - folder 1 (kUuid1/kTitle1)
// | - folder 2 (kUuid2/kTitle2)
AddLocalNodes({FolderBuilder(kTitle1).SetUuid(kUuid1).SetChildren(
{FolderBuilder(kTitle2).SetUuid(kUuid2)})});
// -------- Account bookmarks --------
// bookmark_bar
// | - folder (kUuid2/kTitle1)
AddAccountNodes({FolderBuilder(kTitle1).SetUuid(kUuid2)});
// -------- The merged account nodes --------
// bookmark_bar
// | - folder 2 (kUuid2/kTitle2)
// | - folder 1 (kUuid1/kTitle1)
//
// The node should have been merged with its UUID match, even if the other
// candidate matches by semantics.
EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
EXPECT_CALL(observer_, BookmarkNodeMoved).Times(1);
EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(1);
EXPECT_CALL(observer_, BookmarkNodeChanged).Times(1);
LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();
EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
EXPECT_THAT(model_->account_bookmark_bar_node()->children(),
ElementsAre(IsFolderWithUuid(kTitle2, kUuid2, IsEmpty()),
IsFolderWithUuid(kTitle1, kUuid1, IsEmpty())));
}
TEST_F(
LocalBookmarkToAccountMergerTest,
ShouldIgnoreFolderSemanticsMatchAndLaterMatchByUuidWithSemanticsNodeFirst) {
const std::u16string kLocalOnlyTitle = u"LocalOnlyTitle";
const std::u16string kMatchingTitle = u"MatchingTitle";
const base::Uuid kUuid1 = base::Uuid::GenerateRandomV4();
const base::Uuid kUuid2 = base::Uuid::GenerateRandomV4();
const GURL kUrl("http://foo.com/");
const std::u16string kUrlTitle = u"Bookmark Title";
// -------- Local bookmarks --------
// bookmark_bar
// | - folder (kUuid1/kMatchingTitle)
// | - folder (kUuid2/kLocalOnlyTitle)
// | - bookmark
AddLocalNodes({FolderBuilder(kMatchingTitle).SetUuid(kUuid1),
FolderBuilder(kLocalOnlyTitle)
.SetUuid(kUuid2)
.SetChildren({UrlBuilder(kUrlTitle, kUrl)})});
// -------- Account bookmarks --------
// bookmark_bar
// | - folder (kUuid2/kMatchingTitle)
AddAccountNodes({FolderBuilder(kMatchingTitle).SetUuid(kUuid2)});
// -------- The merged account nodes --------
// bookmark_bar
// | - folder (kUuid2/kLocalOnlyTitle)
// | - bookmark
// | - folder (kUuid1/kMatchingTitle)
//
// The node should have been merged with its UUID match, even if the other
// candidate matches by semantics.
EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
EXPECT_CALL(observer_, BookmarkNodeMoved).Times(2);
EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(1);
EXPECT_CALL(observer_, BookmarkNodeChanged).Times(1);
LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();
EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
EXPECT_THAT(
model_->account_bookmark_bar_node()->children(),
ElementsAre(IsFolderWithUuid(kLocalOnlyTitle, kUuid2,
ElementsAre(IsUrlBookmark(kUrlTitle, kUrl))),
IsFolderWithUuid(kMatchingTitle, kUuid1, IsEmpty())));
}
TEST_F(LocalBookmarkToAccountMergerTest,
ShouldIgnoreFolderSemanticsMatchAndLaterMatchByUuidWithUuidNodeFirst) {
const std::u16string kLocalOnlyTitle = u"LocalOnlyTitle";
const std::u16string kMatchingTitle = u"MatchingTitle";
const base::Uuid kUuid1 = base::Uuid::GenerateRandomV4();
const base::Uuid kUuid2 = base::Uuid::GenerateRandomV4();
const GURL kUrl("http://foo.com/");
const std::u16string kUrlTitle = u"Bookmark Title";
// -------- Local bookmarks --------
// bookmark_bar
// | - folder (kUuid2/kLocalOnlyTitle)
// | - bookmark
// | - folder (kUuid1/kMatchingTitle)
AddLocalNodes({FolderBuilder(kLocalOnlyTitle)
.SetUuid(kUuid2)
.SetChildren({UrlBuilder(kUrlTitle, kUrl)}),
FolderBuilder(kMatchingTitle).SetUuid(kUuid1)});
// -------- Account bookmarks --------
// bookmark_bar
// | - folder (kUuid2/kMatchingTitle)
AddAccountNodes({FolderBuilder(kMatchingTitle).SetUuid(kUuid2)});
// -------- The merged account nodes --------
// bookmark_bar
// | - folder (kUuid2/kLocalOnlyTitle)
// | - bookmark
// | - folder (kUuid1/kMatchingTitle)
//
// The node should have been merged with its UUID match, even if the other
// candidate matches by semantics.
EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
EXPECT_CALL(observer_, BookmarkNodeMoved).Times(2);
EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(1);
EXPECT_CALL(observer_, BookmarkNodeChanged).Times(1);
LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();
EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
EXPECT_THAT(
model_->account_bookmark_bar_node()->children(),
ElementsAre(IsFolderWithUuid(kLocalOnlyTitle, kUuid2,
ElementsAre(IsUrlBookmark(kUrlTitle, kUrl))),
IsFolderWithUuid(kMatchingTitle, kUuid1, IsEmpty())));
}
TEST_F(LocalBookmarkToAccountMergerTest,
ShouldReplaceBookmarkUuidWithConflictingURLs) {
const std::u16string kTitle = u"Title";
const GURL kUrl1("http://www.foo.com/");
const GURL kUrl2("http://www.bar.com/");
const base::Uuid kUuid = base::Uuid::GenerateRandomV4();
// -------- Local bookmarks --------
// bookmark_bar
// | - bookmark (kUuid/kUrl1)
AddLocalNodes({UrlBuilder(kTitle, kUrl1).SetUuid(kUuid)});
// -------- Account bookmarks --------
// bookmark_bar
// | - bookmark (kUuid/kUrl2)
AddAccountNodes({UrlBuilder(kTitle, kUrl2).SetUuid(kUuid)});
// -------- The merged account nodes --------
// bookmark_bar
// | - bookmark (kUuid/kUrl2)
// | - bookmark ([new UUID]/kUrl1)
//
// The conflicting node UUID should have been replaced.
EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
EXPECT_CALL(observer_, BookmarkNodeMoved).Times(1);
EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(0);
EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);
LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();
EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
EXPECT_THAT(model_->account_bookmark_bar_node()->children(),
ElementsAre(IsUrlBookmarkWithUuid(kTitle, kUrl2, kUuid),
IsUrlBookmarkWithUuid(kTitle, kUrl1, Ne(kUuid))));
}
TEST_F(LocalBookmarkToAccountMergerTest,
ShouldReplaceBookmarkUuidWithConflictingTypes) {
const GURL kUrl1("http://www.foo.com/");
const std::u16string kTitle = u"Title";
const base::Uuid kUuid = base::Uuid::GenerateRandomV4();
// -------- Local bookmarks --------
// bookmark_bar
// | - bookmark (kUuid/kUrl1)
AddLocalNodes({UrlBuilder(kTitle, kUrl1).SetUuid(kUuid)});
// -------- Account bookmarks --------
// bookmark_bar
// | - folder(kUuid)
AddAccountNodes({FolderBuilder(kTitle).SetUuid(kUuid)});
// -------- The merged account nodes --------
// bookmark_bar
// | - folder (kUuid)
// | - bookmark ([new UUID])
//
// The conflicting node UUID should have been replaced.
EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
EXPECT_CALL(observer_, BookmarkNodeMoved).Times(1);
EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(0);
EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);
LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();
EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
EXPECT_THAT(model_->account_bookmark_bar_node()->children(),
ElementsAre(IsFolderWithUuid(kTitle, kUuid, IsEmpty()),
IsUrlBookmarkWithUuid(kTitle, kUrl1, Ne(kUuid))));
}
TEST_F(LocalBookmarkToAccountMergerTest,
ShouldReplaceBookmarkUuidWithConflictingTypesAndLocalChildren) {
const std::u16string kFolderTitle = u"Folder Title";
const std::u16string kUrl1Title = u"url1";
const std::u16string kUrl2Title = u"url2";
const GURL kUrl1("http://www.url1.com/");
const GURL kUrl2("http://www.url2.com/");
const base::Uuid kUuid = base::Uuid::GenerateRandomV4();
// -------- Local bookmarks --------
// bookmark_bar
// | - folder (kUuid)
// | - bookmark (kUrl1)
AddLocalNodes({FolderBuilder(kFolderTitle)
.SetUuid(kUuid)
.SetChildren({UrlBuilder(kUrl1Title, kUrl1)})});
// -------- Account bookmarks --------
// bookmark_bar
// | - bookmark (kUuid/kUrl2)
AddAccountNodes({UrlBuilder(kUrl2Title, kUrl2).SetUuid(kUuid)});
// -------- The merged account nodes --------
// bookmark_bar
// | - bookmark (kUuid/kUrl2)
// | - folder ([new UUID])
// | - bookmark (kUrl1)
//
// The conflicting node UUID should have been replaced.
EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
EXPECT_CALL(observer_, BookmarkNodeMoved).Times(1);
EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(0);
EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);
LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeAllNodes();
EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
EXPECT_THAT(model_->account_bookmark_bar_node()->children(),
ElementsAre(IsUrlBookmarkWithUuid(kUrl2Title, kUrl2, kUuid),
IsFolderWithUuid(
kFolderTitle, Ne(kUuid),
ElementsAre(IsUrlBookmark(kUrl1Title, kUrl1)))));
}
TEST_F(LocalBookmarkToAccountMergerTest, ShouldRemoveOneChildAtArbitraryIndex) {
const std::u16string kUrl1Title = u"url1";
const std::u16string kUrl2Title = u"url2";
const std::u16string kUrl3Title = u"url3";
const std::u16string kUrl4Title = u"url4";
const GURL kUrl1("http://www.url1.com/");
const GURL kUrl2("http://www.url2.com/");
const GURL kUrl3("http://www.url3.com/");
const GURL kUrl4("http://www.url4.com/");
AddLocalNodes({UrlBuilder(kUrl1Title, kUrl1), UrlBuilder(kUrl2Title, kUrl2),
UrlBuilder(kUrl3Title, kUrl3), UrlBuilder(kUrl4Title, kUrl4)});
LocalBookmarkToAccountMerger(model_.get())
.RemoveChildrenAtForTesting(
/*parent=*/model_->bookmark_bar_node(),
/*indices_to_remove=*/{2}, FROM_HERE);
EXPECT_THAT(model_->bookmark_bar_node()->children(),
ElementsAre(IsUrlBookmark(kUrl1Title, kUrl1),
IsUrlBookmark(kUrl2Title, kUrl2),
IsUrlBookmark(kUrl4Title, kUrl4)));
LocalBookmarkToAccountMerger(model_.get())
.RemoveChildrenAtForTesting(
/*parent=*/model_->bookmark_bar_node(),
/*indices_to_remove=*/{0}, FROM_HERE);
EXPECT_THAT(model_->bookmark_bar_node()->children(),
ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2),
IsUrlBookmark(kUrl4Title, kUrl4)));
LocalBookmarkToAccountMerger(model_.get())
.RemoveChildrenAtForTesting(
/*parent=*/model_->bookmark_bar_node(),
/*indices_to_remove=*/{1}, FROM_HERE);
EXPECT_THAT(model_->bookmark_bar_node()->children(),
ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2)));
}
TEST_F(LocalBookmarkToAccountMergerTest,
ShouldRemoveMultipleChildrenAtArbitraryIndices) {
const std::u16string kUrl1Title = u"url1";
const std::u16string kUrl2Title = u"url2";
const std::u16string kUrl3Title = u"url3";
const std::u16string kUrl4Title = u"url4";
const std::u16string kUrl5Title = u"url5";
const std::u16string kUrl6Title = u"url6";
const GURL kUrl1("http://www.url1.com/");
const GURL kUrl2("http://www.url2.com/");
const GURL kUrl3("http://www.url3.com/");
const GURL kUrl4("http://www.url4.com/");
const GURL kUrl5("http://www.url5.com/");
const GURL kUrl6("http://www.url6.com/");
AddLocalNodes({UrlBuilder(kUrl1Title, kUrl1), UrlBuilder(kUrl2Title, kUrl2),
UrlBuilder(kUrl3Title, kUrl3), UrlBuilder(kUrl4Title, kUrl4),
UrlBuilder(kUrl5Title, kUrl5), UrlBuilder(kUrl6Title, kUrl6)});
LocalBookmarkToAccountMerger(model_.get())
.RemoveChildrenAtForTesting(
/*parent=*/model_->bookmark_bar_node(),
/*indices_to_remove=*/{0, 2, 3, 5}, FROM_HERE);
EXPECT_THAT(model_->bookmark_bar_node()->children(),
ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2),
IsUrlBookmark(kUrl5Title, kUrl5)));
}
TEST_F(LocalBookmarkToAccountMergerTest, ShouldRemoveAllChildren) {
const std::u16string kUrl1Title = u"url1";
const std::u16string kUrl2Title = u"url2";
const std::u16string kUrl3Title = u"url3";
const std::u16string kUrl4Title = u"url4";
const GURL kUrl1("http://www.url1.com/");
const GURL kUrl2("http://www.url2.com/");
const GURL kUrl3("http://www.url3.com/");
const GURL kUrl4("http://www.url4.com/");
AddLocalNodes({UrlBuilder(kUrl1Title, kUrl1), UrlBuilder(kUrl2Title, kUrl2),
UrlBuilder(kUrl3Title, kUrl3), UrlBuilder(kUrl4Title, kUrl4)});
// When deleting all children, no reordering is expected.
EXPECT_CALL(observer_, BookmarkNodeChildrenReordered).Times(0);
LocalBookmarkToAccountMerger(model_.get())
.RemoveChildrenAtForTesting(
/*parent=*/model_->bookmark_bar_node(),
/*indices_to_remove=*/{0, 1, 2, 3}, FROM_HERE);
EXPECT_THAT(model_->bookmark_bar_node()->children(), IsEmpty());
}
TEST_F(LocalBookmarkToAccountMergerTest, ShouldDoNothingIfNoNodesSelected) {
const std::u16string kUrl1Title = u"url1";
const GURL kUrl1("http://www.url1.com/");
// -------- Local bookmarks --------
// bookmark_bar
// | - bookmark(kUuid/kLocalTitle)
AddLocalNodes({UrlBuilder(kUrl1Title, kUrl1)});
// -------- Account bookmarks --------
// bookmark_bar
AddAccountNodes({});
// -------- The expected merge outcome --------
// Unchanged
EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
EXPECT_CALL(observer_, BookmarkNodeMoved).Times(0);
EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(0);
EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);
LocalBookmarkToAccountMerger(model_.get()).MoveAndMergeSpecificSubtrees({});
EXPECT_THAT(model_->bookmark_bar_node()->children(),
ElementsAre(IsUrlBookmark(kUrl1Title, kUrl1)));
EXPECT_THAT(model_->account_bookmark_bar_node()->children(), IsEmpty());
}
TEST_F(LocalBookmarkToAccountMergerTest,
ShouldDoNothingIfNonExistentIdSelected) {
const std::u16string kUrl1Title = u"url1";
const GURL kUrl1("http://www.url1.com/");
// -------- Local bookmarks --------
// bookmark_bar
// | - bookmark(kUuid/kLocalTitle)
AddLocalNodes({UrlBuilder(kUrl1Title, kUrl1)});
// -------- Account bookmarks --------
// bookmark_bar
AddAccountNodes({});
// -------- The expected merge outcome --------
// Unchanged
EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
EXPECT_CALL(observer_, BookmarkNodeMoved).Times(0);
EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(0);
EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);
LocalBookmarkToAccountMerger(model_.get())
.MoveAndMergeSpecificSubtrees({123456789});
EXPECT_THAT(model_->bookmark_bar_node()->children(),
ElementsAre(IsUrlBookmark(kUrl1Title, kUrl1)));
EXPECT_THAT(model_->account_bookmark_bar_node()->children(), IsEmpty());
}
TEST_F(LocalBookmarkToAccountMergerTest,
ShouldDoNothingIfAccountNodeIdSelected) {
const std::u16string kUrl1Title = u"url1";
const GURL kUrl1("http://www.url1.com/");
const std::u16string kUrl2Title = u"url2";
const GURL kUrl2("http://www.url2.com/");
// -------- Local bookmarks --------
// bookmark_bar
// | - bookmark(kUuid/kLocalTitle)
AddLocalNodes({UrlBuilder(kUrl1Title, kUrl1)});
// -------- Account bookmarks --------
// bookmark_bar
AddAccountNodes({UrlBuilder(kUrl2Title, kUrl2)});
// -------- The expected merge outcome --------
// Unchanged
EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
EXPECT_CALL(observer_, BookmarkNodeMoved).Times(0);
EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(0);
EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);
LocalBookmarkToAccountMerger(model_.get())
.MoveAndMergeSpecificSubtrees(
{model_->account_bookmark_bar_node()->children()[0]->id()});
EXPECT_THAT(model_->bookmark_bar_node()->children(),
ElementsAre(IsUrlBookmark(kUrl1Title, kUrl1)));
EXPECT_THAT(model_->account_bookmark_bar_node()->children(),
ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2)));
}
TEST_F(LocalBookmarkToAccountMergerTest,
ShouldMergeSelectedSubsetOfLocalNodes) {
const std::u16string kUrl1Title = u"url1";
const GURL kUrl1("http://www.url1.com/");
const std::u16string kUrl2Title = u"url2";
const GURL kUrl2("http://www.url2.com/");
const std::u16string kUrl3Title = u"url3";
const GURL kUrl3("http://www.url3.com/");
const std::u16string kUrl4Title = u"url4";
const GURL kUrl4("http://www.url4.com/");
const std::u16string kUrl5Title = u"url5";
const GURL kUrl5("http://www.url5.com/");
// -------- Local bookmarks --------
// bookmark_bar
// | - bookmark(kUrl1Title)
// | - bookmark(kUrl2Title)
AddLocalNodes({UrlBuilder(kUrl1Title, kUrl1), UrlBuilder(kUrl2Title, kUrl2),
UrlBuilder(kUrl3Title, kUrl3), UrlBuilder(kUrl4Title, kUrl4),
UrlBuilder(kUrl5Title, kUrl5)});
// -------- Account bookmarks --------
// bookmark_bar
AddAccountNodes({});
// -------- The expected merge outcome --------
// Move(2, 4)
//
// ---- Local bookmarks ----
// bookmark_bar
// | - bookmark(kUrl1Title)
// | - bookmark(kUrl3Title)
// | - bookmark(kUrl5Title)
//
// ---- Account bookmarks ----
// bookmark_bar
// | - bookmark(kUrl2Title)
// | - bookmark(kUrl4Title)
EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
EXPECT_CALL(observer_, BookmarkNodeMoved).Times(2);
EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(0);
EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);
LocalBookmarkToAccountMerger(model_.get())
.MoveAndMergeSpecificSubtrees(
{model_->bookmark_bar_node()->children()[1]->id(),
model_->bookmark_bar_node()->children()[3]->id()});
EXPECT_THAT(model_->bookmark_bar_node()->children(),
ElementsAre(IsUrlBookmark(kUrl1Title, kUrl1),
IsUrlBookmark(kUrl3Title, kUrl3),
IsUrlBookmark(kUrl5Title, kUrl5)));
EXPECT_THAT(model_->account_bookmark_bar_node()->children(),
ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2),
IsUrlBookmark(kUrl4Title, kUrl4)));
}
TEST_F(LocalBookmarkToAccountMergerTest, ShouldDoNothingIfChildNodeSelected) {
const std::u16string kFolderTitle = u"Folder Title";
const std::u16string kUrl1Title = u"url1";
const GURL kUrl1("http://www.url1.com/");
// -------- Local bookmarks --------
// bookmark_bar
// | - folder (kFolderTitle)
// | - bookmark (kUrl1Title)
AddLocalNodes({FolderBuilder(kFolderTitle)
.SetChildren({UrlBuilder(kUrl1Title, kUrl1)})});
// -------- Account bookmarks --------
// bookmark_bar
AddAccountNodes({});
// -------- The expected merge outcome --------
// Move(kUrl1Title)
//
// Unchanged (the child is not moved).
EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
EXPECT_CALL(observer_, BookmarkNodeMoved).Times(0);
EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(0);
EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);
LocalBookmarkToAccountMerger(model_.get())
.MoveAndMergeSpecificSubtrees(
{model_->bookmark_bar_node()->children()[0]->children()[0]->id()});
EXPECT_THAT(model_->bookmark_bar_node()->children(),
ElementsAre(IsFolder(kFolderTitle, ElementsAre(IsUrlBookmark(
kUrl1Title, kUrl1)))));
EXPECT_THAT(model_->account_bookmark_bar_node()->children(), IsEmpty());
}
TEST_F(LocalBookmarkToAccountMergerTest,
ShouldApplySemanticDedupeWhenUUIDMatchForNotSelectedNode) {
const std::u16string kUrl1Title = u"url1";
const GURL kUrl1("http://www.url1.com/");
const std::u16string kUrl2Title = u"url2";
const GURL kUrl2("http://www.url2.com/");
const std::u16string kUrl3Title = u"url3";
const GURL kUrl3("http://www.url3.com/");
const base::Uuid kUuid1 = base::Uuid::GenerateRandomV4();
const base::Uuid kUuid2 = base::Uuid::GenerateRandomV4();
const base::Uuid kUuid3 = base::Uuid::GenerateRandomV4();
// -------- Local bookmarks --------
// bookmark_bar
// | - bookmark(kUuid1, kUrl1Title, kUrl1)
// | - bookmark(kUuid2, kUrl2Title, kUrl2)
AddLocalNodes({UrlBuilder(kUrl1Title, kUrl1).SetUuid(kUuid1),
UrlBuilder(kUrl2Title, kUrl2).SetUuid(kUuid2)});
// -------- Account bookmarks --------
// bookmark_bar
// | - bookmark(kUuid2, kUrl1Title, kUrl1)
// | - bookmark(kUuid3, kUrl3Title, kUrl3)
AddAccountNodes({UrlBuilder(kUrl1Title, kUrl1).SetUuid(kUuid2),
UrlBuilder(kUrl3Title, kUrl3).SetUuid(kUuid3)});
// -------- The expected merge outcome --------
// Move(kUrl1Title)
// In this case there is a UUID match between the local node with title 2
// (which isn't selected), and the account node with title 1. The semantic
// match is still applied for local and account nodes with title 1.
//
// ---- Local bookmarks ----
// bookmark_bar
// | - bookmark(kUuid2, kUrl2Title, kUrl2)
//
// ---- Account bookmarks ----
// bookmark_bar
// | - bookmark(kUuid2, kUrl1Title, kUrl1)
// | - bookmark(kUuid3, kUrl3Title, kUrl3)
EXPECT_CALL(observer_, BookmarkNodeAdded).Times(0);
EXPECT_CALL(observer_, BookmarkNodeMoved).Times(0);
EXPECT_CALL(observer_, BookmarkNodeRemoved).Times(1);
EXPECT_CALL(observer_, BookmarkNodeChanged).Times(0);
LocalBookmarkToAccountMerger(model_.get())
.MoveAndMergeSpecificSubtrees(
{model_->bookmark_bar_node()->children()[0]->id()});
EXPECT_THAT(model_->bookmark_bar_node()->children(),
ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2)));
EXPECT_THAT(model_->account_bookmark_bar_node()->children(),
ElementsAre(IsUrlBookmarkWithUuid(kUrl1Title, kUrl1, kUuid2),
IsUrlBookmarkWithUuid(kUrl3Title, kUrl3, kUuid3)));
}
TEST_F(LocalBookmarkToAccountMergerTest, ShouldIgnoreManagedNodesWhenSelected) {
const std::u16string kUrl1Title = u"url1";
const std::u16string kUrl2Title = u"url2";
const GURL kUrl1("http://www.url1.com/");
const GURL kUrl2("http://www.url2.com/");
auto local_client = std::make_unique<bookmarks::TestBookmarkClient>();
bookmarks::BookmarkNode* managed_node = local_client->EnableManagedNode();
std::unique_ptr<bookmarks::BookmarkModel> model =
bookmarks::TestBookmarkClient::CreateModelWithClient(
std::move(local_client));
model->CreateAccountPermanentFolders();
// -------- Local bookmarks --------
// bookmark_bar
// |- url1(http://www.url1.com)
// managed_bookmarks
// |- url2(http://www.url2.com)
FolderBuilder::AddChildrenTo(model.get(), model->bookmark_bar_node(),
{UrlBuilder(kUrl1Title, kUrl1)});
FolderBuilder::AddChildrenTo(model.get(), managed_node,
{UrlBuilder(kUrl2Title, kUrl2)});
ASSERT_THAT(managed_node->children(),
ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2)));
// -------- Account bookmarks --------
// bookmark_bar
FolderBuilder::AddChildrenTo(model.get(), model->account_bookmark_bar_node(),
{});
// -------- The expected merge outcome --------
// Unchanged
LocalBookmarkToAccountMerger(model.get())
.MoveAndMergeSpecificSubtrees({managed_node->children()[0]->id()});
ASSERT_THAT(model->bookmark_bar_node()->children(),
ElementsAre(IsUrlBookmark(kUrl1Title, kUrl1)));
ASSERT_THAT(model->account_bookmark_bar_node()->children(), IsEmpty());
// Managed nodes should be excluded from the merge and be left unmodified.
ASSERT_THAT(managed_node->children(),
ElementsAre(IsUrlBookmark(kUrl2Title, kUrl2)));
EXPECT_THAT(model->GetNodesByURL(kUrl2),
ElementsAre(managed_node->children()[0].get()));
}
} // namespace
} // namespace sync_bookmarks