diff options
Diffstat (limited to 'chromium/components/reading_list/core/reading_list_model_unittest.cc')
-rw-r--r-- | chromium/components/reading_list/core/reading_list_model_unittest.cc | 747 |
1 files changed, 747 insertions, 0 deletions
diff --git a/chromium/components/reading_list/core/reading_list_model_unittest.cc b/chromium/components/reading_list/core/reading_list_model_unittest.cc new file mode 100644 index 00000000000..a2b2b57fe46 --- /dev/null +++ b/chromium/components/reading_list/core/reading_list_model_unittest.cc @@ -0,0 +1,747 @@ +// Copyright 2016 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 "components/reading_list/core/reading_list_model.h" + +#include "base/bind.h" +#include "base/memory/ptr_util.h" +#include "base/test/simple_test_clock.h" +#include "components/reading_list/core/reading_list_model_impl.h" +#include "components/reading_list/core/reading_list_model_storage.h" +#include "components/reading_list/core/reading_list_store_delegate.h" +#include "components/sync/model/metadata_change_list.h" +#include "components/sync/model/model_error.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const GURL callback_url("http://example.com"); +const std::string callback_title("test title"); + +base::Time AdvanceAndGetTime(base::SimpleTestClock* clock) { + clock->Advance(base::TimeDelta::FromMilliseconds(10)); + return clock->Now(); +} + +class TestReadingListStorageObserver { + public: + virtual void ReadingListDidSaveEntry() = 0; + virtual void ReadingListDidRemoveEntry() = 0; +}; + +class TestReadingListStorage : public ReadingListModelStorage { + public: + TestReadingListStorage(TestReadingListStorageObserver* observer, + base::SimpleTestClock* clock) + : ReadingListModelStorage( + base::Bind(&syncer::ModelTypeChangeProcessor::Create, + base::RepeatingClosure()), + syncer::READING_LIST), + entries_(new ReadingListStoreDelegate::ReadingListEntries()), + observer_(observer), + clock_(clock) {} + + void AddSampleEntries() { + // Adds timer and interlace read/unread entry creation to avoid having two + // entries with the same creation timestamp. + ReadingListEntry unread_a(GURL("http://unread_a.com"), "unread_a", + AdvanceAndGetTime(clock_)); + entries_->insert( + std::make_pair(GURL("http://unread_a.com"), std::move(unread_a))); + + ReadingListEntry read_a(GURL("http://read_a.com"), "read_a", + AdvanceAndGetTime(clock_)); + read_a.SetRead(true, AdvanceAndGetTime(clock_)); + entries_->insert( + std::make_pair(GURL("http://read_a.com"), std::move(read_a))); + + ReadingListEntry unread_b(GURL("http://unread_b.com"), "unread_b", + AdvanceAndGetTime(clock_)); + entries_->insert( + std::make_pair(GURL("http://unread_b.com"), std::move(unread_b))); + + ReadingListEntry read_b(GURL("http://read_b.com"), "read_b", + AdvanceAndGetTime(clock_)); + read_b.SetRead(true, AdvanceAndGetTime(clock_)); + entries_->insert( + std::make_pair(GURL("http://read_b.com"), std::move(read_b))); + + ReadingListEntry unread_c(GURL("http://unread_c.com"), "unread_c", + AdvanceAndGetTime(clock_)); + entries_->insert( + std::make_pair(GURL("http://unread_c.com"), std::move(unread_c))); + + ReadingListEntry read_c(GURL("http://read_c.com"), "read_c", + AdvanceAndGetTime(clock_)); + read_c.SetRead(true, AdvanceAndGetTime(clock_)); + entries_->insert( + std::make_pair(GURL("http://read_c.com"), std::move(read_c))); + + ReadingListEntry unread_d(GURL("http://unread_d.com"), "unread_d", + AdvanceAndGetTime(clock_)); + entries_->insert( + std::make_pair(GURL("http://unread_d.com"), std::move(unread_d))); + } + + void SetReadingListModel(ReadingListModel* model, + ReadingListStoreDelegate* delegate, + base::Clock* clock) override { + delegate->StoreLoaded(std::move(entries_)); + clock_ = static_cast<base::SimpleTestClock*>(clock); + } + + // Saves or updates an entry. If the entry is not yet in the database, it is + // created. + void SaveEntry(const ReadingListEntry& entry) override { + observer_->ReadingListDidSaveEntry(); + } + + // Removes an entry from the storage. + void RemoveEntry(const ReadingListEntry& entry) override { + observer_->ReadingListDidRemoveEntry(); + } + + std::unique_ptr<ScopedBatchUpdate> EnsureBatchCreated() override { + return std::unique_ptr<ScopedBatchUpdate>(); + } + + // Syncing is not used in this test class. + std::unique_ptr<syncer::MetadataChangeList> CreateMetadataChangeList() + override { + NOTREACHED(); + return std::unique_ptr<syncer::MetadataChangeList>(); + } + + base::Optional<syncer::ModelError> MergeSyncData( + std::unique_ptr<syncer::MetadataChangeList> metadata_change_list, + syncer::EntityDataMap entity_data_map) override { + NOTREACHED(); + return {}; + } + + base::Optional<syncer::ModelError> ApplySyncChanges( + std::unique_ptr<syncer::MetadataChangeList> metadata_change_list, + syncer::EntityChangeList entity_changes) override { + NOTREACHED(); + return {}; + } + + void GetData(StorageKeyList storage_keys, DataCallback callback) override { + NOTREACHED(); + return; + } + + void GetAllData(DataCallback callback) override { + NOTREACHED(); + return; + } + + std::string GetClientTag(const syncer::EntityData& entity_data) override { + NOTREACHED(); + return ""; + } + + std::string GetStorageKey(const syncer::EntityData& entity_data) override { + NOTREACHED(); + return ""; + } + + private: + std::unique_ptr<ReadingListStoreDelegate::ReadingListEntries> entries_; + TestReadingListStorageObserver* observer_; + base::SimpleTestClock* clock_; +}; + +class ReadingListModelTest : public ReadingListModelObserver, + public TestReadingListStorageObserver, + public testing::Test { + public: + ReadingListModelTest() : callback_called_(false) { + auto clock = base::MakeUnique<base::SimpleTestClock>(); + clock_ = clock.get(); + model_ = base::MakeUnique<ReadingListModelImpl>(nullptr, nullptr, + std::move(clock)); + ClearCounts(); + model_->AddObserver(this); + } + ~ReadingListModelTest() override {} + + void SetStorage(std::unique_ptr<TestReadingListStorage> storage, + std::unique_ptr<base::SimpleTestClock> clock) { + clock_ = clock.get(); + model_ = base::MakeUnique<ReadingListModelImpl>(std::move(storage), nullptr, + std::move(clock)); + ClearCounts(); + model_->AddObserver(this); + } + + void ClearCounts() { + observer_loaded_ = observer_started_batch_update_ = + observer_completed_batch_update_ = observer_deleted_ = + observer_remove_ = observer_move_ = observer_add_ = + observer_did_add_ = observer_update_ = observer_did_apply_ = + storage_saved_ = storage_removed_ = 0; + } + + void AssertObserverCount(int observer_loaded, + int observer_started_batch_update, + int observer_completed_batch_update, + int observer_deleted, + int observer_remove, + int observer_move, + int observer_add, + int observer_update, + int observer_did_apply) { + ASSERT_EQ(observer_loaded, observer_loaded_); + ASSERT_EQ(observer_started_batch_update, observer_started_batch_update_); + ASSERT_EQ(observer_completed_batch_update, + observer_completed_batch_update_); + ASSERT_EQ(observer_deleted, observer_deleted_); + ASSERT_EQ(observer_remove, observer_remove_); + ASSERT_EQ(observer_move, observer_move_); + // Add and did_add should be the same. + ASSERT_EQ(observer_add, observer_add_); + ASSERT_EQ(observer_add, observer_did_add_); + ASSERT_EQ(observer_update, observer_update_); + ASSERT_EQ(observer_did_apply, observer_did_apply_); + } + + void AssertStorageCount(int storage_saved, int storage_removed) { + ASSERT_EQ(storage_saved, storage_saved_); + ASSERT_EQ(storage_removed, storage_removed_); + } + + // ReadingListModelObserver + void ReadingListModelLoaded(const ReadingListModel* model) override { + observer_loaded_ += 1; + } + void ReadingListModelBeganBatchUpdates( + const ReadingListModel* model) override { + observer_started_batch_update_ += 1; + } + void ReadingListModelCompletedBatchUpdates( + const ReadingListModel* model) override { + observer_completed_batch_update_ += 1; + } + void ReadingListModelBeingDeleted(const ReadingListModel* model) override { + observer_deleted_ += 1; + } + void ReadingListWillRemoveEntry(const ReadingListModel* model, + const GURL& url) override { + observer_remove_ += 1; + } + void ReadingListWillMoveEntry(const ReadingListModel* model, + const GURL& url) override { + observer_move_ += 1; + } + void ReadingListWillAddEntry(const ReadingListModel* model, + const ReadingListEntry& entry) override { + observer_add_ += 1; + } + void ReadingListDidAddEntry(const ReadingListModel* model, + const GURL& url, + reading_list::EntrySource entry_source) override { + observer_did_add_ += 1; + } + void ReadingListWillUpdateEntry(const ReadingListModel* model, + const GURL& url) override { + observer_update_ += 1; + } + void ReadingListDidApplyChanges(ReadingListModel* model) override { + observer_did_apply_ += 1; + } + + void ReadingListDidSaveEntry() override { storage_saved_ += 1; } + void ReadingListDidRemoveEntry() override { storage_removed_ += 1; } + + size_t UnreadSize() { + size_t size = 0; + for (const auto& url : model_->Keys()) { + const ReadingListEntry* entry = model_->GetEntryByURL(url); + if (!entry->IsRead()) { + size++; + } + } + DCHECK_EQ(size, model_->unread_size()); + return size; + } + + size_t ReadSize() { + size_t size = 0; + for (const auto& url : model_->Keys()) { + const ReadingListEntry* entry = model_->GetEntryByURL(url); + if (entry->IsRead()) { + size++; + } + } + return size; + } + + void Callback(const ReadingListEntry& entry) { + EXPECT_EQ(callback_url, entry.URL()); + EXPECT_EQ(callback_title, entry.Title()); + callback_called_ = true; + } + + bool CallbackCalled() { return callback_called_; } + + protected: + int observer_loaded_; + int observer_started_batch_update_; + int observer_completed_batch_update_; + int observer_deleted_; + int observer_remove_; + int observer_move_; + int observer_add_; + int observer_did_add_; + int observer_update_; + int observer_did_apply_; + int storage_saved_; + int storage_removed_; + bool callback_called_; + + std::unique_ptr<ReadingListModelImpl> model_; + // Owned by |model_|; + base::SimpleTestClock* clock_; +}; + +// Tests creating an empty model. +TEST_F(ReadingListModelTest, EmptyLoaded) { + EXPECT_TRUE(model_->loaded()); + AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0); + EXPECT_EQ(0ul, UnreadSize()); + EXPECT_EQ(0ul, ReadSize()); + model_->Shutdown(); + EXPECT_FALSE(model_->loaded()); + AssertObserverCount(1, 0, 0, 1, 0, 0, 0, 0, 0); +} + +// Tests load model. +TEST_F(ReadingListModelTest, ModelLoaded) { + ClearCounts(); + auto clock = base::MakeUnique<base::SimpleTestClock>(); + auto storage = base::MakeUnique<TestReadingListStorage>(this, clock.get()); + storage->AddSampleEntries(); + SetStorage(std::move(storage), std::move(clock)); + + AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0); + std::map<GURL, std::string> loaded_entries; + int size = 0; + for (const auto& url : model_->Keys()) { + size++; + const ReadingListEntry* entry = model_->GetEntryByURL(url); + loaded_entries[url] = entry->Title(); + } + EXPECT_EQ(size, 7); + EXPECT_EQ(loaded_entries[GURL("http://unread_a.com")], "unread_a"); + EXPECT_EQ(loaded_entries[GURL("http://unread_b.com")], "unread_b"); + EXPECT_EQ(loaded_entries[GURL("http://unread_c.com")], "unread_c"); + EXPECT_EQ(loaded_entries[GURL("http://unread_d.com")], "unread_d"); + EXPECT_EQ(loaded_entries[GURL("http://read_a.com")], "read_a"); + EXPECT_EQ(loaded_entries[GURL("http://read_b.com")], "read_b"); + EXPECT_EQ(loaded_entries[GURL("http://read_c.com")], "read_c"); +} + +// Tests adding entry. +TEST_F(ReadingListModelTest, AddEntry) { + auto clock = base::MakeUnique<base::SimpleTestClock>(); + auto storage = base::MakeUnique<TestReadingListStorage>(this, clock.get()); + SetStorage(std::move(storage), std::move(clock)); + ClearCounts(); + + const ReadingListEntry& entry = + model_->AddEntry(GURL("http://example.com"), "\n \tsample Test ", + reading_list::ADDED_VIA_CURRENT_APP); + EXPECT_EQ(GURL("http://example.com"), entry.URL()); + EXPECT_EQ("sample Test", entry.Title()); + + AssertObserverCount(0, 0, 0, 0, 0, 0, 1, 0, 1); + AssertStorageCount(1, 0); + EXPECT_EQ(1ul, UnreadSize()); + EXPECT_EQ(0ul, ReadSize()); + EXPECT_TRUE(model_->GetLocalUnseenFlag()); + + const ReadingListEntry* other_entry = + model_->GetEntryByURL(GURL("http://example.com")); + EXPECT_NE(other_entry, nullptr); + EXPECT_FALSE(other_entry->IsRead()); + EXPECT_EQ(GURL("http://example.com"), other_entry->URL()); + EXPECT_EQ("sample Test", other_entry->Title()); +} + +// Tests addin entry from sync. +TEST_F(ReadingListModelTest, SyncAddEntry) { + auto clock = base::MakeUnique<base::SimpleTestClock>(); + auto storage = base::MakeUnique<TestReadingListStorage>(this, clock.get()); + SetStorage(std::move(storage), std::move(clock)); + auto entry = base::MakeUnique<ReadingListEntry>( + GURL("http://example.com"), "sample", AdvanceAndGetTime(clock_)); + entry->SetRead(true, AdvanceAndGetTime(clock_)); + ClearCounts(); + + model_->SyncAddEntry(std::move(entry)); + AssertObserverCount(0, 0, 0, 0, 0, 0, 1, 0, 1); + AssertStorageCount(0, 0); + EXPECT_EQ(0ul, UnreadSize()); + EXPECT_EQ(1ul, ReadSize()); + ClearCounts(); +} + +// Tests updating entry from sync. +TEST_F(ReadingListModelTest, SyncMergeEntry) { + auto clock = base::MakeUnique<base::SimpleTestClock>(); + auto storage = base::MakeUnique<TestReadingListStorage>(this, clock.get()); + SetStorage(std::move(storage), std::move(clock)); + model_->AddEntry(GURL("http://example.com"), "sample", + reading_list::ADDED_VIA_CURRENT_APP); + const base::FilePath distilled_path(FILE_PATH_LITERAL("distilled/page.html")); + const GURL distilled_url("http://example.com/distilled"); + int64_t size = 50; + int64_t time = 100; + model_->SetEntryDistilledInfo(GURL("http://example.com"), distilled_path, + distilled_url, size, + base::Time::FromTimeT(time)); + const ReadingListEntry* local_entry = + model_->GetEntryByURL(GURL("http://example.com")); + int64_t local_update_time = local_entry->UpdateTime(); + + auto sync_entry = base::MakeUnique<ReadingListEntry>( + GURL("http://example.com"), "sample", AdvanceAndGetTime(clock_)); + sync_entry->SetRead(true, AdvanceAndGetTime(clock_)); + ASSERT_GT(sync_entry->UpdateTime(), local_update_time); + int64_t sync_update_time = sync_entry->UpdateTime(); + EXPECT_TRUE(sync_entry->DistilledPath().empty()); + + EXPECT_EQ(1ul, UnreadSize()); + EXPECT_EQ(0ul, ReadSize()); + + ReadingListEntry* merged_entry = + model_->SyncMergeEntry(std::move(sync_entry)); + EXPECT_EQ(0ul, UnreadSize()); + EXPECT_EQ(1ul, ReadSize()); + EXPECT_EQ(merged_entry->DistilledPath(), + base::FilePath(FILE_PATH_LITERAL("distilled/page.html"))); + EXPECT_EQ(merged_entry->UpdateTime(), sync_update_time); + EXPECT_EQ(size, merged_entry->DistillationSize()); + EXPECT_EQ(time * base::Time::kMicrosecondsPerSecond, + merged_entry->DistillationTime()); +} + +// Tests deleting entry. +TEST_F(ReadingListModelTest, RemoveEntryByUrl) { + auto clock = base::MakeUnique<base::SimpleTestClock>(); + auto storage = base::MakeUnique<TestReadingListStorage>(this, clock.get()); + SetStorage(std::move(storage), std::move(clock)); + model_->AddEntry(GURL("http://example.com"), "sample", + reading_list::ADDED_VIA_CURRENT_APP); + ClearCounts(); + EXPECT_NE(model_->GetEntryByURL(GURL("http://example.com")), nullptr); + EXPECT_EQ(1ul, UnreadSize()); + EXPECT_EQ(0ul, ReadSize()); + model_->RemoveEntryByURL(GURL("http://example.com")); + AssertObserverCount(0, 0, 0, 0, 1, 0, 0, 0, 1); + AssertStorageCount(0, 1); + EXPECT_EQ(0ul, UnreadSize()); + EXPECT_EQ(0ul, ReadSize()); + EXPECT_EQ(model_->GetEntryByURL(GURL("http://example.com")), nullptr); + + model_->AddEntry(GURL("http://example.com"), "sample", + reading_list::ADDED_VIA_CURRENT_APP); + model_->SetReadStatus(GURL("http://example.com"), true); + ClearCounts(); + EXPECT_NE(model_->GetEntryByURL(GURL("http://example.com")), nullptr); + EXPECT_EQ(0ul, UnreadSize()); + EXPECT_EQ(1ul, ReadSize()); + model_->RemoveEntryByURL(GURL("http://example.com")); + AssertObserverCount(0, 0, 0, 0, 1, 0, 0, 0, 1); + AssertStorageCount(0, 1); + EXPECT_EQ(0ul, UnreadSize()); + EXPECT_EQ(0ul, ReadSize()); + EXPECT_EQ(model_->GetEntryByURL(GURL("http://example.com")), nullptr); +} + +// Tests deleting entry from sync. +TEST_F(ReadingListModelTest, RemoveSyncEntryByUrl) { + auto clock = base::MakeUnique<base::SimpleTestClock>(); + auto storage = base::MakeUnique<TestReadingListStorage>(this, clock.get()); + SetStorage(std::move(storage), std::move(clock)); + model_->AddEntry(GURL("http://example.com"), "sample", + reading_list::ADDED_VIA_CURRENT_APP); + ClearCounts(); + EXPECT_NE(model_->GetEntryByURL(GURL("http://example.com")), nullptr); + EXPECT_EQ(1ul, UnreadSize()); + EXPECT_EQ(0ul, ReadSize()); + model_->SyncRemoveEntry(GURL("http://example.com")); + AssertObserverCount(0, 0, 0, 0, 1, 0, 0, 0, 1); + AssertStorageCount(0, 0); + EXPECT_EQ(0ul, UnreadSize()); + EXPECT_EQ(0ul, ReadSize()); + EXPECT_EQ(model_->GetEntryByURL(GURL("http://example.com")), nullptr); + + model_->AddEntry(GURL("http://example.com"), "sample", + reading_list::ADDED_VIA_CURRENT_APP); + model_->SetReadStatus(GURL("http://example.com"), true); + ClearCounts(); + EXPECT_NE(model_->GetEntryByURL(GURL("http://example.com")), nullptr); + EXPECT_EQ(0ul, UnreadSize()); + EXPECT_EQ(1ul, ReadSize()); + model_->SyncRemoveEntry(GURL("http://example.com")); + AssertObserverCount(0, 0, 0, 0, 1, 0, 0, 0, 1); + AssertStorageCount(0, 0); + EXPECT_EQ(0ul, UnreadSize()); + EXPECT_EQ(0ul, ReadSize()); + EXPECT_EQ(model_->GetEntryByURL(GURL("http://example.com")), nullptr); +} + +// Tests marking entry read. +TEST_F(ReadingListModelTest, ReadEntry) { + model_->AddEntry(GURL("http://example.com"), "sample", + reading_list::ADDED_VIA_CURRENT_APP); + + ClearCounts(); + model_->SetReadStatus(GURL("http://example.com"), true); + AssertObserverCount(0, 0, 0, 0, 0, 1, 0, 0, 1); + EXPECT_EQ(0ul, UnreadSize()); + EXPECT_EQ(1ul, ReadSize()); + EXPECT_EQ(0ul, model_->unseen_size()); + + const ReadingListEntry* other_entry = + model_->GetEntryByURL(GURL("http://example.com")); + EXPECT_NE(other_entry, nullptr); + EXPECT_TRUE(other_entry->IsRead()); + EXPECT_EQ(GURL("http://example.com"), other_entry->URL()); + EXPECT_EQ("sample", other_entry->Title()); +} + +// Tests accessing existing entry. +TEST_F(ReadingListModelTest, EntryFromURL) { + GURL url1("http://example.com"); + GURL url2("http://example2.com"); + std::string entry1_title = "foo bar qux"; + model_->AddEntry(url1, entry1_title, reading_list::ADDED_VIA_CURRENT_APP); + + // Check call with nullptr |read| parameter. + const ReadingListEntry* entry1 = model_->GetEntryByURL(url1); + EXPECT_NE(nullptr, entry1); + EXPECT_EQ(entry1_title, entry1->Title()); + + entry1 = model_->GetEntryByURL(url1); + EXPECT_NE(nullptr, entry1); + EXPECT_EQ(entry1_title, entry1->Title()); + EXPECT_EQ(entry1->IsRead(), false); + model_->SetReadStatus(url1, true); + entry1 = model_->GetEntryByURL(url1); + EXPECT_NE(nullptr, entry1); + EXPECT_EQ(entry1_title, entry1->Title()); + EXPECT_EQ(entry1->IsRead(), true); + + const ReadingListEntry* entry2 = model_->GetEntryByURL(url2); + EXPECT_EQ(nullptr, entry2); +} + +// Tests mark entry unread. +TEST_F(ReadingListModelTest, UnreadEntry) { + // Setup. + model_->AddEntry(GURL("http://example.com"), "sample", + reading_list::ADDED_VIA_CURRENT_APP); + EXPECT_TRUE(model_->GetLocalUnseenFlag()); + model_->SetReadStatus(GURL("http://example.com"), true); + ClearCounts(); + EXPECT_EQ(0ul, UnreadSize()); + EXPECT_EQ(1ul, ReadSize()); + EXPECT_FALSE(model_->GetLocalUnseenFlag()); + + // Action. + model_->SetReadStatus(GURL("http://example.com"), false); + + // Tests. + AssertObserverCount(0, 0, 0, 0, 0, 1, 0, 0, 1); + EXPECT_EQ(1ul, UnreadSize()); + EXPECT_EQ(0ul, ReadSize()); + EXPECT_FALSE(model_->GetLocalUnseenFlag()); + + const ReadingListEntry* other_entry = + model_->GetEntryByURL(GURL("http://example.com")); + EXPECT_NE(other_entry, nullptr); + EXPECT_FALSE(other_entry->IsRead()); + EXPECT_EQ(GURL("http://example.com"), other_entry->URL()); + EXPECT_EQ("sample", other_entry->Title()); +} + +// Tests batch updates observers are called. +TEST_F(ReadingListModelTest, BatchUpdates) { + auto token = model_->BeginBatchUpdates(); + AssertObserverCount(1, 1, 0, 0, 0, 0, 0, 0, 0); + EXPECT_TRUE(model_->IsPerformingBatchUpdates()); + + delete token.release(); + AssertObserverCount(1, 1, 1, 0, 0, 0, 0, 0, 0); + EXPECT_FALSE(model_->IsPerformingBatchUpdates()); +} + +// Tests batch updates are reentrant. +TEST_F(ReadingListModelTest, BatchUpdatesReentrant) { + // When two updates happen at the same time, the notification is only sent + // for beginning of first update and completion of last update. + EXPECT_FALSE(model_->IsPerformingBatchUpdates()); + + auto token = model_->BeginBatchUpdates(); + AssertObserverCount(1, 1, 0, 0, 0, 0, 0, 0, 0); + EXPECT_TRUE(model_->IsPerformingBatchUpdates()); + + auto second_token = model_->BeginBatchUpdates(); + AssertObserverCount(1, 1, 0, 0, 0, 0, 0, 0, 0); + EXPECT_TRUE(model_->IsPerformingBatchUpdates()); + + delete token.release(); + AssertObserverCount(1, 1, 0, 0, 0, 0, 0, 0, 0); + EXPECT_TRUE(model_->IsPerformingBatchUpdates()); + + delete second_token.release(); + AssertObserverCount(1, 1, 1, 0, 0, 0, 0, 0, 0); + EXPECT_FALSE(model_->IsPerformingBatchUpdates()); + + // Consequent updates send notifications. + auto third_token = model_->BeginBatchUpdates(); + AssertObserverCount(1, 2, 1, 0, 0, 0, 0, 0, 0); + EXPECT_TRUE(model_->IsPerformingBatchUpdates()); + + delete third_token.release(); + AssertObserverCount(1, 2, 2, 0, 0, 0, 0, 0, 0); + EXPECT_FALSE(model_->IsPerformingBatchUpdates()); +} + +// Tests setting title on unread entry. +TEST_F(ReadingListModelTest, UpdateEntryTitle) { + const GURL gurl("http://example.com"); + const ReadingListEntry& entry = + model_->AddEntry(gurl, "sample", reading_list::ADDED_VIA_CURRENT_APP); + ClearCounts(); + + model_->SetEntryTitle(gurl, "ping"); + AssertObserverCount(0, 0, 0, 0, 0, 0, 0, 1, 1); + EXPECT_EQ("ping", entry.Title()); +} +// Tests setting distillation state on unread entry. +TEST_F(ReadingListModelTest, UpdateEntryDistilledState) { + const GURL gurl("http://example.com"); + const ReadingListEntry& entry = + model_->AddEntry(gurl, "sample", reading_list::ADDED_VIA_CURRENT_APP); + ClearCounts(); + + model_->SetEntryDistilledState(gurl, ReadingListEntry::PROCESSING); + AssertObserverCount(0, 0, 0, 0, 0, 0, 0, 1, 1); + EXPECT_EQ(ReadingListEntry::PROCESSING, entry.DistilledState()); +} + +// Tests setting distillation info on unread entry. +TEST_F(ReadingListModelTest, UpdateDistilledInfo) { + const GURL gurl("http://example.com"); + const ReadingListEntry& entry = + model_->AddEntry(gurl, "sample", reading_list::ADDED_VIA_CURRENT_APP); + ClearCounts(); + + const base::FilePath distilled_path(FILE_PATH_LITERAL("distilled/page.html")); + const GURL distilled_url("http://example.com/distilled"); + int64_t size = 50; + int64_t time = 100; + model_->SetEntryDistilledInfo(GURL("http://example.com"), distilled_path, + distilled_url, size, + base::Time::FromTimeT(time)); + AssertObserverCount(0, 0, 0, 0, 0, 0, 0, 1, 1); + EXPECT_EQ(ReadingListEntry::PROCESSED, entry.DistilledState()); + EXPECT_EQ(distilled_path, entry.DistilledPath()); + EXPECT_EQ(distilled_url, entry.DistilledURL()); + EXPECT_EQ(size, entry.DistillationSize()); + EXPECT_EQ(time * base::Time::kMicrosecondsPerSecond, + entry.DistillationTime()); +} + +// Tests setting title on read entry. +TEST_F(ReadingListModelTest, UpdateReadEntryTitle) { + const GURL gurl("http://example.com"); + model_->AddEntry(gurl, "sample", reading_list::ADDED_VIA_CURRENT_APP); + model_->SetReadStatus(gurl, true); + const ReadingListEntry* entry = model_->GetEntryByURL(gurl); + ClearCounts(); + + model_->SetEntryTitle(gurl, "ping"); + AssertObserverCount(0, 0, 0, 0, 0, 0, 0, 1, 1); + EXPECT_EQ("ping", entry->Title()); +} + +// Tests setting distillation state on read entry. +TEST_F(ReadingListModelTest, UpdateReadEntryState) { + const GURL gurl("http://example.com"); + model_->AddEntry(gurl, "sample", reading_list::ADDED_VIA_CURRENT_APP); + model_->SetReadStatus(gurl, true); + const ReadingListEntry* entry = model_->GetEntryByURL(gurl); + ClearCounts(); + + model_->SetEntryDistilledState(gurl, ReadingListEntry::PROCESSING); + AssertObserverCount(0, 0, 0, 0, 0, 0, 0, 1, 1); + EXPECT_EQ(ReadingListEntry::PROCESSING, entry->DistilledState()); +} + +// Tests setting distillation info on read entry. +TEST_F(ReadingListModelTest, UpdateReadDistilledInfo) { + const GURL gurl("http://example.com"); + model_->AddEntry(gurl, "sample", reading_list::ADDED_VIA_CURRENT_APP); + model_->SetReadStatus(gurl, true); + const ReadingListEntry* entry = model_->GetEntryByURL(gurl); + ClearCounts(); + + const base::FilePath distilled_path(FILE_PATH_LITERAL("distilled/page.html")); + const GURL distilled_url("http://example.com/distilled"); + int64_t size = 50; + int64_t time = 100; + model_->SetEntryDistilledInfo(GURL("http://example.com"), distilled_path, + distilled_url, size, + base::Time::FromTimeT(time)); + AssertObserverCount(0, 0, 0, 0, 0, 0, 0, 1, 1); + EXPECT_EQ(ReadingListEntry::PROCESSED, entry->DistilledState()); + EXPECT_EQ(distilled_path, entry->DistilledPath()); + EXPECT_EQ(distilled_url, entry->DistilledURL()); + EXPECT_EQ(size, entry->DistillationSize()); + EXPECT_EQ(time * base::Time::kMicrosecondsPerSecond, + entry->DistillationTime()); +} + +// Tests setting ContentSuggestionsExtra info on entry. +TEST_F(ReadingListModelTest, UpdateContentSuggestionsExtra) { + const GURL gurl("http://example.com"); + model_->AddEntry(gurl, "sample", reading_list::ADDED_VIA_CURRENT_APP); + const ReadingListEntry* entry = model_->GetEntryByURL(gurl); + ClearCounts(); + + reading_list::ContentSuggestionsExtra extra; + extra.dismissed = true; + + model_->SetContentSuggestionsExtra(gurl, extra); + AssertObserverCount(0, 0, 0, 0, 0, 0, 0, 1, 1); + EXPECT_EQ(extra.dismissed, entry->ContentSuggestionsExtra()->dismissed); +} + +// Tests that ReadingListModel calls CallbackModelBeingDeleted when destroyed. +TEST_F(ReadingListModelTest, CallbackModelBeingDeleted) { + AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0); + model_.reset(); + AssertObserverCount(1, 0, 0, 1, 0, 0, 0, 0, 0); +} + +// Tests that new line characters and spaces are collapsed in title. +TEST_F(ReadingListModelTest, TestTrimmingTitle) { + const GURL gurl("http://example.com"); + std::string title = "\n This\ttitle \n contains new line \n characters "; + model_->AddEntry(gurl, title, reading_list::ADDED_VIA_CURRENT_APP); + model_->SetReadStatus(gurl, true); + const ReadingListEntry* entry = model_->GetEntryByURL(gurl); + EXPECT_EQ(entry->Title(), "This title contains new line characters"); + model_->SetEntryTitle(gurl, "test"); + EXPECT_EQ(entry->Title(), "test"); + model_->SetEntryTitle(gurl, title); + EXPECT_EQ(entry->Title(), "This title contains new line characters"); +} + +} // namespace |