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 
5 #include "components/ntp_snippets/category_rankers/click_based_category_ranker.h"
6 
7 #include <utility>
8 
9 #include "base/strings/string_number_conversions.h"
10 #include "base/test/metrics/histogram_tester.h"
11 #include "base/test/simple_test_clock.h"
12 #include "base/time/default_clock.h"
13 #include "base/time/time.h"
14 #include "components/ntp_snippets/category.h"
15 #include "components/ntp_snippets/category_rankers/constant_category_ranker.h"
16 #include "components/ntp_snippets/ntp_snippets_constants.h"
17 #include "components/ntp_snippets/time_serialization.h"
18 #include "components/prefs/testing_pref_service.h"
19 #include "testing/gmock/include/gmock/gmock.h"
20 #include "testing/gtest/include/gtest/gtest.h"
21 
22 using testing::ElementsAre;
23 using testing::IsEmpty;
24 
25 namespace ntp_snippets {
26 
27 namespace {
28 
29 const char kHistogramMovedUpCategoryNewIndex[] =
30     "NewTabPage.ContentSuggestions.MovedUpCategoryNewIndex";
31 
32 }  // namespace
33 
34 class ClickBasedCategoryRankerTest : public testing::Test {
35  public:
ClickBasedCategoryRankerTest()36   ClickBasedCategoryRankerTest()
37       : pref_service_(std::make_unique<TestingPrefServiceSimple>()),
38         unused_remote_category_id_(
39             static_cast<int>(KnownCategories::LAST_KNOWN_REMOTE_CATEGORY) + 1) {
40     ClickBasedCategoryRanker::RegisterProfilePrefs(pref_service_->registry());
41 
42     ranker_ = std::make_unique<ClickBasedCategoryRanker>(
43         pref_service_.get(), base::DefaultClock::GetInstance());
44   }
45 
GetUnusedRemoteCategoryID()46   int GetUnusedRemoteCategoryID() { return unused_remote_category_id_++; }
47 
GetUnusedRemoteCategory()48   Category GetUnusedRemoteCategory() {
49     return Category::FromIDValue(GetUnusedRemoteCategoryID());
50   }
51 
CompareCategories(const Category & left,const Category & right)52   bool CompareCategories(const Category& left, const Category& right) {
53     return ranker()->Compare(left, right);
54   }
55 
AddUnusedRemoteCategory()56   Category AddUnusedRemoteCategory() {
57     Category category = GetUnusedRemoteCategory();
58     ranker()->AppendCategoryIfNecessary(category);
59     return category;
60   }
61 
AddUnusedRemoteCategories(int quantity)62   void AddUnusedRemoteCategories(int quantity) {
63     for (int i = 0; i < quantity; ++i) {
64       AddUnusedRemoteCategory();
65     }
66   }
67 
ResetRanker(base::Clock * clock)68   void ResetRanker(base::Clock* clock) {
69     ranker_ =
70         std::make_unique<ClickBasedCategoryRanker>(pref_service_.get(), clock);
71   }
72 
NotifyOnSuggestionOpened(int times,Category category)73   void NotifyOnSuggestionOpened(int times, Category category) {
74     for (int i = 0; i < times; ++i) {
75       ranker()->OnSuggestionOpened(category);
76     }
77   }
78 
NotifyOnCategoryDismissed(Category category)79   void NotifyOnCategoryDismissed(Category category) {
80     ranker()->OnCategoryDismissed(category);
81   }
82 
ConvertKnownCategories(std::vector<KnownCategories> known_categories)83   std::vector<Category> ConvertKnownCategories(
84       std::vector<KnownCategories> known_categories) {
85     std::vector<Category> converted;
86     for (auto known : known_categories) {
87       converted.push_back(Category::FromKnownCategory(known));
88     }
89     return converted;
90   }
91 
ranker()92   ClickBasedCategoryRanker* ranker() { return ranker_.get(); }
93 
94  private:
95   std::unique_ptr<TestingPrefServiceSimple> pref_service_;
96   int unused_remote_category_id_;
97   std::unique_ptr<ClickBasedCategoryRanker> ranker_;
98 
99   DISALLOW_COPY_AND_ASSIGN(ClickBasedCategoryRankerTest);
100 };
101 
TEST_F(ClickBasedCategoryRankerTest,ShouldSortRemoteCategoriesByWhenAdded)102 TEST_F(ClickBasedCategoryRankerTest, ShouldSortRemoteCategoriesByWhenAdded) {
103   const Category first = GetUnusedRemoteCategory();
104   const Category second = GetUnusedRemoteCategory();
105   // Categories are added in decreasing id order to test that they are not
106   // compared by id.
107   ranker()->AppendCategoryIfNecessary(second);
108   ranker()->AppendCategoryIfNecessary(first);
109   EXPECT_TRUE(CompareCategories(second, first));
110   EXPECT_FALSE(CompareCategories(first, second));
111 }
112 
TEST_F(ClickBasedCategoryRankerTest,ShouldSortLocalCategoriesBeforeRemote)113 TEST_F(ClickBasedCategoryRankerTest, ShouldSortLocalCategoriesBeforeRemote) {
114   const Category remote_category = AddUnusedRemoteCategory();
115   const Category local_category =
116       Category::FromKnownCategory(KnownCategories::READING_LIST);
117   EXPECT_TRUE(CompareCategories(local_category, remote_category));
118   EXPECT_FALSE(CompareCategories(remote_category, local_category));
119 }
120 
TEST_F(ClickBasedCategoryRankerTest,CompareShouldReturnFalseForSameCategories)121 TEST_F(ClickBasedCategoryRankerTest,
122        CompareShouldReturnFalseForSameCategories) {
123   const Category remote_category = AddUnusedRemoteCategory();
124   EXPECT_FALSE(CompareCategories(remote_category, remote_category));
125 
126   const Category local_category =
127       Category::FromKnownCategory(KnownCategories::READING_LIST);
128   EXPECT_FALSE(CompareCategories(local_category, local_category));
129 }
130 
TEST_F(ClickBasedCategoryRankerTest,AddingMoreRemoteCategoriesShouldNotChangePreviousOrder)131 TEST_F(ClickBasedCategoryRankerTest,
132        AddingMoreRemoteCategoriesShouldNotChangePreviousOrder) {
133   AddUnusedRemoteCategories(3);
134 
135   Category first = AddUnusedRemoteCategory();
136   Category second = AddUnusedRemoteCategory();
137 
138   ASSERT_TRUE(CompareCategories(first, second));
139   ASSERT_FALSE(CompareCategories(second, first));
140 
141   AddUnusedRemoteCategories(3);
142 
143   EXPECT_TRUE(CompareCategories(first, second));
144   EXPECT_FALSE(CompareCategories(second, first));
145 }
146 
TEST_F(ClickBasedCategoryRankerTest,ShouldChangeOrderOfNonTopCategories)147 TEST_F(ClickBasedCategoryRankerTest, ShouldChangeOrderOfNonTopCategories) {
148   // Add dummy remote categories to ensure that the following categories are not
149   // in the top anymore.
150   AddUnusedRemoteCategories(
151       ClickBasedCategoryRanker::GetNumTopCategoriesWithExtraMargin());
152 
153   Category first = AddUnusedRemoteCategory();
154   Category second = AddUnusedRemoteCategory();
155 
156   ASSERT_TRUE(CompareCategories(first, second));
157   ASSERT_FALSE(CompareCategories(second, first));
158 
159   NotifyOnSuggestionOpened(
160       /*times=*/ClickBasedCategoryRanker::GetPassingMargin(), second);
161 
162   EXPECT_TRUE(CompareCategories(second, first));
163   EXPECT_FALSE(CompareCategories(first, second));
164 }
165 
TEST_F(ClickBasedCategoryRankerTest,ShouldNotChangeOrderRightAfterOrderChange)166 TEST_F(ClickBasedCategoryRankerTest,
167        ShouldNotChangeOrderRightAfterOrderChange) {
168   // Add dummy remote categories to ensure that the following categories are not
169   // in the top anymore.
170   AddUnusedRemoteCategories(
171       ClickBasedCategoryRanker::GetNumTopCategoriesWithExtraMargin());
172 
173   // Two non-top categories are added.
174   Category first = AddUnusedRemoteCategory();
175   Category second = AddUnusedRemoteCategory();
176   ASSERT_TRUE(CompareCategories(first, second));
177   ASSERT_FALSE(CompareCategories(second, first));
178   // Their order is changed.
179   NotifyOnSuggestionOpened(
180       /*times=*/ClickBasedCategoryRanker::GetPassingMargin(), second);
181   ASSERT_TRUE(CompareCategories(second, first));
182   ASSERT_FALSE(CompareCategories(first, second));
183 
184   // Click on the lower category.
185   NotifyOnSuggestionOpened(/*times=*/1, first);
186 
187   // Order should not change.
188   EXPECT_TRUE(CompareCategories(second, first));
189   EXPECT_FALSE(CompareCategories(first, second));
190 }
191 
TEST_F(ClickBasedCategoryRankerTest,ShouldNotMoveCategoryMoreThanOncePerClick)192 TEST_F(ClickBasedCategoryRankerTest,
193        ShouldNotMoveCategoryMoreThanOncePerClick) {
194   // Add dummy remote categories to ensure that the following categories are not
195   // in the top anymore.
196   AddUnusedRemoteCategories(
197       ClickBasedCategoryRanker::GetNumTopCategoriesWithExtraMargin());
198 
199   // Non-top categories are added.
200   Category first = AddUnusedRemoteCategory();
201   Category second = AddUnusedRemoteCategory();
202   Category third = AddUnusedRemoteCategory();
203 
204   // Move the third category up.
205   NotifyOnSuggestionOpened(
206       /*times=*/ClickBasedCategoryRanker::GetPassingMargin(), third);
207   EXPECT_TRUE(CompareCategories(third, second));
208   // But only on one position even though the first category has low counts.
209   EXPECT_TRUE(CompareCategories(first, third));
210   // However, another click must move it further.
211   NotifyOnSuggestionOpened(/*times=*/1, third);
212   EXPECT_TRUE(CompareCategories(third, first));
213 }
214 
TEST_F(ClickBasedCategoryRankerTest,ShouldNotMoveTopCategoryRightAfterThreshold)215 TEST_F(ClickBasedCategoryRankerTest,
216        ShouldNotMoveTopCategoryRightAfterThreshold) {
217   ASSERT_GE(ClickBasedCategoryRanker::GetNumTopCategoriesWithExtraMargin(), 1);
218 
219   // At least one top category is added from the default order.
220   std::vector<KnownCategories> default_order =
221       ConstantCategoryRanker::GetKnownCategoriesDefaultOrder();
222   Category first = Category::FromKnownCategory(default_order[0]);
223   Category second = Category::FromKnownCategory(default_order[1]);
224 
225   // Try to move the second category up as if the first category was non-top.
226   NotifyOnSuggestionOpened(
227       /*times=*/ClickBasedCategoryRanker::GetPassingMargin(), second);
228 
229   // Nothing should change, because the first category is top.
230   EXPECT_TRUE(CompareCategories(first, second));
231 }
232 
TEST_F(ClickBasedCategoryRankerTest,ShouldPersistOrderAndClicksWhenRestarted)233 TEST_F(ClickBasedCategoryRankerTest, ShouldPersistOrderAndClicksWhenRestarted) {
234   // Add dummy remote categories to ensure that the following categories are not
235   // in the top anymore.
236   AddUnusedRemoteCategories(
237       ClickBasedCategoryRanker::GetNumTopCategoriesWithExtraMargin());
238 
239   // Non-top categories are added.
240   Category first = AddUnusedRemoteCategory();
241   Category second = AddUnusedRemoteCategory();
242   Category third = AddUnusedRemoteCategory();
243 
244   // Change the order.
245   NotifyOnSuggestionOpened(
246       /*times=*/ClickBasedCategoryRanker::GetPassingMargin(), third);
247   ASSERT_TRUE(CompareCategories(third, second));
248   ASSERT_TRUE(CompareCategories(first, third));
249 
250   // Simulate Chrome restart.
251   ResetRanker(base::DefaultClock::GetInstance());
252 
253   // The old order must be preserved.
254   EXPECT_TRUE(CompareCategories(third, second));
255 
256   // Clicks must be preserved as well.
257   NotifyOnSuggestionOpened(/*times=*/1, third);
258   EXPECT_TRUE(CompareCategories(third, first));
259 }
260 
TEST_F(ClickBasedCategoryRankerTest,ShouldDecayClickCountsWithTime)261 TEST_F(ClickBasedCategoryRankerTest, ShouldDecayClickCountsWithTime) {
262   // Add dummy remote categories to ensure that the following categories are not
263   // in the top anymore.
264   AddUnusedRemoteCategories(
265       ClickBasedCategoryRanker::GetNumTopCategoriesWithExtraMargin());
266 
267   // Non-top categories are added.
268   Category first = AddUnusedRemoteCategory();
269   Category second = AddUnusedRemoteCategory();
270 
271   const int first_clicks = 10 * ClickBasedCategoryRanker::GetPassingMargin();
272 
273   // Simulate the user using the first category for a long time (and not using
274   // anything else).
275   NotifyOnSuggestionOpened(/*times=*/first_clicks, first);
276 
277   // Let multiple years pass by.
278   base::SimpleTestClock test_clock;
279   test_clock.SetNow(base::Time::Now() + base::TimeDelta::FromDays(1000));
280   // Reset the ranker to pick up the new clock.
281   ResetRanker(&test_clock);
282 
283   // The user behavior changes and they start using the second category instead.
284   // According to our requirenments after such a long time it should take less
285   // than |first_clicks| for the second category to outperform the first one.
286   int second_clicks = 0;
287   while (CompareCategories(first, second) && second_clicks < first_clicks) {
288     NotifyOnSuggestionOpened(/*times=*/1, second);
289     second_clicks++;
290   }
291   EXPECT_THAT(second_clicks, testing::Lt(first_clicks));
292 }
293 
TEST_F(ClickBasedCategoryRankerTest,ShouldDecayAfterClearHistory)294 TEST_F(ClickBasedCategoryRankerTest, ShouldDecayAfterClearHistory) {
295   std::vector<KnownCategories> default_order =
296       ConstantCategoryRanker::GetKnownCategoriesDefaultOrder();
297   Category first = Category::FromKnownCategory(default_order[0]);
298   Category second = Category::FromKnownCategory(default_order[1]);
299 
300   // The user clears entire history.
301   ranker()->ClearHistory(/*begin=*/base::Time(),
302                          /*end=*/base::Time::Max());
303 
304   // Check whether decay happens by clicking on the first category and
305   // waiting.
306   const int first_clicks = 10 * ClickBasedCategoryRanker::GetPassingMargin();
307   NotifyOnSuggestionOpened(/*times=*/first_clicks, first);
308 
309   // Let multiple years pass by.
310   base::SimpleTestClock test_clock;
311   test_clock.SetNow(base::Time::Now() + base::TimeDelta::FromDays(1000));
312   // Reset the ranker to pick up the new clock.
313   ResetRanker(&test_clock);
314 
315   // It should take less than |first_clicks| for the second category to
316   // overtake because of decays.
317   int second_clicks = 0;
318   while (CompareCategories(first, second) && second_clicks < first_clicks) {
319     NotifyOnSuggestionOpened(/*times=*/1, second);
320     second_clicks++;
321   }
322   EXPECT_THAT(second_clicks, testing::Lt(first_clicks));
323 }
324 
TEST_F(ClickBasedCategoryRankerTest,ShouldRemoveLastDecayTimeOnClearHistory)325 TEST_F(ClickBasedCategoryRankerTest, ShouldRemoveLastDecayTimeOnClearHistory) {
326   ASSERT_NE(ranker()->GetLastDecayTime(), DeserializeTime(0));
327 
328   // The user clears entire history.
329   ranker()->ClearHistory(/*begin=*/base::Time(),
330                          /*end=*/base::Time::Max());
331 
332   EXPECT_EQ(ranker()->GetLastDecayTime(), DeserializeTime(0));
333 }
334 
TEST_F(ClickBasedCategoryRankerTest,ShouldPersistLastDecayTimeWhenRestarted)335 TEST_F(ClickBasedCategoryRankerTest, ShouldPersistLastDecayTimeWhenRestarted) {
336   base::Time before = ranker()->GetLastDecayTime();
337   ASSERT_NE(before, DeserializeTime(0));
338 
339   // Ensure that |Now()| is different from |before| by injecting our clock.
340   base::SimpleTestClock test_clock;
341   test_clock.SetNow(base::Time::Now() + base::TimeDelta::FromSeconds(10));
342   ResetRanker(&test_clock);
343 
344   EXPECT_EQ(before, ranker()->GetLastDecayTime());
345 }
346 
TEST_F(ClickBasedCategoryRankerTest,ShouldMoveCategoryDownWhenDismissed)347 TEST_F(ClickBasedCategoryRankerTest, ShouldMoveCategoryDownWhenDismissed) {
348   // Take top categories.
349   std::vector<KnownCategories> default_order =
350       ConstantCategoryRanker::GetKnownCategoriesDefaultOrder();
351   Category first = Category::FromKnownCategory(default_order[0]);
352   Category second = Category::FromKnownCategory(default_order[1]);
353 
354   ASSERT_TRUE(CompareCategories(first, second));
355   NotifyOnCategoryDismissed(first);
356   EXPECT_FALSE(CompareCategories(first, second));
357 }
358 
TEST_F(ClickBasedCategoryRankerTest,ShouldMoveSecondToLastCategoryDownWhenDismissed)359 TEST_F(ClickBasedCategoryRankerTest,
360        ShouldMoveSecondToLastCategoryDownWhenDismissed) {
361   // Add categories to the bottom.
362   Category first = AddUnusedRemoteCategory();
363   Category second = AddUnusedRemoteCategory();
364 
365   ASSERT_TRUE(CompareCategories(first, second));
366   NotifyOnCategoryDismissed(first);
367   EXPECT_FALSE(CompareCategories(first, second));
368 }
369 
TEST_F(ClickBasedCategoryRankerTest,ShouldNotMoveCategoryTooMuchDownWhenDismissed)370 TEST_F(ClickBasedCategoryRankerTest,
371        ShouldNotMoveCategoryTooMuchDownWhenDismissed) {
372   // Add enough categories to the end.
373   std::vector<Category> categories;
374   const int penalty = ClickBasedCategoryRanker::GetDismissedCategoryPenalty();
375   for (int i = 0; i < 2 * penalty + 10; ++i) {
376     categories.push_back(AddUnusedRemoteCategory());
377   }
378 
379   const int target = penalty + 1;
380   Category target_category = categories[target];
381   for (int i = 0; i < static_cast<int>(categories.size()); ++i) {
382     ASSERT_EQ(i < target, CompareCategories(categories[i], target_category));
383   }
384 
385   // This should move exactly |penalty| categories up.
386   NotifyOnCategoryDismissed(categories[target]);
387 
388   // Reflect expected change in |categories|.
389   const int expected = target + penalty;
390   for (int i = target; i + 1 <= expected; ++i) {
391     std::swap(categories[i], categories[i + 1]);
392   }
393 
394   for (int i = 0; i < static_cast<int>(categories.size()); ++i) {
395     EXPECT_EQ(i < expected, CompareCategories(categories[i], target_category));
396   }
397 }
398 
TEST_F(ClickBasedCategoryRankerTest,ShouldNotChangeOrderOfOtherCategoriesWhenDismissed)399 TEST_F(ClickBasedCategoryRankerTest,
400        ShouldNotChangeOrderOfOtherCategoriesWhenDismissed) {
401   // Add enough categories to the end.
402   std::vector<Category> categories;
403   const int penalty = ClickBasedCategoryRanker::GetDismissedCategoryPenalty();
404   for (int i = 0; i < 2 * penalty + 10; ++i) {
405     categories.push_back(AddUnusedRemoteCategory());
406   }
407 
408   int target = penalty + 1;
409   // This should not change order of all other categories.
410   NotifyOnCategoryDismissed(categories[target]);
411 
412   categories.erase(categories.begin() + target);
413   for (int first = 0; first < static_cast<int>(categories.size()); ++first) {
414     for (int second = 0; second < static_cast<int>(categories.size());
415          ++second) {
416       EXPECT_EQ(first < second,
417                 CompareCategories(categories[first], categories[second]));
418     }
419   }
420 }
421 
TEST_F(ClickBasedCategoryRankerTest,ShouldNotMoveLastCategoryWhenDismissed)422 TEST_F(ClickBasedCategoryRankerTest, ShouldNotMoveLastCategoryWhenDismissed) {
423   Category first = AddUnusedRemoteCategory();
424   Category second = AddUnusedRemoteCategory();
425 
426   ASSERT_TRUE(CompareCategories(first, second));
427   NotifyOnCategoryDismissed(second);
428   EXPECT_TRUE(CompareCategories(first, second));
429 }
430 
TEST_F(ClickBasedCategoryRankerTest,ShouldRestoreDefaultOrderOnClearHistory)431 TEST_F(ClickBasedCategoryRankerTest, ShouldRestoreDefaultOrderOnClearHistory) {
432   std::vector<KnownCategories> default_order =
433       ConstantCategoryRanker::GetKnownCategoriesDefaultOrder();
434   Category first = Category::FromKnownCategory(default_order[0]);
435   Category second = Category::FromKnownCategory(default_order[1]);
436 
437   ASSERT_TRUE(CompareCategories(first, second));
438 
439   // Change the order.
440   while (CompareCategories(first, second)) {
441     NotifyOnSuggestionOpened(
442         /*times=*/ClickBasedCategoryRanker::GetPassingMargin(), second);
443   }
444 
445   ASSERT_FALSE(CompareCategories(first, second));
446 
447   // The user clears history.
448   ranker()->ClearHistory(/*begin=*/base::Time(),
449                          /*end=*/base::Time::Max());
450 
451   // The default order must be restored.
452   EXPECT_TRUE(CompareCategories(first, second));
453 }
454 
TEST_F(ClickBasedCategoryRankerTest,ShouldPreserveRemoteCategoriesOnClearHistory)455 TEST_F(ClickBasedCategoryRankerTest,
456        ShouldPreserveRemoteCategoriesOnClearHistory) {
457   Category first = AddUnusedRemoteCategory();
458   Category second = AddUnusedRemoteCategory();
459 
460   ASSERT_TRUE(CompareCategories(first, second));
461 
462   // The user clears history.
463   ranker()->ClearHistory(/*begin=*/base::Time(),
464                          /*end=*/base::Time::Max());
465 
466   // The order does not matter, but the ranker should not die.
467   CompareCategories(first, second);
468 }
469 
TEST_F(ClickBasedCategoryRankerTest,ShouldIgnorePartialClearHistory)470 TEST_F(ClickBasedCategoryRankerTest, ShouldIgnorePartialClearHistory) {
471   Category first = AddUnusedRemoteCategory();
472   Category second = AddUnusedRemoteCategory();
473 
474   ASSERT_TRUE(CompareCategories(first, second));
475 
476   // Change the order.
477   while (CompareCategories(first, second)) {
478     NotifyOnSuggestionOpened(
479         /*times=*/ClickBasedCategoryRanker::GetPassingMargin(), second);
480   }
481 
482   ASSERT_FALSE(CompareCategories(first, second));
483 
484   // The user partially clears history.
485   base::Time begin = base::Time::Now() - base::TimeDelta::FromHours(1),
486              end = base::Time::Max();
487   ranker()->ClearHistory(begin, end);
488 
489   // The order should not be cleared.
490   EXPECT_FALSE(CompareCategories(first, second));
491 }
492 
TEST_F(ClickBasedCategoryRankerTest,ShouldEmitNewIndexWhenCategoryMovedUpDueToClick)493 TEST_F(ClickBasedCategoryRankerTest,
494        ShouldEmitNewIndexWhenCategoryMovedUpDueToClick) {
495   base::HistogramTester histogram_tester;
496 
497   std::vector<KnownCategories> default_order =
498       ConstantCategoryRanker::GetKnownCategoriesDefaultOrder();
499   Category first = Category::FromKnownCategory(default_order[0]);
500   Category second = Category::FromKnownCategory(default_order[1]);
501 
502   ASSERT_TRUE(CompareCategories(first, second));
503 
504   // Increase the score of |second| until the order changes.
505   while (CompareCategories(first, second)) {
506     EXPECT_THAT(
507         histogram_tester.GetAllSamples(kHistogramMovedUpCategoryNewIndex),
508         IsEmpty());
509     ranker()->OnSuggestionOpened(second);
510   }
511   ASSERT_FALSE(CompareCategories(first, second));
512   EXPECT_THAT(histogram_tester.GetAllSamples(kHistogramMovedUpCategoryNewIndex),
513               ElementsAre(base::Bucket(/*min=*/0, /*count=*/1)));
514 }
515 
TEST_F(ClickBasedCategoryRankerTest,ShouldNotEmitNewIndexWhenCategoryDismissed)516 TEST_F(ClickBasedCategoryRankerTest,
517        ShouldNotEmitNewIndexWhenCategoryDismissed) {
518   base::HistogramTester histogram_tester;
519 
520   std::vector<KnownCategories> default_order =
521       ConstantCategoryRanker::GetKnownCategoriesDefaultOrder();
522   Category category = Category::FromKnownCategory(default_order[0]);
523 
524   ASSERT_THAT(histogram_tester.GetAllSamples(kHistogramMovedUpCategoryNewIndex),
525               IsEmpty());
526 
527   NotifyOnCategoryDismissed(category);
528 
529   EXPECT_THAT(histogram_tester.GetAllSamples(kHistogramMovedUpCategoryNewIndex),
530               IsEmpty());
531 }
532 
TEST_F(ClickBasedCategoryRankerTest,ShouldNotEmitNewIndexOfMovedUpCategoryWhenHistoryCleared)533 TEST_F(ClickBasedCategoryRankerTest,
534        ShouldNotEmitNewIndexOfMovedUpCategoryWhenHistoryCleared) {
535   std::vector<KnownCategories> default_order =
536       ConstantCategoryRanker::GetKnownCategoriesDefaultOrder();
537   Category first = Category::FromKnownCategory(default_order[0]);
538   Category second = Category::FromKnownCategory(default_order[1]);
539 
540   ASSERT_TRUE(CompareCategories(first, second));
541 
542   // Increase the score of |second| until the order changes.
543   while (CompareCategories(first, second)) {
544     ranker()->OnSuggestionOpened(second);
545   }
546   ASSERT_FALSE(CompareCategories(first, second));
547 
548   // The histogram tester is created here to ignore previous events.
549   base::HistogramTester histogram_tester;
550   ranker()->ClearHistory(/*begin=*/base::Time(),
551                          /*end=*/base::Time::Max());
552 
553   // ClearHistory should restore the default order.
554   ASSERT_TRUE(CompareCategories(first, second));
555 
556   EXPECT_THAT(histogram_tester.GetAllSamples(kHistogramMovedUpCategoryNewIndex),
557               IsEmpty());
558 }
559 
TEST_F(ClickBasedCategoryRankerTest,ShouldInsertCategoryBeforeSelectedCategory)560 TEST_F(ClickBasedCategoryRankerTest,
561        ShouldInsertCategoryBeforeSelectedCategory) {
562   std::vector<KnownCategories> default_order =
563       ConstantCategoryRanker::GetKnownCategoriesDefaultOrder();
564   Category first = Category::FromKnownCategory(default_order[0]);
565   Category second = Category::FromKnownCategory(default_order[1]);
566 
567   ASSERT_TRUE(CompareCategories(first, second));
568 
569   Category inserted = GetUnusedRemoteCategory();
570 
571   ranker()->InsertCategoryBeforeIfNecessary(inserted, second);
572   EXPECT_TRUE(CompareCategories(first, inserted));
573   EXPECT_TRUE(CompareCategories(inserted, second));
574 }
575 
TEST_F(ClickBasedCategoryRankerTest,ShouldInsertMultipleCategoriesBeforeSelectedCategory)576 TEST_F(ClickBasedCategoryRankerTest,
577        ShouldInsertMultipleCategoriesBeforeSelectedCategory) {
578   std::vector<KnownCategories> default_order =
579       ConstantCategoryRanker::GetKnownCategoriesDefaultOrder();
580   Category first = Category::FromKnownCategory(default_order[0]);
581   Category second = Category::FromKnownCategory(default_order[1]);
582 
583   ASSERT_TRUE(CompareCategories(first, second));
584 
585   Category first_inserted = GetUnusedRemoteCategory();
586   Category second_inserted = GetUnusedRemoteCategory();
587 
588   ranker()->InsertCategoryBeforeIfNecessary(first_inserted, second);
589   ranker()->InsertCategoryBeforeIfNecessary(second_inserted, second);
590   EXPECT_TRUE(CompareCategories(first, first_inserted));
591   EXPECT_TRUE(CompareCategories(first_inserted, second_inserted));
592   EXPECT_TRUE(CompareCategories(second_inserted, second));
593 }
594 
TEST_F(ClickBasedCategoryRankerTest,ShouldInsertCategoryBeforeFirstCategory)595 TEST_F(ClickBasedCategoryRankerTest, ShouldInsertCategoryBeforeFirstCategory) {
596   std::vector<KnownCategories> default_order =
597       ConstantCategoryRanker::GetKnownCategoriesDefaultOrder();
598   Category first = Category::FromKnownCategory(default_order[0]);
599   Category inserted = GetUnusedRemoteCategory();
600 
601   ranker()->InsertCategoryBeforeIfNecessary(inserted, first);
602   EXPECT_TRUE(CompareCategories(inserted, first));
603 }
604 
TEST_F(ClickBasedCategoryRankerTest,ShouldInsertCategoryBeforeRemoteCategory)605 TEST_F(ClickBasedCategoryRankerTest, ShouldInsertCategoryBeforeRemoteCategory) {
606   std::vector<KnownCategories> default_order =
607       ConstantCategoryRanker::GetKnownCategoriesDefaultOrder();
608   Category remote = AddUnusedRemoteCategory();
609   Category inserted = GetUnusedRemoteCategory();
610 
611   ranker()->InsertCategoryBeforeIfNecessary(inserted, remote);
612   EXPECT_TRUE(CompareCategories(inserted, remote));
613 }
614 
TEST_F(ClickBasedCategoryRankerTest,ShouldNotChangeRemainingOrderWhenInsertingBeforeCategory)615 TEST_F(ClickBasedCategoryRankerTest,
616        ShouldNotChangeRemainingOrderWhenInsertingBeforeCategory) {
617   std::vector<KnownCategories> default_order =
618       ConstantCategoryRanker::GetKnownCategoriesDefaultOrder();
619   Category anchor = Category::FromKnownCategory(default_order[0]);
620   Category inserted = GetUnusedRemoteCategory();
621 
622   ranker()->InsertCategoryBeforeIfNecessary(inserted, anchor);
623   std::vector<Category> converted_categories =
624       ConvertKnownCategories(default_order);
625   for (size_t i = 0; i + 1 < converted_categories.size(); ++i) {
626     EXPECT_TRUE(CompareCategories(converted_categories[i],
627                                   converted_categories[i + 1]));
628   }
629 }
630 
TEST_F(ClickBasedCategoryRankerTest,ShouldInsertCategoriesBeforeAndAfterSameCategory)631 TEST_F(ClickBasedCategoryRankerTest,
632        ShouldInsertCategoriesBeforeAndAfterSameCategory) {
633   std::vector<KnownCategories> default_order =
634       ConstantCategoryRanker::GetKnownCategoriesDefaultOrder();
635   Category first = Category::FromKnownCategory(default_order[0]);
636   Category second = Category::FromKnownCategory(default_order[1]);
637   ASSERT_TRUE(CompareCategories(first, second));
638 
639   Category first_before = GetUnusedRemoteCategory();
640   ranker()->InsertCategoryBeforeIfNecessary(first_before, second);
641 
642   Category first_after = GetUnusedRemoteCategory();
643   ranker()->InsertCategoryAfterIfNecessary(first_after, second);
644 
645   Category second_before = GetUnusedRemoteCategory();
646   ranker()->InsertCategoryBeforeIfNecessary(second_before, second);
647 
648   Category second_after = GetUnusedRemoteCategory();
649   ranker()->InsertCategoryAfterIfNecessary(second_after, second);
650 
651   EXPECT_TRUE(CompareCategories(first_before, second_before));
652   EXPECT_TRUE(CompareCategories(second_before, second));
653   EXPECT_TRUE(CompareCategories(second, second_after));
654   EXPECT_TRUE(CompareCategories(second_after, first_after));
655 }
656 
TEST_F(ClickBasedCategoryRankerTest,ShouldInsertCategoriesBeforeAndAfterDifferentCategories)657 TEST_F(ClickBasedCategoryRankerTest,
658        ShouldInsertCategoriesBeforeAndAfterDifferentCategories) {
659   std::vector<KnownCategories> default_order =
660       ConstantCategoryRanker::GetKnownCategoriesDefaultOrder();
661   Category first = Category::FromKnownCategory(default_order[0]);
662   Category second = Category::FromKnownCategory(default_order[1]);
663   ASSERT_TRUE(CompareCategories(first, second));
664 
665   Category first_before = GetUnusedRemoteCategory();
666   ranker()->InsertCategoryBeforeIfNecessary(first_before, second);
667 
668   Category first_after = GetUnusedRemoteCategory();
669   ranker()->InsertCategoryAfterIfNecessary(first_after, first);
670 
671   Category second_before = GetUnusedRemoteCategory();
672   ranker()->InsertCategoryBeforeIfNecessary(second_before, second);
673 
674   Category second_after = GetUnusedRemoteCategory();
675   ranker()->InsertCategoryAfterIfNecessary(second_after, first);
676 
677   EXPECT_TRUE(CompareCategories(first, second_after));
678   EXPECT_TRUE(CompareCategories(second_after, first_after));
679   EXPECT_TRUE(CompareCategories(first_after, first_before));
680   EXPECT_TRUE(CompareCategories(first_before, second_before));
681   EXPECT_TRUE(CompareCategories(second_before, second));
682 }
683 
TEST_F(ClickBasedCategoryRankerTest,ShouldNotEmitNewIndexWhenCategoryInserted)684 TEST_F(ClickBasedCategoryRankerTest,
685        ShouldNotEmitNewIndexWhenCategoryInserted) {
686   base::HistogramTester histogram_tester;
687 
688   std::vector<KnownCategories> default_order =
689       ConstantCategoryRanker::GetKnownCategoriesDefaultOrder();
690   Category first = Category::FromKnownCategory(default_order[0]);
691 
692   ASSERT_THAT(histogram_tester.GetAllSamples(kHistogramMovedUpCategoryNewIndex),
693               IsEmpty());
694 
695   Category before = GetUnusedRemoteCategory();
696   ranker()->InsertCategoryBeforeIfNecessary(before, first);
697 
698   Category after = GetUnusedRemoteCategory();
699   ranker()->InsertCategoryAfterIfNecessary(after, first);
700 
701   EXPECT_THAT(histogram_tester.GetAllSamples(kHistogramMovedUpCategoryNewIndex),
702               IsEmpty());
703 }
704 
705 // TODO(vitaliii): Reuse these tests for ConstantCategoryRanker.
TEST_F(ClickBasedCategoryRankerTest,ShouldInsertCategoryAfterSelectedCategory)706 TEST_F(ClickBasedCategoryRankerTest,
707        ShouldInsertCategoryAfterSelectedCategory) {
708   std::vector<KnownCategories> default_order =
709       ConstantCategoryRanker::GetKnownCategoriesDefaultOrder();
710   Category first = Category::FromKnownCategory(default_order[0]);
711   Category second = Category::FromKnownCategory(default_order[1]);
712 
713   ASSERT_TRUE(CompareCategories(first, second));
714 
715   Category inserted = GetUnusedRemoteCategory();
716 
717   ranker()->InsertCategoryAfterIfNecessary(inserted, first);
718   EXPECT_TRUE(CompareCategories(first, inserted));
719   EXPECT_TRUE(CompareCategories(inserted, second));
720 }
721 
TEST_F(ClickBasedCategoryRankerTest,ShouldInsertMultipleCategoriesAfterSelectedCategory)722 TEST_F(ClickBasedCategoryRankerTest,
723        ShouldInsertMultipleCategoriesAfterSelectedCategory) {
724   std::vector<KnownCategories> default_order =
725       ConstantCategoryRanker::GetKnownCategoriesDefaultOrder();
726   Category first = Category::FromKnownCategory(default_order[0]);
727   Category second = Category::FromKnownCategory(default_order[1]);
728 
729   ASSERT_TRUE(CompareCategories(first, second));
730 
731   Category first_inserted = GetUnusedRemoteCategory();
732   Category second_inserted = GetUnusedRemoteCategory();
733 
734   ranker()->InsertCategoryAfterIfNecessary(first_inserted, first);
735   ranker()->InsertCategoryAfterIfNecessary(second_inserted, first);
736   EXPECT_TRUE(CompareCategories(first, second_inserted));
737   EXPECT_TRUE(CompareCategories(second_inserted, first_inserted));
738   EXPECT_TRUE(CompareCategories(first_inserted, second));
739 }
740 
TEST_F(ClickBasedCategoryRankerTest,ShouldInsertCategoryAfterLastCategory)741 TEST_F(ClickBasedCategoryRankerTest, ShouldInsertCategoryAfterLastCategory) {
742   Category last = AddUnusedRemoteCategory();
743   Category inserted = GetUnusedRemoteCategory();
744 
745   ranker()->InsertCategoryAfterIfNecessary(inserted, last);
746   EXPECT_TRUE(CompareCategories(last, inserted));
747 }
748 
TEST_F(ClickBasedCategoryRankerTest,ShouldNotChangeRemainingOrderWhenInsertingAfterCategory)749 TEST_F(ClickBasedCategoryRankerTest,
750        ShouldNotChangeRemainingOrderWhenInsertingAfterCategory) {
751   std::vector<KnownCategories> default_order =
752       ConstantCategoryRanker::GetKnownCategoriesDefaultOrder();
753   Category anchor = Category::FromKnownCategory(default_order[0]);
754   Category inserted = GetUnusedRemoteCategory();
755 
756   ranker()->InsertCategoryAfterIfNecessary(inserted, anchor);
757   std::vector<Category> converted_categories =
758       ConvertKnownCategories(default_order);
759   for (size_t i = 0; i + 1 < converted_categories.size(); ++i) {
760     EXPECT_TRUE(CompareCategories(converted_categories[i],
761                                   converted_categories[i + 1]));
762   }
763 }
764 
TEST_F(ClickBasedCategoryRankerTest,ShouldAssignScoreToInsertedCategoriesBasedOnAnchor)765 TEST_F(ClickBasedCategoryRankerTest,
766        ShouldAssignScoreToInsertedCategoriesBasedOnAnchor) {
767   Category anchor = AddUnusedRemoteCategory();
768   NotifyOnSuggestionOpened(/*times=*/25, anchor);
769 
770   Category inserted_before = GetUnusedRemoteCategory();
771   ranker()->InsertCategoryBeforeIfNecessary(inserted_before, anchor);
772 
773   Category inserted_after = GetUnusedRemoteCategory();
774   ranker()->InsertCategoryAfterIfNecessary(inserted_after, anchor);
775 
776   Category tester = AddUnusedRemoteCategory();
777   NotifyOnSuggestionOpened(/*times=*/20, tester);
778   EXPECT_TRUE(CompareCategories(inserted_before, tester));
779   EXPECT_TRUE(CompareCategories(inserted_after, tester));
780 }
781 
782 }  // namespace ntp_snippets
783