content_suggestions_service_unittest.cc 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659
  1. // Copyright 2016 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "components/ntp_snippets/content_suggestions_service.h"
  5. #include <memory>
  6. #include <utility>
  7. #include <vector>
  8. #include "base/bind.h"
  9. #include "base/run_loop.h"
  10. #include "base/strings/string_number_conversions.h"
  11. #include "base/test/mock_callback.h"
  12. #include "base/test/task_environment.h"
  13. #include "base/time/default_clock.h"
  14. #include "components/feed/core/shared_prefs/pref_names.h"
  15. #include "components/ntp_snippets/category_info.h"
  16. #include "components/ntp_snippets/category_rankers/constant_category_ranker.h"
  17. #include "components/ntp_snippets/category_rankers/fake_category_ranker.h"
  18. #include "components/ntp_snippets/category_rankers/mock_category_ranker.h"
  19. #include "components/ntp_snippets/category_status.h"
  20. #include "components/ntp_snippets/content_suggestion.h"
  21. #include "components/ntp_snippets/content_suggestions_provider.h"
  22. #include "components/ntp_snippets/mock_content_suggestions_provider.h"
  23. #include "components/ntp_snippets/remote/remote_suggestions_provider_impl.h"
  24. #include "components/ntp_snippets/user_classifier.h"
  25. #include "components/prefs/testing_pref_service.h"
  26. #include "testing/gmock/include/gmock/gmock.h"
  27. #include "testing/gtest/include/gtest/gtest.h"
  28. #include "ui/gfx/image/image.h"
  29. using testing::_;
  30. using testing::ElementsAre;
  31. using testing::Eq;
  32. using testing::InvokeWithoutArgs;
  33. using testing::IsEmpty;
  34. using testing::Mock;
  35. using testing::NiceMock;
  36. using testing::Property;
  37. using testing::Return;
  38. using testing::SizeIs;
  39. using testing::StrictMock;
  40. using testing::UnorderedElementsAre;
  41. namespace ntp_snippets {
  42. namespace {
  43. class MockServiceObserver : public ContentSuggestionsService::Observer {
  44. public:
  45. MockServiceObserver() = default;
  46. MockServiceObserver(const MockServiceObserver&) = delete;
  47. MockServiceObserver& operator=(const MockServiceObserver&) = delete;
  48. ~MockServiceObserver() override = default;
  49. MOCK_METHOD1(OnNewSuggestions, void(Category category));
  50. MOCK_METHOD2(OnCategoryStatusChanged,
  51. void(Category changed_category, CategoryStatus new_status));
  52. MOCK_METHOD1(OnSuggestionInvalidated,
  53. void(const ContentSuggestion::ID& suggestion_id));
  54. MOCK_METHOD0(OnFullRefreshRequired, void());
  55. MOCK_METHOD0(ContentSuggestionsServiceShutdown, void());
  56. };
  57. } // namespace
  58. class ContentSuggestionsServiceTest : public testing::Test {
  59. public:
  60. ContentSuggestionsServiceTest() = default;
  61. ContentSuggestionsServiceTest(const ContentSuggestionsServiceTest&) = delete;
  62. ContentSuggestionsServiceTest& operator=(
  63. const ContentSuggestionsServiceTest&) = delete;
  64. void SetUp() override {
  65. RegisterPrefs();
  66. CreateContentSuggestionsService(ContentSuggestionsService::State::ENABLED);
  67. }
  68. void TearDown() override {
  69. service_->Shutdown();
  70. service_.reset();
  71. }
  72. // Verifies that exactly the suggestions with the given |numbers| are
  73. // returned by the service for the given |category|.
  74. void ExpectThatSuggestionsAre(Category category, std::vector<int> numbers) {
  75. std::vector<Category> categories = service()->GetCategories();
  76. auto category_position =
  77. std::find(categories.begin(), categories.end(), category);
  78. if (!numbers.empty()) {
  79. EXPECT_NE(categories.end(), category_position);
  80. }
  81. for (const auto& suggestion :
  82. service()->GetSuggestionsForCategory(category)) {
  83. std::string id_within_category = suggestion.id().id_within_category();
  84. int id;
  85. ASSERT_TRUE(base::StringToInt(id_within_category, &id));
  86. auto number_position = std::find(numbers.begin(), numbers.end(), id);
  87. if (number_position == numbers.end()) {
  88. ADD_FAILURE() << "Unexpected suggestion with ID " << id;
  89. } else {
  90. numbers.erase(number_position);
  91. }
  92. }
  93. for (int number : numbers) {
  94. ADD_FAILURE() << "Suggestion number " << number
  95. << " not present, though expected";
  96. }
  97. }
  98. const std::map<Category, ContentSuggestionsProvider*, Category::CompareByID>&
  99. providers() {
  100. return service()->providers_by_category_;
  101. }
  102. const std::map<Category, ContentSuggestionsProvider*, Category::CompareByID>&
  103. dismissed_providers() {
  104. return service()->dismissed_providers_by_category_;
  105. }
  106. MockContentSuggestionsProvider* MakeRegisteredMockProvider(
  107. Category provided_category) {
  108. return MakeRegisteredMockProvider(
  109. std::vector<Category>({provided_category}));
  110. }
  111. MockContentSuggestionsProvider* MakeRegisteredMockProvider(
  112. const std::vector<Category>& provided_categories) {
  113. auto provider =
  114. std::make_unique<testing::StrictMock<MockContentSuggestionsProvider>>(
  115. service(), provided_categories);
  116. MockContentSuggestionsProvider* result = provider.get();
  117. service()->RegisterProvider(std::move(provider));
  118. return result;
  119. }
  120. void SetCategoryRanker(std::unique_ptr<CategoryRanker> category_ranker) {
  121. category_ranker_ = std::move(category_ranker);
  122. }
  123. MOCK_METHOD1(OnImageFetched, void(const gfx::Image&));
  124. protected:
  125. void RegisterPrefs() {
  126. ContentSuggestionsService::RegisterProfilePrefs(pref_service_->registry());
  127. RemoteSuggestionsProviderImpl::RegisterProfilePrefs(
  128. pref_service_->registry());
  129. feed::prefs::RegisterFeedSharedProfilePrefs(pref_service_->registry());
  130. UserClassifier::RegisterProfilePrefs(pref_service_->registry());
  131. }
  132. void CreateContentSuggestionsService(
  133. ContentSuggestionsService::State enabled) {
  134. ASSERT_FALSE(service_);
  135. // TODO(jkrcal): Replace by a mock.
  136. auto user_classifier = std::make_unique<UserClassifier>(
  137. pref_service_.get(), base::DefaultClock::GetInstance());
  138. service_ = std::make_unique<ContentSuggestionsService>(
  139. enabled, /*identity_manager=*/nullptr, /*history_service=*/nullptr,
  140. /*large_icon_service=*/nullptr, pref_service_.get(),
  141. std::move(category_ranker_), std::move(user_classifier),
  142. /*scheduler=*/nullptr);
  143. }
  144. void ResetService() {
  145. service_->Shutdown();
  146. service_.reset();
  147. CreateContentSuggestionsService(ContentSuggestionsService::State::ENABLED);
  148. }
  149. ContentSuggestionsService* service() { return service_.get(); }
  150. // Returns a suggestion instance for testing.
  151. ContentSuggestion CreateSuggestion(Category category, int number) {
  152. return ContentSuggestion(
  153. category, base::NumberToString(number),
  154. GURL("http://testsuggestion/" + base::NumberToString(number)));
  155. }
  156. std::vector<ContentSuggestion> CreateSuggestions(
  157. Category category,
  158. const std::vector<int>& numbers) {
  159. std::vector<ContentSuggestion> result;
  160. for (int number : numbers) {
  161. result.push_back(CreateSuggestion(category, number));
  162. }
  163. return result;
  164. }
  165. private:
  166. std::unique_ptr<ContentSuggestionsService> service_;
  167. std::unique_ptr<TestingPrefServiceSimple> pref_service_{
  168. std::make_unique<TestingPrefServiceSimple>()};
  169. std::unique_ptr<CategoryRanker> category_ranker_{
  170. std::make_unique<ConstantCategoryRanker>()};
  171. };
  172. class ContentSuggestionsServiceDisabledTest
  173. : public ContentSuggestionsServiceTest {
  174. public:
  175. void SetUp() override {
  176. RegisterPrefs();
  177. CreateContentSuggestionsService(ContentSuggestionsService::State::DISABLED);
  178. }
  179. };
  180. TEST_F(ContentSuggestionsServiceTest, ShouldRegisterProviders) {
  181. EXPECT_THAT(service()->state(),
  182. Eq(ContentSuggestionsService::State::ENABLED));
  183. Category articles_category =
  184. Category::FromKnownCategory(KnownCategories::ARTICLES);
  185. ASSERT_THAT(providers(), IsEmpty());
  186. EXPECT_THAT(service()->GetCategories(), IsEmpty());
  187. EXPECT_THAT(service()->GetCategoryStatus(articles_category),
  188. Eq(CategoryStatus::NOT_PROVIDED));
  189. MockContentSuggestionsProvider* provider1 =
  190. MakeRegisteredMockProvider(articles_category);
  191. provider1->FireCategoryStatusChangedWithCurrentStatus(articles_category);
  192. ASSERT_THAT(providers().count(articles_category), Eq(1ul));
  193. EXPECT_THAT(providers().at(articles_category), Eq(provider1));
  194. EXPECT_THAT(providers().size(), Eq(1ul));
  195. EXPECT_THAT(service()->GetCategories(),
  196. UnorderedElementsAre(articles_category));
  197. EXPECT_THAT(service()->GetCategoryStatus(articles_category),
  198. Eq(CategoryStatus::AVAILABLE));
  199. }
  200. TEST_F(ContentSuggestionsServiceDisabledTest, ShouldDoNothingWhenDisabled) {
  201. Category articles_category =
  202. Category::FromKnownCategory(KnownCategories::ARTICLES);
  203. EXPECT_THAT(service()->state(),
  204. Eq(ContentSuggestionsService::State::DISABLED));
  205. EXPECT_THAT(providers(), IsEmpty());
  206. EXPECT_THAT(service()->GetCategoryStatus(articles_category),
  207. Eq(CategoryStatus::ALL_SUGGESTIONS_EXPLICITLY_DISABLED));
  208. EXPECT_THAT(service()->GetCategories(), IsEmpty());
  209. EXPECT_THAT(service()->GetSuggestionsForCategory(articles_category),
  210. IsEmpty());
  211. }
  212. TEST_F(ContentSuggestionsServiceTest, ShouldRedirectFetchSuggestionImage) {
  213. Category articles_category =
  214. Category::FromKnownCategory(KnownCategories::ARTICLES);
  215. MockContentSuggestionsProvider* provider1 =
  216. MakeRegisteredMockProvider(articles_category);
  217. provider1->FireSuggestionsChanged(articles_category,
  218. CreateSuggestions(articles_category, {1}));
  219. ContentSuggestion::ID suggestion_id(articles_category, "1");
  220. EXPECT_CALL(*provider1, FetchSuggestionImageMock(suggestion_id, _));
  221. service()->FetchSuggestionImage(
  222. suggestion_id,
  223. base::BindOnce(&ContentSuggestionsServiceTest::OnImageFetched,
  224. base::Unretained(this)));
  225. }
  226. TEST_F(ContentSuggestionsServiceTest,
  227. ShouldCallbackEmptyImageForUnavailableProvider) {
  228. base::test::SingleThreadTaskEnvironment task_environment;
  229. base::RunLoop run_loop;
  230. // Assuming there will never be a category with the id below.
  231. ContentSuggestion::ID suggestion_id(Category::FromIDValue(21563), "TestID");
  232. EXPECT_CALL(*this, OnImageFetched(Property(&gfx::Image::IsEmpty, Eq(true))))
  233. .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
  234. service()->FetchSuggestionImage(
  235. suggestion_id,
  236. base::BindOnce(&ContentSuggestionsServiceTest::OnImageFetched,
  237. base::Unretained(this)));
  238. run_loop.Run();
  239. }
  240. TEST_F(ContentSuggestionsServiceTest, ShouldRedirectSuggestionInvalidated) {
  241. Category articles_category =
  242. Category::FromKnownCategory(KnownCategories::ARTICLES);
  243. MockContentSuggestionsProvider* provider =
  244. MakeRegisteredMockProvider(articles_category);
  245. NiceMock<MockServiceObserver> observer;
  246. service()->AddObserver(&observer);
  247. provider->FireSuggestionsChanged(
  248. articles_category, CreateSuggestions(articles_category, {11, 12, 13}));
  249. ExpectThatSuggestionsAre(articles_category, {11, 12, 13});
  250. ContentSuggestion::ID suggestion_id(articles_category, "12");
  251. EXPECT_CALL(observer, OnSuggestionInvalidated(suggestion_id));
  252. provider->FireSuggestionInvalidated(suggestion_id);
  253. ExpectThatSuggestionsAre(articles_category, {11, 13});
  254. Mock::VerifyAndClearExpectations(&observer);
  255. // Unknown IDs must be forwarded (though no change happens to the service's
  256. // internal data structures) because previously opened UIs, which can still
  257. // show the invalidated suggestion, must be notified.
  258. ContentSuggestion::ID unknown_id(articles_category, "1234");
  259. EXPECT_CALL(observer, OnSuggestionInvalidated(unknown_id));
  260. provider->FireSuggestionInvalidated(unknown_id);
  261. ExpectThatSuggestionsAre(articles_category, {11, 13});
  262. Mock::VerifyAndClearExpectations(&observer);
  263. service()->RemoveObserver(&observer);
  264. }
  265. TEST_F(ContentSuggestionsServiceTest, ShouldForwardSuggestions) {
  266. Category articles_category =
  267. Category::FromKnownCategory(KnownCategories::ARTICLES);
  268. // Create and register providers
  269. MockContentSuggestionsProvider* provider1 =
  270. MakeRegisteredMockProvider(articles_category);
  271. provider1->FireCategoryStatusChangedWithCurrentStatus(articles_category);
  272. ASSERT_THAT(providers().count(articles_category), Eq(1ul));
  273. EXPECT_THAT(providers().at(articles_category), Eq(provider1));
  274. // Create and register observer
  275. MockServiceObserver observer;
  276. service()->AddObserver(&observer);
  277. // Send suggestions 1 and 2
  278. EXPECT_CALL(observer, OnNewSuggestions(articles_category));
  279. provider1->FireSuggestionsChanged(
  280. articles_category, CreateSuggestions(articles_category, {1, 2}));
  281. ExpectThatSuggestionsAre(articles_category, {1, 2});
  282. Mock::VerifyAndClearExpectations(&observer);
  283. // Send them again, make sure they're not reported twice
  284. EXPECT_CALL(observer, OnNewSuggestions(articles_category));
  285. provider1->FireSuggestionsChanged(
  286. articles_category, CreateSuggestions(articles_category, {1, 2}));
  287. ExpectThatSuggestionsAre(articles_category, {1, 2});
  288. Mock::VerifyAndClearExpectations(&observer);
  289. // Send suggestion 1 only
  290. EXPECT_CALL(observer, OnNewSuggestions(articles_category));
  291. provider1->FireSuggestionsChanged(articles_category,
  292. CreateSuggestions(articles_category, {1}));
  293. ExpectThatSuggestionsAre(articles_category, {1});
  294. Mock::VerifyAndClearExpectations(&observer);
  295. // Shutdown the service
  296. EXPECT_CALL(observer, ContentSuggestionsServiceShutdown());
  297. service()->Shutdown();
  298. service()->RemoveObserver(&observer);
  299. // The service will receive two Shutdown() calls.
  300. }
  301. TEST_F(ContentSuggestionsServiceTest,
  302. ShouldNotReturnCategoryInfoForNonexistentCategory) {
  303. Category category =
  304. Category::FromKnownCategory(KnownCategories::READING_LIST);
  305. absl::optional<CategoryInfo> result = service()->GetCategoryInfo(category);
  306. EXPECT_FALSE(result.has_value());
  307. }
  308. TEST_F(ContentSuggestionsServiceTest, ShouldReturnCategoryInfo) {
  309. Category category =
  310. Category::FromKnownCategory(KnownCategories::READING_LIST);
  311. MockContentSuggestionsProvider* provider =
  312. MakeRegisteredMockProvider(category);
  313. provider->FireCategoryStatusChangedWithCurrentStatus(category);
  314. absl::optional<CategoryInfo> result = service()->GetCategoryInfo(category);
  315. ASSERT_TRUE(result.has_value());
  316. CategoryInfo expected = provider->GetCategoryInfo(category);
  317. const CategoryInfo& actual = result.value();
  318. EXPECT_THAT(expected.title(), Eq(actual.title()));
  319. EXPECT_THAT(expected.card_layout(), Eq(actual.card_layout()));
  320. EXPECT_THAT(expected.additional_action(), Eq(actual.additional_action()));
  321. }
  322. TEST_F(ContentSuggestionsServiceTest,
  323. ShouldRegisterNewCategoryOnNewSuggestions) {
  324. Category category =
  325. Category::FromKnownCategory(KnownCategories::READING_LIST);
  326. MockContentSuggestionsProvider* provider =
  327. MakeRegisteredMockProvider(category);
  328. provider->FireCategoryStatusChangedWithCurrentStatus(category);
  329. MockServiceObserver observer;
  330. service()->AddObserver(&observer);
  331. // Provider starts providing |new_category| without calling
  332. // |OnCategoryStatusChanged|. This is supported for now until further
  333. // reconsideration.
  334. Category new_category =
  335. Category::FromKnownCategory(KnownCategories::ARTICLES);
  336. provider->SetProvidedCategories(
  337. std::vector<Category>({category, new_category}));
  338. EXPECT_CALL(observer, OnNewSuggestions(new_category));
  339. EXPECT_CALL(observer,
  340. OnCategoryStatusChanged(new_category, CategoryStatus::AVAILABLE));
  341. provider->FireSuggestionsChanged(new_category,
  342. CreateSuggestions(new_category, {1, 2}));
  343. ExpectThatSuggestionsAre(new_category, {1, 2});
  344. ASSERT_THAT(providers().count(category), Eq(1ul));
  345. EXPECT_THAT(providers().at(category), Eq(provider));
  346. EXPECT_THAT(service()->GetCategoryStatus(category),
  347. Eq(CategoryStatus::AVAILABLE));
  348. ASSERT_THAT(providers().count(new_category), Eq(1ul));
  349. EXPECT_THAT(providers().at(new_category), Eq(provider));
  350. EXPECT_THAT(service()->GetCategoryStatus(new_category),
  351. Eq(CategoryStatus::AVAILABLE));
  352. service()->RemoveObserver(&observer);
  353. }
  354. TEST_F(ContentSuggestionsServiceTest,
  355. ShouldRegisterNewCategoryOnCategoryStatusChanged) {
  356. Category category =
  357. Category::FromKnownCategory(KnownCategories::READING_LIST);
  358. MockContentSuggestionsProvider* provider =
  359. MakeRegisteredMockProvider(category);
  360. provider->FireCategoryStatusChangedWithCurrentStatus(category);
  361. MockServiceObserver observer;
  362. service()->AddObserver(&observer);
  363. // Provider starts providing |new_category| and calls
  364. // |OnCategoryStatusChanged|, but the category is not yet available.
  365. Category new_category =
  366. Category::FromKnownCategory(KnownCategories::ARTICLES);
  367. provider->SetProvidedCategories(
  368. std::vector<Category>({category, new_category}));
  369. EXPECT_CALL(observer, OnCategoryStatusChanged(new_category,
  370. CategoryStatus::INITIALIZING));
  371. provider->FireCategoryStatusChanged(new_category,
  372. CategoryStatus::INITIALIZING);
  373. ASSERT_THAT(providers().count(new_category), Eq(1ul));
  374. EXPECT_THAT(providers().at(new_category), Eq(provider));
  375. ExpectThatSuggestionsAre(new_category, std::vector<int>());
  376. EXPECT_THAT(service()->GetCategoryStatus(new_category),
  377. Eq(CategoryStatus::INITIALIZING));
  378. EXPECT_THAT(service()->GetCategories(),
  379. UnorderedElementsAre(category, new_category));
  380. service()->RemoveObserver(&observer);
  381. }
  382. TEST_F(ContentSuggestionsServiceTest, ShouldRemoveCategoryWhenNotProvided) {
  383. Category category =
  384. Category::FromKnownCategory(KnownCategories::READING_LIST);
  385. MockContentSuggestionsProvider* provider =
  386. MakeRegisteredMockProvider(category);
  387. NiceMock<MockServiceObserver> observer;
  388. service()->AddObserver(&observer);
  389. provider->FireSuggestionsChanged(category,
  390. CreateSuggestions(category, {1, 2}));
  391. ExpectThatSuggestionsAre(category, {1, 2});
  392. EXPECT_CALL(observer,
  393. OnCategoryStatusChanged(category, CategoryStatus::NOT_PROVIDED));
  394. provider->FireCategoryStatusChanged(category, CategoryStatus::NOT_PROVIDED);
  395. EXPECT_THAT(service()->GetCategoryStatus(category),
  396. Eq(CategoryStatus::NOT_PROVIDED));
  397. EXPECT_TRUE(service()->GetCategories().empty());
  398. ExpectThatSuggestionsAre(category, std::vector<int>());
  399. service()->RemoveObserver(&observer);
  400. }
  401. TEST_F(ContentSuggestionsServiceTest,
  402. ShouldForwardClearHistoryToCategoryRanker) {
  403. auto mock_ranker = std::make_unique<MockCategoryRanker>();
  404. MockCategoryRanker* raw_mock_ranker = mock_ranker.get();
  405. SetCategoryRanker(std::move(mock_ranker));
  406. // The service is recreated to pick up the new ranker.
  407. ResetService();
  408. base::Time begin = base::Time::FromTimeT(123),
  409. end = base::Time::FromTimeT(456);
  410. EXPECT_CALL(*raw_mock_ranker, ClearHistory(begin, end));
  411. base::RepeatingCallback<bool(const GURL& url)> filter;
  412. service()->ClearHistory(begin, end, filter);
  413. }
  414. TEST_F(ContentSuggestionsServiceTest, ShouldForwardFetch) {
  415. Category category = Category::FromKnownCategory(KnownCategories::ARTICLES);
  416. std::set<std::string> known_suggestions;
  417. MockContentSuggestionsProvider* provider =
  418. MakeRegisteredMockProvider(category);
  419. provider->FireCategoryStatusChangedWithCurrentStatus(category);
  420. EXPECT_CALL(*provider, FetchMock(category, known_suggestions, _));
  421. service()->Fetch(category, known_suggestions, FetchDoneCallback());
  422. }
  423. TEST_F(ContentSuggestionsServiceTest, DismissAndRestoreCategory) {
  424. // Register a category with one suggestion.
  425. Category category = Category::FromKnownCategory(KnownCategories::ARTICLES);
  426. MockContentSuggestionsProvider* provider =
  427. MakeRegisteredMockProvider(category);
  428. provider->FireCategoryStatusChangedWithCurrentStatus(category);
  429. provider->FireSuggestionsChanged(category, CreateSuggestions(category, {42}));
  430. EXPECT_THAT(service()->GetCategories(), UnorderedElementsAre(category));
  431. EXPECT_THAT(service()->GetCategoryStatus(category),
  432. Eq(CategoryStatus::AVAILABLE));
  433. ExpectThatSuggestionsAre(category, {42});
  434. EXPECT_THAT(providers().count(category), Eq(1ul));
  435. EXPECT_THAT(dismissed_providers(), IsEmpty());
  436. // Dismissing the category clears the suggestions for it.
  437. service()->DismissCategory(category);
  438. EXPECT_THAT(service()->GetCategories(), IsEmpty());
  439. EXPECT_THAT(service()->GetCategoryStatus(category),
  440. Eq(CategoryStatus::NOT_PROVIDED));
  441. EXPECT_THAT(service()->GetSuggestionsForCategory(category), IsEmpty());
  442. EXPECT_THAT(providers(), IsEmpty());
  443. EXPECT_THAT(dismissed_providers().count(category), Eq(1ul));
  444. // Restoring the dismissed category makes it available again but it is still
  445. // empty.
  446. service()->RestoreDismissedCategories();
  447. EXPECT_THAT(service()->GetCategories(), UnorderedElementsAre(category));
  448. EXPECT_THAT(service()->GetCategoryStatus(category),
  449. Eq(CategoryStatus::AVAILABLE));
  450. EXPECT_THAT(service()->GetSuggestionsForCategory(category), IsEmpty());
  451. EXPECT_THAT(providers().count(category), Eq(1ul));
  452. EXPECT_THAT(dismissed_providers(), IsEmpty());
  453. }
  454. TEST_F(ContentSuggestionsServiceTest, ShouldRestoreDismissedCategories) {
  455. // Create and register provider.
  456. Category category1 = Category::FromIDValue(1);
  457. Category category2 = Category::FromIDValue(2);
  458. // Setup and verify initial state.
  459. MockContentSuggestionsProvider* provider =
  460. MakeRegisteredMockProvider({category1, category2});
  461. provider->FireCategoryStatusChangedWithCurrentStatus(category1);
  462. provider->FireCategoryStatusChangedWithCurrentStatus(category2);
  463. ASSERT_THAT(service()->GetCategoryStatus(category1),
  464. Eq(CategoryStatus::AVAILABLE));
  465. ASSERT_THAT(service()->GetCategoryStatus(category2),
  466. Eq(CategoryStatus::AVAILABLE));
  467. // Dismiss all the categories. None should be provided now.
  468. service()->DismissCategory(category1);
  469. service()->DismissCategory(category2);
  470. ASSERT_THAT(service()->GetCategoryStatus(category1),
  471. Eq(CategoryStatus::NOT_PROVIDED));
  472. ASSERT_THAT(service()->GetCategoryStatus(category2),
  473. Eq(CategoryStatus::NOT_PROVIDED));
  474. // Receiving a status change notification should not change anything.
  475. provider->FireCategoryStatusChanged(category1, CategoryStatus::AVAILABLE);
  476. EXPECT_THAT(service()->GetCategoryStatus(category1),
  477. Eq(CategoryStatus::NOT_PROVIDED));
  478. EXPECT_THAT(service()->GetCategoryStatus(category2),
  479. Eq(CategoryStatus::NOT_PROVIDED));
  480. // Receiving a notification without suggestions should not change anything.
  481. provider->FireSuggestionsChanged(category1, std::vector<ContentSuggestion>());
  482. EXPECT_THAT(service()->GetCategoryStatus(category1),
  483. Eq(CategoryStatus::NOT_PROVIDED));
  484. EXPECT_THAT(service()->GetCategoryStatus(category2),
  485. Eq(CategoryStatus::NOT_PROVIDED));
  486. // Receiving suggestions should make the notified category available.
  487. provider->FireSuggestionsChanged(category1,
  488. CreateSuggestions(category1, {1, 2}));
  489. EXPECT_THAT(service()->GetCategoryStatus(category1),
  490. Eq(CategoryStatus::AVAILABLE));
  491. EXPECT_THAT(service()->GetCategoryStatus(category2),
  492. Eq(CategoryStatus::NOT_PROVIDED));
  493. }
  494. TEST_F(ContentSuggestionsServiceTest, ShouldRestoreDismissalsFromPrefs) {
  495. // Register a category with one suggestion.
  496. Category category = Category::FromKnownCategory(KnownCategories::ARTICLES);
  497. MockContentSuggestionsProvider* provider =
  498. MakeRegisteredMockProvider(category);
  499. provider->FireCategoryStatusChangedWithCurrentStatus(category);
  500. // For a regular initialisation, the category is not dismissed.
  501. ASSERT_FALSE(service()->IsCategoryDismissed(category));
  502. // Dismiss the category.
  503. service()->DismissCategory(category);
  504. ASSERT_TRUE(service()->IsCategoryDismissed(category));
  505. // Simulate a Chrome restart. The category should still be dismissed.
  506. ResetService();
  507. EXPECT_TRUE(service()->IsCategoryDismissed(category));
  508. // Ensure that the provider registered at initialisation is used after
  509. // restoration.
  510. provider = MakeRegisteredMockProvider(category);
  511. provider->FireCategoryStatusChangedWithCurrentStatus(category);
  512. EXPECT_TRUE(service()->IsCategoryDismissed(category));
  513. service()->RestoreDismissedCategories();
  514. EXPECT_FALSE(service()->IsCategoryDismissed(category));
  515. EXPECT_THAT(providers().find(category)->second, Eq(provider));
  516. }
  517. TEST_F(ContentSuggestionsServiceTest, ShouldReturnCategoriesInOrderToDisplay) {
  518. const Category first_category = Category::FromRemoteCategory(1);
  519. const Category second_category = Category::FromRemoteCategory(2);
  520. auto fake_ranker = std::make_unique<FakeCategoryRanker>();
  521. FakeCategoryRanker* raw_fake_ranker = fake_ranker.get();
  522. SetCategoryRanker(std::move(fake_ranker));
  523. raw_fake_ranker->SetOrder({first_category, second_category});
  524. // The service is recreated to pick up the new ranker.
  525. ResetService();
  526. MockContentSuggestionsProvider* provider =
  527. MakeRegisteredMockProvider({first_category, second_category});
  528. provider->FireCategoryStatusChangedWithCurrentStatus(first_category);
  529. provider->FireCategoryStatusChangedWithCurrentStatus(second_category);
  530. EXPECT_THAT(service()->GetCategories(),
  531. ElementsAre(first_category, second_category));
  532. // The order to display (in the ranker) changes.
  533. raw_fake_ranker->SetOrder({second_category, first_category});
  534. // Categories order should reflect the new order.
  535. EXPECT_THAT(service()->GetCategories(),
  536. ElementsAre(second_category, first_category));
  537. }
  538. TEST_F(ContentSuggestionsServiceTest,
  539. ShouldForwardDismissedCategoryToCategoryRanker) {
  540. auto mock_ranker = std::make_unique<MockCategoryRanker>();
  541. MockCategoryRanker* raw_mock_ranker = mock_ranker.get();
  542. SetCategoryRanker(std::move(mock_ranker));
  543. // The service is recreated to pick up the new ranker.
  544. ResetService();
  545. Category category = Category::FromKnownCategory(KnownCategories::ARTICLES);
  546. MockContentSuggestionsProvider* provider =
  547. MakeRegisteredMockProvider(category);
  548. provider->FireCategoryStatusChangedWithCurrentStatus(category);
  549. EXPECT_CALL(*raw_mock_ranker, OnCategoryDismissed(category));
  550. service()->DismissCategory(category);
  551. }
  552. } // namespace ntp_snippets