| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/ntp_snippets/category_rankers/click_based_category_ranker.h" |
| |
| #include <utility> |
| |
| #include "base/strings/string_number_conversions.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/simple_test_clock.h" |
| #include "base/time/default_clock.h" |
| #include "base/time/time.h" |
| #include "components/ntp_snippets/category.h" |
| #include "components/ntp_snippets/category_rankers/constant_category_ranker.h" |
| #include "components/ntp_snippets/features.h" |
| #include "components/ntp_snippets/ntp_snippets_constants.h" |
| #include "components/ntp_snippets/time_serialization.h" |
| #include "components/prefs/testing_pref_service.h" |
| #include "components/variations/variations_params_manager.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using testing::ElementsAre; |
| using testing::IsEmpty; |
| |
| namespace ntp_snippets { |
| |
| namespace { |
| |
| const char kHistogramMovedUpCategoryNewIndex[] = |
| "NewTabPage.ContentSuggestions.MovedUpCategoryNewIndex"; |
| |
| } // namespace |
| |
| class ClickBasedCategoryRankerTest : public testing::Test { |
| public: |
| ClickBasedCategoryRankerTest() |
| : pref_service_(std::make_unique<TestingPrefServiceSimple>()), |
| unused_remote_category_id_( |
| static_cast<int>(KnownCategories::LAST_KNOWN_REMOTE_CATEGORY) + 1) { |
| ClickBasedCategoryRanker::RegisterProfilePrefs(pref_service_->registry()); |
| |
| ranker_ = std::make_unique<ClickBasedCategoryRanker>( |
| pref_service_.get(), base::DefaultClock::GetInstance()); |
| } |
| |
| int GetUnusedRemoteCategoryID() { return unused_remote_category_id_++; } |
| |
| Category GetUnusedRemoteCategory() { |
| return Category::FromIDValue(GetUnusedRemoteCategoryID()); |
| } |
| |
| bool CompareCategories(const Category& left, const Category& right) { |
| return ranker()->Compare(left, right); |
| } |
| |
| Category AddUnusedRemoteCategory() { |
| Category category = GetUnusedRemoteCategory(); |
| ranker()->AppendCategoryIfNecessary(category); |
| return category; |
| } |
| |
| void AddUnusedRemoteCategories(int quantity) { |
| for (int i = 0; i < quantity; ++i) { |
| AddUnusedRemoteCategory(); |
| } |
| } |
| |
| void ResetRanker(base::Clock* clock) { |
| ranker_ = |
| std::make_unique<ClickBasedCategoryRanker>(pref_service_.get(), clock); |
| } |
| |
| void NotifyOnSuggestionOpened(int times, Category category) { |
| for (int i = 0; i < times; ++i) { |
| ranker()->OnSuggestionOpened(category); |
| } |
| } |
| |
| void NotifyOnCategoryDismissed(Category category) { |
| ranker()->OnCategoryDismissed(category); |
| } |
| |
| void SetDismissedCategoryPenaltyVariationParam(int value) { |
| variation_params_manager_.SetVariationParamsWithFeatureAssociations( |
| kCategoryRanker.name, |
| {{"click_based_category_ranker-dismissed_category_penalty", |
| base::IntToString(value)}}, |
| {kCategoryRanker.name}); |
| } |
| |
| void SetPromotedCategoryVariationParam(int value) { |
| variation_params_manager_.SetVariationParamsWithFeatureAssociations( |
| kCategoryRanker.name, |
| {{"click_based_category_ranker-promoted_category", |
| base::IntToString(value)}}, |
| {kCategoryRanker.name}); |
| } |
| |
| std::vector<Category> ConvertKnownCategories( |
| std::vector<KnownCategories> known_categories) { |
| std::vector<Category> converted; |
| for (auto known : known_categories) { |
| converted.push_back(Category::FromKnownCategory(known)); |
| } |
| return converted; |
| } |
| |
| ClickBasedCategoryRanker* ranker() { return ranker_.get(); } |
| |
| private: |
| std::unique_ptr<TestingPrefServiceSimple> pref_service_; |
| int unused_remote_category_id_; |
| std::unique_ptr<ClickBasedCategoryRanker> ranker_; |
| variations::testing::VariationParamsManager variation_params_manager_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ClickBasedCategoryRankerTest); |
| }; |
| |
| TEST_F(ClickBasedCategoryRankerTest, ShouldSortRemoteCategoriesByWhenAdded) { |
| const Category first = GetUnusedRemoteCategory(); |
| const Category second = GetUnusedRemoteCategory(); |
| // Categories are added in decreasing id order to test that they are not |
| // compared by id. |
| ranker()->AppendCategoryIfNecessary(second); |
| ranker()->AppendCategoryIfNecessary(first); |
| EXPECT_TRUE(CompareCategories(second, first)); |
| EXPECT_FALSE(CompareCategories(first, second)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, ShouldSortLocalCategoriesBeforeRemote) { |
| const Category remote_category = AddUnusedRemoteCategory(); |
| const Category local_category = |
| Category::FromKnownCategory(KnownCategories::BOOKMARKS); |
| EXPECT_TRUE(CompareCategories(local_category, remote_category)); |
| EXPECT_FALSE(CompareCategories(remote_category, local_category)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, |
| CompareShouldReturnFalseForSameCategories) { |
| const Category remote_category = AddUnusedRemoteCategory(); |
| EXPECT_FALSE(CompareCategories(remote_category, remote_category)); |
| |
| const Category local_category = |
| Category::FromKnownCategory(KnownCategories::BOOKMARKS); |
| EXPECT_FALSE(CompareCategories(local_category, local_category)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, |
| AddingMoreRemoteCategoriesShouldNotChangePreviousOrder) { |
| AddUnusedRemoteCategories(3); |
| |
| Category first = AddUnusedRemoteCategory(); |
| Category second = AddUnusedRemoteCategory(); |
| |
| ASSERT_TRUE(CompareCategories(first, second)); |
| ASSERT_FALSE(CompareCategories(second, first)); |
| |
| AddUnusedRemoteCategories(3); |
| |
| EXPECT_TRUE(CompareCategories(first, second)); |
| EXPECT_FALSE(CompareCategories(second, first)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, ShouldChangeOrderOfNonTopCategories) { |
| // Add dummy remote categories to ensure that the following categories are not |
| // in the top anymore. |
| AddUnusedRemoteCategories( |
| ClickBasedCategoryRanker::GetNumTopCategoriesWithExtraMargin()); |
| |
| Category first = AddUnusedRemoteCategory(); |
| Category second = AddUnusedRemoteCategory(); |
| |
| ASSERT_TRUE(CompareCategories(first, second)); |
| ASSERT_FALSE(CompareCategories(second, first)); |
| |
| NotifyOnSuggestionOpened( |
| /*times=*/ClickBasedCategoryRanker::GetPassingMargin(), second); |
| |
| EXPECT_TRUE(CompareCategories(second, first)); |
| EXPECT_FALSE(CompareCategories(first, second)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, |
| ShouldNotChangeOrderRightAfterOrderChange) { |
| // Add dummy remote categories to ensure that the following categories are not |
| // in the top anymore. |
| AddUnusedRemoteCategories( |
| ClickBasedCategoryRanker::GetNumTopCategoriesWithExtraMargin()); |
| |
| // Two non-top categories are added. |
| Category first = AddUnusedRemoteCategory(); |
| Category second = AddUnusedRemoteCategory(); |
| ASSERT_TRUE(CompareCategories(first, second)); |
| ASSERT_FALSE(CompareCategories(second, first)); |
| // Their order is changed. |
| NotifyOnSuggestionOpened( |
| /*times=*/ClickBasedCategoryRanker::GetPassingMargin(), second); |
| ASSERT_TRUE(CompareCategories(second, first)); |
| ASSERT_FALSE(CompareCategories(first, second)); |
| |
| // Click on the lower category. |
| NotifyOnSuggestionOpened(/*times=*/1, first); |
| |
| // Order should not change. |
| EXPECT_TRUE(CompareCategories(second, first)); |
| EXPECT_FALSE(CompareCategories(first, second)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, |
| ShouldNotMoveCategoryMoreThanOncePerClick) { |
| // Add dummy remote categories to ensure that the following categories are not |
| // in the top anymore. |
| AddUnusedRemoteCategories( |
| ClickBasedCategoryRanker::GetNumTopCategoriesWithExtraMargin()); |
| |
| // Non-top categories are added. |
| Category first = AddUnusedRemoteCategory(); |
| Category second = AddUnusedRemoteCategory(); |
| Category third = AddUnusedRemoteCategory(); |
| |
| // Move the third category up. |
| NotifyOnSuggestionOpened( |
| /*times=*/ClickBasedCategoryRanker::GetPassingMargin(), third); |
| EXPECT_TRUE(CompareCategories(third, second)); |
| // But only on one position even though the first category has low counts. |
| EXPECT_TRUE(CompareCategories(first, third)); |
| // However, another click must move it further. |
| NotifyOnSuggestionOpened(/*times=*/1, third); |
| EXPECT_TRUE(CompareCategories(third, first)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, |
| ShouldNotMoveTopCategoryRightAfterThreshold) { |
| ASSERT_GE(ClickBasedCategoryRanker::GetNumTopCategoriesWithExtraMargin(), 1); |
| |
| // At least one top category is added from the default order. |
| std::vector<KnownCategories> default_order = |
| ConstantCategoryRanker::GetKnownCategoriesDefaultOrder(); |
| Category first = Category::FromKnownCategory(default_order[0]); |
| Category second = Category::FromKnownCategory(default_order[1]); |
| |
| // Try to move the second category up as if the first category was non-top. |
| NotifyOnSuggestionOpened( |
| /*times=*/ClickBasedCategoryRanker::GetPassingMargin(), second); |
| |
| // Nothing should change, because the first category is top. |
| EXPECT_TRUE(CompareCategories(first, second)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, ShouldPersistOrderAndClicksWhenRestarted) { |
| // Add dummy remote categories to ensure that the following categories are not |
| // in the top anymore. |
| AddUnusedRemoteCategories( |
| ClickBasedCategoryRanker::GetNumTopCategoriesWithExtraMargin()); |
| |
| // Non-top categories are added. |
| Category first = AddUnusedRemoteCategory(); |
| Category second = AddUnusedRemoteCategory(); |
| Category third = AddUnusedRemoteCategory(); |
| |
| // Change the order. |
| NotifyOnSuggestionOpened( |
| /*times=*/ClickBasedCategoryRanker::GetPassingMargin(), third); |
| ASSERT_TRUE(CompareCategories(third, second)); |
| ASSERT_TRUE(CompareCategories(first, third)); |
| |
| // Simulate Chrome restart. |
| ResetRanker(base::DefaultClock::GetInstance()); |
| |
| // The old order must be preserved. |
| EXPECT_TRUE(CompareCategories(third, second)); |
| |
| // Clicks must be preserved as well. |
| NotifyOnSuggestionOpened(/*times=*/1, third); |
| EXPECT_TRUE(CompareCategories(third, first)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, ShouldDecayClickCountsWithTime) { |
| // Add dummy remote categories to ensure that the following categories are not |
| // in the top anymore. |
| AddUnusedRemoteCategories( |
| ClickBasedCategoryRanker::GetNumTopCategoriesWithExtraMargin()); |
| |
| // Non-top categories are added. |
| Category first = AddUnusedRemoteCategory(); |
| Category second = AddUnusedRemoteCategory(); |
| |
| const int first_clicks = 10 * ClickBasedCategoryRanker::GetPassingMargin(); |
| |
| // Simulate the user using the first category for a long time (and not using |
| // anything else). |
| NotifyOnSuggestionOpened(/*times=*/first_clicks, first); |
| |
| // Let multiple years pass by. |
| base::SimpleTestClock test_clock; |
| test_clock.SetNow(base::Time::Now() + base::TimeDelta::FromDays(1000)); |
| // Reset the ranker to pick up the new clock. |
| ResetRanker(&test_clock); |
| |
| // The user behavior changes and they start using the second category instead. |
| // According to our requirenments after such a long time it should take less |
| // than |first_clicks| for the second category to outperform the first one. |
| int second_clicks = 0; |
| while (CompareCategories(first, second) && second_clicks < first_clicks) { |
| NotifyOnSuggestionOpened(/*times=*/1, second); |
| second_clicks++; |
| } |
| EXPECT_THAT(second_clicks, testing::Lt(first_clicks)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, ShouldDecayAfterClearHistory) { |
| std::vector<KnownCategories> default_order = |
| ConstantCategoryRanker::GetKnownCategoriesDefaultOrder(); |
| Category first = Category::FromKnownCategory(default_order[0]); |
| Category second = Category::FromKnownCategory(default_order[1]); |
| |
| // The user clears entire history. |
| ranker()->ClearHistory(/*begin=*/base::Time(), |
| /*end=*/base::Time::Max()); |
| |
| // Check whether decay happens by clicking on the first category and |
| // waiting. |
| const int first_clicks = 10 * ClickBasedCategoryRanker::GetPassingMargin(); |
| NotifyOnSuggestionOpened(/*times=*/first_clicks, first); |
| |
| // Let multiple years pass by. |
| base::SimpleTestClock test_clock; |
| test_clock.SetNow(base::Time::Now() + base::TimeDelta::FromDays(1000)); |
| // Reset the ranker to pick up the new clock. |
| ResetRanker(&test_clock); |
| |
| // It should take less than |first_clicks| for the second category to |
| // overtake because of decays. |
| int second_clicks = 0; |
| while (CompareCategories(first, second) && second_clicks < first_clicks) { |
| NotifyOnSuggestionOpened(/*times=*/1, second); |
| second_clicks++; |
| } |
| EXPECT_THAT(second_clicks, testing::Lt(first_clicks)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, ShouldRemoveLastDecayTimeOnClearHistory) { |
| ASSERT_NE(ranker()->GetLastDecayTime(), DeserializeTime(0)); |
| |
| // The user clears entire history. |
| ranker()->ClearHistory(/*begin=*/base::Time(), |
| /*end=*/base::Time::Max()); |
| |
| EXPECT_EQ(ranker()->GetLastDecayTime(), DeserializeTime(0)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, ShouldPersistLastDecayTimeWhenRestarted) { |
| base::Time before = ranker()->GetLastDecayTime(); |
| ASSERT_NE(before, DeserializeTime(0)); |
| |
| // Ensure that |Now()| is different from |before| by injecting our clock. |
| base::SimpleTestClock test_clock; |
| test_clock.SetNow(base::Time::Now() + base::TimeDelta::FromSeconds(10)); |
| ResetRanker(&test_clock); |
| |
| EXPECT_EQ(before, ranker()->GetLastDecayTime()); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, ShouldMoveCategoryDownWhenDismissed) { |
| SetDismissedCategoryPenaltyVariationParam(2); |
| |
| // Take top categories. |
| std::vector<KnownCategories> default_order = |
| ConstantCategoryRanker::GetKnownCategoriesDefaultOrder(); |
| Category first = Category::FromKnownCategory(default_order[0]); |
| Category second = Category::FromKnownCategory(default_order[1]); |
| |
| ASSERT_TRUE(CompareCategories(first, second)); |
| NotifyOnCategoryDismissed(first); |
| EXPECT_FALSE(CompareCategories(first, second)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, |
| ShouldMoveSecondToLastCategoryDownWhenDismissed) { |
| SetDismissedCategoryPenaltyVariationParam(2); |
| |
| // Add categories to the bottom. |
| Category first = AddUnusedRemoteCategory(); |
| Category second = AddUnusedRemoteCategory(); |
| |
| ASSERT_TRUE(CompareCategories(first, second)); |
| NotifyOnCategoryDismissed(first); |
| EXPECT_FALSE(CompareCategories(first, second)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, |
| ShouldNotMoveCategoryTooMuchDownWhenDismissed) { |
| SetDismissedCategoryPenaltyVariationParam(2); |
| |
| // Add enough categories to the end. |
| std::vector<Category> categories; |
| const int penalty = ClickBasedCategoryRanker::GetDismissedCategoryPenalty(); |
| for (int i = 0; i < 2 * penalty + 10; ++i) { |
| categories.push_back(AddUnusedRemoteCategory()); |
| } |
| |
| const int target = penalty + 1; |
| Category target_category = categories[target]; |
| for (int i = 0; i < static_cast<int>(categories.size()); ++i) { |
| ASSERT_EQ(i < target, CompareCategories(categories[i], target_category)); |
| } |
| |
| // This should move exactly |penalty| categories up. |
| NotifyOnCategoryDismissed(categories[target]); |
| |
| // Reflect expected change in |categories|. |
| const int expected = target + penalty; |
| for (int i = target; i + 1 <= expected; ++i) { |
| std::swap(categories[i], categories[i + 1]); |
| } |
| |
| for (int i = 0; i < static_cast<int>(categories.size()); ++i) { |
| EXPECT_EQ(i < expected, CompareCategories(categories[i], target_category)); |
| } |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, |
| ShouldNotChangeOrderOfOtherCategoriesWhenDismissed) { |
| SetDismissedCategoryPenaltyVariationParam(2); |
| |
| // Add enough categories to the end. |
| std::vector<Category> categories; |
| const int penalty = ClickBasedCategoryRanker::GetDismissedCategoryPenalty(); |
| for (int i = 0; i < 2 * penalty + 10; ++i) { |
| categories.push_back(AddUnusedRemoteCategory()); |
| } |
| |
| int target = penalty + 1; |
| // This should not change order of all other categories. |
| NotifyOnCategoryDismissed(categories[target]); |
| |
| categories.erase(categories.begin() + target); |
| for (int first = 0; first < static_cast<int>(categories.size()); ++first) { |
| for (int second = 0; second < static_cast<int>(categories.size()); |
| ++second) { |
| EXPECT_EQ(first < second, |
| CompareCategories(categories[first], categories[second])); |
| } |
| } |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, ShouldNotMoveLastCategoryWhenDismissed) { |
| SetDismissedCategoryPenaltyVariationParam(2); |
| |
| Category first = AddUnusedRemoteCategory(); |
| Category second = AddUnusedRemoteCategory(); |
| |
| ASSERT_TRUE(CompareCategories(first, second)); |
| NotifyOnCategoryDismissed(second); |
| EXPECT_TRUE(CompareCategories(first, second)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, |
| ShouldReduceLastCategoryClicksWhenDismissed) { |
| SetDismissedCategoryPenaltyVariationParam(2); |
| |
| Category first = AddUnusedRemoteCategory(); |
| Category second = AddUnusedRemoteCategory(); |
| |
| ASSERT_TRUE(CompareCategories(first, second)); |
| |
| NotifyOnSuggestionOpened(/*times=*/1, second); |
| |
| // This should reduce the click count back to 0. |
| NotifyOnCategoryDismissed(second); |
| |
| // Try to move the second category up assuming that the previous click is |
| // still there. |
| NotifyOnSuggestionOpened( |
| /*times=*/ClickBasedCategoryRanker::GetPassingMargin() - 1, second); |
| |
| EXPECT_TRUE(CompareCategories(first, second)); |
| |
| NotifyOnSuggestionOpened(/*times=*/1, second); |
| EXPECT_FALSE(CompareCategories(first, second)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, |
| ShouldTakeVariationValueForDismissedCategoryPenalty) { |
| const int penalty = 10203; |
| SetDismissedCategoryPenaltyVariationParam(penalty); |
| EXPECT_EQ(penalty, ClickBasedCategoryRanker::GetDismissedCategoryPenalty()); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, |
| ShouldDoNothingWhenCategoryDismissedIfPenaltyIsZero) { |
| SetDismissedCategoryPenaltyVariationParam(0); |
| |
| // Add dummy remote categories to ensure that the following categories are not |
| // in the top anymore. |
| AddUnusedRemoteCategories( |
| ClickBasedCategoryRanker::GetNumTopCategoriesWithExtraMargin()); |
| |
| Category first = AddUnusedRemoteCategory(); |
| Category second = AddUnusedRemoteCategory(); |
| Category third = AddUnusedRemoteCategory(); |
| |
| NotifyOnSuggestionOpened(/*times=*/1, second); |
| |
| // This should be ignored, because the penalty is set to 0. |
| NotifyOnCategoryDismissed(second); |
| |
| // The second category should stay where it was. |
| EXPECT_TRUE(CompareCategories(first, second)); |
| EXPECT_TRUE(CompareCategories(second, third)); |
| |
| // Try to move the second category up assuming that the previous click is |
| // still there. |
| NotifyOnSuggestionOpened( |
| /*times=*/ClickBasedCategoryRanker::GetPassingMargin() - 1, second); |
| |
| // It should overtake the first category, because the dismissal should be |
| // ignored and the click should remain. |
| EXPECT_FALSE(CompareCategories(first, second)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, ShouldRestoreDefaultOrderOnClearHistory) { |
| std::vector<KnownCategories> default_order = |
| ConstantCategoryRanker::GetKnownCategoriesDefaultOrder(); |
| Category first = Category::FromKnownCategory(default_order[0]); |
| Category second = Category::FromKnownCategory(default_order[1]); |
| |
| ASSERT_TRUE(CompareCategories(first, second)); |
| |
| // Change the order. |
| while (CompareCategories(first, second)) { |
| NotifyOnSuggestionOpened( |
| /*times=*/ClickBasedCategoryRanker::GetPassingMargin(), second); |
| } |
| |
| ASSERT_FALSE(CompareCategories(first, second)); |
| |
| // The user clears history. |
| ranker()->ClearHistory(/*begin=*/base::Time(), |
| /*end=*/base::Time::Max()); |
| |
| // The default order must be restored. |
| EXPECT_TRUE(CompareCategories(first, second)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, |
| ShouldPreserveRemoteCategoriesOnClearHistory) { |
| Category first = AddUnusedRemoteCategory(); |
| Category second = AddUnusedRemoteCategory(); |
| |
| ASSERT_TRUE(CompareCategories(first, second)); |
| |
| // The user clears history. |
| ranker()->ClearHistory(/*begin=*/base::Time(), |
| /*end=*/base::Time::Max()); |
| |
| // The order does not matter, but the ranker should not die. |
| CompareCategories(first, second); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, ShouldIgnorePartialClearHistory) { |
| Category first = AddUnusedRemoteCategory(); |
| Category second = AddUnusedRemoteCategory(); |
| |
| ASSERT_TRUE(CompareCategories(first, second)); |
| |
| // Change the order. |
| while (CompareCategories(first, second)) { |
| NotifyOnSuggestionOpened( |
| /*times=*/ClickBasedCategoryRanker::GetPassingMargin(), second); |
| } |
| |
| ASSERT_FALSE(CompareCategories(first, second)); |
| |
| // The user partially clears history. |
| base::Time begin = base::Time::Now() - base::TimeDelta::FromHours(1), |
| end = base::Time::Max(); |
| ranker()->ClearHistory(begin, end); |
| |
| // The order should not be cleared. |
| EXPECT_FALSE(CompareCategories(first, second)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, ShouldPromoteCategory) { |
| const Category downloads = |
| Category::FromKnownCategory(KnownCategories::DOWNLOADS); |
| const Category bookmarks = |
| Category::FromKnownCategory(KnownCategories::BOOKMARKS); |
| const Category articles = |
| Category::FromKnownCategory(KnownCategories::ARTICLES); |
| ASSERT_TRUE(CompareCategories(downloads, bookmarks)); |
| ASSERT_TRUE(CompareCategories(bookmarks, articles)); |
| SetPromotedCategoryVariationParam(articles.id()); |
| ResetRanker(base::DefaultClock::GetInstance()); |
| EXPECT_TRUE(CompareCategories(articles, downloads)); |
| EXPECT_TRUE(CompareCategories(articles, bookmarks)); |
| EXPECT_FALSE(CompareCategories(downloads, articles)); |
| EXPECT_FALSE(CompareCategories(bookmarks, articles)); |
| EXPECT_FALSE(CompareCategories(articles, articles)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, |
| ShouldHandleInvalidCategoryIDForPromotion) { |
| SetPromotedCategoryVariationParam( |
| static_cast<int>(KnownCategories::LOCAL_CATEGORIES_COUNT)); |
| ResetRanker(base::DefaultClock::GetInstance()); |
| // Make sure we have the default order. |
| EXPECT_TRUE(CompareCategories( |
| Category::FromKnownCategory(KnownCategories::READING_LIST), |
| Category::FromKnownCategory(KnownCategories::DOWNLOADS))); |
| EXPECT_TRUE(CompareCategories( |
| Category::FromKnownCategory(KnownCategories::DOWNLOADS), |
| Category::FromKnownCategory(KnownCategories::BOOKMARKS))); |
| EXPECT_TRUE(CompareCategories( |
| Category::FromKnownCategory(KnownCategories::BOOKMARKS), |
| Category::FromKnownCategory(KnownCategories::ARTICLES))); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, ShouldEndPromotionOnSectionDismissal) { |
| const Category downloads = |
| Category::FromKnownCategory(KnownCategories::DOWNLOADS); |
| const Category articles = |
| Category::FromKnownCategory(KnownCategories::ARTICLES); |
| ASSERT_TRUE(CompareCategories(downloads, articles)); |
| |
| SetPromotedCategoryVariationParam(articles.id()); |
| ResetRanker(base::DefaultClock::GetInstance()); |
| |
| ASSERT_TRUE(CompareCategories(articles, downloads)); |
| |
| ranker()->OnCategoryDismissed(articles); |
| EXPECT_FALSE(CompareCategories(articles, downloads)); |
| EXPECT_TRUE(CompareCategories(downloads, articles)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, |
| ShouldResumePromotionAfter2WeeksSinceDismissal) { |
| const Category downloads = |
| Category::FromKnownCategory(KnownCategories::DOWNLOADS); |
| const Category articles = |
| Category::FromKnownCategory(KnownCategories::ARTICLES); |
| ASSERT_TRUE(CompareCategories(downloads, articles)); |
| |
| SetPromotedCategoryVariationParam(articles.id()); |
| ResetRanker(base::DefaultClock::GetInstance()); |
| ASSERT_TRUE(CompareCategories(articles, downloads)); |
| |
| ranker()->OnCategoryDismissed(articles); |
| ASSERT_FALSE(CompareCategories(articles, downloads)); |
| |
| // Simulate a little over 2 weeks of time passing. |
| base::SimpleTestClock test_clock; |
| test_clock.SetNow(base::Time::Now() + base::TimeDelta::FromDays(15)); |
| ResetRanker(&test_clock); |
| EXPECT_TRUE(CompareCategories(articles, downloads)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, |
| ShouldEmitNewIndexWhenCategoryMovedUpDueToClick) { |
| base::HistogramTester histogram_tester; |
| |
| std::vector<KnownCategories> default_order = |
| ConstantCategoryRanker::GetKnownCategoriesDefaultOrder(); |
| Category first = Category::FromKnownCategory(default_order[0]); |
| Category second = Category::FromKnownCategory(default_order[1]); |
| |
| ASSERT_TRUE(CompareCategories(first, second)); |
| |
| // Increase the score of |second| until the order changes. |
| while (CompareCategories(first, second)) { |
| EXPECT_THAT( |
| histogram_tester.GetAllSamples(kHistogramMovedUpCategoryNewIndex), |
| IsEmpty()); |
| ranker()->OnSuggestionOpened(second); |
| } |
| ASSERT_FALSE(CompareCategories(first, second)); |
| EXPECT_THAT(histogram_tester.GetAllSamples(kHistogramMovedUpCategoryNewIndex), |
| ElementsAre(base::Bucket(/*min=*/0, /*count=*/1))); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, |
| ShouldNotEmitNewIndexWhenCategoryDismissed) { |
| base::HistogramTester histogram_tester; |
| |
| std::vector<KnownCategories> default_order = |
| ConstantCategoryRanker::GetKnownCategoriesDefaultOrder(); |
| Category category = Category::FromKnownCategory(default_order[0]); |
| |
| ASSERT_THAT(histogram_tester.GetAllSamples(kHistogramMovedUpCategoryNewIndex), |
| IsEmpty()); |
| |
| NotifyOnCategoryDismissed(category); |
| |
| EXPECT_THAT(histogram_tester.GetAllSamples(kHistogramMovedUpCategoryNewIndex), |
| IsEmpty()); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, |
| ShouldNotEmitNewIndexOfMovedUpCategoryWhenHistoryCleared) { |
| std::vector<KnownCategories> default_order = |
| ConstantCategoryRanker::GetKnownCategoriesDefaultOrder(); |
| Category first = Category::FromKnownCategory(default_order[0]); |
| Category second = Category::FromKnownCategory(default_order[1]); |
| |
| ASSERT_TRUE(CompareCategories(first, second)); |
| |
| // Increase the score of |second| until the order changes. |
| while (CompareCategories(first, second)) { |
| ranker()->OnSuggestionOpened(second); |
| } |
| ASSERT_FALSE(CompareCategories(first, second)); |
| |
| // The histogram tester is created here to ignore previous events. |
| base::HistogramTester histogram_tester; |
| ranker()->ClearHistory(/*begin=*/base::Time(), |
| /*end=*/base::Time::Max()); |
| |
| // ClearHistory should restore the default order. |
| ASSERT_TRUE(CompareCategories(first, second)); |
| |
| EXPECT_THAT(histogram_tester.GetAllSamples(kHistogramMovedUpCategoryNewIndex), |
| IsEmpty()); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, |
| ShouldNotEmitNewIndexWhenCategoryPromoted) { |
| base::HistogramTester histogram_tester; |
| |
| std::vector<KnownCategories> default_order = |
| ConstantCategoryRanker::GetKnownCategoriesDefaultOrder(); |
| Category first = Category::FromKnownCategory(default_order[0]); |
| Category second = Category::FromKnownCategory(default_order[1]); |
| |
| ASSERT_TRUE(CompareCategories(first, second)); |
| |
| ASSERT_THAT(histogram_tester.GetAllSamples(kHistogramMovedUpCategoryNewIndex), |
| IsEmpty()); |
| |
| SetPromotedCategoryVariationParam(second.id()); |
| ResetRanker(base::DefaultClock::GetInstance()); |
| |
| ASSERT_FALSE(CompareCategories(first, second)); |
| |
| EXPECT_THAT(histogram_tester.GetAllSamples(kHistogramMovedUpCategoryNewIndex), |
| IsEmpty()); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, |
| ShouldInsertCategoryBeforeSelectedCategory) { |
| std::vector<KnownCategories> default_order = |
| ConstantCategoryRanker::GetKnownCategoriesDefaultOrder(); |
| Category first = Category::FromKnownCategory(default_order[0]); |
| Category second = Category::FromKnownCategory(default_order[1]); |
| |
| ASSERT_TRUE(CompareCategories(first, second)); |
| |
| Category inserted = GetUnusedRemoteCategory(); |
| |
| ranker()->InsertCategoryBeforeIfNecessary(inserted, second); |
| EXPECT_TRUE(CompareCategories(first, inserted)); |
| EXPECT_TRUE(CompareCategories(inserted, second)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, |
| ShouldInsertMultipleCategoriesBeforeSelectedCategory) { |
| std::vector<KnownCategories> default_order = |
| ConstantCategoryRanker::GetKnownCategoriesDefaultOrder(); |
| Category first = Category::FromKnownCategory(default_order[0]); |
| Category second = Category::FromKnownCategory(default_order[1]); |
| |
| ASSERT_TRUE(CompareCategories(first, second)); |
| |
| Category first_inserted = GetUnusedRemoteCategory(); |
| Category second_inserted = GetUnusedRemoteCategory(); |
| |
| ranker()->InsertCategoryBeforeIfNecessary(first_inserted, second); |
| ranker()->InsertCategoryBeforeIfNecessary(second_inserted, second); |
| EXPECT_TRUE(CompareCategories(first, first_inserted)); |
| EXPECT_TRUE(CompareCategories(first_inserted, second_inserted)); |
| EXPECT_TRUE(CompareCategories(second_inserted, second)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, ShouldInsertCategoryBeforeFirstCategory) { |
| std::vector<KnownCategories> default_order = |
| ConstantCategoryRanker::GetKnownCategoriesDefaultOrder(); |
| Category first = Category::FromKnownCategory(default_order[0]); |
| Category inserted = GetUnusedRemoteCategory(); |
| |
| ranker()->InsertCategoryBeforeIfNecessary(inserted, first); |
| EXPECT_TRUE(CompareCategories(inserted, first)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, ShouldInsertCategoryBeforeRemoteCategory) { |
| std::vector<KnownCategories> default_order = |
| ConstantCategoryRanker::GetKnownCategoriesDefaultOrder(); |
| Category remote = AddUnusedRemoteCategory(); |
| Category inserted = GetUnusedRemoteCategory(); |
| |
| ranker()->InsertCategoryBeforeIfNecessary(inserted, remote); |
| EXPECT_TRUE(CompareCategories(inserted, remote)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, |
| ShouldNotChangeRemainingOrderWhenInsertingBeforeCategory) { |
| std::vector<KnownCategories> default_order = |
| ConstantCategoryRanker::GetKnownCategoriesDefaultOrder(); |
| Category anchor = Category::FromKnownCategory(default_order[2]); |
| Category inserted = GetUnusedRemoteCategory(); |
| |
| ranker()->InsertCategoryBeforeIfNecessary(inserted, anchor); |
| std::vector<Category> converted_categories = |
| ConvertKnownCategories(default_order); |
| for (size_t i = 0; i + 1 < converted_categories.size(); ++i) { |
| EXPECT_TRUE(CompareCategories(converted_categories[i], |
| converted_categories[i + 1])); |
| } |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, |
| ShouldInsertCategoriesBeforeAndAfterSameCategory) { |
| std::vector<KnownCategories> default_order = |
| ConstantCategoryRanker::GetKnownCategoriesDefaultOrder(); |
| Category first = Category::FromKnownCategory(default_order[0]); |
| Category second = Category::FromKnownCategory(default_order[1]); |
| Category third = Category::FromKnownCategory(default_order[2]); |
| ASSERT_TRUE(CompareCategories(first, second)); |
| ASSERT_TRUE(CompareCategories(second, third)); |
| |
| Category first_before = GetUnusedRemoteCategory(); |
| ranker()->InsertCategoryBeforeIfNecessary(first_before, second); |
| |
| Category first_after = GetUnusedRemoteCategory(); |
| ranker()->InsertCategoryAfterIfNecessary(first_after, second); |
| |
| Category second_before = GetUnusedRemoteCategory(); |
| ranker()->InsertCategoryBeforeIfNecessary(second_before, second); |
| |
| Category second_after = GetUnusedRemoteCategory(); |
| ranker()->InsertCategoryAfterIfNecessary(second_after, second); |
| |
| EXPECT_TRUE(CompareCategories(first_before, second_before)); |
| EXPECT_TRUE(CompareCategories(second_before, second)); |
| EXPECT_TRUE(CompareCategories(second, second_after)); |
| EXPECT_TRUE(CompareCategories(second_after, first_after)); |
| EXPECT_TRUE(CompareCategories(first_after, third)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, |
| ShouldInsertCategoriesBeforeAndAfterDifferentCategories) { |
| std::vector<KnownCategories> default_order = |
| ConstantCategoryRanker::GetKnownCategoriesDefaultOrder(); |
| Category first = Category::FromKnownCategory(default_order[0]); |
| Category second = Category::FromKnownCategory(default_order[1]); |
| ASSERT_TRUE(CompareCategories(first, second)); |
| |
| Category first_before = GetUnusedRemoteCategory(); |
| ranker()->InsertCategoryBeforeIfNecessary(first_before, second); |
| |
| Category first_after = GetUnusedRemoteCategory(); |
| ranker()->InsertCategoryAfterIfNecessary(first_after, first); |
| |
| Category second_before = GetUnusedRemoteCategory(); |
| ranker()->InsertCategoryBeforeIfNecessary(second_before, second); |
| |
| Category second_after = GetUnusedRemoteCategory(); |
| ranker()->InsertCategoryAfterIfNecessary(second_after, first); |
| |
| EXPECT_TRUE(CompareCategories(first, second_after)); |
| EXPECT_TRUE(CompareCategories(second_after, first_after)); |
| EXPECT_TRUE(CompareCategories(first_after, first_before)); |
| EXPECT_TRUE(CompareCategories(first_before, second_before)); |
| EXPECT_TRUE(CompareCategories(second_before, second)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, |
| ShouldNotEmitNewIndexWhenCategoryInserted) { |
| base::HistogramTester histogram_tester; |
| |
| std::vector<KnownCategories> default_order = |
| ConstantCategoryRanker::GetKnownCategoriesDefaultOrder(); |
| Category first = Category::FromKnownCategory(default_order[0]); |
| |
| ASSERT_THAT(histogram_tester.GetAllSamples(kHistogramMovedUpCategoryNewIndex), |
| IsEmpty()); |
| |
| Category before = GetUnusedRemoteCategory(); |
| ranker()->InsertCategoryBeforeIfNecessary(before, first); |
| |
| Category after = GetUnusedRemoteCategory(); |
| ranker()->InsertCategoryAfterIfNecessary(after, first); |
| |
| EXPECT_THAT(histogram_tester.GetAllSamples(kHistogramMovedUpCategoryNewIndex), |
| IsEmpty()); |
| } |
| |
| // TODO(vitaliii): Reuse these tests for ConstantCategoryRanker. |
| TEST_F(ClickBasedCategoryRankerTest, |
| ShouldInsertCategoryAfterSelectedCategory) { |
| std::vector<KnownCategories> default_order = |
| ConstantCategoryRanker::GetKnownCategoriesDefaultOrder(); |
| Category first = Category::FromKnownCategory(default_order[0]); |
| Category second = Category::FromKnownCategory(default_order[1]); |
| |
| ASSERT_TRUE(CompareCategories(first, second)); |
| |
| Category inserted = GetUnusedRemoteCategory(); |
| |
| ranker()->InsertCategoryAfterIfNecessary(inserted, first); |
| EXPECT_TRUE(CompareCategories(first, inserted)); |
| EXPECT_TRUE(CompareCategories(inserted, second)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, |
| ShouldInsertMultipleCategoriesAfterSelectedCategory) { |
| std::vector<KnownCategories> default_order = |
| ConstantCategoryRanker::GetKnownCategoriesDefaultOrder(); |
| Category first = Category::FromKnownCategory(default_order[0]); |
| Category second = Category::FromKnownCategory(default_order[1]); |
| |
| ASSERT_TRUE(CompareCategories(first, second)); |
| |
| Category first_inserted = GetUnusedRemoteCategory(); |
| Category second_inserted = GetUnusedRemoteCategory(); |
| |
| ranker()->InsertCategoryAfterIfNecessary(first_inserted, first); |
| ranker()->InsertCategoryAfterIfNecessary(second_inserted, first); |
| EXPECT_TRUE(CompareCategories(first, second_inserted)); |
| EXPECT_TRUE(CompareCategories(second_inserted, first_inserted)); |
| EXPECT_TRUE(CompareCategories(first_inserted, second)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, ShouldInsertCategoryAfterLastCategory) { |
| Category last = AddUnusedRemoteCategory(); |
| Category inserted = GetUnusedRemoteCategory(); |
| |
| ranker()->InsertCategoryAfterIfNecessary(inserted, last); |
| EXPECT_TRUE(CompareCategories(last, inserted)); |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, |
| ShouldNotChangeRemainingOrderWhenInsertingAfterCategory) { |
| std::vector<KnownCategories> default_order = |
| ConstantCategoryRanker::GetKnownCategoriesDefaultOrder(); |
| Category anchor = Category::FromKnownCategory(default_order[2]); |
| Category inserted = GetUnusedRemoteCategory(); |
| |
| ranker()->InsertCategoryAfterIfNecessary(inserted, anchor); |
| std::vector<Category> converted_categories = |
| ConvertKnownCategories(default_order); |
| for (size_t i = 0; i + 1 < converted_categories.size(); ++i) { |
| EXPECT_TRUE(CompareCategories(converted_categories[i], |
| converted_categories[i + 1])); |
| } |
| } |
| |
| TEST_F(ClickBasedCategoryRankerTest, |
| ShouldAssignScoreToInsertedCategoriesBasedOnAnchor) { |
| Category anchor = AddUnusedRemoteCategory(); |
| NotifyOnSuggestionOpened(/*times=*/25, anchor); |
| |
| Category inserted_before = GetUnusedRemoteCategory(); |
| ranker()->InsertCategoryBeforeIfNecessary(inserted_before, anchor); |
| |
| Category inserted_after = GetUnusedRemoteCategory(); |
| ranker()->InsertCategoryAfterIfNecessary(inserted_after, anchor); |
| |
| Category tester = AddUnusedRemoteCategory(); |
| NotifyOnSuggestionOpened(/*times=*/20, tester); |
| EXPECT_TRUE(CompareCategories(inserted_before, tester)); |
| EXPECT_TRUE(CompareCategories(inserted_after, tester)); |
| } |
| |
| } // namespace ntp_snippets |