blob: dd451c3c361bdd0faffc2b3494d2fb5a692858f2 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/omnibox/browser/history_cluster_provider.h"
#include <memory>
#include "base/files/scoped_temp_dir.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/task_environment.h"
#include "components/history/core/browser/history_service.h"
#include "components/history_clusters/core/config.h"
#include "components/history_clusters/core/history_clusters_prefs.h"
#include "components/history_clusters/core/history_clusters_service.h"
#include "components/history_clusters/core/history_clusters_service_test_api.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "components/omnibox/browser/autocomplete_provider.h"
#include "components/omnibox/browser/autocomplete_provider_listener.h"
#include "components/omnibox/browser/fake_autocomplete_provider.h"
#include "components/omnibox/browser/fake_autocomplete_provider_client.h"
#include "components/omnibox/browser/omnibox_triggered_feature_service.h"
#include "components/omnibox/browser/search_provider.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/metrics_proto/omnibox_event.pb.h"
#include "third_party/omnibox_proto/groups.pb.h"
AutocompleteMatch CreateMatch(std::u16string contents,
bool is_search = true,
int relevance = 100) {
AutocompleteMatch match;
match.contents = contents;
match.type = is_search ? AutocompleteMatchType::SEARCH_SUGGEST
: AutocompleteMatchType::HISTORY_URL;
match.relevance = relevance;
return match;
}
class HistoryClustersProviderTest : public testing::Test,
public AutocompleteProviderListener {
public:
void SetUp() override {
config_.is_journeys_enabled_no_locale_check = true;
config_.omnibox_history_cluster_provider = true;
history_clusters::SetConfigForTesting(config_);
CHECK(history_dir_.CreateUniqueTempDir());
history_service_ =
history::CreateHistoryService(history_dir_.GetPath(), true);
history_clusters_service_ =
std::make_unique<history_clusters::HistoryClustersService>(
"en-US", history_service_.get(),
/*entity_metadata_provider=*/nullptr,
/*url_loader_factory=*/nullptr,
/*engagement_score_provider=*/nullptr,
/*template_url_service=*/nullptr,
/*optimization_guide_decider=*/nullptr, /*pref_service=*/nullptr);
history_clusters_service_test_api_ =
std::make_unique<history_clusters::HistoryClustersServiceTestApi>(
history_clusters_service_.get(), history_service_.get());
history_clusters_service_test_api_->SetAllKeywordsCache(
{{u"keyword", {}}, {u"keyword2", {}}});
autocomplete_provider_client_ =
std::make_unique<FakeAutocompleteProviderClient>();
autocomplete_provider_client_->set_history_clusters_service(
history_clusters_service_.get());
static_cast<TestingPrefServiceSimple*>(
autocomplete_provider_client_->GetPrefs())
->registry()
->RegisterBooleanPref(history_clusters::prefs::kVisible, true);
search_provider_ =
new FakeAutocompleteProvider(AutocompleteProvider::Type::TYPE_SEARCH);
history_url_provider_ = new FakeAutocompleteProvider(
AutocompleteProvider::Type::TYPE_HISTORY_URL);
history_quick_provider_ = new FakeAutocompleteProvider(
AutocompleteProvider::Type::TYPE_HISTORY_QUICK);
provider_ = new HistoryClusterProvider(
autocomplete_provider_client_.get(), this, search_provider_.get(),
history_url_provider_.get(), history_quick_provider_.get());
}
~HistoryClustersProviderTest() override {
// The provider will kick off an async task to refresh the keyword cache.
// Wait for it to avoid it possibly being processed after the next test case
// begins.
history::BlockUntilHistoryProcessesPendingRequests(history_service_.get());
}
// AutocompleteProviderListener
void OnProviderUpdate(bool updated_matches,
const AutocompleteProvider* provider) override {
on_provider_update_calls_.push_back(updated_matches);
}
void VerifyFeatureTriggered(bool expected) {
EXPECT_EQ(
autocomplete_provider_client_->GetOmniboxTriggeredFeatureService()
->GetFeatureTriggeredInSession(
metrics::OmniboxEventProto_Feature_HISTORY_CLUSTER_SUGGESTION),
expected);
}
// Tracks `OnProviderUpdate()` invocations.
std::vector<bool> on_provider_update_calls_;
base::test::TaskEnvironment task_environment_;
base::ScopedTempDir history_dir_;
std::unique_ptr<history::HistoryService> history_service_;
std::unique_ptr<history_clusters::HistoryClustersService>
history_clusters_service_;
std::unique_ptr<FakeAutocompleteProviderClient> autocomplete_provider_client_;
scoped_refptr<FakeAutocompleteProvider> search_provider_;
scoped_refptr<FakeAutocompleteProvider> history_url_provider_;
scoped_refptr<FakeAutocompleteProvider> history_quick_provider_;
scoped_refptr<HistoryClusterProvider> provider_;
std::unique_ptr<history_clusters::HistoryClustersServiceTestApi>
history_clusters_service_test_api_;
history_clusters::Config config_;
};
TEST_F(HistoryClustersProviderTest, WantAsynchronousMatchesFalse) {
// When `input.omit_asynchronous_matches_` is true, should not attempt
// to provide suggestions.
AutocompleteInput input;
input.set_omit_asynchronous_matches(true);
EXPECT_TRUE(provider_->done());
provider_->Start(input, false);
EXPECT_TRUE(provider_->done());
}
TEST_F(HistoryClustersProviderTest, SyncSearchMatches) {
// Test the unlikely, but valid, case where the search provider completes
// before the history cluster provider begins.
AutocompleteInput input;
input.set_omit_asynchronous_matches(false);
search_provider_->matches_ = {CreateMatch(u"keyword")};
search_provider_->done_ = true;
EXPECT_TRUE(provider_->done());
provider_->Start(input, false);
EXPECT_TRUE(provider_->done());
ASSERT_EQ(provider_->matches().size(), 1u);
EXPECT_EQ(provider_->matches()[0].relevance, 900);
EXPECT_EQ(provider_->matches()[0].description, u"keyword");
EXPECT_EQ(provider_->matches()[0].contents, u"Resume your journey");
EXPECT_EQ(provider_->matches()[0].fill_into_edit, u"keyword");
EXPECT_EQ(provider_->matches()[0].destination_url,
GURL("chrome://history/journeys?q=keyword"));
EXPECT_TRUE(on_provider_update_calls_.empty());
}
TEST_F(HistoryClustersProviderTest, AsyncSearchMatches) {
// Test the more common case where the search provider completes after the
// history cluster provider begins.
AutocompleteInput input;
input.set_omit_asynchronous_matches(false);
// `done()` should be true before starting.
EXPECT_TRUE(provider_->done());
// `done()` should be false after starting.
search_provider_->done_ = false;
provider_->Start(input, false);
EXPECT_FALSE(provider_->done());
// Calling `Start()` or `OnProviderUpdate()` should not process search matches
// if the search provider is not done when called.
search_provider_->matches_ = {CreateMatch(u"keyword")};
provider_->Start(input, false);
provider_->OnProviderUpdate(true, nullptr);
search_provider_->done_ = true;
EXPECT_FALSE(provider_->done());
EXPECT_TRUE(provider_->matches().empty());
// Calling `OnProviderUpdate()` should process search matches if the search
// provider is done.
provider_->OnProviderUpdate(true, nullptr);
EXPECT_TRUE(provider_->done());
ASSERT_EQ(provider_->matches().size(), 1u);
EXPECT_EQ(provider_->matches()[0].description, u"keyword");
EXPECT_THAT(on_provider_update_calls_, testing::ElementsAre(true));
}
TEST_F(HistoryClustersProviderTest, EmptySyncSearchMatches) {
// Test the sync case where the search provider finds no matches.
AutocompleteInput input;
input.set_omit_asynchronous_matches(false);
provider_->Start(input, false);
EXPECT_TRUE(provider_->done());
EXPECT_TRUE(provider_->matches().empty());
EXPECT_TRUE(on_provider_update_calls_.empty());
}
TEST_F(HistoryClustersProviderTest, EmptyAsyncSearchMatches) {
// Test the async case where the search provider finds no matches.
AutocompleteInput input;
input.set_omit_asynchronous_matches(false);
search_provider_->done_ = false;
provider_->Start(input, false);
search_provider_->done_ = true;
EXPECT_FALSE(provider_->done());
provider_->OnProviderUpdate(false, nullptr);
EXPECT_TRUE(provider_->done());
EXPECT_TRUE(provider_->matches().empty());
EXPECT_THAT(on_provider_update_calls_, testing::ElementsAre(false));
}
TEST_F(HistoryClustersProviderTest, MultipassSearchMatches) {
// Test the case where the search provider finds matches in multiple passes.
// This is typically the case; search-what-you-typed and search-history
// suggestions are produced syncly, while the other search types from
// the server are produced asyncly.
AutocompleteInput input;
input.set_omit_asynchronous_matches(false);
// Simulate receiving sync search matches.
search_provider_->done_ = false;
search_provider_->matches_.push_back(CreateMatch(u"keyword"));
search_provider_->matches_.push_back(CreateMatch(u"dolphin"));
provider_->Start(input, false);
EXPECT_FALSE(provider_->done());
// Simulate receiving async search matches.
provider_->OnProviderUpdate(true, nullptr);
EXPECT_FALSE(provider_->done());
// Simulate receiving the last set of async search matches.
search_provider_->done_ = true;
provider_->OnProviderUpdate(true, nullptr);
EXPECT_TRUE(provider_->done());
ASSERT_EQ(provider_->matches().size(), 1u);
EXPECT_EQ(provider_->matches()[0].description, u"keyword");
EXPECT_THAT(on_provider_update_calls_, testing::ElementsAre(true));
}
TEST_F(HistoryClustersProviderTest, MultipassSyncSearchMatches) {
// Like `MultipassSearchMatches` above, test the case where the search
// provider tries multiple passes. But in this case, it finds matches in only
// the sync pass.
AutocompleteInput input;
input.set_omit_asynchronous_matches(false);
// Simulate receiving sync search matches.
search_provider_->done_ = false;
search_provider_->matches_.push_back(CreateMatch(u"keyword"));
search_provider_->matches_.push_back(CreateMatch(u"Levon Aronian"));
provider_->Start(input, false);
EXPECT_FALSE(provider_->done());
// Simulate receiving async search update with no matches.
search_provider_->done_ = true;
provider_->OnProviderUpdate(false, nullptr);
EXPECT_TRUE(provider_->done());
ASSERT_EQ(provider_->matches().size(), 1u);
EXPECT_THAT(on_provider_update_calls_, testing::ElementsAre(true));
}
TEST_F(HistoryClustersProviderTest, NoKeywordMatches) {
// Test the case where none of the search matches match a keyword.
AutocompleteInput input;
input.set_omit_asynchronous_matches(false);
search_provider_->matches_ = {CreateMatch(u"key"), CreateMatch(u"keyworddd"),
CreateMatch(u"Tigran Petrosian")};
search_provider_->done_ = false;
provider_->Start(input, false);
search_provider_->done_ = true;
provider_->OnProviderUpdate(false, nullptr);
EXPECT_TRUE(provider_->done());
EXPECT_TRUE(provider_->matches().empty());
// ALso test that `provider_` calls `OnProviderUpdate()` with false when it
// completes asyncly without matches.
EXPECT_THAT(on_provider_update_calls_, testing::ElementsAre(false));
}
TEST_F(HistoryClustersProviderTest, MultipleKeyworddMatches) {
// Test the case where multiple of the search matches match a keyword.
AutocompleteInput input;
input.set_omit_asynchronous_matches(false);
search_provider_->matches_ = {CreateMatch(u"keyword2"),
CreateMatch(u"keyword"),
CreateMatch(u"Lilit Mkrtchian")};
search_provider_->done_ = true;
provider_->Start(input, false);
EXPECT_TRUE(provider_->done());
ASSERT_EQ(provider_->matches().size(), 1u);
EXPECT_EQ(provider_->matches()[0].description, u"keyword2");
// Also test that `provider_` does not call `OnProviderUpdate()` when it
// completes syncly, even if it has matches.
EXPECT_TRUE(on_provider_update_calls_.empty());
}
TEST_F(HistoryClustersProviderTest, NavIntent_TopAndHighScoringNav) {
// When there is a nav suggest that is both high scoring (>1300) and top
// scoring, don't provide history cluster suggestions.
AutocompleteInput input;
input.set_omit_asynchronous_matches(false);
search_provider_->done_ = false;
history_url_provider_->done_ = false;
provider_->Start(input, false);
// Simulate history URL provider completing.
history_url_provider_->matches_ = {CreateMatch(u"history", false, 1500)};
history_url_provider_->done_ = true;
provider_->OnProviderUpdate(true, nullptr);
EXPECT_FALSE(provider_->done());
// Simulate search provider completing.
search_provider_->matches_ = {CreateMatch(u"keyword", true, 1499)};
search_provider_->done_ = true;
provider_->OnProviderUpdate(true, nullptr);
EXPECT_TRUE(provider_->done());
EXPECT_TRUE(provider_->matches().empty());
EXPECT_THAT(on_provider_update_calls_, testing::ElementsAre(false));
}
TEST_F(HistoryClustersProviderTest, SearchIntent_TopScoringNav) {
// When there is a nav suggest that is top scoring but not high scoring
// (>1300), provide history cluster suggestions.
AutocompleteInput input;
input.set_omit_asynchronous_matches(false);
search_provider_->done_ = false;
history_url_provider_->done_ = false;
provider_->Start(input, false);
// Simulate history URL provider completing.
history_url_provider_->matches_ = {CreateMatch(u"history", false, 1200)};
history_url_provider_->done_ = true;
provider_->OnProviderUpdate(true, nullptr);
EXPECT_FALSE(provider_->done());
// Simulate search provider completing.
search_provider_->matches_ = {CreateMatch(u"keyword", true, 100)};
search_provider_->done_ = true;
provider_->OnProviderUpdate(true, nullptr);
EXPECT_TRUE(provider_->done());
ASSERT_EQ(provider_->matches().size(), 1u);
EXPECT_THAT(on_provider_update_calls_, testing::ElementsAre(true));
}
TEST_F(HistoryClustersProviderTest, SearchIntent_HighScoringNav) {
// When there is a nav suggest that is high scoring (>1300) but not top
// scoring, provide history cluster suggestions.
AutocompleteInput input;
input.set_omit_asynchronous_matches(false);
search_provider_->done_ = false;
history_quick_provider_->done_ = false;
provider_->Start(input, false);
// Simulate history URL provider completing.
history_quick_provider_->matches_ = {CreateMatch(u"history", false, 1500)};
history_quick_provider_->done_ = true;
provider_->OnProviderUpdate(true, nullptr);
EXPECT_FALSE(provider_->done());
// Simulate search provider completing.
search_provider_->matches_ = {CreateMatch(u"keyword", true, 1501)};
search_provider_->done_ = true;
provider_->OnProviderUpdate(true, nullptr);
EXPECT_TRUE(provider_->done());
ASSERT_EQ(provider_->matches().size(), 1u);
EXPECT_THAT(on_provider_update_calls_, testing::ElementsAre(true));
}
TEST_F(HistoryClustersProviderTest, Counterfactual_Disabled) {
AutocompleteInput input;
input.set_omit_asynchronous_matches(false);
// When there are no matches, should not log the feature triggered.
search_provider_->done_ = true;
provider_->Start(input, false);
EXPECT_TRUE(provider_->matches().empty());
VerifyFeatureTriggered(false);
// When there are matches, should log the feature triggered.
search_provider_->matches_ = {CreateMatch(u"keyword")};
provider_->Start(input, false);
EXPECT_FALSE(provider_->matches().empty());
VerifyFeatureTriggered(true);
}
TEST_F(HistoryClustersProviderTest, Counterfactual_Enabled) {
config_.omnibox_history_cluster_provider_counterfactual = true;
history_clusters::SetConfigForTesting(config_);
AutocompleteInput input;
input.set_omit_asynchronous_matches(false);
// When there are no matches, should not log the feature triggered.
search_provider_->done_ = true;
provider_->Start(input, false);
EXPECT_TRUE(provider_->matches().empty());
VerifyFeatureTriggered(false);
// When there are matches, should (a) log the feature triggered and (b) hide
// the matches.
search_provider_->matches_ = {CreateMatch(u"keyword")};
provider_->Start(input, false);
EXPECT_TRUE(provider_->matches().empty());
VerifyFeatureTriggered(true);
}
TEST_F(HistoryClustersProviderTest, Grouping_Ranking) {
// Should not have groups.
AutocompleteInput input;
input.set_omit_asynchronous_matches(false);
search_provider_->matches_ = {CreateMatch(u"keyword")};
search_provider_->done_ = true;
provider_->Start(input, false);
ASSERT_EQ(provider_->matches().size(), 1u);
EXPECT_EQ(provider_->matches()[0].suggestion_group_id, absl::nullopt);
}