summaryrefslogtreecommitdiff
path: root/chromium/components/ntp_snippets/reading_list
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2017-07-12 14:07:37 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2017-07-17 10:29:26 +0000
commitec02ee4181c49b61fce1c8fb99292dbb8139cc90 (patch)
tree25cde714b2b71eb639d1cd53f5a22e9ba76e14ef /chromium/components/ntp_snippets/reading_list
parentbb09965444b5bb20b096a291445170876225268d (diff)
downloadqtwebengine-chromium-ec02ee4181c49b61fce1c8fb99292dbb8139cc90.tar.gz
BASELINE: Update Chromium to 59.0.3071.134
Change-Id: Id02ef6fb2204c5fd21668a1c3e6911c83b17585a Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Diffstat (limited to 'chromium/components/ntp_snippets/reading_list')
-rw-r--r--chromium/components/ntp_snippets/reading_list/reading_list_distillation_state_util.cc50
-rw-r--r--chromium/components/ntp_snippets/reading_list/reading_list_distillation_state_util.h23
-rw-r--r--chromium/components/ntp_snippets/reading_list/reading_list_suggestions_provider.cc248
-rw-r--r--chromium/components/ntp_snippets/reading_list/reading_list_suggestions_provider.h83
-rw-r--r--chromium/components/ntp_snippets/reading_list/reading_list_suggestions_provider_unittest.cc158
5 files changed, 562 insertions, 0 deletions
diff --git a/chromium/components/ntp_snippets/reading_list/reading_list_distillation_state_util.cc b/chromium/components/ntp_snippets/reading_list/reading_list_distillation_state_util.cc
new file mode 100644
index 00000000000..e7361ad1300
--- /dev/null
+++ b/chromium/components/ntp_snippets/reading_list/reading_list_distillation_state_util.cc
@@ -0,0 +1,50 @@
+// Copyright 2017 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/ntp_snippets/reading_list/reading_list_distillation_state_util.h"
+
+#include "base/logging.h"
+
+namespace ntp_snippets {
+
+ReadingListEntry::DistillationState ReadingListStateFromSuggestionState(
+ ReadingListSuggestionExtra::ReadingListSuggestionDistilledState
+ distilled_state) {
+ switch (distilled_state) {
+ case ReadingListSuggestionExtra::ReadingListSuggestionDistilledState::
+ PENDING:
+ return ReadingListEntry::WAITING;
+ case ReadingListSuggestionExtra::ReadingListSuggestionDistilledState::
+ SUCCESS:
+ return ReadingListEntry::PROCESSED;
+ case ReadingListSuggestionExtra::ReadingListSuggestionDistilledState::
+ FAILURE:
+ return ReadingListEntry::DISTILLATION_ERROR;
+ }
+ NOTREACHED();
+ return ReadingListEntry::PROCESSING;
+}
+
+ReadingListSuggestionExtra::ReadingListSuggestionDistilledState
+SuggestionStateFromReadingListState(
+ ReadingListEntry::DistillationState distilled_state) {
+ switch (distilled_state) {
+ case ReadingListEntry::WILL_RETRY:
+ case ReadingListEntry::PROCESSING:
+ case ReadingListEntry::WAITING:
+ return ReadingListSuggestionExtra::ReadingListSuggestionDistilledState::
+ PENDING;
+ case ReadingListEntry::PROCESSED:
+ return ReadingListSuggestionExtra::ReadingListSuggestionDistilledState::
+ SUCCESS;
+ case ReadingListEntry::DISTILLATION_ERROR:
+ return ReadingListSuggestionExtra::ReadingListSuggestionDistilledState::
+ FAILURE;
+ }
+ NOTREACHED();
+ return ReadingListSuggestionExtra::ReadingListSuggestionDistilledState::
+ PENDING;
+}
+
+} // namespace ntp_snippets
diff --git a/chromium/components/ntp_snippets/reading_list/reading_list_distillation_state_util.h b/chromium/components/ntp_snippets/reading_list/reading_list_distillation_state_util.h
new file mode 100644
index 00000000000..a00553fc98a
--- /dev/null
+++ b/chromium/components/ntp_snippets/reading_list/reading_list_distillation_state_util.h
@@ -0,0 +1,23 @@
+// Copyright 2017 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.
+
+#ifndef COMPONENTS_NTP_SNIPPETS_READING_LIST_READING_LIST_DISTILLATION_STATE_UTIL_H_
+#define COMPONENTS_NTP_SNIPPETS_READING_LIST_READING_LIST_DISTILLATION_STATE_UTIL_H_
+
+#include "components/ntp_snippets/content_suggestion.h"
+#include "components/reading_list/core/reading_list_entry.h"
+
+namespace ntp_snippets {
+
+ReadingListEntry::DistillationState ReadingListStateFromSuggestionState(
+ ReadingListSuggestionExtra::ReadingListSuggestionDistilledState
+ distilled_state);
+
+ReadingListSuggestionExtra::ReadingListSuggestionDistilledState
+SuggestionStateFromReadingListState(
+ ReadingListEntry::DistillationState distilled_state);
+
+} // namespace ntp_snippets
+
+#endif // COMPONENTS_NTP_SNIPPETS_READING_LIST_READING_LIST_DISTILLATION_STATE_UTIL_H_
diff --git a/chromium/components/ntp_snippets/reading_list/reading_list_suggestions_provider.cc b/chromium/components/ntp_snippets/reading_list/reading_list_suggestions_provider.cc
new file mode 100644
index 00000000000..ed11ed83fda
--- /dev/null
+++ b/chromium/components/ntp_snippets/reading_list/reading_list_suggestions_provider.cc
@@ -0,0 +1,248 @@
+// Copyright 2017 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/ntp_snippets/reading_list/reading_list_suggestions_provider.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/memory/ptr_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/ntp_snippets/category.h"
+#include "components/ntp_snippets/reading_list/reading_list_distillation_state_util.h"
+#include "components/reading_list/core/reading_list_entry.h"
+#include "components/reading_list/core/reading_list_model.h"
+#include "components/strings/grit/components_strings.h"
+#include "components/url_formatter/url_formatter.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/image/image.h"
+
+namespace ntp_snippets {
+
+namespace {
+// Max number of entries to return.
+const int kMaxEntries = 3;
+
+bool CompareEntries(const ReadingListEntry* lhs, const ReadingListEntry* rhs) {
+ return lhs->UpdateTime() > rhs->UpdateTime();
+}
+}
+
+ReadingListSuggestionsProvider::ReadingListSuggestionsProvider(
+ ContentSuggestionsProvider::Observer* observer,
+ ReadingListModel* reading_list_model)
+ : ContentSuggestionsProvider(observer),
+ category_status_(CategoryStatus::AVAILABLE_LOADING),
+ provided_category_(
+ Category::FromKnownCategory(KnownCategories::READING_LIST)),
+ reading_list_model_(reading_list_model),
+ scoped_observer_(this) {
+ observer->OnCategoryStatusChanged(this, provided_category_, category_status_);
+
+ // If the ReadingListModel is loaded, this will trigger a call to
+ // ReadingListModelLoaded. Keep it as last instruction.
+ scoped_observer_.Add(reading_list_model_);
+}
+
+ReadingListSuggestionsProvider::~ReadingListSuggestionsProvider(){};
+
+CategoryStatus ReadingListSuggestionsProvider::GetCategoryStatus(
+ Category category) {
+ DCHECK_EQ(category, provided_category_);
+ return category_status_;
+}
+
+CategoryInfo ReadingListSuggestionsProvider::GetCategoryInfo(
+ Category category) {
+ DCHECK_EQ(category, provided_category_);
+
+ return CategoryInfo(l10n_util::GetStringUTF16(
+ IDS_NTP_READING_LIST_SUGGESTIONS_SECTION_HEADER),
+ ContentSuggestionsCardLayout::FULL_CARD,
+ ContentSuggestionsAdditionalAction::VIEW_ALL,
+ /*show_if_empty=*/false,
+ l10n_util::GetStringUTF16(
+ IDS_NTP_READING_LIST_SUGGESTIONS_SECTION_EMPTY));
+}
+
+void ReadingListSuggestionsProvider::DismissSuggestion(
+ const ContentSuggestion::ID& suggestion_id) {
+ if (!reading_list_model_) {
+ return;
+ }
+
+ DCHECK(reading_list_model_->loaded());
+ GURL url(suggestion_id.id_within_category());
+ SetDismissedState(url, true);
+}
+
+void ReadingListSuggestionsProvider::FetchSuggestionImage(
+ const ContentSuggestion::ID& suggestion_id,
+ const ImageFetchedCallback& callback) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(callback, gfx::Image()));
+}
+
+void ReadingListSuggestionsProvider::Fetch(
+ const Category& category,
+ const std::set<std::string>& known_suggestion_ids,
+ const FetchDoneCallback& callback) {
+ LOG(DFATAL) << "ReadingListSuggestionsProvider has no |Fetch| functionality!";
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback,
+ Status(StatusCode::PERMANENT_ERROR,
+ "ReadingListSuggestionsProvider has no |Fetch| "
+ "functionality!"),
+ base::Passed(std::vector<ContentSuggestion>())));
+}
+
+void ReadingListSuggestionsProvider::ClearHistory(
+ base::Time begin,
+ base::Time end,
+ const base::Callback<bool(const GURL& url)>& filter) {
+ // Ignored, Reading List does not depend on history.
+}
+
+void ReadingListSuggestionsProvider::ClearCachedSuggestions(Category category) {
+ DCHECK_EQ(category, provided_category_);
+ // Ignored.
+}
+
+void ReadingListSuggestionsProvider::GetDismissedSuggestionsForDebugging(
+ Category category,
+ const DismissedSuggestionsCallback& callback) {
+ if (!reading_list_model_ || reading_list_model_->IsPerformingBatchUpdates()) {
+ callback.Run(std::vector<ContentSuggestion>());
+ return;
+ }
+
+ DCHECK(reading_list_model_->loaded());
+ std::vector<const ReadingListEntry*> entries;
+ for (const GURL& url : reading_list_model_->Keys()) {
+ const ReadingListEntry* entry = reading_list_model_->GetEntryByURL(url);
+ if (entry->ContentSuggestionsExtra()->dismissed) {
+ entries.emplace_back(entry);
+ }
+ }
+
+ std::sort(entries.begin(), entries.end(), CompareEntries);
+
+ std::vector<ContentSuggestion> suggestions;
+ for (const ReadingListEntry* entry : entries) {
+ suggestions.emplace_back(ConvertEntry(entry));
+ }
+
+ callback.Run(std::move(suggestions));
+}
+
+void ReadingListSuggestionsProvider::ClearDismissedSuggestionsForDebugging(
+ Category category) {
+ for (const auto& url : reading_list_model_->Keys()) {
+ SetDismissedState(url, false);
+ }
+}
+
+void ReadingListSuggestionsProvider::ReadingListModelLoaded(
+ const ReadingListModel* model) {
+ DCHECK(model == reading_list_model_);
+ FetchReadingListInternal();
+}
+
+void ReadingListSuggestionsProvider::ReadingListModelBeingDeleted(
+ const ReadingListModel* model) {
+ DCHECK(model == reading_list_model_);
+ scoped_observer_.Remove(reading_list_model_);
+ reading_list_model_ = nullptr;
+}
+
+void ReadingListSuggestionsProvider::ReadingListDidApplyChanges(
+ ReadingListModel* model) {
+ DCHECK(model == reading_list_model_);
+
+ FetchReadingListInternal();
+}
+
+void ReadingListSuggestionsProvider::ReadingListModelCompletedBatchUpdates(
+ const ReadingListModel* model) {
+ DCHECK(model == reading_list_model_);
+
+ FetchReadingListInternal();
+}
+
+void ReadingListSuggestionsProvider::FetchReadingListInternal() {
+ if (!reading_list_model_ || reading_list_model_->IsPerformingBatchUpdates()) {
+ return;
+ }
+
+ DCHECK(reading_list_model_->loaded());
+ std::vector<const ReadingListEntry*> entries;
+ for (const GURL& url : reading_list_model_->Keys()) {
+ const ReadingListEntry* entry = reading_list_model_->GetEntryByURL(url);
+ if (!entry->IsRead() && !entry->ContentSuggestionsExtra()->dismissed) {
+ entries.emplace_back(entry);
+ }
+ }
+
+ if (entries.size() > kMaxEntries) {
+ // Get the |kMaxEntries| most recent entries.
+ std::partial_sort(entries.begin(), entries.begin() + kMaxEntries,
+ entries.end(), CompareEntries);
+ entries.resize(kMaxEntries);
+ } else {
+ std::sort(entries.begin(), entries.end(), CompareEntries);
+ }
+
+ std::vector<ContentSuggestion> suggestions;
+ for (const ReadingListEntry* entry : entries) {
+ suggestions.emplace_back(ConvertEntry(entry));
+ }
+
+ NotifyStatusChanged(CategoryStatus::AVAILABLE);
+ observer()->OnNewSuggestions(this, provided_category_,
+ std::move(suggestions));
+}
+
+ContentSuggestion ReadingListSuggestionsProvider::ConvertEntry(
+ const ReadingListEntry* entry) {
+ ContentSuggestion suggestion(provided_category_, entry->URL().spec(),
+ entry->URL());
+
+ if (!entry->Title().empty()) {
+ suggestion.set_title(base::UTF8ToUTF16(entry->Title()));
+ } else {
+ suggestion.set_title(url_formatter::FormatUrl(entry->URL()));
+ }
+ suggestion.set_publisher_name(
+ url_formatter::FormatUrl(entry->URL().GetOrigin()));
+
+ auto extra = base::MakeUnique<ReadingListSuggestionExtra>();
+ extra->distilled_state =
+ SuggestionStateFromReadingListState(entry->DistilledState());
+ extra->favicon_page_url =
+ entry->DistilledURL().is_valid() ? entry->DistilledURL() : entry->URL();
+ suggestion.set_reading_list_suggestion_extra(std::move(extra));
+
+ return suggestion;
+}
+
+void ReadingListSuggestionsProvider::NotifyStatusChanged(
+ CategoryStatus new_status) {
+ if (category_status_ == new_status) {
+ return;
+ }
+ category_status_ = new_status;
+ observer()->OnCategoryStatusChanged(this, provided_category_, new_status);
+}
+
+void ReadingListSuggestionsProvider::SetDismissedState(const GURL& url,
+ bool dismissed) {
+ reading_list::ContentSuggestionsExtra extra;
+ extra.dismissed = dismissed;
+ reading_list_model_->SetContentSuggestionsExtra(url, extra);
+}
+
+} // namespace ntp_snippets
diff --git a/chromium/components/ntp_snippets/reading_list/reading_list_suggestions_provider.h b/chromium/components/ntp_snippets/reading_list/reading_list_suggestions_provider.h
new file mode 100644
index 00000000000..433d9493b80
--- /dev/null
+++ b/chromium/components/ntp_snippets/reading_list/reading_list_suggestions_provider.h
@@ -0,0 +1,83 @@
+// Copyright 2017 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.
+
+#ifndef COMPONENTS_NTP_SNIPPETS_READING_LIST_READING_LIST_SUGGESTIONS_PROVIDER_H_
+#define COMPONENTS_NTP_SNIPPETS_READING_LIST_READING_LIST_SUGGESTIONS_PROVIDER_H_
+
+#include <set>
+#include <string>
+
+#include "base/scoped_observer.h"
+#include "components/ntp_snippets/callbacks.h"
+#include "components/ntp_snippets/category.h"
+#include "components/ntp_snippets/category_info.h"
+#include "components/ntp_snippets/category_status.h"
+#include "components/ntp_snippets/content_suggestion.h"
+#include "components/ntp_snippets/content_suggestions_provider.h"
+#include "components/reading_list/core/reading_list_model_observer.h"
+
+class ReadingListModel;
+
+namespace ntp_snippets {
+
+// Provides content suggestions from the Reading List.
+class ReadingListSuggestionsProvider : public ContentSuggestionsProvider,
+ public ReadingListModelObserver {
+ public:
+ ReadingListSuggestionsProvider(ContentSuggestionsProvider::Observer* observer,
+ ReadingListModel* reading_list_model);
+ ~ReadingListSuggestionsProvider() override;
+
+ // ContentSuggestionsProvider implementation.
+ CategoryStatus GetCategoryStatus(Category category) override;
+ CategoryInfo GetCategoryInfo(Category category) override;
+ void DismissSuggestion(const ContentSuggestion::ID& suggestion_id) override;
+ void FetchSuggestionImage(const ContentSuggestion::ID& suggestion_id,
+ const ImageFetchedCallback& callback) override;
+ void Fetch(const Category& category,
+ const std::set<std::string>& known_suggestion_ids,
+ const FetchDoneCallback& callback) override;
+ void ClearHistory(
+ base::Time begin,
+ base::Time end,
+ const base::Callback<bool(const GURL& url)>& filter) override;
+ void ClearCachedSuggestions(Category category) override;
+ void GetDismissedSuggestionsForDebugging(
+ Category category,
+ const DismissedSuggestionsCallback& callback) override;
+ void ClearDismissedSuggestionsForDebugging(Category category) override;
+
+ // ReadingListModelObserver implementation.
+ void ReadingListModelLoaded(const ReadingListModel* model) override;
+ void ReadingListModelBeingDeleted(const ReadingListModel* model) override;
+ void ReadingListDidApplyChanges(ReadingListModel* model) override;
+ void ReadingListModelCompletedBatchUpdates(
+ const ReadingListModel* model) override;
+
+ private:
+ // The actual method to fetch Reading List entries. Must be called after the
+ // model is loaded.
+ void FetchReadingListInternal();
+
+ // Converts |entry| to ContentSuggestion.
+ ContentSuggestion ConvertEntry(const ReadingListEntry* entry);
+
+ // Updates the |category_status_| and notifies the |observer_|, if necessary.
+ void NotifyStatusChanged(CategoryStatus new_status);
+
+ // Sets the dismissed status of the entry to |dismissed|.
+ void SetDismissedState(const GURL& url, bool dismissed);
+
+ CategoryStatus category_status_;
+ const Category provided_category_;
+
+ ReadingListModel* reading_list_model_;
+ ScopedObserver<ReadingListModel, ReadingListModelObserver> scoped_observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(ReadingListSuggestionsProvider);
+};
+
+} // namespace ntp_snippets
+
+#endif // COMPONENTS_NTP_SNIPPETS_READING_LIST_READING_LIST_SUGGESTIONS_PROVIDER_H_
diff --git a/chromium/components/ntp_snippets/reading_list/reading_list_suggestions_provider_unittest.cc b/chromium/components/ntp_snippets/reading_list/reading_list_suggestions_provider_unittest.cc
new file mode 100644
index 00000000000..3ed07071891
--- /dev/null
+++ b/chromium/components/ntp_snippets/reading_list/reading_list_suggestions_provider_unittest.cc
@@ -0,0 +1,158 @@
+// Copyright 2017 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/ntp_snippets/reading_list/reading_list_suggestions_provider.h"
+
+#include "base/memory/ptr_util.h"
+#include "base/test/simple_test_clock.h"
+#include "components/ntp_snippets/mock_content_suggestions_provider_observer.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"
+
+namespace ntp_snippets {
+
+namespace {
+
+const char kTitleUnread1[] = "title1";
+const char kTitleUnread2[] = "title2";
+const char kTitleUnread3[] = "title3";
+const char kTitleUnread4[] = "title4";
+const char kTitleRead1[] = "title_read1";
+
+using ::testing::_;
+using ::testing::ElementsAre;
+using ::testing::IsEmpty;
+using ::testing::Property;
+
+class ReadingListSuggestionsProviderTest : public ::testing::Test {
+ public:
+ ReadingListSuggestionsProviderTest() {
+ std::unique_ptr<base::SimpleTestClock> clock =
+ base::MakeUnique<base::SimpleTestClock>();
+ clock_ = clock.get();
+ model_ = base::MakeUnique<ReadingListModelImpl>(
+ /*storage_layer=*/nullptr, /*pref_service=*/nullptr, std::move(clock));
+ }
+
+ void CreateProvider() {
+ EXPECT_CALL(observer_,
+ OnCategoryStatusChanged(_, ReadingListCategory(),
+ CategoryStatus::AVAILABLE_LOADING))
+ .RetiresOnSaturation();
+ EXPECT_CALL(observer_, OnCategoryStatusChanged(_, ReadingListCategory(),
+ CategoryStatus::AVAILABLE))
+ .RetiresOnSaturation();
+ provider_ = base::MakeUnique<ReadingListSuggestionsProvider>(&observer_,
+ model_.get());
+ }
+
+ Category ReadingListCategory() {
+ return Category::FromKnownCategory(KnownCategories::READING_LIST);
+ }
+
+ void AddEntries() {
+ model_->AddEntry(url_unread1_, kTitleUnread1,
+ reading_list::ADDED_VIA_CURRENT_APP);
+ clock_->Advance(base::TimeDelta::FromMilliseconds(10));
+ model_->AddEntry(url_unread2_, kTitleUnread2,
+ reading_list::ADDED_VIA_CURRENT_APP);
+ clock_->Advance(base::TimeDelta::FromMilliseconds(10));
+ model_->AddEntry(url_read1_, kTitleRead1,
+ reading_list::ADDED_VIA_CURRENT_APP);
+ model_->SetReadStatus(url_read1_, true);
+ clock_->Advance(base::TimeDelta::FromMilliseconds(10));
+ model_->AddEntry(url_unread3_, kTitleUnread3,
+ reading_list::ADDED_VIA_CURRENT_APP);
+ clock_->Advance(base::TimeDelta::FromMilliseconds(10));
+ model_->AddEntry(url_unread4_, kTitleUnread4,
+ reading_list::ADDED_VIA_CURRENT_APP);
+ }
+
+ protected:
+ base::SimpleTestClock* clock_;
+ std::unique_ptr<ReadingListModelImpl> model_;
+ testing::StrictMock<MockContentSuggestionsProviderObserver> observer_;
+ std::unique_ptr<ReadingListSuggestionsProvider> provider_;
+
+ const GURL url_unread1_{"http://www.foo1.bar"};
+ const GURL url_unread2_{"http://www.foo2.bar"};
+ const GURL url_unread3_{"http://www.foo3.bar"};
+ const GURL url_unread4_{"http://www.foo4.bar"};
+ const GURL url_read1_{"http://www.bar.foor"};
+};
+
+TEST_F(ReadingListSuggestionsProviderTest, CategoryInfo) {
+ EXPECT_CALL(observer_, OnNewSuggestions(_, ReadingListCategory(), IsEmpty()))
+ .RetiresOnSaturation();
+ CreateProvider();
+
+ CategoryInfo categoryInfo = provider_->GetCategoryInfo(ReadingListCategory());
+ EXPECT_EQ(ContentSuggestionsAdditionalAction::VIEW_ALL,
+ categoryInfo.additional_action());
+}
+
+TEST_F(ReadingListSuggestionsProviderTest, ReturnsThreeLatestUnreadSuggestion) {
+ AddEntries();
+
+ EXPECT_CALL(
+ observer_,
+ OnNewSuggestions(
+ _, ReadingListCategory(),
+ ElementsAre(Property(&ContentSuggestion::url, url_unread4_),
+ Property(&ContentSuggestion::url, url_unread3_),
+ Property(&ContentSuggestion::url, url_unread2_))));
+
+ CreateProvider();
+}
+
+// Tests that the provider returns only unread suggestions even if there is less
+// unread suggestions than the maximum number of suggestions.
+TEST_F(ReadingListSuggestionsProviderTest, ReturnsOnlyUnreadSuggestion) {
+ GURL url_unread1 = GURL("http://www.foo1.bar");
+ GURL url_read1 = GURL("http://www.bar.foor");
+ std::string title_unread1 = "title1";
+ std::string title_read1 = "title_read1";
+ model_->AddEntry(url_unread1, title_unread1,
+ reading_list::ADDED_VIA_CURRENT_APP);
+ clock_->Advance(base::TimeDelta::FromMilliseconds(10));
+ model_->AddEntry(url_read1, title_read1, reading_list::ADDED_VIA_CURRENT_APP);
+ model_->SetReadStatus(url_read1, true);
+
+ EXPECT_CALL(observer_,
+ OnNewSuggestions(
+ _, ReadingListCategory(),
+ ElementsAre(Property(&ContentSuggestion::url, url_unread1))));
+
+ CreateProvider();
+}
+
+TEST_F(ReadingListSuggestionsProviderTest, DismissesEntry) {
+ AddEntries();
+
+ EXPECT_CALL(
+ observer_,
+ OnNewSuggestions(
+ _, ReadingListCategory(),
+ ElementsAre(Property(&ContentSuggestion::url, url_unread4_),
+ Property(&ContentSuggestion::url, url_unread3_),
+ Property(&ContentSuggestion::url, url_unread2_))));
+
+ CreateProvider();
+
+ EXPECT_CALL(
+ observer_,
+ OnNewSuggestions(
+ _, ReadingListCategory(),
+ ElementsAre(Property(&ContentSuggestion::url, url_unread4_),
+ Property(&ContentSuggestion::url, url_unread2_),
+ Property(&ContentSuggestion::url, url_unread1_))));
+
+ provider_->DismissSuggestion(
+ ContentSuggestion::ID(ReadingListCategory(), url_unread3_.spec()));
+}
+
+} // namespace
+
+} // namespace ntp_snippets