blob: 57670f56316a99053ec0566ab88d1d6e01cf7091 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/reading_list/android/reading_list_manager_impl.h"
#include <memory>
#include <string>
#include <utility>
#include "base/guid.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/simple_test_clock.h"
#include "chrome/browser/reading_list/android/reading_list_manager.h"
#include "components/bookmarks/browser/bookmark_node.h"
#include "components/bookmarks/browser/bookmark_utils.h"
#include "components/reading_list/core/reading_list_model_impl.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using BookmarkNode = bookmarks::BookmarkNode;
using ReadingListEntries = ReadingListModelImpl::ReadingListEntries;
namespace {
constexpr char kURL[] = "https://www.example.com";
constexpr char kURL1[] = "https://www.anotherexample.com";
constexpr char kTitle[] =
"In earlier tellings, the dog had a better reputation than the cat, "
"however the president vetoed it.";
constexpr char kTitle1[] = "boring title about dogs.";
constexpr char kReadStatusKey[] = "read_status";
constexpr char kReadStatusRead[] = "true";
constexpr char kReadStatusUnread[] = "false";
constexpr char kInvalidUTF8[] = "\xc3\x28";
class MockObserver : public ReadingListManager::Observer {
public:
MockObserver() = default;
~MockObserver() override = default;
// ReadingListManager::Observer implementation.
MOCK_METHOD(void, ReadingListLoaded, (), (override));
MOCK_METHOD(void, ReadingListChanged, (), (override));
};
class ReadingListManagerImplTest : public testing::Test {
public:
ReadingListManagerImplTest() = default;
~ReadingListManagerImplTest() override = default;
void SetUp() override {
reading_list_model_ = std::make_unique<ReadingListModelImpl>(
/*storage_layer=*/nullptr, /*pref_service=*/nullptr, &clock_);
manager_ =
std::make_unique<ReadingListManagerImpl>(reading_list_model_.get());
manager_->AddObserver(observer());
EXPECT_TRUE(manager()->IsLoaded());
}
void TearDown() override { manager_->RemoveObserver(observer()); }
protected:
ReadingListManager* manager() { return manager_.get(); }
ReadingListModelImpl* reading_list_model() {
return reading_list_model_.get();
}
base::SimpleTestClock* clock() { return &clock_; }
MockObserver* observer() { return &observer_; }
const BookmarkNode* Add(const GURL& url, const std::string& title) {
EXPECT_CALL(*observer(), ReadingListChanged());
return manager()->Add(url, title);
}
void Delete(const GURL& url) {
EXPECT_CALL(*observer(), ReadingListChanged());
manager()->Delete(url);
}
void SetReadStatus(const GURL& url, bool read) {
EXPECT_CALL(*observer(), ReadingListChanged());
manager()->SetReadStatus(url, read);
}
private:
base::SimpleTestClock clock_;
std::unique_ptr<ReadingListModelImpl> reading_list_model_;
std::unique_ptr<ReadingListManager> manager_;
MockObserver observer_;
};
// Verifies the states without any reading list data.
TEST_F(ReadingListManagerImplTest, RootWithEmptyReadingList) {
const auto* root = manager()->GetRoot();
ASSERT_TRUE(root);
EXPECT_TRUE(root->is_folder());
EXPECT_EQ(0u, manager()->size());
}
// Verifies load data into reading list model will update |manager_| as well.
TEST_F(ReadingListManagerImplTest, Load) {
// Load data into reading list model.
auto entries = std::make_unique<ReadingListEntries>();
GURL url(kURL);
entries->emplace(url, ReadingListEntry(url, kTitle, clock()->Now()));
reading_list_model()->StoreLoaded(std::move(entries));
const auto* node = manager()->Get(url);
EXPECT_TRUE(node);
EXPECT_EQ(url, node->url());
EXPECT_EQ(1u, manager()->size());
EXPECT_EQ(1u, manager()->unread_size());
}
// Verifes Add(), Get(), Delete() API in reading list manager.
TEST_F(ReadingListManagerImplTest, AddGetDelete) {
// Adds a node.
GURL url(kURL);
Add(url, kTitle);
EXPECT_EQ(1u, manager()->size());
EXPECT_EQ(1u, manager()->unread_size());
EXPECT_EQ(1u, manager()->GetRoot()->children().size())
<< "The reading list node should be the child of the root.";
// Gets the node, and verifies its content.
const BookmarkNode* node = manager()->Get(url);
ASSERT_TRUE(node);
EXPECT_EQ(url, node->url());
EXPECT_EQ(kTitle, base::UTF16ToUTF8(node->GetTitle()));
std::string read_status;
node->GetMetaInfo(kReadStatusKey, &read_status);
EXPECT_EQ(kReadStatusUnread, read_status)
<< "By default the reading list node is marked as unread.";
// Gets an invalid URL.
EXPECT_EQ(nullptr, manager()->Get(GURL("invalid spec")));
// Deletes the node.
Delete(url);
EXPECT_EQ(0u, manager()->size());
EXPECT_EQ(0u, manager()->unread_size());
EXPECT_TRUE(manager()->GetRoot()->children().empty());
}
// Verifies GetNodeByID() and IsReadingListBookmark() works correctly.
TEST_F(ReadingListManagerImplTest, GetNodeByIDIsReadingListBookmark) {
GURL url(kURL);
const auto* node = Add(url, kTitle);
// Find the root.
EXPECT_EQ(manager()->GetRoot(),
manager()->GetNodeByID(manager()->GetRoot()->id()));
EXPECT_TRUE(manager()->IsReadingListBookmark(manager()->GetRoot()));
// Find existing node.
EXPECT_EQ(node, manager()->GetNodeByID(node->id()));
EXPECT_TRUE(manager()->IsReadingListBookmark(node));
// Non existing node.
node = manager()->GetNodeByID(12345);
EXPECT_FALSE(node);
EXPECT_FALSE(manager()->IsReadingListBookmark(node));
// Node with the same URL but not in the tree.
auto node_same_url =
std::make_unique<BookmarkNode>(0, base::GUID::GenerateRandomV4(), url);
EXPECT_FALSE(manager()->IsReadingListBookmark(node_same_url.get()));
}
// Verifies GetMatchingNodes() API in reading list manager.
TEST_F(ReadingListManagerImplTest, GetMatchingNodes) {
manager()->Add(GURL(kURL), kTitle);
manager()->Add(GURL(kURL1), kTitle1);
EXPECT_EQ(2u, manager()->size());
// Search with a multi-word query text.
std::vector<const BookmarkNode*> results;
bookmarks::QueryFields query;
query.word_phrase_query.reset(
new base::string16(base::ASCIIToUTF16("dog cat")));
manager()->GetMatchingNodes(query, 5, &results);
EXPECT_EQ(1u, results.size());
// Search with a single word query text.
results.clear();
query.word_phrase_query.reset(new base::string16(base::ASCIIToUTF16("dog")));
manager()->GetMatchingNodes(query, 5, &results);
EXPECT_EQ(2u, results.size());
// Search with empty string. Shouldn't match anything.
results.clear();
query.word_phrase_query.reset(new base::string16());
manager()->GetMatchingNodes(query, 5, &results);
EXPECT_EQ(0u, results.size());
}
TEST_F(ReadingListManagerImplTest, GetMatchingNodesWithMaxCount) {
manager()->Add(GURL(kURL), kTitle);
manager()->Add(GURL(kURL1), kTitle1);
EXPECT_EQ(2u, manager()->size());
// Search with a query text.
std::vector<const BookmarkNode*> results;
bookmarks::QueryFields query;
query.word_phrase_query.reset(new base::string16(base::ASCIIToUTF16("dog")));
manager()->GetMatchingNodes(query, 5, &results);
EXPECT_EQ(2u, results.size());
// Search with having pre-existing elements in |results|.
manager()->GetMatchingNodes(query, 5, &results);
EXPECT_EQ(4u, results.size());
// Max count should never be exceeded.
manager()->GetMatchingNodes(query, 5, &results);
EXPECT_EQ(5u, results.size());
manager()->GetMatchingNodes(query, 5, &results);
EXPECT_EQ(5u, results.size());
}
// If Add() the same URL twice, the first bookmark node pointer will be
// invalidated.
TEST_F(ReadingListManagerImplTest, AddTwice) {
// Adds a node twice.
GURL url(kURL);
Add(url, kTitle);
const auto* new_node = Add(url, kTitle1);
EXPECT_EQ(kTitle1, base::UTF16ToUTF8(new_node->GetTitle()));
EXPECT_EQ(url, new_node->url());
}
// If Add() with an invalid title, nullptr will be returned.
TEST_F(ReadingListManagerImplTest, AddInvalidTitle) {
GURL url(kURL);
// Use an invalid UTF8 string.
base::string16 dummy;
EXPECT_FALSE(
base::UTF8ToUTF16(kInvalidUTF8, base::size(kInvalidUTF8), &dummy));
const auto* new_node = Add(url, std::string(kInvalidUTF8));
EXPECT_EQ(nullptr, new_node)
<< "Should return nullptr when failed to parse the title.";
}
// If Add() with an invalid URL, nullptr will be returned.
TEST_F(ReadingListManagerImplTest, AddInvalidURL) {
GURL invalid_url("chrome://flags");
EXPECT_FALSE(reading_list_model()->IsUrlSupported(invalid_url));
// Use an invalid URL, the observer method ReadingListDidAddEntry() won't be
// invoked.
const auto* new_node = manager()->Add(invalid_url, kTitle);
EXPECT_EQ(nullptr, new_node)
<< "Should return nullptr when the URL scheme is not supported.";
}
// Verifes SetReadStatus()/GetReadStatus() API.
TEST_F(ReadingListManagerImplTest, ReadStatus) {
GURL url(kURL);
// No op when no reading list entries.
manager()->SetReadStatus(url, true);
EXPECT_EQ(0u, manager()->size());
// Add a node and mark as read.
Add(url, kTitle);
SetReadStatus(url, true);
const BookmarkNode* node = manager()->Get(url);
ASSERT_TRUE(node);
EXPECT_EQ(url, node->url());
std::string read_status;
node->GetMetaInfo(kReadStatusKey, &read_status);
EXPECT_EQ(kReadStatusRead, read_status);
EXPECT_EQ(0u, manager()->unread_size());
EXPECT_TRUE(manager()->GetReadStatus(node));
// Mark as unread.
SetReadStatus(url, false);
node = manager()->Get(url);
node->GetMetaInfo(kReadStatusKey, &read_status);
EXPECT_EQ(kReadStatusUnread, read_status);
EXPECT_EQ(1u, manager()->unread_size());
EXPECT_FALSE(manager()->GetReadStatus(node));
// Node not in the reading list should return false.
auto other_node =
std::make_unique<BookmarkNode>(0, base::GUID::GenerateRandomV4(), url);
EXPECT_FALSE(manager()->GetReadStatus(node));
// Root node should return false.
EXPECT_FALSE(manager()->GetReadStatus(manager()->GetRoot()));
}
// Verifies the bookmark node is added when sync or other source adds the
// reading list entry from |reading_list_model_|.
TEST_F(ReadingListManagerImplTest, ReadingListDidAddEntry) {
GURL url(kURL);
EXPECT_CALL(*observer(), ReadingListChanged()).RetiresOnSaturation();
reading_list_model()->AddEntry(url, kTitle, reading_list::ADDED_VIA_SYNC);
const auto* node = manager()->Get(url);
EXPECT_TRUE(node);
EXPECT_EQ(url, node->url());
EXPECT_EQ(1u, manager()->size());
}
// Verifies the bookmark node is deleted when sync or other source deletes the
// reading list entry from |reading_list_model_|.
TEST_F(ReadingListManagerImplTest, ReadingListWillRemoveEntry) {
GURL url(kURL);
// Adds a node.
const auto* node = Add(url, kTitle);
EXPECT_TRUE(node);
EXPECT_EQ(url, node->url());
EXPECT_EQ(1u, manager()->size());
// Removes it from |reading_list_model_|.
EXPECT_CALL(*observer(), ReadingListChanged()).RetiresOnSaturation();
reading_list_model()->RemoveEntryByURL(url);
node = manager()->Get(url);
EXPECT_FALSE(node);
EXPECT_EQ(0u, manager()->size());
}
// Verifies the bookmark node is updated when sync or other source updates the
// reading list entry from |reading_list_model_|.
TEST_F(ReadingListManagerImplTest, ReadingListWillMoveEntry) {
GURL url(kURL);
// Adds a node.
const auto* node = Add(url, kTitle);
EXPECT_TRUE(node);
EXPECT_FALSE(manager()->GetReadStatus(node));
SetReadStatus(url, true);
EXPECT_TRUE(manager()->GetReadStatus(node));
}
} // namespace