| // 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/ntp_snippets/content_suggestions_service.h" |
| |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/utf_string_conversions.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 "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/gfx/image/image.h" |
| |
| using testing::ElementsAre; |
| using testing::Eq; |
| using testing::InvokeWithoutArgs; |
| using testing::IsEmpty; |
| using testing::Mock; |
| using testing::Property; |
| using testing::_; |
| |
| namespace ntp_snippets { |
| |
| namespace { |
| |
| class MockProvider : public ContentSuggestionsProvider { |
| public: |
| MockProvider(Observer* observer, |
| CategoryFactory* category_factory, |
| const std::vector<Category>& provided_categories) |
| : ContentSuggestionsProvider(observer, category_factory) { |
| SetProvidedCategories(provided_categories); |
| } |
| |
| void SetProvidedCategories(const std::vector<Category>& provided_categories) { |
| statuses_.clear(); |
| provided_categories_ = provided_categories; |
| for (Category category : provided_categories) { |
| statuses_[category.id()] = CategoryStatus::AVAILABLE; |
| } |
| } |
| |
| CategoryStatus GetCategoryStatus(Category category) override { |
| return statuses_[category.id()]; |
| } |
| |
| CategoryInfo GetCategoryInfo(Category category) override { |
| return CategoryInfo(base::ASCIIToUTF16("Section title"), |
| ContentSuggestionsCardLayout::FULL_CARD, true, true); |
| } |
| |
| void FireSuggestionsChanged( |
| Category category, |
| std::vector<ContentSuggestion> suggestions) { |
| observer()->OnNewSuggestions(this, category, std::move(suggestions)); |
| } |
| |
| void FireCategoryStatusChanged(Category category, CategoryStatus new_status) { |
| statuses_[category.id()] = new_status; |
| observer()->OnCategoryStatusChanged(this, category, new_status); |
| } |
| |
| void FireCategoryStatusChangedWithCurrentStatus(Category category) { |
| observer()->OnCategoryStatusChanged(this, category, |
| statuses_[category.id()]); |
| } |
| |
| void FireSuggestionInvalidated(const ContentSuggestion::ID& suggestion_id) { |
| observer()->OnSuggestionInvalidated(this, suggestion_id); |
| } |
| |
| MOCK_METHOD3(ClearHistory, |
| void(base::Time begin, |
| base::Time end, |
| const base::Callback<bool(const GURL& url)>& filter)); |
| MOCK_METHOD1(ClearCachedSuggestions, void(Category category)); |
| MOCK_METHOD2(GetDismissedSuggestionsForDebugging, |
| void(Category category, |
| const DismissedSuggestionsCallback& callback)); |
| MOCK_METHOD1(ClearDismissedSuggestionsForDebugging, void(Category category)); |
| MOCK_METHOD1(DismissSuggestion, |
| void(const ContentSuggestion::ID& suggestion_id)); |
| MOCK_METHOD2(FetchSuggestionImage, |
| void(const ContentSuggestion::ID& suggestion_id, |
| const ImageFetchedCallback& callback)); |
| |
| private: |
| std::vector<Category> provided_categories_; |
| std::map<int, CategoryStatus> statuses_; |
| }; |
| |
| class MockServiceObserver : public ContentSuggestionsService::Observer { |
| public: |
| MockServiceObserver() = default; |
| ~MockServiceObserver() override = default; |
| |
| MOCK_METHOD1(OnNewSuggestions, void(Category category)); |
| MOCK_METHOD2(OnCategoryStatusChanged, |
| void(Category changed_category, CategoryStatus new_status)); |
| MOCK_METHOD1(OnSuggestionInvalidated, |
| void(const ContentSuggestion::ID& suggestion_id)); |
| MOCK_METHOD0(ContentSuggestionsServiceShutdown, void()); |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(MockServiceObserver); |
| }; |
| |
| } // namespace |
| |
| class ContentSuggestionsServiceTest : public testing::Test { |
| public: |
| ContentSuggestionsServiceTest() {} |
| |
| void SetUp() override { |
| CreateContentSuggestionsService(ContentSuggestionsService::State::ENABLED); |
| } |
| |
| void TearDown() override { |
| service_->Shutdown(); |
| service_.reset(); |
| } |
| |
| // Verifies that exactly the suggestions with the given |numbers| are |
| // returned by the service for the given |category|. |
| void ExpectThatSuggestionsAre(Category category, std::vector<int> numbers) { |
| std::vector<Category> categories = service()->GetCategories(); |
| auto position = std::find(categories.begin(), categories.end(), category); |
| if (!numbers.empty()) { |
| EXPECT_NE(categories.end(), position); |
| } |
| |
| for (const auto& suggestion : |
| service()->GetSuggestionsForCategory(category)) { |
| std::string id_within_category = suggestion.id().id_within_category(); |
| int id; |
| ASSERT_TRUE(base::StringToInt(id_within_category, &id)); |
| auto position = std::find(numbers.begin(), numbers.end(), id); |
| if (position == numbers.end()) { |
| ADD_FAILURE() << "Unexpected suggestion with ID " << id; |
| } else { |
| numbers.erase(position); |
| } |
| } |
| for (int number : numbers) { |
| ADD_FAILURE() << "Suggestion number " << number |
| << " not present, though expected"; |
| } |
| } |
| |
| const std::map<Category, ContentSuggestionsProvider*, Category::CompareByID>& |
| providers() { |
| return service()->providers_by_category_; |
| } |
| |
| CategoryFactory* category_factory() { return service()->category_factory(); } |
| |
| Category FromKnownCategory(KnownCategories known_category) { |
| return service()->category_factory()->FromKnownCategory(known_category); |
| } |
| |
| Category FromRemoteCategory(int remote_category) { |
| return service()->category_factory()->FromRemoteCategory(remote_category); |
| } |
| |
| MockProvider* RegisterProvider(Category provided_category) { |
| return RegisterProvider(std::vector<Category>({provided_category})); |
| } |
| |
| MockProvider* RegisterProvider( |
| const std::vector<Category>& provided_categories) { |
| std::unique_ptr<MockProvider> provider = base::MakeUnique<MockProvider>( |
| service(), category_factory(), provided_categories); |
| MockProvider* result = provider.get(); |
| service()->RegisterProvider(std::move(provider)); |
| return result; |
| } |
| |
| MOCK_METHOD1(OnImageFetched, void(const gfx::Image&)); |
| |
| protected: |
| void CreateContentSuggestionsService( |
| ContentSuggestionsService::State enabled) { |
| ASSERT_FALSE(service_); |
| service_.reset(new ContentSuggestionsService(enabled, |
| nullptr /* history_service */, |
| nullptr /* pref_service */)); |
| } |
| |
| ContentSuggestionsService* service() { return service_.get(); } |
| |
| // Returns a suggestion instance for testing. |
| ContentSuggestion CreateSuggestion(Category category, int number) { |
| return ContentSuggestion( |
| category, base::IntToString(number), |
| GURL("http://testsuggestion/" + base::IntToString(number))); |
| } |
| |
| std::vector<ContentSuggestion> CreateSuggestions( |
| Category category, |
| const std::vector<int>& numbers) { |
| std::vector<ContentSuggestion> result; |
| for (int number : numbers) { |
| result.push_back(CreateSuggestion(category, number)); |
| } |
| return result; |
| } |
| |
| private: |
| std::unique_ptr<ContentSuggestionsService> service_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ContentSuggestionsServiceTest); |
| }; |
| |
| class ContentSuggestionsServiceDisabledTest |
| : public ContentSuggestionsServiceTest { |
| public: |
| void SetUp() override { |
| CreateContentSuggestionsService(ContentSuggestionsService::State::DISABLED); |
| } |
| }; |
| |
| TEST_F(ContentSuggestionsServiceTest, ShouldRegisterProviders) { |
| EXPECT_THAT(service()->state(), |
| Eq(ContentSuggestionsService::State::ENABLED)); |
| Category articles_category = FromKnownCategory(KnownCategories::ARTICLES); |
| Category offline_pages_category = |
| FromKnownCategory(KnownCategories::DOWNLOADS); |
| ASSERT_THAT(providers(), IsEmpty()); |
| EXPECT_THAT(service()->GetCategories(), IsEmpty()); |
| EXPECT_THAT(service()->GetCategoryStatus(articles_category), |
| Eq(CategoryStatus::NOT_PROVIDED)); |
| EXPECT_THAT(service()->GetCategoryStatus(offline_pages_category), |
| Eq(CategoryStatus::NOT_PROVIDED)); |
| |
| MockProvider* provider1 = RegisterProvider(articles_category); |
| provider1->FireCategoryStatusChangedWithCurrentStatus(articles_category); |
| EXPECT_THAT(providers().count(offline_pages_category), Eq(0ul)); |
| ASSERT_THAT(providers().count(articles_category), Eq(1ul)); |
| EXPECT_THAT(providers().at(articles_category), Eq(provider1)); |
| EXPECT_THAT(providers().size(), Eq(1ul)); |
| EXPECT_THAT(service()->GetCategories(), ElementsAre(articles_category)); |
| EXPECT_THAT(service()->GetCategoryStatus(articles_category), |
| Eq(CategoryStatus::AVAILABLE)); |
| EXPECT_THAT(service()->GetCategoryStatus(offline_pages_category), |
| Eq(CategoryStatus::NOT_PROVIDED)); |
| |
| MockProvider* provider2 = RegisterProvider(offline_pages_category); |
| provider2->FireCategoryStatusChangedWithCurrentStatus(offline_pages_category); |
| ASSERT_THAT(providers().count(offline_pages_category), Eq(1ul)); |
| EXPECT_THAT(providers().at(articles_category), Eq(provider1)); |
| ASSERT_THAT(providers().count(articles_category), Eq(1ul)); |
| EXPECT_THAT(providers().at(offline_pages_category), Eq(provider2)); |
| EXPECT_THAT(providers().size(), Eq(2ul)); |
| EXPECT_THAT(service()->GetCategories(), |
| ElementsAre(offline_pages_category, articles_category)); |
| EXPECT_THAT(service()->GetCategoryStatus(articles_category), |
| Eq(CategoryStatus::AVAILABLE)); |
| EXPECT_THAT(service()->GetCategoryStatus(offline_pages_category), |
| Eq(CategoryStatus::AVAILABLE)); |
| } |
| |
| TEST_F(ContentSuggestionsServiceDisabledTest, ShouldDoNothingWhenDisabled) { |
| Category articles_category = FromKnownCategory(KnownCategories::ARTICLES); |
| Category offline_pages_category = |
| FromKnownCategory(KnownCategories::DOWNLOADS); |
| EXPECT_THAT(service()->state(), |
| Eq(ContentSuggestionsService::State::DISABLED)); |
| EXPECT_THAT(providers(), IsEmpty()); |
| EXPECT_THAT(service()->GetCategoryStatus(articles_category), |
| Eq(CategoryStatus::ALL_SUGGESTIONS_EXPLICITLY_DISABLED)); |
| EXPECT_THAT(service()->GetCategoryStatus(offline_pages_category), |
| Eq(CategoryStatus::ALL_SUGGESTIONS_EXPLICITLY_DISABLED)); |
| EXPECT_THAT(service()->GetCategories(), IsEmpty()); |
| EXPECT_THAT(service()->GetSuggestionsForCategory(articles_category), |
| IsEmpty()); |
| } |
| |
| TEST_F(ContentSuggestionsServiceTest, ShouldRedirectFetchSuggestionImage) { |
| Category articles_category = FromKnownCategory(KnownCategories::ARTICLES); |
| Category offline_pages_category = |
| FromKnownCategory(KnownCategories::DOWNLOADS); |
| MockProvider* provider1 = RegisterProvider(articles_category); |
| MockProvider* provider2 = RegisterProvider(offline_pages_category); |
| |
| provider1->FireSuggestionsChanged(articles_category, |
| CreateSuggestions(articles_category, {1})); |
| ContentSuggestion::ID suggestion_id(articles_category, "1"); |
| |
| EXPECT_CALL(*provider1, FetchSuggestionImage(suggestion_id, _)); |
| EXPECT_CALL(*provider2, FetchSuggestionImage(_, _)).Times(0); |
| service()->FetchSuggestionImage( |
| suggestion_id, base::Bind(&ContentSuggestionsServiceTest::OnImageFetched, |
| base::Unretained(this))); |
| } |
| |
| TEST_F(ContentSuggestionsServiceTest, |
| ShouldCallbackEmptyImageForUnavailableProvider) { |
| // Setup the current thread's MessageLoop. |
| base::MessageLoop message_loop; |
| |
| base::RunLoop run_loop; |
| // Assuming there will never be a category with the id below. |
| ContentSuggestion::ID suggestion_id(category_factory()->FromIDValue(21563), |
| "TestID"); |
| EXPECT_CALL(*this, OnImageFetched(Property(&gfx::Image::IsEmpty, Eq(true)))) |
| .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit)); |
| service()->FetchSuggestionImage( |
| suggestion_id, base::Bind(&ContentSuggestionsServiceTest::OnImageFetched, |
| base::Unretained(this))); |
| run_loop.Run(); |
| } |
| |
| TEST_F(ContentSuggestionsServiceTest, ShouldRedirectDismissSuggestion) { |
| Category articles_category = FromKnownCategory(KnownCategories::ARTICLES); |
| Category offline_pages_category = |
| FromKnownCategory(KnownCategories::DOWNLOADS); |
| MockProvider* provider1 = RegisterProvider(articles_category); |
| MockProvider* provider2 = RegisterProvider(offline_pages_category); |
| |
| provider2->FireSuggestionsChanged( |
| offline_pages_category, CreateSuggestions(offline_pages_category, {11})); |
| ContentSuggestion::ID suggestion_id(offline_pages_category, "11"); |
| |
| EXPECT_CALL(*provider1, DismissSuggestion(_)).Times(0); |
| EXPECT_CALL(*provider2, DismissSuggestion(suggestion_id)); |
| service()->DismissSuggestion(suggestion_id); |
| } |
| |
| TEST_F(ContentSuggestionsServiceTest, ShouldRedirectSuggestionInvalidated) { |
| Category articles_category = FromKnownCategory(KnownCategories::ARTICLES); |
| |
| MockProvider* provider = RegisterProvider(articles_category); |
| MockServiceObserver observer; |
| service()->AddObserver(&observer); |
| |
| provider->FireSuggestionsChanged( |
| articles_category, CreateSuggestions(articles_category, {11, 12, 13})); |
| ExpectThatSuggestionsAre(articles_category, {11, 12, 13}); |
| |
| ContentSuggestion::ID suggestion_id(articles_category, "12"); |
| EXPECT_CALL(observer, OnSuggestionInvalidated(suggestion_id)); |
| provider->FireSuggestionInvalidated(suggestion_id); |
| ExpectThatSuggestionsAre(articles_category, {11, 13}); |
| Mock::VerifyAndClearExpectations(&observer); |
| |
| // Unknown IDs must be forwarded (though no change happens to the service's |
| // internal data structures) because previously opened UIs, which can still |
| // show the invalidated suggestion, must be notified. |
| ContentSuggestion::ID unknown_id(articles_category, "1234"); |
| EXPECT_CALL(observer, OnSuggestionInvalidated(unknown_id)); |
| provider->FireSuggestionInvalidated(unknown_id); |
| ExpectThatSuggestionsAre(articles_category, {11, 13}); |
| Mock::VerifyAndClearExpectations(&observer); |
| |
| service()->RemoveObserver(&observer); |
| } |
| |
| TEST_F(ContentSuggestionsServiceTest, ShouldForwardSuggestions) { |
| Category articles_category = FromKnownCategory(KnownCategories::ARTICLES); |
| Category offline_pages_category = |
| FromKnownCategory(KnownCategories::DOWNLOADS); |
| |
| // Create and register providers |
| MockProvider* provider1 = RegisterProvider(articles_category); |
| provider1->FireCategoryStatusChangedWithCurrentStatus(articles_category); |
| MockProvider* provider2 = RegisterProvider(offline_pages_category); |
| provider2->FireCategoryStatusChangedWithCurrentStatus(offline_pages_category); |
| ASSERT_THAT(providers().count(articles_category), Eq(1ul)); |
| EXPECT_THAT(providers().at(articles_category), Eq(provider1)); |
| ASSERT_THAT(providers().count(offline_pages_category), Eq(1ul)); |
| EXPECT_THAT(providers().at(offline_pages_category), Eq(provider2)); |
| |
| // Create and register observer |
| MockServiceObserver observer; |
| service()->AddObserver(&observer); |
| |
| // Send suggestions 1 and 2 |
| EXPECT_CALL(observer, OnNewSuggestions(articles_category)); |
| provider1->FireSuggestionsChanged( |
| articles_category, CreateSuggestions(articles_category, {1, 2})); |
| ExpectThatSuggestionsAre(articles_category, {1, 2}); |
| Mock::VerifyAndClearExpectations(&observer); |
| |
| // Send them again, make sure they're not reported twice |
| EXPECT_CALL(observer, OnNewSuggestions(articles_category)); |
| provider1->FireSuggestionsChanged( |
| articles_category, CreateSuggestions(articles_category, {1, 2})); |
| ExpectThatSuggestionsAre(articles_category, {1, 2}); |
| ExpectThatSuggestionsAre(offline_pages_category, std::vector<int>()); |
| Mock::VerifyAndClearExpectations(&observer); |
| |
| // Send suggestions 13 and 14 |
| EXPECT_CALL(observer, OnNewSuggestions(offline_pages_category)); |
| provider2->FireSuggestionsChanged( |
| offline_pages_category, CreateSuggestions(articles_category, {13, 14})); |
| ExpectThatSuggestionsAre(articles_category, {1, 2}); |
| ExpectThatSuggestionsAre(offline_pages_category, {13, 14}); |
| Mock::VerifyAndClearExpectations(&observer); |
| |
| // Send suggestion 1 only |
| EXPECT_CALL(observer, OnNewSuggestions(articles_category)); |
| provider1->FireSuggestionsChanged(articles_category, |
| CreateSuggestions(articles_category, {1})); |
| ExpectThatSuggestionsAre(articles_category, {1}); |
| ExpectThatSuggestionsAre(offline_pages_category, {13, 14}); |
| Mock::VerifyAndClearExpectations(&observer); |
| |
| // provider2 reports BOOKMARKS as unavailable |
| EXPECT_CALL(observer, OnCategoryStatusChanged( |
| offline_pages_category, |
| CategoryStatus::CATEGORY_EXPLICITLY_DISABLED)); |
| provider2->FireCategoryStatusChanged( |
| offline_pages_category, CategoryStatus::CATEGORY_EXPLICITLY_DISABLED); |
| EXPECT_THAT(service()->GetCategoryStatus(articles_category), |
| Eq(CategoryStatus::AVAILABLE)); |
| EXPECT_THAT(service()->GetCategoryStatus(offline_pages_category), |
| Eq(CategoryStatus::CATEGORY_EXPLICITLY_DISABLED)); |
| ExpectThatSuggestionsAre(articles_category, {1}); |
| ExpectThatSuggestionsAre(offline_pages_category, std::vector<int>()); |
| Mock::VerifyAndClearExpectations(&observer); |
| |
| // Shutdown the service |
| EXPECT_CALL(observer, ContentSuggestionsServiceShutdown()); |
| service()->Shutdown(); |
| service()->RemoveObserver(&observer); |
| // The service will receive two Shutdown() calls. |
| } |
| |
| TEST_F(ContentSuggestionsServiceTest, |
| ShouldNotReturnCategoryInfoForNonexistentCategory) { |
| Category category = FromKnownCategory(KnownCategories::DOWNLOADS); |
| base::Optional<CategoryInfo> result = service()->GetCategoryInfo(category); |
| EXPECT_FALSE(result.has_value()); |
| } |
| |
| TEST_F(ContentSuggestionsServiceTest, ShouldReturnCategoryInfo) { |
| Category category = FromKnownCategory(KnownCategories::DOWNLOADS); |
| MockProvider* provider = RegisterProvider(category); |
| provider->FireCategoryStatusChangedWithCurrentStatus(category); |
| base::Optional<CategoryInfo> result = service()->GetCategoryInfo(category); |
| ASSERT_TRUE(result.has_value()); |
| CategoryInfo expected = provider->GetCategoryInfo(category); |
| const CategoryInfo& actual = result.value(); |
| EXPECT_THAT(expected.title(), Eq(actual.title())); |
| EXPECT_THAT(expected.card_layout(), Eq(actual.card_layout())); |
| EXPECT_THAT(expected.has_more_button(), Eq(actual.has_more_button())); |
| } |
| |
| TEST_F(ContentSuggestionsServiceTest, |
| ShouldRegisterNewCategoryOnNewSuggestions) { |
| Category category = FromKnownCategory(KnownCategories::DOWNLOADS); |
| MockProvider* provider = RegisterProvider(category); |
| provider->FireCategoryStatusChangedWithCurrentStatus(category); |
| MockServiceObserver observer; |
| service()->AddObserver(&observer); |
| |
| // Provider starts providing |new_category| without calling |
| // |OnCategoryStatusChanged|. This is supported for now until further |
| // reconsideration. |
| Category new_category = FromKnownCategory(KnownCategories::ARTICLES); |
| provider->SetProvidedCategories( |
| std::vector<Category>({category, new_category})); |
| |
| EXPECT_CALL(observer, OnNewSuggestions(new_category)); |
| EXPECT_CALL(observer, |
| OnCategoryStatusChanged(new_category, CategoryStatus::AVAILABLE)); |
| provider->FireSuggestionsChanged(new_category, |
| CreateSuggestions(new_category, {1, 2})); |
| |
| ExpectThatSuggestionsAre(new_category, {1, 2}); |
| ASSERT_THAT(providers().count(category), Eq(1ul)); |
| EXPECT_THAT(providers().at(category), Eq(provider)); |
| EXPECT_THAT(service()->GetCategoryStatus(category), |
| Eq(CategoryStatus::AVAILABLE)); |
| ASSERT_THAT(providers().count(new_category), Eq(1ul)); |
| EXPECT_THAT(providers().at(new_category), Eq(provider)); |
| EXPECT_THAT(service()->GetCategoryStatus(new_category), |
| Eq(CategoryStatus::AVAILABLE)); |
| |
| service()->RemoveObserver(&observer); |
| } |
| |
| TEST_F(ContentSuggestionsServiceTest, |
| ShouldRegisterNewCategoryOnCategoryStatusChanged) { |
| Category category = FromKnownCategory(KnownCategories::DOWNLOADS); |
| MockProvider* provider = RegisterProvider(category); |
| provider->FireCategoryStatusChangedWithCurrentStatus(category); |
| MockServiceObserver observer; |
| service()->AddObserver(&observer); |
| |
| // Provider starts providing |new_category| and calls |
| // |OnCategoryStatusChanged|, but the category is not yet available. |
| Category new_category = FromKnownCategory(KnownCategories::ARTICLES); |
| provider->SetProvidedCategories( |
| std::vector<Category>({category, new_category})); |
| EXPECT_CALL(observer, OnCategoryStatusChanged(new_category, |
| CategoryStatus::INITIALIZING)); |
| provider->FireCategoryStatusChanged(new_category, |
| CategoryStatus::INITIALIZING); |
| |
| ASSERT_THAT(providers().count(new_category), Eq(1ul)); |
| EXPECT_THAT(providers().at(new_category), Eq(provider)); |
| ExpectThatSuggestionsAre(new_category, std::vector<int>()); |
| EXPECT_THAT(service()->GetCategoryStatus(new_category), |
| Eq(CategoryStatus::INITIALIZING)); |
| EXPECT_THAT(service()->GetCategories(), |
| Eq(std::vector<Category>({category, new_category}))); |
| |
| service()->RemoveObserver(&observer); |
| } |
| |
| TEST_F(ContentSuggestionsServiceTest, ShouldRemoveCategoryWhenNotProvided) { |
| Category category = FromKnownCategory(KnownCategories::DOWNLOADS); |
| MockProvider* provider = RegisterProvider(category); |
| MockServiceObserver observer; |
| service()->AddObserver(&observer); |
| |
| provider->FireSuggestionsChanged(category, |
| CreateSuggestions(category, {1, 2})); |
| ExpectThatSuggestionsAre(category, {1, 2}); |
| |
| EXPECT_CALL(observer, |
| OnCategoryStatusChanged(category, CategoryStatus::NOT_PROVIDED)); |
| provider->FireCategoryStatusChanged(category, CategoryStatus::NOT_PROVIDED); |
| |
| EXPECT_THAT(service()->GetCategoryStatus(category), |
| Eq(CategoryStatus::NOT_PROVIDED)); |
| EXPECT_TRUE(service()->GetCategories().empty()); |
| ExpectThatSuggestionsAre(category, std::vector<int>()); |
| |
| service()->RemoveObserver(&observer); |
| } |
| |
| // This tests the temporary special-casing of the bookmarks section: If it is |
| // empty, it should appear at the end; see crbug.com/640568. |
| TEST_F(ContentSuggestionsServiceTest, ShouldPutBookmarksAtEndIfEmpty) { |
| // Register a bookmarks provider and an arbitrary remote provider. |
| Category bookmarks = FromKnownCategory(KnownCategories::BOOKMARKS); |
| MockProvider* bookmarks_provider = RegisterProvider(bookmarks); |
| bookmarks_provider->FireCategoryStatusChangedWithCurrentStatus(bookmarks); |
| Category remote = FromRemoteCategory(123); |
| MockProvider* remote_provider = RegisterProvider(remote); |
| remote_provider->FireCategoryStatusChangedWithCurrentStatus(remote); |
| |
| // By default, the bookmarks category is empty, so it should be at the end. |
| EXPECT_THAT(service()->GetCategories(), ElementsAre(remote, bookmarks)); |
| |
| // Add two bookmark suggestions; now bookmarks should be in the front. |
| bookmarks_provider->FireSuggestionsChanged( |
| bookmarks, CreateSuggestions(bookmarks, {1, 2})); |
| EXPECT_THAT(service()->GetCategories(), ElementsAre(bookmarks, remote)); |
| // Dismiss the first suggestion; bookmarks should stay in the front. |
| service()->DismissSuggestion(CreateSuggestion(bookmarks, 1).id()); |
| EXPECT_THAT(service()->GetCategories(), ElementsAre(bookmarks, remote)); |
| // Dismiss the second suggestion; now bookmarks should go back to the end. |
| service()->DismissSuggestion(CreateSuggestion(bookmarks, 2).id()); |
| EXPECT_THAT(service()->GetCategories(), ElementsAre(remote, bookmarks)); |
| |
| // Same thing, but invalidate instead of dismissing. |
| bookmarks_provider->FireSuggestionsChanged( |
| bookmarks, CreateSuggestions(bookmarks, {1, 2})); |
| EXPECT_THAT(service()->GetCategories(), ElementsAre(bookmarks, remote)); |
| bookmarks_provider->FireSuggestionInvalidated( |
| ContentSuggestion::ID(bookmarks, "1")); |
| EXPECT_THAT(service()->GetCategories(), ElementsAre(bookmarks, remote)); |
| bookmarks_provider->FireSuggestionInvalidated( |
| ContentSuggestion::ID(bookmarks, "2")); |
| EXPECT_THAT(service()->GetCategories(), ElementsAre(remote, bookmarks)); |
| |
| // Same thing, but now the bookmarks category updates "naturally". |
| bookmarks_provider->FireSuggestionsChanged( |
| bookmarks, CreateSuggestions(bookmarks, {1, 2})); |
| EXPECT_THAT(service()->GetCategories(), ElementsAre(bookmarks, remote)); |
| bookmarks_provider->FireSuggestionsChanged(bookmarks, |
| CreateSuggestions(bookmarks, {1})); |
| EXPECT_THAT(service()->GetCategories(), ElementsAre(bookmarks, remote)); |
| bookmarks_provider->FireSuggestionsChanged( |
| bookmarks, CreateSuggestions(bookmarks, std::vector<int>())); |
| EXPECT_THAT(service()->GetCategories(), ElementsAre(remote, bookmarks)); |
| } |
| |
| TEST_F(ContentSuggestionsServiceTest, ShouldForwardClearHistory) { |
| Category category = FromKnownCategory(KnownCategories::DOWNLOADS); |
| MockProvider* provider = RegisterProvider(category); |
| base::Time begin = base::Time::FromTimeT(123), |
| end = base::Time::FromTimeT(456); |
| EXPECT_CALL(*provider, ClearHistory(begin, end, _)); |
| base::Callback<bool(const GURL& url)> filter; |
| service()->ClearHistory(begin, end, filter); |
| } |
| |
| } // namespace ntp_snippets |