| // Copyright 2015 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_tiles/most_visited_sites.h" |
| |
| #include <stddef.h> |
| |
| #include <memory> |
| #include <ostream> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/callback_list.h" |
| #include "base/command_line.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/utf_string_conversions.h" |
| #include "base/task/cancelable_task_tracker.h" |
| #include "base/test/sequenced_worker_pool_owner.h" |
| #include "components/history/core/browser/top_sites.h" |
| #include "components/ntp_tiles/icon_cacher.h" |
| #include "components/ntp_tiles/json_unsafe_parser.h" |
| #include "components/ntp_tiles/popular_sites_impl.h" |
| #include "components/ntp_tiles/pref_names.h" |
| #include "components/ntp_tiles/switches.h" |
| #include "components/sync_preferences/testing_pref_service_syncable.h" |
| #include "net/url_request/test_url_fetcher_factory.h" |
| #include "net/url_request/url_request_test_util.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace ntp_tiles { |
| |
| // Defined for googletest. Must be defined in the same namespace. |
| void PrintTo(const NTPTile& tile, std::ostream* os) { |
| *os << "{\"" << tile.title << "\", \"" << tile.url << "\", " |
| << static_cast<int>(tile.source) << "}"; |
| } |
| |
| namespace { |
| |
| using history::MostVisitedURL; |
| using history::MostVisitedURLList; |
| using history::TopSites; |
| using suggestions::ChromeSuggestion; |
| using suggestions::SuggestionsProfile; |
| using suggestions::SuggestionsService; |
| using testing::AtLeast; |
| using testing::ByMove; |
| using testing::ElementsAre; |
| using testing::InSequence; |
| using testing::Invoke; |
| using testing::Mock; |
| using testing::Return; |
| using testing::ReturnRef; |
| using testing::SizeIs; |
| using testing::StrictMock; |
| using testing::_; |
| |
| MATCHER_P3(MatchesTile, |
| title, |
| url, |
| source, |
| std::string("has title \"") + title + std::string("\" and url \"") + |
| url + |
| std::string("\" and source ") + |
| testing::PrintToString(static_cast<int>(source))) { |
| return arg.title == base::ASCIIToUTF16(title) && arg.url == GURL(url) && |
| arg.source == source; |
| } |
| |
| // testing::InvokeArgument<N> does not work with base::Callback, fortunately |
| // gmock makes it simple to create action templates that do for the various |
| // possible numbers of arguments. |
| ACTION_TEMPLATE(InvokeCallbackArgument, |
| HAS_1_TEMPLATE_PARAMS(int, k), |
| AND_1_VALUE_PARAMS(p0)) { |
| ::std::tr1::get<k>(args).Run(p0); |
| } |
| |
| NTPTile MakeTile(const std::string& title, |
| const std::string& url, |
| NTPTileSource source) { |
| NTPTile tile; |
| tile.title = base::ASCIIToUTF16(title); |
| tile.url = GURL(url); |
| tile.source = source; |
| return tile; |
| } |
| |
| ChromeSuggestion MakeSuggestion(const std::string& title, |
| const std::string& url) { |
| ChromeSuggestion suggestion; |
| suggestion.set_title(title); |
| suggestion.set_url(url); |
| return suggestion; |
| } |
| |
| SuggestionsProfile MakeProfile( |
| const std::vector<ChromeSuggestion>& suggestions) { |
| SuggestionsProfile profile; |
| for (const ChromeSuggestion& suggestion : suggestions) { |
| *profile.add_suggestions() = suggestion; |
| } |
| return profile; |
| } |
| |
| MostVisitedURL MakeMostVisitedURL(const std::string& title, |
| const std::string& url) { |
| MostVisitedURL result; |
| result.title = base::ASCIIToUTF16(title); |
| result.url = GURL(url); |
| return result; |
| } |
| |
| class MockTopSites : public TopSites { |
| public: |
| MOCK_METHOD0(ShutdownOnUIThread, void()); |
| MOCK_METHOD3(SetPageThumbnail, |
| bool(const GURL& url, |
| const gfx::Image& thumbnail, |
| const ThumbnailScore& score)); |
| MOCK_METHOD3(SetPageThumbnailToJPEGBytes, |
| bool(const GURL& url, |
| const base::RefCountedMemory* memory, |
| const ThumbnailScore& score)); |
| MOCK_METHOD2(GetMostVisitedURLs, |
| void(const GetMostVisitedURLsCallback& callback, |
| bool include_forced_urls)); |
| MOCK_METHOD3(GetPageThumbnail, |
| bool(const GURL& url, |
| bool prefix_match, |
| scoped_refptr<base::RefCountedMemory>* bytes)); |
| MOCK_METHOD2(GetPageThumbnailScore, |
| bool(const GURL& url, ThumbnailScore* score)); |
| MOCK_METHOD2(GetTemporaryPageThumbnailScore, |
| bool(const GURL& url, ThumbnailScore* score)); |
| MOCK_METHOD0(SyncWithHistory, void()); |
| MOCK_CONST_METHOD0(HasBlacklistedItems, bool()); |
| MOCK_METHOD1(AddBlacklistedURL, void(const GURL& url)); |
| MOCK_METHOD1(RemoveBlacklistedURL, void(const GURL& url)); |
| MOCK_METHOD1(IsBlacklisted, bool(const GURL& url)); |
| MOCK_METHOD0(ClearBlacklistedURLs, void()); |
| MOCK_METHOD0(StartQueryForMostVisited, base::CancelableTaskTracker::TaskId()); |
| MOCK_METHOD1(IsKnownURL, bool(const GURL& url)); |
| MOCK_CONST_METHOD1(GetCanonicalURLString, |
| const std::string&(const GURL& url)); |
| MOCK_METHOD0(IsNonForcedFull, bool()); |
| MOCK_METHOD0(IsForcedFull, bool()); |
| MOCK_CONST_METHOD0(loaded, bool()); |
| MOCK_METHOD0(GetPrepopulatedPages, history::PrepopulatedPageList()); |
| MOCK_METHOD2(AddForcedURL, bool(const GURL& url, const base::Time& time)); |
| MOCK_METHOD1(OnNavigationCommitted, void(const GURL& url)); |
| |
| protected: |
| ~MockTopSites() override = default; |
| }; |
| |
| class MockSuggestionsService : public SuggestionsService { |
| public: |
| MOCK_METHOD0(FetchSuggestionsData, bool()); |
| MOCK_CONST_METHOD0(GetSuggestionsDataFromCache, |
| base::Optional<SuggestionsProfile>()); |
| MOCK_METHOD1(AddCallback, |
| std::unique_ptr<ResponseCallbackList::Subscription>( |
| const ResponseCallback& callback)); |
| MOCK_METHOD2(GetPageThumbnail, |
| void(const GURL& url, const BitmapCallback& callback)); |
| MOCK_METHOD3(GetPageThumbnailWithURL, |
| void(const GURL& url, |
| const GURL& thumbnail_url, |
| const BitmapCallback& callback)); |
| MOCK_METHOD1(BlacklistURL, bool(const GURL& candidate_url)); |
| MOCK_METHOD1(UndoBlacklistURL, bool(const GURL& url)); |
| MOCK_METHOD0(ClearBlacklist, void()); |
| }; |
| |
| class MockMostVisitedSitesObserver : public MostVisitedSites::Observer { |
| public: |
| MOCK_METHOD1(OnMostVisitedURLsAvailable, void(const NTPTilesVector& tiles)); |
| MOCK_METHOD1(OnIconMadeAvailable, void(const GURL& site_url)); |
| }; |
| |
| class MockIconCacher : public IconCacher { |
| public: |
| MOCK_METHOD2(StartFetch, |
| void(PopularSites::Site site, |
| const base::Callback<void(bool)>& done)); |
| }; |
| |
| class PopularSitesFactoryForTest { |
| public: |
| PopularSitesFactoryForTest( |
| bool enabled, |
| sync_preferences::TestingPrefServiceSyncable* pref_service) |
| : prefs_(pref_service), |
| url_fetcher_factory_(/*default_factory=*/nullptr), |
| url_request_context_(new net::TestURLRequestContextGetter( |
| base::ThreadTaskRunnerHandle::Get())), |
| worker_pool_owner_(/*max_threads=*/2, "PopularSitesFactoryForTest.") { |
| if (enabled) { |
| PopularSitesImpl::RegisterProfilePrefs(pref_service->registry()); |
| |
| prefs_->SetString(prefs::kPopularSitesOverrideCountry, "IN"); |
| prefs_->SetString(prefs::kPopularSitesOverrideVersion, "7"); |
| |
| url_fetcher_factory_.SetFakeResponse( |
| GURL("https://www.gstatic.com/chrome/ntp/suggested_sites_IN_7.json"), |
| R"([{ |
| "title": "PopularSite1", |
| "url": "http://popularsite1/", |
| "favicon_url": "http://popularsite1/favicon.ico" |
| }, |
| { |
| "title": "PopularSite2", |
| "url": "http://popularsite2/", |
| "favicon_url": "http://popularsite2/favicon.ico" |
| }, |
| ])", |
| net::HTTP_OK, net::URLRequestStatus::SUCCESS); |
| } |
| } |
| |
| std::unique_ptr<PopularSites> New() { |
| return base::MakeUnique<PopularSitesImpl>( |
| worker_pool_owner_.pool().get(), prefs_, |
| /*template_url_service=*/nullptr, |
| /*variations_service=*/nullptr, url_request_context_.get(), |
| /*directory=*/base::FilePath(), base::Bind(JsonUnsafeParser::Parse)); |
| } |
| |
| private: |
| PrefService* prefs_; |
| net::FakeURLFetcherFactory url_fetcher_factory_; |
| scoped_refptr<net::TestURLRequestContextGetter> url_request_context_; |
| base::SequencedWorkerPoolOwner worker_pool_owner_; |
| }; |
| |
| // CallbackList-like container without Subscription, mimicking the |
| // implementation in TopSites (which doesn't use base::CallbackList). |
| class TopSitesCallbackList { |
| public: |
| // Second argument declared to match the signature of GetMostVisitedURLs(). |
| void Add(const TopSites::GetMostVisitedURLsCallback& callback, |
| testing::Unused) { |
| callbacks_.push_back(callback); |
| } |
| |
| void ClearAndNotify(const MostVisitedURLList& list) { |
| std::vector<TopSites::GetMostVisitedURLsCallback> callbacks; |
| callbacks.swap(callbacks_); |
| for (const auto& callback : callbacks) { |
| callback.Run(list); |
| } |
| } |
| |
| bool empty() const { return callbacks_.empty(); } |
| |
| private: |
| std::vector<TopSites::GetMostVisitedURLsCallback> callbacks_; |
| }; |
| |
| // Param specifies whether Popular Sites is enabled via variations. |
| class MostVisitedSitesTest : public ::testing::TestWithParam<bool> { |
| protected: |
| MostVisitedSitesTest() |
| : popular_sites_factory_(/*enabled=*/GetParam(), &pref_service_), |
| mock_top_sites_(new StrictMock<MockTopSites>()) { |
| MostVisitedSites::RegisterProfilePrefs(pref_service_.registry()); |
| |
| if (IsPopularSitesEnabledViaVariations()) { |
| base::CommandLine::ForCurrentProcess()->AppendSwitch( |
| switches::kEnableNTPPopularSites); |
| } else { |
| base::CommandLine::ForCurrentProcess()->AppendSwitch( |
| switches::kDisableNTPPopularSites); |
| } |
| |
| // We use StrictMock to make sure the object is not used unless Popular |
| // Sites is enabled. |
| auto icon_cacher = base::MakeUnique<StrictMock<MockIconCacher>>(); |
| |
| if (IsPopularSitesEnabledViaVariations()) { |
| // Populate Popular Sites' internal cache by mimicking a past usage of |
| // PopularSitesImpl. |
| auto tmp_popular_sites = popular_sites_factory_.New(); |
| base::RunLoop loop; |
| bool save_success = false; |
| tmp_popular_sites->MaybeStartFetch( |
| /*force_download=*/true, |
| base::Bind( |
| [](bool* save_success, base::RunLoop* loop, bool success) { |
| *save_success = success; |
| loop->Quit(); |
| }, |
| &save_success, &loop)); |
| loop.Run(); |
| EXPECT_TRUE(save_success); |
| |
| // With PopularSites enabled, blacklist is exercised. |
| EXPECT_CALL(*mock_top_sites_, IsBlacklisted(_)) |
| .WillRepeatedly(Return(false)); |
| // Mock icon cacher never replies, and we also don't verify whether the |
| // code uses it correctly. |
| EXPECT_CALL(*icon_cacher, StartFetch(_, _)).Times(AtLeast(0)); |
| } |
| |
| most_visited_sites_ = base::MakeUnique<MostVisitedSites>( |
| &pref_service_, mock_top_sites_, &mock_suggestions_service_, |
| popular_sites_factory_.New(), std::move(icon_cacher), |
| /*supervisor=*/nullptr); |
| } |
| |
| bool IsPopularSitesEnabledViaVariations() const { return GetParam(); } |
| |
| bool VerifyAndClearExpectations() { |
| base::RunLoop().RunUntilIdle(); |
| const bool success = |
| Mock::VerifyAndClearExpectations(mock_top_sites_.get()) && |
| Mock::VerifyAndClearExpectations(&mock_suggestions_service_) && |
| Mock::VerifyAndClearExpectations(&mock_observer_); |
| // For convenience, restore the expectations for IsBlacklisted(). |
| if (IsPopularSitesEnabledViaVariations()) { |
| EXPECT_CALL(*mock_top_sites_, IsBlacklisted(_)) |
| .WillRepeatedly(Return(false)); |
| } |
| return success; |
| } |
| |
| base::CallbackList<SuggestionsService::ResponseCallback::RunType> |
| suggestions_service_callbacks_; |
| TopSitesCallbackList top_sites_callbacks_; |
| |
| base::MessageLoop message_loop_; |
| sync_preferences::TestingPrefServiceSyncable pref_service_; |
| PopularSitesFactoryForTest popular_sites_factory_; |
| scoped_refptr<StrictMock<MockTopSites>> mock_top_sites_; |
| StrictMock<MockSuggestionsService> mock_suggestions_service_; |
| StrictMock<MockMostVisitedSitesObserver> mock_observer_; |
| std::unique_ptr<MostVisitedSites> most_visited_sites_; |
| }; |
| |
| TEST_P(MostVisitedSitesTest, ShouldStartNoCallInConstructor) { |
| // No call to mocks expected by the mere fact of instantiating |
| // MostVisitedSites. |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_P(MostVisitedSitesTest, ShouldHandleTopSitesCacheHit) { |
| // If cached, TopSites returns the tiles synchronously, running the callback |
| // even before the function returns. |
| EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_, false)) |
| .WillRepeatedly(InvokeCallbackArgument<0>( |
| MostVisitedURLList{MakeMostVisitedURL("Site 1", "http://site1/")})); |
| |
| InSequence seq; |
| EXPECT_CALL(*mock_top_sites_, SyncWithHistory()); |
| EXPECT_CALL(mock_suggestions_service_, AddCallback(_)) |
| .WillOnce(Invoke(&suggestions_service_callbacks_, |
| &SuggestionsService::ResponseCallbackList::Add)); |
| EXPECT_CALL(mock_suggestions_service_, GetSuggestionsDataFromCache()) |
| .WillOnce(Return(SuggestionsProfile())); // Empty cache. |
| if (IsPopularSitesEnabledViaVariations()) { |
| EXPECT_CALL( |
| mock_observer_, |
| OnMostVisitedURLsAvailable(ElementsAre( |
| MatchesTile("Site 1", "http://site1/", NTPTileSource::TOP_SITES), |
| MatchesTile("PopularSite1", "http://popularsite1/", |
| NTPTileSource::POPULAR), |
| MatchesTile("PopularSite2", "http://popularsite2/", |
| NTPTileSource::POPULAR)))); |
| } else { |
| EXPECT_CALL(mock_observer_, |
| OnMostVisitedURLsAvailable(ElementsAre(MatchesTile( |
| "Site 1", "http://site1/", NTPTileSource::TOP_SITES)))); |
| } |
| EXPECT_CALL(mock_suggestions_service_, FetchSuggestionsData()) |
| .WillOnce(Return(true)); |
| |
| most_visited_sites_->SetMostVisitedURLsObserver(&mock_observer_, |
| /*num_sites=*/3); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(suggestions_service_callbacks_.empty()); |
| CHECK(top_sites_callbacks_.empty()); |
| } |
| |
| INSTANTIATE_TEST_CASE_P(MostVisitedSitesTest, |
| MostVisitedSitesTest, |
| ::testing::Bool()); |
| |
| class MostVisitedSitesWithCacheHitTest : public MostVisitedSitesTest { |
| public: |
| // Constructor sets the common expectations for the case where suggestions |
| // service has cached results when the observer is registered. |
| MostVisitedSitesWithCacheHitTest() { |
| InSequence seq; |
| EXPECT_CALL(*mock_top_sites_, SyncWithHistory()); |
| EXPECT_CALL(mock_suggestions_service_, AddCallback(_)) |
| .WillOnce(Invoke(&suggestions_service_callbacks_, |
| &SuggestionsService::ResponseCallbackList::Add)); |
| EXPECT_CALL(mock_suggestions_service_, GetSuggestionsDataFromCache()) |
| .WillOnce(Return(MakeProfile({ |
| MakeSuggestion("Site 1", "http://site1/"), |
| MakeSuggestion("Site 2", "http://site2/"), |
| MakeSuggestion("Site 3", "http://site3/"), |
| }))); |
| if (IsPopularSitesEnabledViaVariations()) { |
| EXPECT_CALL(mock_observer_, |
| OnMostVisitedURLsAvailable(ElementsAre( |
| MatchesTile("Site 1", "http://site1/", |
| NTPTileSource::SUGGESTIONS_SERVICE), |
| MatchesTile("Site 2", "http://site2/", |
| NTPTileSource::SUGGESTIONS_SERVICE), |
| MatchesTile("Site 3", "http://site3/", |
| NTPTileSource::SUGGESTIONS_SERVICE), |
| MatchesTile("PopularSite1", "http://popularsite1/", |
| NTPTileSource::POPULAR)))); |
| } else { |
| EXPECT_CALL(mock_observer_, |
| OnMostVisitedURLsAvailable(ElementsAre( |
| MatchesTile("Site 1", "http://site1/", |
| NTPTileSource::SUGGESTIONS_SERVICE), |
| MatchesTile("Site 2", "http://site2/", |
| NTPTileSource::SUGGESTIONS_SERVICE), |
| MatchesTile("Site 3", "http://site3/", |
| NTPTileSource::SUGGESTIONS_SERVICE)))); |
| } |
| EXPECT_CALL(mock_suggestions_service_, FetchSuggestionsData()) |
| .WillOnce(Return(true)); |
| |
| most_visited_sites_->SetMostVisitedURLsObserver(&mock_observer_, |
| /*num_sites=*/4); |
| VerifyAndClearExpectations(); |
| |
| EXPECT_FALSE(suggestions_service_callbacks_.empty()); |
| EXPECT_TRUE(top_sites_callbacks_.empty()); |
| } |
| }; |
| |
| TEST_P(MostVisitedSitesWithCacheHitTest, ShouldFavorSuggestionsServiceCache) { |
| // Constructor sets basic expectations for a suggestions service cache hit. |
| } |
| |
| TEST_P(MostVisitedSitesWithCacheHitTest, |
| ShouldPropagateUpdateBySuggestionsService) { |
| EXPECT_CALL(mock_observer_, |
| OnMostVisitedURLsAvailable(ElementsAre( |
| MatchesTile("Site 4", "http://site4/", |
| NTPTileSource::SUGGESTIONS_SERVICE), |
| MatchesTile("Site 5", "http://site5/", |
| NTPTileSource::SUGGESTIONS_SERVICE), |
| MatchesTile("Site 6", "http://site6/", |
| NTPTileSource::SUGGESTIONS_SERVICE), |
| MatchesTile("Site 7", "http://site7/", |
| NTPTileSource::SUGGESTIONS_SERVICE)))); |
| suggestions_service_callbacks_.Notify( |
| MakeProfile({MakeSuggestion("Site 4", "http://site4/"), |
| MakeSuggestion("Site 5", "http://site5/"), |
| MakeSuggestion("Site 6", "http://site6/"), |
| MakeSuggestion("Site 7", "http://site7/")})); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_P(MostVisitedSitesWithCacheHitTest, ShouldTruncateList) { |
| EXPECT_CALL(mock_observer_, OnMostVisitedURLsAvailable(SizeIs(4))); |
| suggestions_service_callbacks_.Notify( |
| MakeProfile({MakeSuggestion("Site 4", "http://site4/"), |
| MakeSuggestion("Site 5", "http://site5/"), |
| MakeSuggestion("Site 6", "http://site6/"), |
| MakeSuggestion("Site 7", "http://site7/"), |
| MakeSuggestion("Site 8", "http://site8/")})); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_P(MostVisitedSitesWithCacheHitTest, |
| ShouldCompleteWithPopularSitesIffEnabled) { |
| if (IsPopularSitesEnabledViaVariations()) { |
| EXPECT_CALL(mock_observer_, |
| OnMostVisitedURLsAvailable(ElementsAre( |
| MatchesTile("Site 4", "http://site4/", |
| NTPTileSource::SUGGESTIONS_SERVICE), |
| MatchesTile("PopularSite1", "http://popularsite1/", |
| NTPTileSource::POPULAR), |
| MatchesTile("PopularSite2", "http://popularsite2/", |
| NTPTileSource::POPULAR)))); |
| } else { |
| EXPECT_CALL( |
| mock_observer_, |
| OnMostVisitedURLsAvailable(ElementsAre(MatchesTile( |
| "Site 4", "http://site4/", NTPTileSource::SUGGESTIONS_SERVICE)))); |
| } |
| suggestions_service_callbacks_.Notify( |
| MakeProfile({MakeSuggestion("Site 4", "http://site4/")})); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_P(MostVisitedSitesWithCacheHitTest, |
| ShouldSwitchToTopSitesIfEmptyUpdateBySuggestionsService) { |
| EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_, false)) |
| .WillOnce(Invoke(&top_sites_callbacks_, &TopSitesCallbackList::Add)); |
| suggestions_service_callbacks_.Notify(SuggestionsProfile()); |
| VerifyAndClearExpectations(); |
| |
| EXPECT_CALL( |
| mock_observer_, |
| OnMostVisitedURLsAvailable(ElementsAre( |
| MatchesTile("Site 4", "http://site4/", NTPTileSource::TOP_SITES), |
| MatchesTile("Site 5", "http://site5/", NTPTileSource::TOP_SITES), |
| MatchesTile("Site 6", "http://site6/", NTPTileSource::TOP_SITES), |
| MatchesTile("Site 7", "http://site7/", NTPTileSource::TOP_SITES)))); |
| top_sites_callbacks_.ClearAndNotify( |
| {MakeMostVisitedURL("Site 4", "http://site4/"), |
| MakeMostVisitedURL("Site 5", "http://site5/"), |
| MakeMostVisitedURL("Site 6", "http://site6/"), |
| MakeMostVisitedURL("Site 7", "http://site7/")}); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| INSTANTIATE_TEST_CASE_P(MostVisitedSitesWithCacheHitTest, |
| MostVisitedSitesWithCacheHitTest, |
| ::testing::Bool()); |
| |
| class MostVisitedSitesWithEmptyCacheTest : public MostVisitedSitesTest { |
| public: |
| // Constructor sets the common expectations for the case where suggestions |
| // service doesn't have cached results when the observer is registered. |
| MostVisitedSitesWithEmptyCacheTest() { |
| InSequence seq; |
| EXPECT_CALL(*mock_top_sites_, SyncWithHistory()); |
| EXPECT_CALL(mock_suggestions_service_, AddCallback(_)) |
| .WillOnce(Invoke(&suggestions_service_callbacks_, |
| &SuggestionsService::ResponseCallbackList::Add)); |
| EXPECT_CALL(mock_suggestions_service_, GetSuggestionsDataFromCache()) |
| .WillOnce(Return(SuggestionsProfile())); // Empty cache. |
| EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_, false)) |
| .WillOnce(Invoke(&top_sites_callbacks_, &TopSitesCallbackList::Add)); |
| EXPECT_CALL(mock_suggestions_service_, FetchSuggestionsData()) |
| .WillOnce(Return(true)); |
| |
| most_visited_sites_->SetMostVisitedURLsObserver(&mock_observer_, |
| /*num_sites=*/3); |
| VerifyAndClearExpectations(); |
| |
| EXPECT_FALSE(suggestions_service_callbacks_.empty()); |
| EXPECT_FALSE(top_sites_callbacks_.empty()); |
| } |
| }; |
| |
| TEST_P(MostVisitedSitesWithEmptyCacheTest, |
| ShouldQueryTopSitesAndSuggestionsService) { |
| // Constructor sets basic expectations for a suggestions service cache miss. |
| } |
| |
| TEST_P(MostVisitedSitesWithEmptyCacheTest, |
| ShouldCompleteWithPopularSitesIffEnabled) { |
| if (IsPopularSitesEnabledViaVariations()) { |
| EXPECT_CALL(mock_observer_, |
| OnMostVisitedURLsAvailable(ElementsAre( |
| MatchesTile("Site 4", "http://site4/", |
| NTPTileSource::SUGGESTIONS_SERVICE), |
| MatchesTile("PopularSite1", "http://popularsite1/", |
| NTPTileSource::POPULAR), |
| MatchesTile("PopularSite2", "http://popularsite2/", |
| NTPTileSource::POPULAR)))); |
| } else { |
| EXPECT_CALL( |
| mock_observer_, |
| OnMostVisitedURLsAvailable(ElementsAre(MatchesTile( |
| "Site 4", "http://site4/", NTPTileSource::SUGGESTIONS_SERVICE)))); |
| } |
| suggestions_service_callbacks_.Notify( |
| MakeProfile({MakeSuggestion("Site 4", "http://site4/")})); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_P(MostVisitedSitesWithEmptyCacheTest, |
| ShouldIgnoreTopSitesIfSuggestionsServiceFaster) { |
| // Reply from suggestions service triggers and update to our observer. |
| EXPECT_CALL(mock_observer_, |
| OnMostVisitedURLsAvailable(ElementsAre( |
| MatchesTile("Site 1", "http://site1/", |
| NTPTileSource::SUGGESTIONS_SERVICE), |
| MatchesTile("Site 2", "http://site2/", |
| NTPTileSource::SUGGESTIONS_SERVICE), |
| MatchesTile("Site 3", "http://site3/", |
| NTPTileSource::SUGGESTIONS_SERVICE)))); |
| suggestions_service_callbacks_.Notify( |
| MakeProfile({MakeSuggestion("Site 1", "http://site1/"), |
| MakeSuggestion("Site 2", "http://site2/"), |
| MakeSuggestion("Site 3", "http://site3/")})); |
| VerifyAndClearExpectations(); |
| |
| // Reply from top sites is ignored (i.e. not reported to observer). |
| top_sites_callbacks_.ClearAndNotify( |
| {MakeMostVisitedURL("Site 4", "http://site4/")}); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_P(MostVisitedSitesWithEmptyCacheTest, |
| ShouldExposeTopSitesIfSuggestionsServiceFasterButEmpty) { |
| // Empty reply from suggestions service causes no update to our observer. |
| suggestions_service_callbacks_.Notify(SuggestionsProfile()); |
| VerifyAndClearExpectations(); |
| |
| // Reply from top sites is propagated to observer. |
| EXPECT_CALL( |
| mock_observer_, |
| OnMostVisitedURLsAvailable(ElementsAre( |
| MatchesTile("Site 1", "http://site1/", NTPTileSource::TOP_SITES), |
| MatchesTile("Site 2", "http://site2/", NTPTileSource::TOP_SITES), |
| MatchesTile("Site 3", "http://site3/", NTPTileSource::TOP_SITES)))); |
| top_sites_callbacks_.ClearAndNotify( |
| {MakeMostVisitedURL("Site 1", "http://site1/"), |
| MakeMostVisitedURL("Site 2", "http://site2/"), |
| MakeMostVisitedURL("Site 3", "http://site3/")}); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_P(MostVisitedSitesWithEmptyCacheTest, |
| ShouldFavorSuggestionsServiceAlthoughSlower) { |
| // Reply from top sites is propagated to observer. |
| EXPECT_CALL( |
| mock_observer_, |
| OnMostVisitedURLsAvailable(ElementsAre( |
| MatchesTile("Site 1", "http://site1/", NTPTileSource::TOP_SITES), |
| MatchesTile("Site 2", "http://site2/", NTPTileSource::TOP_SITES), |
| MatchesTile("Site 3", "http://site3/", NTPTileSource::TOP_SITES)))); |
| top_sites_callbacks_.ClearAndNotify( |
| {MakeMostVisitedURL("Site 1", "http://site1/"), |
| MakeMostVisitedURL("Site 2", "http://site2/"), |
| MakeMostVisitedURL("Site 3", "http://site3/")}); |
| VerifyAndClearExpectations(); |
| |
| // Reply from suggestions service overrides top sites. |
| InSequence seq; |
| EXPECT_CALL(mock_observer_, |
| OnMostVisitedURLsAvailable(ElementsAre( |
| MatchesTile("Site 4", "http://site4/", |
| NTPTileSource::SUGGESTIONS_SERVICE), |
| MatchesTile("Site 5", "http://site5/", |
| NTPTileSource::SUGGESTIONS_SERVICE), |
| MatchesTile("Site 6", "http://site6/", |
| NTPTileSource::SUGGESTIONS_SERVICE)))); |
| suggestions_service_callbacks_.Notify( |
| MakeProfile({MakeSuggestion("Site 4", "http://site4/"), |
| MakeSuggestion("Site 5", "http://site5/"), |
| MakeSuggestion("Site 6", "http://site6/")})); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_P(MostVisitedSitesWithEmptyCacheTest, |
| ShouldIgnoreSuggestionsServiceIfSlowerAndEmpty) { |
| // Reply from top sites is propagated to observer. |
| EXPECT_CALL( |
| mock_observer_, |
| OnMostVisitedURLsAvailable(ElementsAre( |
| MatchesTile("Site 1", "http://site1/", NTPTileSource::TOP_SITES), |
| MatchesTile("Site 2", "http://site2/", NTPTileSource::TOP_SITES), |
| MatchesTile("Site 3", "http://site3/", NTPTileSource::TOP_SITES)))); |
| top_sites_callbacks_.ClearAndNotify( |
| {MakeMostVisitedURL("Site 1", "http://site1/"), |
| MakeMostVisitedURL("Site 2", "http://site2/"), |
| MakeMostVisitedURL("Site 3", "http://site3/")}); |
| VerifyAndClearExpectations(); |
| |
| // Reply from suggestions service is empty and thus ignored. However, the |
| // current implementation issues a redundant query to TopSites. |
| // TODO(mastiz): Avoid this redundant call. |
| EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_, false)) |
| .WillOnce(Invoke(&top_sites_callbacks_, &TopSitesCallbackList::Add)); |
| suggestions_service_callbacks_.Notify(SuggestionsProfile()); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_P(MostVisitedSitesWithEmptyCacheTest, ShouldPropagateUpdateByTopSites) { |
| // Reply from top sites is propagated to observer. |
| EXPECT_CALL( |
| mock_observer_, |
| OnMostVisitedURLsAvailable(ElementsAre( |
| MatchesTile("Site 1", "http://site1/", NTPTileSource::TOP_SITES), |
| MatchesTile("Site 2", "http://site2/", NTPTileSource::TOP_SITES), |
| MatchesTile("Site 3", "http://site3/", NTPTileSource::TOP_SITES)))); |
| top_sites_callbacks_.ClearAndNotify( |
| {MakeMostVisitedURL("Site 1", "http://site1/"), |
| MakeMostVisitedURL("Site 2", "http://site2/"), |
| MakeMostVisitedURL("Site 3", "http://site3/")}); |
| VerifyAndClearExpectations(); |
| |
| // Reply from suggestions service is empty and thus ignored. However, the |
| // current implementation issues a redundant query to TopSites. |
| // TODO(mastiz): Avoid this redundant call. |
| EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_, false)) |
| .WillOnce(Invoke(&top_sites_callbacks_, &TopSitesCallbackList::Add)); |
| suggestions_service_callbacks_.Notify(SuggestionsProfile()); |
| VerifyAndClearExpectations(); |
| |
| // Update from top sites is propagated to observer. |
| EXPECT_CALL( |
| mock_observer_, |
| OnMostVisitedURLsAvailable(ElementsAre( |
| MatchesTile("Site 4", "http://site4/", NTPTileSource::TOP_SITES), |
| MatchesTile("Site 5", "http://site5/", NTPTileSource::TOP_SITES), |
| MatchesTile("Site 6", "http://site6/", NTPTileSource::TOP_SITES)))); |
| top_sites_callbacks_.ClearAndNotify( |
| {MakeMostVisitedURL("Site 4", "http://site4/"), |
| MakeMostVisitedURL("Site 5", "http://site5/"), |
| MakeMostVisitedURL("Site 6", "http://site6/")}); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| INSTANTIATE_TEST_CASE_P(MostVisitedSitesWithEmptyCacheTest, |
| MostVisitedSitesWithEmptyCacheTest, |
| ::testing::Bool()); |
| |
| // This a test for MostVisitedSites::MergeTiles(...) method, and thus has the |
| // same scope as the method itself. This tests merging popular sites with |
| // personal tiles. |
| // More important things out of the scope of testing presently: |
| // - Removing blacklisted tiles. |
| // - Correct host extraction from the URL. |
| // - Ensuring personal tiles are not duplicated in popular tiles. |
| TEST(MostVisitedSitesMergeTest, ShouldMergeTilesWithPersonalOnly) { |
| std::vector<NTPTile> personal_tiles{ |
| MakeTile("Site 1", "https://www.site1.com/", NTPTileSource::TOP_SITES), |
| MakeTile("Site 2", "https://www.site2.com/", NTPTileSource::TOP_SITES), |
| MakeTile("Site 3", "https://www.site3.com/", NTPTileSource::TOP_SITES), |
| MakeTile("Site 4", "https://www.site4.com/", NTPTileSource::TOP_SITES), |
| }; |
| // Without any popular tiles, the result after merge should be the personal |
| // tiles. |
| EXPECT_THAT(MostVisitedSites::MergeTiles(std::move(personal_tiles), |
| /*whitelist_tiles=*/NTPTilesVector(), |
| /*popular_tiles=*/NTPTilesVector()), |
| ElementsAre(MatchesTile("Site 1", "https://www.site1.com/", |
| NTPTileSource::TOP_SITES), |
| MatchesTile("Site 2", "https://www.site2.com/", |
| NTPTileSource::TOP_SITES), |
| MatchesTile("Site 3", "https://www.site3.com/", |
| NTPTileSource::TOP_SITES), |
| MatchesTile("Site 4", "https://www.site4.com/", |
| NTPTileSource::TOP_SITES))); |
| } |
| |
| TEST(MostVisitedSitesMergeTest, ShouldMergeTilesWithPopularOnly) { |
| std::vector<NTPTile> popular_tiles{ |
| MakeTile("Site 1", "https://www.site1.com/", NTPTileSource::POPULAR), |
| MakeTile("Site 2", "https://www.site2.com/", NTPTileSource::POPULAR), |
| MakeTile("Site 3", "https://www.site3.com/", NTPTileSource::POPULAR), |
| MakeTile("Site 4", "https://www.site4.com/", NTPTileSource::POPULAR), |
| }; |
| // Without any personal tiles, the result after merge should be the popular |
| // tiles. |
| EXPECT_THAT( |
| MostVisitedSites::MergeTiles(/*personal_tiles=*/NTPTilesVector(), |
| /*whitelist_tiles=*/NTPTilesVector(), |
| /*popular_tiles=*/std::move(popular_tiles)), |
| ElementsAre(MatchesTile("Site 1", "https://www.site1.com/", |
| NTPTileSource::POPULAR), |
| MatchesTile("Site 2", "https://www.site2.com/", |
| NTPTileSource::POPULAR), |
| MatchesTile("Site 3", "https://www.site3.com/", |
| NTPTileSource::POPULAR), |
| MatchesTile("Site 4", "https://www.site4.com/", |
| NTPTileSource::POPULAR))); |
| } |
| |
| TEST(MostVisitedSitesMergeTest, ShouldMergeTilesFavoringPersonalOverPopular) { |
| std::vector<NTPTile> popular_tiles{ |
| MakeTile("Site 1", "https://www.site1.com/", NTPTileSource::POPULAR), |
| MakeTile("Site 2", "https://www.site2.com/", NTPTileSource::POPULAR), |
| }; |
| std::vector<NTPTile> personal_tiles{ |
| MakeTile("Site 3", "https://www.site3.com/", NTPTileSource::TOP_SITES), |
| MakeTile("Site 4", "https://www.site4.com/", NTPTileSource::TOP_SITES), |
| }; |
| EXPECT_THAT( |
| MostVisitedSites::MergeTiles(std::move(personal_tiles), |
| /*whitelist_tiles=*/NTPTilesVector(), |
| /*popular_tiles=*/std::move(popular_tiles)), |
| ElementsAre(MatchesTile("Site 3", "https://www.site3.com/", |
| NTPTileSource::TOP_SITES), |
| MatchesTile("Site 4", "https://www.site4.com/", |
| NTPTileSource::TOP_SITES), |
| MatchesTile("Site 1", "https://www.site1.com/", |
| NTPTileSource::POPULAR), |
| MatchesTile("Site 2", "https://www.site2.com/", |
| NTPTileSource::POPULAR))); |
| } |
| |
| } // namespace |
| } // namespace ntp_tiles |