blob: c51993d8952bcde02820c1d4c59092244dea8e00 [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/actions/history_clusters_action.h"
#include <algorithm>
#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/core/test/history_service_test_util.h"
#include "components/history_clusters/core/config.h"
#include "components/history_clusters/core/features.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/actions/omnibox_action.h"
#include "components/omnibox/browser/actions/omnibox_action_concepts.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "components/omnibox/browser/autocomplete_result.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace history_clusters {
struct MatchData {
std::u16string contents = u"keyword"; // Also assigned to `destination_url`.
int relevance = 1000;
AutocompleteMatchType::Type type =
AutocompleteMatchType::Type::SEARCH_SUGGEST;
bool already_has_action = false;
bool expect_history_clusters_action = false;
};
ACMatches CreateACMatches(std::vector<MatchData> matches_data) {
ACMatches matches;
matches.reserve(matches_data.size());
std::ranges::transform(
matches_data, std::back_inserter(matches), [](const auto& match_data) {
AutocompleteMatch match(nullptr, match_data.relevance, true,
match_data.type);
match.contents = match_data.contents;
match.destination_url = GURL(u"https://" + match_data.contents);
if (match_data.already_has_action) {
match.actions.push_back(base::MakeRefCounted<OmniboxAction>(
OmniboxAction::LabelStrings{}, GURL{}));
}
return match;
});
return matches;
}
class HistoryClustersActionTest : public testing::Test {
public:
HistoryClustersActionTest() = default;
// `history_dir_` needs to be initialized once only.
void SetUp() override {
CHECK(history_dir_.CreateUniqueTempDir());
history_service_ =
history::CreateHistoryService(history_dir_.GetPath(), true);
prefs_enabled_.registry()->RegisterBooleanPref(
history_clusters::prefs::kVisible, true);
prefs_enabled_.registry()->RegisterDictionaryPref(
history_clusters::prefs::kShortCache);
prefs_enabled_.registry()->RegisterDictionaryPref(
history_clusters::prefs::kAllCache);
search_actions_config_.is_journeys_enabled_no_locale_check = true;
search_actions_config_.omnibox_action = true;
search_actions_config_.omnibox_action_on_navigation_intents = false;
SetConfigForTesting(search_actions_config_);
}
// `history_clusters_service_` needs to be initialized repeatedly since it
// caches `config.is_journeys_enabled_no_locale_check` on initialization.
void SetUpWithConfig(Config config) {
SetConfigForTesting(config);
history_clusters_service_ = std::make_unique<HistoryClustersService>(
"en-US", history_service_.get(),
/*url_loader_factory=*/nullptr,
/*engagement_score_provider=*/nullptr,
/*template_url_service=*/nullptr,
/*optimization_guide_decider=*/nullptr, &prefs_enabled_);
history_clusters_service_test_api_ =
std::make_unique<HistoryClustersServiceTestApi>(
history_clusters_service_.get(), history_service_.get());
history_clusters_service_test_api_->SetAllKeywordsCache(
{{u"keyword", history::ClusterKeywordData()}});
}
void TestAttachHistoryClustersActions(std::vector<MatchData> matches_data,
HistoryClustersService* service) {
AutocompleteResult result;
result.AppendMatches(CreateACMatches(matches_data));
AttachHistoryClustersActions(service, result);
for (size_t i = 0; i < matches_data.size(); ++i) {
bool has_history_clusters_action =
result.match_at(i)->GetActionAt(0u) &&
result.match_at(i)->GetActionAt(0u)->ActionId() ==
OmniboxActionId::HISTORY_CLUSTERS;
EXPECT_EQ(has_history_clusters_action,
matches_data[i].expect_history_clusters_action);
}
// `AttachHistoryClustersActions()` 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());
}
void TestAttachHistoryClustersActions(std::vector<MatchData> matches_data) {
TestAttachHistoryClustersActions(matches_data,
history_clusters_service_.get());
}
void SetHistoryClustersVisiblePref(bool value) {
prefs_enabled_.SetBoolean(history_clusters::prefs::kVisible, value);
// Make the history clusters visible pref managed.
prefs_enabled_.SetManagedPref(history_clusters::prefs::kVisible,
std::make_unique<base::Value>(value));
}
base::test::TaskEnvironment task_environment_;
base::ScopedTempDir history_dir_;
std::unique_ptr<history::HistoryService> history_service_;
std::unique_ptr<HistoryClustersService> history_clusters_service_;
std::unique_ptr<HistoryClustersServiceTestApi>
history_clusters_service_test_api_;
// Commonly used configs & prefs used or derived from in the tests.
Config search_actions_config_;
TestingPrefServiceSimple prefs_enabled_;
};
TEST_F(HistoryClustersActionTest, AttachHistoryClustersActions) {
{
SCOPED_TRACE("Shouldn't add action if history cluster service is nullptr.");
TestAttachHistoryClustersActions({{}}, nullptr);
}
{
SCOPED_TRACE("Shouldn't add action if journey feature is disabled.");
Config config = search_actions_config_;
config.is_journeys_enabled_no_locale_check = false;
SetUpWithConfig(config);
TestAttachHistoryClustersActions({{}});
}
{
SCOPED_TRACE("Shouldn't add action if action chip feature is disabled.");
Config config = search_actions_config_;
config.omnibox_action = false;
SetUpWithConfig(config);
TestAttachHistoryClustersActions({{}});
}
{
SCOPED_TRACE("Shouldn't add action if `kVisible` pref is false.");
SetUpWithConfig(search_actions_config_);
SetHistoryClustersVisiblePref(false);
TestAttachHistoryClustersActions({{}}, history_clusters_service_.get());
// Reset this back to true for future tests.
SetHistoryClustersVisiblePref(true);
}
{
SCOPED_TRACE("Shouldn't add action if no matches.");
SetUpWithConfig(search_actions_config_);
TestAttachHistoryClustersActions({});
}
{
SCOPED_TRACE(
"Shouldn't add action if `result` contains a pedal, even if it's on a "
"different match.");
Config config = search_actions_config_;
config.omnibox_action_with_pedals = false;
SetUpWithConfig(config);
TestAttachHistoryClustersActions({
{},
{.contents = u"pedal-match",
.relevance = 500,
.already_has_action = true},
});
}
{
SCOPED_TRACE("Should add action if a search suggestion matches.");
SetUpWithConfig(search_actions_config_);
TestAttachHistoryClustersActions(
{{.expect_history_clusters_action = true}});
}
{
SCOPED_TRACE(
"Should add action if an action is search entity "
"suggestion matches.");
SetUpWithConfig(search_actions_config_);
TestAttachHistoryClustersActions(
{{.type = AutocompleteMatchType::Type::SEARCH_SUGGEST_ENTITY,
.expect_history_clusters_action = true}});
}
{
SCOPED_TRACE("Should not add action if a navigation suggestion matches.");
SetUpWithConfig(search_actions_config_);
TestAttachHistoryClustersActions(
{{.type = AutocompleteMatchType::Type::HISTORY_TITLE}});
}
{
SCOPED_TRACE(
"Should add action if both a search and navigation suggestions "
"match. The search suggestion should have an action, even if it is "
"ranked & scored lower.");
SetUpWithConfig(search_actions_config_);
TestAttachHistoryClustersActions(
{{.type = AutocompleteMatchType::Type::HISTORY_TITLE},
{.relevance = 900, .expect_history_clusters_action = true}});
}
{
SCOPED_TRACE(
"Should add action only to first matching suggestion, even if it is "
"scored lower.");
SetUpWithConfig(search_actions_config_);
TestAttachHistoryClustersActions(
{{
.contents = u"bad-keyword",
.relevance = 1200,
},
{.relevance = 900, .expect_history_clusters_action = true},
{}});
}
{
SCOPED_TRACE(
"Should add action if a search suggestion matches and the top-scoring "
"suggestion is a low score navigation suggestion.");
SetUpWithConfig(search_actions_config_);
TestAttachHistoryClustersActions(
{{u"keyword", 1000, AutocompleteMatchType::Type::HISTORY_TITLE},
{u"keyword", 900, AutocompleteMatchType::Type::SEARCH_SUGGEST, false,
true}});
}
{
SCOPED_TRACE(
"Should not add action if a search suggestion matches and the top "
"scoring suggestion is a high score navigation suggestion, even if it "
"doesn't match.");
SetUpWithConfig(search_actions_config_);
TestAttachHistoryClustersActions(
{{.relevance = 1340},
{
.contents = u"bad-keyword",
.relevance = 1350,
.type = AutocompleteMatchType::Type::HISTORY_TITLE,
}});
}
{
SCOPED_TRACE(
"Should add action if a search suggestion matches and the top "
"scoring suggestion is a search suggestion even if there is a high "
"score navigation suggestion.");
SetUpWithConfig(search_actions_config_);
TestAttachHistoryClustersActions(
{{.relevance = 1340,
.type = AutocompleteMatchType::Type::HISTORY_TITLE},
{.relevance = 1350, .expect_history_clusters_action = true}});
}
}
} // namespace history_clusters