| // 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/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 "components/sync/model_impl/client_tag_based_model_type_processor.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( |
| // TODO(crbug.com/823380): Consider using mock/fake here. |
| std::make_unique<syncer::ClientTagBasedModelTypeProcessor>( |
| syncer::READING_LIST, |
| /*dump_stack=*/base::RepeatingClosure())), |
| 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::EntityChangeList entity_data) 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 GetAllDataForDebugging(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) { |
| model_ = std::make_unique<ReadingListModelImpl>(nullptr, nullptr, &clock_); |
| ClearCounts(); |
| model_->AddObserver(this); |
| } |
| ~ReadingListModelTest() override {} |
| |
| void SetStorage(std::unique_ptr<TestReadingListStorage> storage) { |
| model_ = std::make_unique<ReadingListModelImpl>(std::move(storage), nullptr, |
| &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_; |
| 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()); |
| // Shutdown() does not delete the model observer. Verify that deleting the |
| // model will delete the model observer. |
| model_.reset(); |
| AssertObserverCount(1, 0, 0, 1, 0, 0, 0, 0, 0); |
| } |
| |
| // Tests load model. |
| TEST_F(ReadingListModelTest, ModelLoaded) { |
| ClearCounts(); |
| auto storage = std::make_unique<TestReadingListStorage>(this, &clock_); |
| storage->AddSampleEntries(); |
| SetStorage(std::move(storage)); |
| |
| 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 storage = std::make_unique<TestReadingListStorage>(this, &clock_); |
| SetStorage(std::move(storage)); |
| 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 adding an entry that already exists. |
| TEST_F(ReadingListModelTest, AddExistingEntry) { |
| auto storage = std::make_unique<TestReadingListStorage>(this, &clock_); |
| SetStorage(std::move(storage)); |
| GURL url = GURL("http://example.com"); |
| std::string title = "\n \tsample Test "; |
| model_->AddEntry(url, title, reading_list::ADDED_VIA_CURRENT_APP); |
| ClearCounts(); |
| |
| const ReadingListEntry& entry = |
| model_->AddEntry(url, title, reading_list::ADDED_VIA_CURRENT_APP); |
| EXPECT_EQ(GURL("http://example.com"), entry.URL()); |
| EXPECT_EQ("sample Test", entry.Title()); |
| |
| AssertObserverCount(0, 1, 1, 0, 1, 0, 1, 0, 2); |
| AssertStorageCount(1, 1); |
| 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 storage = std::make_unique<TestReadingListStorage>(this, &clock_); |
| SetStorage(std::move(storage)); |
| auto entry = std::make_unique<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 storage = std::make_unique<TestReadingListStorage>(this, &clock_); |
| SetStorage(std::move(storage)); |
| 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 = std::make_unique<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 storage = std::make_unique<TestReadingListStorage>(this, &clock_); |
| SetStorage(std::move(storage)); |
| 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 storage = std::make_unique<TestReadingListStorage>(this, &clock_); |
| SetStorage(std::move(storage)); |
| 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 |