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