blob: 5fd6b32058a24b3dda9028ff6f2a9fb4ec8d6a99 [file] [log] [blame]
// Copyright 2014 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/bookmarks/browser/bookmark_utils.h"
#include <stddef.h>
#include <array>
#include <memory>
#include <utility>
#include <vector>
#include "base/memory/raw_ptr.h"
#include "base/scoped_observation.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "components/bookmarks/browser/base_bookmark_model_observer.h"
#include "components/bookmarks/browser/bookmark_client.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/browser/bookmark_model_observer.h"
#include "components/bookmarks/browser/bookmark_node_data.h"
#include "components/bookmarks/common/bookmark_metrics.h"
#include "components/bookmarks/test/test_bookmark_client.h"
#include "components/signin/public/base/signin_switches.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
namespace bookmarks {
namespace {
using base::ASCIIToUTF16;
using std::string;
using testing::ElementsAre;
using testing::UnorderedElementsAre;
class BookmarkUtilsTest : public testing::Test,
public BaseBookmarkModelObserver {
public:
BookmarkUtilsTest()
: task_environment_(base::test::TaskEnvironment::MainThreadType::UI) {}
BookmarkUtilsTest(const BookmarkUtilsTest&) = delete;
BookmarkUtilsTest& operator=(const BookmarkUtilsTest&) = delete;
~BookmarkUtilsTest() override {}
// Copy and paste is not yet supported on iOS. http://crbug.com/228147
#if !BUILDFLAG(IS_IOS)
void TearDown() override {
ui::Clipboard::DestroyClipboardForCurrentThread();
}
#endif // !BUILDFLAG(IS_IOS)
// Certain user actions require multiple changes to the bookmark model,
// however these modifications need to be atomic for the undo framework. The
// BaseBookmarkModelObserver is used to inform the boundaries of the user
// action. For example, when multiple bookmarks are cut to the clipboard we
// expect one call each to GroupedBookmarkChangesBeginning/Ended.
void ExpectGroupedChangeCount(int expected_beginning_count,
int expected_ended_count) {
// The undo framework is not used under Android. Thus the group change
// events will not be fired and so should not be tested for Android.
#if !BUILDFLAG(IS_ANDROID)
EXPECT_EQ(grouped_changes_beginning_count_, expected_beginning_count);
EXPECT_EQ(grouped_changes_ended_count_, expected_ended_count);
#endif
}
base::HistogramTester* histogram() { return &histogram_; }
private:
// BaseBookmarkModelObserver:
void BookmarkModelChanged() override {}
void GroupedBookmarkChangesBeginning() override {
++grouped_changes_beginning_count_;
}
void GroupedBookmarkChangesEnded() override {
++grouped_changes_ended_count_;
}
// Some of these tests exercise account bookmarks.
base::test::ScopedFeatureList features_override_{
switches::kSyncEnableBookmarksInTransportMode};
// Clipboard requires a full TaskEnvironment.
base::test::TaskEnvironment task_environment_;
int grouped_changes_beginning_count_{0};
int grouped_changes_ended_count_{0};
base::HistogramTester histogram_;
};
// A bookmark client that suggests a save location for new nodes.
class SuggestFolderClient : public TestBookmarkClient {
public:
SuggestFolderClient() = default;
SuggestFolderClient(const SuggestFolderClient&) = delete;
SuggestFolderClient& operator=(const SuggestFolderClient&) = delete;
~SuggestFolderClient() override = default;
const BookmarkNode* GetSuggestedSaveLocation(const GURL& url) override {
return suggested_save_location_.get();
}
void SetSuggestedSaveLocation(const BookmarkNode* node) {
suggested_save_location_ = node;
}
private:
raw_ptr<const BookmarkNode> suggested_save_location_;
};
TEST_F(BookmarkUtilsTest, GetBookmarksMatchingPropertiesWordPhraseQuery) {
std::unique_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
const BookmarkNode* node1 = model->AddURL(model->other_node(), 0, u"foo bar",
GURL("http://www.google.com"));
const BookmarkNode* node2 = model->AddURL(model->other_node(), 0, u"baz buz",
GURL("http://www.cnn.com"));
const BookmarkNode* folder1 =
model->AddFolder(model->other_node(), 0, u"foo");
QueryFields query;
query.word_phrase_query = std::make_unique<std::u16string>();
// No nodes are returned for empty string.
*query.word_phrase_query = u"";
EXPECT_TRUE(GetBookmarksMatchingProperties(model.get(), query, 100).empty());
// No nodes are returned for space-only string.
*query.word_phrase_query = u" ";
EXPECT_TRUE(GetBookmarksMatchingProperties(model.get(), query, 100).empty());
// Node "foo bar" and folder "foo" are returned in search results.
*query.word_phrase_query = u"foo";
EXPECT_THAT(GetBookmarksMatchingProperties(model.get(), query, 100),
UnorderedElementsAre(folder1, node1));
// Ensure url matches return in search results.
*query.word_phrase_query = u"cnn";
EXPECT_THAT(GetBookmarksMatchingProperties(model.get(), query, 100),
UnorderedElementsAre(node2));
// Ensure folder "foo" is not returned in more specific search.
*query.word_phrase_query = u"foo bar";
EXPECT_THAT(GetBookmarksMatchingProperties(model.get(), query, 100),
UnorderedElementsAre(node1));
// Bookmark Bar and Other Bookmarks are not returned in search results.
*query.word_phrase_query = u"Bookmark";
EXPECT_TRUE(GetBookmarksMatchingProperties(model.get(), query, 100).empty());
}
// Check exact matching against a URL query.
TEST_F(BookmarkUtilsTest, GetBookmarksMatchingPropertiesUrl) {
std::unique_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
const BookmarkNode* node1 = model->AddURL(model->other_node(), 0, u"Google",
GURL("https://www.google.com/"));
model->AddURL(model->other_node(), 0, u"Google Calendar",
GURL("https://www.google.com/calendar"));
model->AddFolder(model->other_node(), 0, u"Folder");
QueryFields query;
query.url = std::make_unique<std::u16string>();
*query.url = u"https://www.google.com/";
EXPECT_THAT(GetBookmarksMatchingProperties(model.get(), query, 100),
UnorderedElementsAre(node1));
*query.url = u"calendar";
EXPECT_TRUE(GetBookmarksMatchingProperties(model.get(), query, 100).empty());
// Empty URL should not match folders.
*query.url = u"";
EXPECT_TRUE(GetBookmarksMatchingProperties(model.get(), query, 100).empty());
}
// Check exact matching against a title query.
TEST_F(BookmarkUtilsTest, GetBookmarksMatchingPropertiesTitle) {
std::unique_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
const BookmarkNode* node1 = model->AddURL(model->other_node(), 0, u"Google",
GURL("https://www.google.com/"));
model->AddURL(model->other_node(), 0, u"Google Calendar",
GURL("https://www.google.com/calendar"));
const BookmarkNode* folder1 =
model->AddFolder(model->other_node(), 0, u"Folder");
QueryFields query;
query.title = std::make_unique<std::u16string>();
*query.title = u"Google";
EXPECT_THAT(GetBookmarksMatchingProperties(model.get(), query, 100),
UnorderedElementsAre(node1));
*query.title = u"Calendar";
EXPECT_TRUE(GetBookmarksMatchingProperties(model.get(), query, 100).empty());
// Title should match folders.
*query.title = u"Folder";
EXPECT_THAT(GetBookmarksMatchingProperties(model.get(), query, 100),
UnorderedElementsAre(folder1));
}
// Check matching against a query with multiple predicates.
TEST_F(BookmarkUtilsTest, GetBookmarksMatchingPropertiesConjunction) {
std::unique_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
const BookmarkNode* node1 = model->AddURL(model->other_node(), 0, u"Google",
GURL("https://www.google.com/"));
model->AddURL(model->other_node(), 0, u"Google Calendar",
GURL("https://www.google.com/calendar"));
model->AddFolder(model->other_node(), 0, u"Folder");
QueryFields query;
// Test all fields matching.
query.word_phrase_query = std::make_unique<std::u16string>(u"www");
query.url = std::make_unique<std::u16string>(u"https://www.google.com/");
query.title = std::make_unique<std::u16string>(u"Google");
EXPECT_THAT(GetBookmarksMatchingProperties(model.get(), query, 100),
UnorderedElementsAre(node1));
auto fields = std::to_array<std::unique_ptr<std::u16string>*>({
&query.word_phrase_query,
&query.url,
&query.title,
});
// Test two fields matching.
for (size_t i = 0; i < std::size(fields); i++) {
std::unique_ptr<std::u16string> original_value(fields[i]->release());
EXPECT_THAT(GetBookmarksMatchingProperties(model.get(), query, 100),
UnorderedElementsAre(node1));
*fields[i] = std::move(original_value);
}
// Test two fields matching with one non-matching field.
for (size_t i = 0; i < std::size(fields); i++) {
std::unique_ptr<std::u16string> original_value(fields[i]->release());
*fields[i] = std::make_unique<std::u16string>(u"fjdkslafjkldsa");
EXPECT_TRUE(
GetBookmarksMatchingProperties(model.get(), query, 100).empty());
*fields[i] = std::move(original_value);
}
}
// Ensures the BookmarkClient has the power to suggest the parent for new nodes.
TEST_F(BookmarkUtilsTest, GetParentForNewNodes_ClientOverride) {
std::unique_ptr<SuggestFolderClient> client =
std::make_unique<SuggestFolderClient>();
SuggestFolderClient* client_ptr = client.get();
std::unique_ptr<BookmarkModel> model(
TestBookmarkClient::CreateModelWithClient(std::move(client)));
const BookmarkNode* folder_to_suggest =
model->AddFolder(model->bookmark_bar_node(), 0, u"Suggested");
const BookmarkNode* folder1 =
model->AddFolder(model->bookmark_bar_node(), 1, u"Folder 1");
ASSERT_EQ(folder1, GetParentForNewNodes(model.get(), GURL()));
client_ptr->SetSuggestedSaveLocation(folder_to_suggest);
ASSERT_EQ(folder_to_suggest, GetParentForNewNodes(model.get(), GURL()));
client_ptr = nullptr;
}
// Verifies that meta info is copied when nodes are cloned.
TEST_F(BookmarkUtilsTest, CloneMetaInfo) {
std::unique_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
// Add a node containing meta info.
const BookmarkNode* node = model->AddURL(model->other_node(), 0, u"foo bar",
GURL("http://www.google.com"));
model->SetNodeMetaInfo(node, "somekey", "somevalue");
model->SetNodeMetaInfo(node, "someotherkey", "someothervalue");
// Clone node to a different folder.
const BookmarkNode* folder =
model->AddFolder(model->bookmark_bar_node(), 0, u"Folder");
std::vector<BookmarkNodeData::Element> elements;
BookmarkNodeData::Element node_data(node);
elements.push_back(node_data);
EXPECT_EQ(0u, folder->children().size());
CloneBookmarkNode(model.get(), elements, folder, 0, false);
ASSERT_EQ(1u, folder->children().size());
// Verify that the cloned node contains the same meta info.
const BookmarkNode* clone = folder->children().front().get();
ASSERT_TRUE(clone->GetMetaInfoMap());
EXPECT_EQ(2u, clone->GetMetaInfoMap()->size());
std::string value;
EXPECT_TRUE(clone->GetMetaInfo("somekey", &value));
EXPECT_EQ("somevalue", value);
EXPECT_TRUE(clone->GetMetaInfo("someotherkey", &value));
EXPECT_EQ("someothervalue", value);
histogram()->ExpectTotalCount("Bookmarks.Clone.NumCloned", 1);
histogram()->ExpectBucketCount("Bookmarks.Clone.NumCloned", 1, 1);
}
TEST_F(BookmarkUtilsTest, RemoveAllBookmarks) {
// Load a model with an managed node that is not editable.
auto client = std::make_unique<TestBookmarkClient>();
BookmarkNode* managed_node = client->EnableManagedNode();
std::unique_ptr<BookmarkModel> model(
TestBookmarkClient::CreateModelWithClient(std::move(client)));
EXPECT_TRUE(model->bookmark_bar_node()->children().empty());
EXPECT_TRUE(model->other_node()->children().empty());
EXPECT_TRUE(model->mobile_node()->children().empty());
EXPECT_TRUE(managed_node->children().empty());
const std::u16string title = u"Title";
const GURL url("http://google.com");
model->AddURL(model->bookmark_bar_node(), 0, title, url);
model->AddURL(model->other_node(), 0, title, url);
model->AddURL(model->mobile_node(), 0, title, url);
model->AddURL(managed_node, 0, title, url);
std::vector<raw_ptr<const BookmarkNode, VectorExperimental>> nodes =
model->GetNodesByURL(url);
ASSERT_EQ(4u, nodes.size());
RemoveAllBookmarks(model.get(), url, FROM_HERE);
nodes = model->GetNodesByURL(url);
ASSERT_EQ(1u, nodes.size());
EXPECT_TRUE(model->bookmark_bar_node()->children().empty());
EXPECT_TRUE(model->other_node()->children().empty());
EXPECT_TRUE(model->mobile_node()->children().empty());
EXPECT_EQ(1u, managed_node->children().size());
}
TEST_F(BookmarkUtilsTest, CleanUpUrlForMatching) {
EXPECT_EQ(u"http://foo.com/", CleanUpUrlForMatching(GURL("http://foo.com"),
/*adjustments=*/nullptr));
EXPECT_EQ(u"http://foo.com/", CleanUpUrlForMatching(GURL("http://Foo.com"),
/*adjustments=*/nullptr));
}
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
TEST_F(
BookmarkUtilsTest,
GetRecentlyUsedFoldersWithOnlyLocalBookmarks_PermanentNodesOrderUnaffectedByDisplay) {
std::unique_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
// Note that because `local_other_bookmark` is a child of a permanent node its
// use in `GetMostRecentlyUsedFoldersForDisplay()` will not cause
// `other_node()` to be displayed before more recently modified folders.
const BookmarkNode* const local_other_bookmark = model->AddURL(
model->other_node(), 0, u"Title", GURL("http://google.com"));
model->SetDateFolderModified(model->other_node(),
base::Time::FromMillisecondsSinceUnixEpoch(1));
model->SetDateFolderModified(model->bookmark_bar_node(),
base::Time::FromMillisecondsSinceUnixEpoch(2));
const bookmarks::BookmarkNodesSplitByAccountAndLocal mru_bookmarks =
bookmarks::GetMostRecentlyUsedFoldersForDisplay(model.get(),
local_other_bookmark);
// Permanent nodes display in a fixed order even if a bookmark in
// `other_node()` is currently displayed.
EXPECT_TRUE(mru_bookmarks.account_nodes.empty());
EXPECT_THAT(mru_bookmarks.local_nodes,
ElementsAre(model->bookmark_bar_node(), model->other_node()));
}
TEST_F(
BookmarkUtilsTest,
GetRecentlyUsedFoldersWithOnlyLocalBookmarks_NonPermanentNodesOrderAffectedByDisplay) {
std::unique_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
// Add two folders.
const BookmarkNode* const folder1 =
model->AddFolder(model->other_node(), 0, u"Folder1");
const BookmarkNode* const folder2 =
model->AddFolder(model->other_node(), 0, u"Folder2");
// Add a new bookmark to `folder1`.
const BookmarkNode* const bookmark =
model->AddURL(folder1, 0, u"Title", GURL("http://google.com"));
// Set `folder2` to most recent.
model->SetDateFolderModified(folder1,
base::Time::FromMillisecondsSinceUnixEpoch(1));
model->SetDateFolderModified(folder2,
base::Time::FromMillisecondsSinceUnixEpoch(2));
const bookmarks::BookmarkNodesSplitByAccountAndLocal mru_bookmarks =
bookmarks::GetMostRecentlyUsedFoldersForDisplay(model.get(), bookmark);
// `folder1` as a parent to `bookmark` displays first even though not most
// recent.
EXPECT_THAT(mru_bookmarks.local_nodes,
ElementsAre(folder1, folder2, model->bookmark_bar_node(),
model->other_node()));
}
TEST_F(
BookmarkUtilsTest,
GetRecentlyUsedFoldersWithOnlyLocalBookmarks_DateFolderModifiedChangesOrder) {
std::unique_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
const BookmarkNode* const local_other_bookmark = model->AddURL(
model->other_node(), 0, u"Title", GURL("http://google.com"));
// Add two folders.
const BookmarkNode* const folder1 =
model->AddFolder(model->other_node(), 0, u"Folder1");
const BookmarkNode* const folder2 =
model->AddFolder(model->other_node(), 0, u"Folder2");
// `folder2` is set to most recent.
model->SetDateFolderModified(folder1,
base::Time::FromMillisecondsSinceUnixEpoch(1));
model->SetDateFolderModified(folder2,
base::Time::FromMillisecondsSinceUnixEpoch(2));
bookmarks::BookmarkNodesSplitByAccountAndLocal mru_bookmarks =
bookmarks::GetMostRecentlyUsedFoldersForDisplay(model.get(),
local_other_bookmark);
EXPECT_THAT(mru_bookmarks.local_nodes,
ElementsAre(folder2, folder1, model->bookmark_bar_node(),
model->other_node()));
// `folder1` is set to most recent.
model->SetDateFolderModified(folder1,
base::Time::FromMillisecondsSinceUnixEpoch(3));
mru_bookmarks = bookmarks::GetMostRecentlyUsedFoldersForDisplay(
model.get(), local_other_bookmark);
EXPECT_THAT(mru_bookmarks.local_nodes,
ElementsAre(folder1, folder2, model->bookmark_bar_node(),
model->other_node()));
}
TEST_F(
BookmarkUtilsTest,
GetRecentlyUsedFoldersWithOnlyLocalBookmarks_PermanentFoldersAlwaysShownWithCustomFolders) {
std::unique_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
const BookmarkNode* const local_other_bookmark = model->AddURL(
model->other_node(), 0, u"Title", GURL("http://google.com"));
// Add more than 5 custom folders. The first one will not be displayed, as
// only the 5 most recent ones are chosen for display.
std::vector<const BookmarkNode*> custom_nodes;
for (int i = 0; i < 6; i++) {
custom_nodes.push_back(
model->AddFolder(model->other_node(), 0, u"CustomFolder"));
}
CHECK_EQ(6u, custom_nodes.size());
const bookmarks::BookmarkNodesSplitByAccountAndLocal mru_bookmarks =
bookmarks::GetMostRecentlyUsedFoldersForDisplay(model.get(),
local_other_bookmark);
// Only 5 custom nodes should be displayed. The permanent ones come last.
EXPECT_TRUE(mru_bookmarks.account_nodes.empty());
EXPECT_THAT(mru_bookmarks.local_nodes,
ElementsAre(custom_nodes[5], custom_nodes[4], custom_nodes[3],
custom_nodes[2], custom_nodes[1],
model->bookmark_bar_node(), model->other_node()));
}
TEST_F(
BookmarkUtilsTest,
GetRecentlyUsedFoldersWithOnlyLocalBookmarks_CustomFolderShownWhenChildNodeDisplayed) {
std::unique_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
std::vector<const BookmarkNode*> custom_nodes;
// Add a first custom folder node and a new bookmark to it.
custom_nodes.push_back(
model->AddFolder(model->other_node(), 0, u"CustomFolder"));
const BookmarkNode* const bookmark =
model->AddURL(custom_nodes[0], 0, u"Title", GURL("http://google.com"));
// Add 5 more custom folders now to make sure the first custom folder will
// not be the most recently modified one.
for (int i = 0; i < 5; i++) {
custom_nodes.push_back(
model->AddFolder(model->other_node(), 0, u"CustomFolder"));
}
CHECK_EQ(6u, custom_nodes.size());
const bookmarks::BookmarkNodesSplitByAccountAndLocal mru_bookmarks =
bookmarks::GetMostRecentlyUsedFoldersForDisplay(model.get(), bookmark);
// Only 5 custom nodes should be displayed. The parent node of the currently
// displayed bookmark comes first. The permanent nodes come last.
EXPECT_TRUE(mru_bookmarks.account_nodes.empty());
EXPECT_THAT(mru_bookmarks.local_nodes,
ElementsAre(custom_nodes[0], custom_nodes[5], custom_nodes[4],
custom_nodes[3], custom_nodes[2],
model->bookmark_bar_node(), model->other_node()));
}
TEST_F(
BookmarkUtilsTest,
GetRecentlyUsedFoldersWithOnlyLocalBookmarks_CustomFolderShownWhenRecentlyModified) {
std::unique_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
const BookmarkNode* const local_other_bookmark = model->AddURL(
model->other_node(), 0, u"Title", GURL("http://google.com"));
// Add more than 5 custom folders.
std::vector<const BookmarkNode*> custom_nodes;
for (int i = 0; i < 6; i++) {
custom_nodes.push_back(
model->AddFolder(model->other_node(), 0, u"CustomFolder"));
}
CHECK_EQ(6u, custom_nodes.size());
// The first folder is set to most recently modified.
model->SetDateFolderModified(custom_nodes[0], base::Time::Now());
const bookmarks::BookmarkNodesSplitByAccountAndLocal mru_bookmarks =
bookmarks::GetMostRecentlyUsedFoldersForDisplay(model.get(),
local_other_bookmark);
// Only 5 custom nodes should be displayed. The recently modified node comes
// first. The permanent nodes come last.
EXPECT_TRUE(mru_bookmarks.account_nodes.empty());
EXPECT_THAT(mru_bookmarks.local_nodes,
ElementsAre(custom_nodes[0], custom_nodes[5], custom_nodes[4],
custom_nodes[3], custom_nodes[2],
model->bookmark_bar_node(), model->other_node()));
}
TEST_F(
BookmarkUtilsTest,
GetRecentlyUsedFoldersWithAccountBookmarks_PermanentNodesOrderUnaffectedByDisplay) {
std::unique_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
model->CreateAccountPermanentFolders();
// Note that because `bookmark_in_account_other_node` is a child of a
// permanent node its use in `GetMostRecentlyUsedFoldersForDisplay()` will not
// cause `account_other_node()` to be displayed before more recently modified
// folders.
const BookmarkNode* const bookmark_in_account_other_node = model->AddURL(
model->account_other_node(), 0, u"Title", GURL("http://google.com"));
const bookmarks::BookmarkNodesSplitByAccountAndLocal mru_bookmarks =
bookmarks::GetMostRecentlyUsedFoldersForDisplay(
model.get(), bookmark_in_account_other_node);
// No local nodes display by default.
EXPECT_TRUE(mru_bookmarks.local_nodes.empty());
// Permanent nodes are only added to account nodes by default. They display in
// a fixed order even if a bookmark in `account_other_node()` is currently
// displayed.
EXPECT_THAT(mru_bookmarks.account_nodes,
ElementsAre(model->account_bookmark_bar_node(),
model->account_other_node()));
}
TEST_F(BookmarkUtilsTest,
GetRecentlyUsedFoldersWithAccountBookmarks_LocalPermanentNodesNotShown) {
std::unique_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
model->CreateAccountPermanentFolders();
const BookmarkNode* const bookmark_in_account_other_node = model->AddURL(
model->account_other_node(), 0, u"Title", GURL("http://google.com"));
// The most recent folders under account and local are split up as the
// topmost entries.
const BookmarkNode* const account_folder =
model->AddFolder(model->account_other_node(), 0, u"Folder");
const BookmarkNode* const local_folder =
model->AddFolder(model->other_node(), 0, u"Folder2");
model->SetDateFolderModified(account_folder,
base::Time::FromMillisecondsSinceUnixEpoch(20));
// Older than `account_folder` but not filtered out (not permanent node).
model->SetDateFolderModified(local_folder,
base::Time::FromMillisecondsSinceUnixEpoch(10));
const bookmarks::BookmarkNodesSplitByAccountAndLocal mru_bookmarks =
bookmarks::GetMostRecentlyUsedFoldersForDisplay(
model.get(), bookmark_in_account_other_node);
// Permanent account folders are included, permanent local folders are not.
EXPECT_THAT(mru_bookmarks.account_nodes,
ElementsAre(account_folder, model->account_bookmark_bar_node(),
model->account_other_node()));
EXPECT_THAT(mru_bookmarks.local_nodes, ElementsAre(local_folder));
}
TEST_F(
BookmarkUtilsTest,
GetRecentlyUsedFoldersWithAccountBookmarks_LocalPermanentNodeShownWhenChildDisplayed) {
std::unique_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
model->CreateAccountPermanentFolders();
const BookmarkNode* const local_other_bookmark = model->AddURL(
model->other_node(), 0, u"Title", GURL("http://google.com"));
// Make sure local permanent nodes are not the most recently modified ones.
model->SetDateFolderModified(model->other_node(),
base::Time::FromMillisecondsSinceUnixEpoch(1));
model->SetDateFolderModified(model->account_other_node(),
base::Time::FromMillisecondsSinceUnixEpoch(2));
const bookmarks::BookmarkNodesSplitByAccountAndLocal mru_bookmarks =
bookmarks::GetMostRecentlyUsedFoldersForDisplay(model.get(),
local_other_bookmark);
// Local permanent node included when its child is being displayed.
EXPECT_THAT(mru_bookmarks.local_nodes, ElementsAre(model->other_node()));
}
TEST_F(
BookmarkUtilsTest,
GetRecentlyUsedFoldersWithAccountBookmarks_LocalPermanentNodeShownWhenRecentlyModified) {
std::unique_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
model->CreateAccountPermanentFolders();
const base::Time account_folders_created_time = base::Time::Now();
const BookmarkNode* const bookmark_in_account_other_node = model->AddURL(
model->account_other_node(), 0, u"Title", GURL("http://google.com"));
// Make sure a local permanent node is the most recently modified one.
model->SetDateFolderModified(model->account_other_node(),
account_folders_created_time + base::Seconds(1));
model->SetDateFolderModified(model->bookmark_bar_node(),
account_folders_created_time + base::Seconds(2));
const bookmarks::BookmarkNodesSplitByAccountAndLocal mru_bookmarks =
bookmarks::GetMostRecentlyUsedFoldersForDisplay(
model.get(), bookmark_in_account_other_node);
// Local permanent node included when most recently modified.
EXPECT_THAT(mru_bookmarks.local_nodes,
ElementsAre(model->bookmark_bar_node()));
}
TEST_F(
BookmarkUtilsTest,
GetRecentlyUsedFoldersWithAccountBookmarks_LocalPermanentNodeDisplayedOnlyOnce) {
std::unique_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
model->CreateAccountPermanentFolders();
const BookmarkNode* const local_bookmark = model->AddURL(
model->bookmark_bar_node(), 0, u"Title", GURL("http://google.com"));
// Make sure a local permanent node is the most recently modified one.
model->SetDateFolderModified(model->account_other_node(),
base::Time::FromMillisecondsSinceUnixEpoch(1));
model->SetDateFolderModified(model->bookmark_bar_node(),
base::Time::FromMillisecondsSinceUnixEpoch(2));
// Display the bookmark saved to the local permanent node.
const bookmarks::BookmarkNodesSplitByAccountAndLocal mru_bookmarks =
bookmarks::GetMostRecentlyUsedFoldersForDisplay(model.get(),
local_bookmark);
// The local permanent node is only included once, even though there are two
// conditions fulfilled which would add it to the most recently used folders.
EXPECT_THAT(mru_bookmarks.local_nodes,
ElementsAre(model->bookmark_bar_node()));
}
TEST_F(
BookmarkUtilsTest,
GetRecentlyUsedFoldersWithAccountBookmarks_LocalPermanentNodesDisplayedLast) {
std::unique_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
model->CreateAccountPermanentFolders();
const base::Time account_folders_created_time = base::Time::Now();
const BookmarkNode* const bookmark_in_account_other_node = model->AddURL(
model->account_other_node(), 0, u"Title", GURL("http://google.com"));
const BookmarkNode* const local_bookmark = model->AddURL(
model->bookmark_bar_node(), 0, u"Title", GURL("http://google.com"));
// Add two local folders.
const BookmarkNode* const folder1 =
model->AddFolder(model->bookmark_bar_node(), 0, u"Folder");
const BookmarkNode* const folder2 =
model->AddFolder(model->bookmark_bar_node(), 0, u"Folder2");
// Set the account other node as most recently modified.
model->SetDateFolderModified(folder1, account_folders_created_time);
model->SetDateFolderModified(model->bookmark_bar_node(),
account_folders_created_time + base::Seconds(1));
model->SetDateFolderModified(folder2,
account_folders_created_time + base::Seconds(2));
model->SetDateFolderModified(model->account_other_node(),
account_folders_created_time + base::Seconds(3));
bookmarks::BookmarkNodesSplitByAccountAndLocal mru_bookmarks =
bookmarks::GetMostRecentlyUsedFoldersForDisplay(
model.get(), bookmark_in_account_other_node);
// When the permanent node is not the one most recently modified or the parent
// of a currently displayed bookmark, it is not included in the list at all.
EXPECT_THAT(mru_bookmarks.local_nodes, ElementsAre(folder2, folder1));
mru_bookmarks = bookmarks::GetMostRecentlyUsedFoldersForDisplay(
model.get(), local_bookmark);
// When the permanent node is the parent of the displayed node, it is added at
// the end.
mru_bookmarks = bookmarks::GetMostRecentlyUsedFoldersForDisplay(
model.get(), local_bookmark);
EXPECT_THAT(mru_bookmarks.local_nodes,
ElementsAre(folder2, folder1, model->bookmark_bar_node()));
// When the permanent node is the most recently modified node, it is added at
// the end.
model->SetDateFolderModified(
model->bookmark_bar_node(),
account_folders_created_time + base::Seconds(10));
mru_bookmarks = bookmarks::GetMostRecentlyUsedFoldersForDisplay(
model.get(), bookmark_in_account_other_node);
EXPECT_THAT(mru_bookmarks.local_nodes,
ElementsAre(folder2, folder1, model->bookmark_bar_node()));
}
TEST_F(
BookmarkUtilsTest,
GetRecentlyUsedFoldersWithAccountBookmarks_MultipleLocalPermanentNodesDisplayed) {
std::unique_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
model->CreateAccountPermanentFolders();
const base::Time account_folders_created_time = base::Time::Now();
const BookmarkNode* const local_bookmark = model->AddURL(
model->bookmark_bar_node(), 0, u"Title", GURL("http://google.com"));
// Set the local other node as most recently modified.
model->SetDateFolderModified(model->bookmark_bar_node(),
account_folders_created_time + base::Seconds(1));
model->SetDateFolderModified(model->account_bookmark_bar_node(),
account_folders_created_time + base::Seconds(1));
model->SetDateFolderModified(model->account_other_node(),
account_folders_created_time + base::Seconds(1));
model->SetDateFolderModified(model->other_node(),
account_folders_created_time + base::Seconds(2));
const bookmarks::BookmarkNodesSplitByAccountAndLocal mru_bookmarks =
bookmarks::GetMostRecentlyUsedFoldersForDisplay(model.get(),
local_bookmark);
// The local permanent nodes are both shown as one of them is the parent of
// the displayed node, and one is the most recently modified.
EXPECT_THAT(mru_bookmarks.local_nodes,
ElementsAre(model->other_node(), model->bookmark_bar_node()));
}
TEST_F(
BookmarkUtilsTest,
GetRecentlyUsedFoldersWithAccountBookmarks_AccountPermanentFoldersAlwaysShownWithCustomFolders) {
std::unique_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
model->CreateAccountPermanentFolders();
const BookmarkNode* const bookmark_in_account_other_node = model->AddURL(
model->account_other_node(), 0, u"Title", GURL("http://google.com"));
// Add more than 5 custom folders. The first one will not be displayed, as
// only the 5 most recent ones are chosen for display.
std::vector<const BookmarkNode*> custom_nodes;
for (int i = 0; i < 6; i++) {
custom_nodes.push_back(model->AddFolder(model->account_other_node(), 0,
u"CustomAccountFolder"));
}
CHECK_EQ(6u, custom_nodes.size());
const bookmarks::BookmarkNodesSplitByAccountAndLocal mru_bookmarks =
bookmarks::GetMostRecentlyUsedFoldersForDisplay(
model.get(), bookmark_in_account_other_node);
// Only 5 custom nodes should be displayed. The account permanent ones come
// last. Local permanent nodes should not be included.
EXPECT_TRUE(mru_bookmarks.local_nodes.empty());
EXPECT_THAT(mru_bookmarks.account_nodes,
ElementsAre(custom_nodes[5], custom_nodes[4], custom_nodes[3],
custom_nodes[2], custom_nodes[1],
model->account_bookmark_bar_node(),
model->account_other_node()));
}
TEST_F(
BookmarkUtilsTest,
GetRecentlyUsedFoldersWithAccountBookmarks_CustomFolderShownWhenChildNodeDisplayed) {
std::unique_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
model->CreateAccountPermanentFolders();
std::vector<const BookmarkNode*> custom_nodes;
// Add a first custom folder node and a new bookmark to it.
custom_nodes.push_back(
model->AddFolder(model->account_other_node(), 0, u"CustomAccountFolder"));
const BookmarkNode* const bookmark =
model->AddURL(custom_nodes[0], 0, u"Title", GURL("http://google.com"));
// Add 5 more custom folders now to make sure the first custom folder will
// not be the most recently modified one.
for (int i = 0; i < 5; i++) {
custom_nodes.push_back(model->AddFolder(model->account_other_node(), 0,
u"CustomAccountFolder"));
}
CHECK_EQ(6u, custom_nodes.size());
const bookmarks::BookmarkNodesSplitByAccountAndLocal mru_bookmarks =
bookmarks::GetMostRecentlyUsedFoldersForDisplay(model.get(), bookmark);
// Only 5 custom nodes should be displayed. The parent node of the currently
// displayed bookmark comes first. The account permanent nodes come last.
// Local permanent nodes should not be included.
EXPECT_TRUE(mru_bookmarks.local_nodes.empty());
EXPECT_THAT(mru_bookmarks.account_nodes,
ElementsAre(custom_nodes[0], custom_nodes[5], custom_nodes[4],
custom_nodes[3], custom_nodes[2],
model->account_bookmark_bar_node(),
model->account_other_node()));
}
TEST_F(
BookmarkUtilsTest,
GetRecentlyUsedFoldersWithAccountBookmarks_CustomFolderShownWhenRecentlyModified) {
std::unique_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
model->CreateAccountPermanentFolders();
const BookmarkNode* const bookmark_in_account_other_node = model->AddURL(
model->account_other_node(), 0, u"Title", GURL("http://google.com"));
// Add more than 5 custom folders.
std::vector<const BookmarkNode*> custom_nodes;
for (int i = 0; i < 6; i++) {
custom_nodes.push_back(model->AddFolder(model->account_other_node(), 0,
u"CustomAccountFolder"));
}
CHECK_EQ(6u, custom_nodes.size());
// The first folder is set to most recently modified.
model->SetDateFolderModified(custom_nodes[0], base::Time::Now());
const bookmarks::BookmarkNodesSplitByAccountAndLocal mru_bookmarks =
bookmarks::GetMostRecentlyUsedFoldersForDisplay(
model.get(), bookmark_in_account_other_node);
// Only 5 custom nodes should be displayed. The recently modified node comes
// first. The account permanent nodes come last. Local permanent nodes should
// not be included.
EXPECT_TRUE(mru_bookmarks.local_nodes.empty());
EXPECT_THAT(mru_bookmarks.account_nodes,
ElementsAre(custom_nodes[0], custom_nodes[5], custom_nodes[4],
custom_nodes[3], custom_nodes[2],
model->account_bookmark_bar_node(),
model->account_other_node()));
}
TEST_F(
BookmarkUtilsTest,
GetRecentlyUsedFoldersWithAccountBookmarks_CustomFolderMaximumDisplayedIndependentOfStorage) {
std::unique_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
model->CreateAccountPermanentFolders();
const BookmarkNode* const bookmark_in_account_other_node = model->AddURL(
model->account_other_node(), 0, u"Title", GURL("http://google.com"));
// Add 3 custom folders to each account and local permanent nodes.
std::vector<const BookmarkNode*> custom_nodes;
for (int i = 0; i < 3; i++) {
custom_nodes.push_back(
model->AddFolder(model->other_node(), 0, u"CustomLocalFolder"));
custom_nodes.push_back(model->AddFolder(model->account_other_node(), 0,
u"CustomAccountFolder"));
}
CHECK_EQ(6u, custom_nodes.size());
const bookmarks::BookmarkNodesSplitByAccountAndLocal mru_bookmarks =
bookmarks::GetMostRecentlyUsedFoldersForDisplay(
model.get(), bookmark_in_account_other_node);
// Only 5 custom nodes should be displayed. These are the most recently added
// ones. The account permanent nodes come last. Local permanent nodes should
// not be included.
EXPECT_THAT(mru_bookmarks.account_nodes,
ElementsAre(custom_nodes[5], custom_nodes[3], custom_nodes[1],
model->account_bookmark_bar_node(),
model->account_other_node()));
EXPECT_THAT(mru_bookmarks.local_nodes,
ElementsAre(custom_nodes[4], custom_nodes[2]));
}
TEST_F(BookmarkUtilsTest, GetPermanentNodesForDisplayWithOnlyLocalBookmarks) {
std::unique_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
const BookmarkNodesSplitByAccountAndLocal permanent_display_nodes =
GetPermanentNodesForDisplay(model.get());
EXPECT_TRUE(permanent_display_nodes.account_nodes.empty());
EXPECT_THAT(permanent_display_nodes.local_nodes,
ElementsAre(model->bookmark_bar_node(), model->other_node()));
}
TEST_F(BookmarkUtilsTest, GetPermanentNodesForDisplayWithAccountBookmarks) {
std::unique_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
model->CreateAccountPermanentFolders();
BookmarkNodesSplitByAccountAndLocal permanent_display_nodes =
GetPermanentNodesForDisplay(model.get());
EXPECT_FALSE(HasLocalOrSyncableBookmarks(model.get()));
EXPECT_TRUE(permanent_display_nodes.local_nodes.empty());
EXPECT_THAT(permanent_display_nodes.account_nodes,
ElementsAre(model->account_bookmark_bar_node(),
model->account_other_node()));
// With visible local/syncable bookmarks we should display visible local
// permanent nodes too.
model->AddURL(model->other_node(), 0, u"Title", GURL("http://google.com"));
EXPECT_TRUE(HasLocalOrSyncableBookmarks(model.get()));
permanent_display_nodes = GetPermanentNodesForDisplay(model.get());
// Account nodes still showing.
EXPECT_THAT(permanent_display_nodes.account_nodes,
ElementsAre(model->account_bookmark_bar_node(),
model->account_other_node()));
// Local nodes too.
EXPECT_THAT(permanent_display_nodes.local_nodes,
ElementsAre(model->bookmark_bar_node(), model->other_node()));
}
TEST_F(BookmarkUtilsTest, GetMostRecentlyModifiedUserFolders_DefaultOrder) {
std::unique_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
model->CreateAccountPermanentFolders();
std::vector<const BookmarkNode*> recently_modified =
GetMostRecentlyModifiedUserFolders(model.get());
// The permanent account nodes should come first in order, then the local
// ones in undefined order. The account other node comes first. Mobile nodes
// are not shown.
ASSERT_EQ(4u, recently_modified.size());
EXPECT_EQ(model->account_other_node(), recently_modified[0]);
EXPECT_EQ(model->account_bookmark_bar_node(), recently_modified[1]);
}
TEST_F(
BookmarkUtilsTest,
GetMostRecentlyModifiedUserFolders_LocalFolderModifiedBeforeAccountFoldersAdded) {
std::unique_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
// The local node is set to last used, so it should be first in the list.
model->SetDateFolderModified(model->bookmark_bar_node(), base::Time::Now());
std::vector<const BookmarkNode*> recently_modified =
GetMostRecentlyModifiedUserFolders(model.get());
EXPECT_THAT(recently_modified,
ElementsAre(model->bookmark_bar_node(), model->other_node()));
// Creating the account permanent folders should push the account other node
// to the front of the list.
model->CreateAccountPermanentFolders();
recently_modified = GetMostRecentlyModifiedUserFolders(model.get());
// The permanent account nodes should come first in order, then the local
// ones. The account other node comes first. Mobile nodes are not shown.
EXPECT_THAT(recently_modified,
ElementsAre(model->account_other_node(),
model->account_bookmark_bar_node(),
model->bookmark_bar_node(), model->other_node()));
}
TEST_F(
BookmarkUtilsTest,
GetMostRecentlyModifiedUserFolders_LocalFolderModifiedAfterAccountFoldersAdded) {
std::unique_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
model->CreateAccountPermanentFolders();
// The local node is used after the account nodes were added, so it should be
// first in the list.
model->SetDateFolderModified(model->bookmark_bar_node(), base::Time::Now());
std::vector<const BookmarkNode*> recently_modified =
GetMostRecentlyModifiedUserFolders(model.get());
// The local bookmark bar should come first in order. Mobile nodes are not
// shown.
ASSERT_EQ(4u, recently_modified.size());
EXPECT_EQ(model->bookmark_bar_node(), recently_modified[0]);
}
TEST_F(
BookmarkUtilsTest,
GetMostRecentlyModifiedUserFolders_DefinedModifiedOrderWithCustomLocalFolders) {
std::unique_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel());
model->CreateAccountPermanentFolders();
const BookmarkNode* const local_folder1 =
model->AddFolder(model->other_node(), 0, u"Folder");
const BookmarkNode* const local_folder2 =
model->AddFolder(model->bookmark_bar_node(), 0, u"Folder2");
model->SetDateFolderModified(model->account_other_node(),
base::Time::FromMillisecondsSinceUnixEpoch(0));
model->SetDateFolderModified(model->account_bookmark_bar_node(),
base::Time::FromMillisecondsSinceUnixEpoch(1));
model->SetDateFolderModified(model->bookmark_bar_node(),
base::Time::FromMillisecondsSinceUnixEpoch(2));
model->SetDateFolderModified(local_folder2,
base::Time::FromMillisecondsSinceUnixEpoch(3));
model->SetDateFolderModified(model->other_node(),
base::Time::FromMillisecondsSinceUnixEpoch(4));
model->SetDateFolderModified(local_folder1,
base::Time::FromMillisecondsSinceUnixEpoch(5));
// This simulates signing out and in again, or turning account storage for
// bookmarks off and on through the settings. Doing this should row the
// account other node first, and then follow the order of the last recently
// modified folders.
model->RemoveAccountPermanentFolders();
model->CreateAccountPermanentFolders();
const std::vector<const BookmarkNode*> recently_modified =
GetMostRecentlyModifiedUserFolders(model.get());
// The permanent account nodes should come first in order, then the local
// permanent folders and non-permanent nodes. The account other node comes
// first. Mobile nodes are not shown.
EXPECT_THAT(recently_modified,
ElementsAre(model->account_other_node(),
model->account_bookmark_bar_node(), local_folder1,
model->other_node(), local_folder2,
model->bookmark_bar_node()));
}
#endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
} // namespace
} // namespace bookmarks