blob: 6366dce228a6e949b80d7899e79ac1708aa223bd [file] [log] [blame]
// Copyright 2014 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/autocomplete_match.h"
#include <stddef.h>
#include <utility>
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "components/omnibox/browser/actions/omnibox_action.h"
#include "components/omnibox/browser/actions/omnibox_action_in_suggest.h"
#include "components/omnibox/browser/actions/omnibox_answer_action.h"
#include "components/omnibox/browser/actions/omnibox_pedal.h"
#include "components/omnibox/browser/actions/omnibox_pedal_concepts.h"
#include "components/omnibox/browser/autocomplete_match_type.h"
#include "components/omnibox/browser/autocomplete_provider.h"
#include "components/omnibox/browser/fake_autocomplete_provider.h"
#include "components/omnibox/browser/omnibox_field_trial.h"
#include "components/omnibox/browser/test_scheme_classifier.h"
#include "components/omnibox/common/omnibox_features.h"
#include "components/search_engines/template_url.h"
#include "components/search_engines/template_url_data.h"
#include "components/search_engines/template_url_starter_pack_data.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/metrics_proto/omnibox_scoring_signals.pb.h"
#include "third_party/omnibox_proto/entity_info.pb.h"
#include "third_party/omnibox_proto/suggest_template_info.pb.h"
#include "ui/gfx/vector_icon_types.h"
#include "url/gurl.h"
using ScoringSignals = ::metrics::OmniboxScoringSignals;
namespace {
class FakeOmniboxAction : public OmniboxAction {
public:
explicit FakeOmniboxAction(OmniboxActionId id)
: OmniboxAction(LabelStrings(u"", u"", u"", u""), GURL{}), id_(id) {}
OmniboxActionId ActionId() const override { return id_; }
private:
~FakeOmniboxAction() override = default;
OmniboxActionId id_{};
};
void TestSetAllowedToBeDefault(int caseI,
const std::string input_text,
bool input_prevent_inline_autocomplete,
const std::string match_inline_autocompletion,
const std::string expected_inline_autocompletion,
bool expected_allowed_to_be_default_match) {
AutocompleteInput input(base::UTF8ToUTF16(input_text),
metrics::OmniboxEventProto::OTHER,
TestSchemeClassifier());
input.set_prevent_inline_autocomplete(input_prevent_inline_autocomplete);
AutocompleteMatch match;
match.inline_autocompletion = base::UTF8ToUTF16(match_inline_autocompletion);
match.SetAllowedToBeDefault(input);
EXPECT_EQ(base::UTF16ToUTF8(match.inline_autocompletion).c_str(),
expected_inline_autocompletion)
<< "case " << caseI;
EXPECT_EQ(match.allowed_to_be_default_match,
expected_allowed_to_be_default_match)
<< "case " << caseI;
}
AutocompleteMatch CreateACMatchWithScoringSignals(
int typed_count,
int visit_count,
int elapsed_time_last_visit_secs,
int shortcut_visit_count,
int shortest_shortcut_len,
bool is_host_only,
int num_bookmarks_of_url,
int first_bookmark_title_match_position,
int total_bookmark_title_match_length,
int num_input_terms_matched_by_bookmark_title,
int first_url_match_position,
int total_url_match_length,
bool host_match_at_word_boundary,
int total_path_match_length,
int total_query_or_ref_match_length,
int total_title_match_length,
bool has_non_scheme_www_match,
int num_input_terms_matched_by_title,
int num_input_terms_matched_by_url,
int length_of_url,
float site_engagement,
bool allowed_to_be_default_match) {
AutocompleteMatch match;
match.scoring_signals = std::make_optional<ScoringSignals>();
match.scoring_signals->set_typed_count(typed_count);
match.scoring_signals->set_visit_count(visit_count);
match.scoring_signals->set_elapsed_time_last_visit_secs(
elapsed_time_last_visit_secs);
match.scoring_signals->set_shortcut_visit_count(shortcut_visit_count);
match.scoring_signals->set_shortest_shortcut_len(shortest_shortcut_len);
match.scoring_signals->set_is_host_only(is_host_only);
match.scoring_signals->set_num_bookmarks_of_url(num_bookmarks_of_url);
match.scoring_signals->set_first_bookmark_title_match_position(
first_bookmark_title_match_position);
match.scoring_signals->set_total_bookmark_title_match_length(
total_bookmark_title_match_length);
match.scoring_signals->set_num_input_terms_matched_by_bookmark_title(
num_input_terms_matched_by_bookmark_title);
match.scoring_signals->set_first_url_match_position(first_url_match_position);
match.scoring_signals->set_total_url_match_length(total_url_match_length);
match.scoring_signals->set_host_match_at_word_boundary(
host_match_at_word_boundary);
match.scoring_signals->set_total_path_match_length(total_path_match_length);
match.scoring_signals->set_total_query_or_ref_match_length(
total_query_or_ref_match_length);
match.scoring_signals->set_total_title_match_length(total_title_match_length);
match.scoring_signals->set_has_non_scheme_www_match(has_non_scheme_www_match);
match.scoring_signals->set_num_input_terms_matched_by_title(
num_input_terms_matched_by_title);
match.scoring_signals->set_num_input_terms_matched_by_url(
num_input_terms_matched_by_url);
match.scoring_signals->set_length_of_url(length_of_url);
match.scoring_signals->set_site_engagement(site_engagement);
match.scoring_signals->set_allowed_to_be_default_match(
allowed_to_be_default_match);
return match;
}
// Use a test fixture to ensure that any scoped settings that are set during the
// test are cleared after the test is terminated.
class AutocompleteMatchTest : public testing::Test {
protected:
void TearDown() override {
RichAutocompletionParams::ClearParamsForTesting();
}
};
} // namespace
TEST_F(AutocompleteMatchTest, MoreRelevant) {
struct RelevantCases {
int r1;
int r2;
bool expected_result;
} cases[] = {
{10, 0, true}, {10, -5, true}, {-5, 10, false},
{0, 10, false}, {-10, -5, false}, {-5, -10, true},
};
AutocompleteMatch m1(nullptr, 0, false,
AutocompleteMatchType::URL_WHAT_YOU_TYPED);
AutocompleteMatch m2(nullptr, 0, false,
AutocompleteMatchType::URL_WHAT_YOU_TYPED);
for (const auto& caseI : cases) {
m1.relevance = caseI.r1;
m2.relevance = caseI.r2;
EXPECT_EQ(caseI.expected_result, AutocompleteMatch::MoreRelevant(m1, m2));
}
}
TEST_F(AutocompleteMatchTest, MergeClassifications) {
// Merging two empty vectors should result in an empty vector.
EXPECT_EQ(std::string(),
AutocompleteMatch::ClassificationsToString(
AutocompleteMatch::MergeClassifications(
AutocompleteMatch::ACMatchClassifications(),
AutocompleteMatch::ACMatchClassifications())));
// If one vector is empty and the other is "trivial" but non-empty (i.e. (0,
// NONE)), the non-empty vector should be returned.
EXPECT_EQ("0,0", AutocompleteMatch::ClassificationsToString(
AutocompleteMatch::MergeClassifications(
AutocompleteMatch::ClassificationsFromString("0,0"),
AutocompleteMatch::ACMatchClassifications())));
EXPECT_EQ("0,0",
AutocompleteMatch::ClassificationsToString(
AutocompleteMatch::MergeClassifications(
AutocompleteMatch::ACMatchClassifications(),
AutocompleteMatch::ClassificationsFromString("0,0"))));
// Ditto if the one-entry vector is non-trivial.
EXPECT_EQ("0,1", AutocompleteMatch::ClassificationsToString(
AutocompleteMatch::MergeClassifications(
AutocompleteMatch::ClassificationsFromString("0,1"),
AutocompleteMatch::ACMatchClassifications())));
EXPECT_EQ("0,1",
AutocompleteMatch::ClassificationsToString(
AutocompleteMatch::MergeClassifications(
AutocompleteMatch::ACMatchClassifications(),
AutocompleteMatch::ClassificationsFromString("0,1"))));
// Merge an unstyled one-entry vector with a styled one-entry vector.
EXPECT_EQ("0,1",
AutocompleteMatch::ClassificationsToString(
AutocompleteMatch::MergeClassifications(
AutocompleteMatch::ClassificationsFromString("0,0"),
AutocompleteMatch::ClassificationsFromString("0,1"))));
// Test simple cases of overlap.
EXPECT_EQ(
"0,3,"
"1,2",
AutocompleteMatch::ClassificationsToString(
AutocompleteMatch::MergeClassifications(
AutocompleteMatch::ClassificationsFromString("0,1,"
"1,0"),
AutocompleteMatch::ClassificationsFromString("0,2"))));
EXPECT_EQ(
"0,3,"
"1,2",
AutocompleteMatch::ClassificationsToString(
AutocompleteMatch::MergeClassifications(
AutocompleteMatch::ClassificationsFromString("0,2"),
AutocompleteMatch::ClassificationsFromString("0,1,"
"1,0"))));
// Test the case where both vectors have classifications at the same
// positions.
EXPECT_EQ("0,3",
AutocompleteMatch::ClassificationsToString(
AutocompleteMatch::MergeClassifications(
AutocompleteMatch::ClassificationsFromString("0,1,"
"1,2"),
AutocompleteMatch::ClassificationsFromString("0,2,"
"1,1"))));
// Test an arbitrary complicated case.
EXPECT_EQ(
"0,2,"
"1,0,"
"2,1,"
"4,3,"
"5,7,"
"6,3,"
"7,7,"
"15,1,"
"17,0",
AutocompleteMatch::ClassificationsToString(
AutocompleteMatch::MergeClassifications(
AutocompleteMatch::ClassificationsFromString("0,0,"
"2,1,"
"4,3,"
"7,7,"
"10,6,"
"15,0"),
AutocompleteMatch::ClassificationsFromString("0,2,"
"1,0,"
"5,7,"
"6,1,"
"17,0"))));
}
TEST_F(AutocompleteMatchTest, GetMatchComponents) {
struct MatchComponentsTestData {
const std::string url;
std::vector<std::string> input_terms;
bool expected_match_in_scheme;
bool expected_match_in_subdomain;
};
MatchComponentsTestData test_cases[] = {
// Match in scheme.
{"http://www.google.com", {"ht"}, true, false},
// Match within the scheme, but not starting at the beginning, i.e. "ttp".
{"http://www.google.com", {"tp"}, false, false},
// Sanity check that HTTPS still works.
{"https://www.google.com", {"http"}, true, false},
// Match within the subdomain.
{"http://www.google.com", {"www"}, false, true},
{"http://www.google.com", {"www."}, false, true},
// Don't consider matches on the '.' delimiter as a match_in_subdomain.
{"http://www.google.com", {"."}, false, false},
{"http://www.google.com", {".goo"}, false, false},
// Matches within the domain.
{"http://www.google.com", {"goo"}, false, false},
// Verify that in private registries, we detect matches in subdomains.
{"http://www.appspot.com", {"www"}, false, true},
// Matches spanning the scheme, subdomain, and domain.
{"http://www.google.com", {"http://www.goo"}, true, true},
{"http://www.google.com", {"ht", "www"}, true, true},
// But we should not flag match_in_subdomain if there is no subdomain.
{"http://google.com", {"http://goo"}, true, false},
// Matches spanning the subdomain and path.
{"http://www.google.com/abc", {"www.google.com/ab"}, false, true},
{"http://www.google.com/abc", {"www", "ab"}, false, true},
// Matches spanning the scheme, subdomain, and path.
{"http://www.google.com/abc", {"http://www.google.com/ab"}, true, true},
{"http://www.google.com/abc", {"ht", "ww", "ab"}, true, true},
// Intranet sites.
{"http://foobar/biz", {"foobar"}, false, false},
{"http://foobar/biz", {"biz"}, false, false},
// Ensure something sane happens when the URL input is invalid.
{"", {""}, false, false},
{"foobar", {"bar"}, false, false},
};
for (auto& test_case : test_cases) {
SCOPED_TRACE(testing::Message()
<< " url=" << test_case.url << " first input term="
<< test_case.input_terms[0] << " expected_match_in_scheme="
<< test_case.expected_match_in_scheme
<< " expected_match_in_subdomain="
<< test_case.expected_match_in_subdomain);
bool match_in_scheme = false;
bool match_in_subdomain = false;
std::vector<AutocompleteMatch::MatchPosition> match_positions;
for (auto& term : test_case.input_terms) {
size_t start = test_case.url.find(term);
ASSERT_NE(std::string::npos, start);
size_t end = start + term.size();
match_positions.push_back(std::make_pair(start, end));
}
AutocompleteMatch::GetMatchComponents(GURL(test_case.url), match_positions,
&match_in_scheme,
&match_in_subdomain);
EXPECT_EQ(test_case.expected_match_in_scheme, match_in_scheme);
EXPECT_EQ(test_case.expected_match_in_subdomain, match_in_subdomain);
}
}
TEST_F(AutocompleteMatchTest, FormatUrlForSuggestionDisplay) {
// This test does not need to verify url_formatter's functionality in-depth,
// since url_formatter has its own unit tests. This test is to validate that
// flipping feature flags and varying the trim_scheme parameter toggles the
// correct behavior within AutocompleteMatch::GetFormatTypes.
struct FormatUrlTestData {
const std::string url;
bool preserve_scheme;
bool preserve_subdomain;
const wchar_t* expected_result;
void Validate() {
SCOPED_TRACE(testing::Message()
<< " url=" << url << " preserve_scheme=" << preserve_scheme
<< " preserve_subdomain=" << preserve_subdomain
<< " expected_result=" << expected_result);
auto format_types = AutocompleteMatch::GetFormatTypes(preserve_scheme,
preserve_subdomain);
EXPECT_EQ(base::WideToUTF16(expected_result),
url_formatter::FormatUrl(GURL(url), format_types,
base::UnescapeRule::SPACES, nullptr,
nullptr, nullptr));
}
};
FormatUrlTestData normal_cases[] = {
// Test the |preserve_scheme| parameter.
{"http://google.com", false, false, L"google.com"},
{"https://google.com", false, false, L"google.com"},
{"http://google.com", true, false, L"http://google.com"},
{"https://google.com", true, false, L"https://google.com"},
// Test the |preserve_subdomain| parameter.
{"http://www.google.com", false, false, L"google.com"},
{"http://www.google.com", false, true, L"www.google.com"},
// Test that paths are preserved in the default case.
{"http://google.com/foobar", false, false, L"google.com/foobar"},
};
for (FormatUrlTestData& test_case : normal_cases)
test_case.Validate();
}
TEST_F(AutocompleteMatchTest, SupportsDeletion) {
// A non-deletable match with no duplicates.
AutocompleteMatch m(nullptr, 0, false,
AutocompleteMatchType::URL_WHAT_YOU_TYPED);
EXPECT_FALSE(m.SupportsDeletion());
// A deletable match with no duplicates.
AutocompleteMatch m1(nullptr, 0, true,
AutocompleteMatchType::URL_WHAT_YOU_TYPED);
EXPECT_TRUE(m1.SupportsDeletion());
// A non-deletable match, with non-deletable duplicates.
m.duplicate_matches.push_back(AutocompleteMatch(
nullptr, 0, false, AutocompleteMatchType::URL_WHAT_YOU_TYPED));
m.duplicate_matches.push_back(AutocompleteMatch(
nullptr, 0, false, AutocompleteMatchType::URL_WHAT_YOU_TYPED));
EXPECT_FALSE(m.SupportsDeletion());
// A non-deletable match, with at least one deletable duplicate.
m.duplicate_matches.push_back(AutocompleteMatch(
nullptr, 0, true, AutocompleteMatchType::URL_WHAT_YOU_TYPED));
EXPECT_TRUE(m.SupportsDeletion());
}
TEST_F(AutocompleteMatchTest, GURLToStrippedGURL) {
struct {
const char* url1;
const char* url2;
const char* input;
bool equal;
} test_cases[] = {
// Sanity check cases.
{"http://google.com", "http://google.com", "", true},
{"http://google.com", "http://www.google.com", "", true},
{"http://google.com", "http://facebook.com", "", false},
{"http://google.com", "https://google.com", "", true},
// Because we provided scheme, must match in scheme.
{"http://google.com", "https://google.com", "http://google.com", false},
{"https://www.apple.com/", "http://www.apple.com/",
"https://www.apple.com/", false},
{"https://www.apple.com/", "https://www.apple.com/",
"https://www.apple.com/", true},
// Ignore ref if not in input.
{"http://drive.google.com/doc/blablabla#page=10",
"http://drive.google.com/doc/blablabla#page=111", "", true},
{"http://drive.google.com/doc/blablabla#page=10",
"http://drive.google.com/doc/blablabla#page=111",
"http://drive.google.com/doc/blablabla", true},
{"file:///usr/local/bin/tuxpenguin#ref1",
"file:///usr/local/bin/tuxpenguin#ref2", "", true},
{"file:///usr/local/bin/tuxpenguin#ref1",
"file:///usr/local/bin/tuxpenguin#ref2",
"file:///usr/local/bin/tuxpenguin", true},
// Do not ignore ref if in input.
{"http://drive.google.com/doc/blablabla#page=10",
"http://drive.google.com/doc/blablabla#page=111",
"http://drive.google.com/doc/blablabla#p", false},
{"file:///usr/local/bin/tuxpenguin#ref1",
"file:///usr/local/bin/tuxpenguin#ref2",
"file:///usr/local/bin/tuxpenguin#r", false}};
for (const auto& test_case : test_cases) {
SCOPED_TRACE(std::string(test_case.url1) + " vs " + test_case.url2 +
", input '" + test_case.input + "'");
AutocompleteInput input(base::ASCIIToUTF16(test_case.input),
test_case.input[0]
? metrics::OmniboxEventProto::OTHER
: metrics::OmniboxEventProto::BLANK,
TestSchemeClassifier());
const auto stripped_url1 = AutocompleteMatch::GURLToStrippedGURL(
/*url=*/GURL(test_case.url1),
/*input=*/input,
/*template_url=*/nullptr,
/*search_terms=*/std::u16string(),
/*keep_search_intent_params=*/false);
const auto stripped_url2 = AutocompleteMatch::GURLToStrippedGURL(
/*url=*/GURL(test_case.url2),
/*input=*/input,
/*template_url=*/nullptr,
/*search_terms=*/std::u16string(),
/*keep_search_intent_params=*/false);
EXPECT_EQ(test_case.equal, stripped_url1 == stripped_url2);
}
}
// Structure containing URL pairs for deduping-related tests.
struct DuplicateCase {
const wchar_t* input;
const std::string url1;
const std::string url2;
const bool expected_duplicate;
};
// Runs deduping logic against URLs in |duplicate_case| and makes sure they are
// unique or matched as duplicates as expected.
void CheckDuplicateCase(const DuplicateCase& duplicate_case) {
SCOPED_TRACE("input=" + base::WideToUTF8(duplicate_case.input) +
" url1=" + duplicate_case.url1 + " url2=" + duplicate_case.url2);
AutocompleteInput input(base::WideToUTF16(duplicate_case.input),
metrics::OmniboxEventProto::OTHER,
TestSchemeClassifier());
AutocompleteMatch m1(nullptr, 100, false,
AutocompleteMatchType::URL_WHAT_YOU_TYPED);
m1.destination_url = GURL(duplicate_case.url1);
m1.ComputeStrippedDestinationURL(input, nullptr);
AutocompleteMatch m2(nullptr, 100, false,
AutocompleteMatchType::URL_WHAT_YOU_TYPED);
m2.destination_url = GURL(duplicate_case.url2);
m2.ComputeStrippedDestinationURL(input, nullptr);
EXPECT_EQ(duplicate_case.expected_duplicate,
m1.stripped_destination_url == m2.stripped_destination_url);
EXPECT_TRUE(m1.stripped_destination_url.is_valid());
EXPECT_TRUE(m2.stripped_destination_url.is_valid());
}
TEST_F(AutocompleteMatchTest, Duplicates) {
DuplicateCase cases[] = {
{L"g", "http://www.google.com/", "https://www.google.com/", true},
{L"g", "http://www.google.com/", "http://www.google.com", true},
{L"g", "http://google.com/", "http://www.google.com/", true},
{L"g", "http://www.google.com/", "HTTP://www.GOOGLE.com/", true},
{L"g", "http://www.google.com/", "http://www.google.com", true},
{L"g", "https://www.google.com/", "http://google.com", true},
{L"g", "http://www.google.com/", "wss://www.google.com/", false},
{L"g", "http://www.google.com/1", "http://www.google.com/1/", false},
{L"g", "http://www.google.com/", "http://www.google.com/1", false},
{L"g", "http://www.google.com/", "http://www.goo.com/", false},
{L"g", "http://www.google.com/", "http://w2.google.com/", false},
{L"g", "http://www.google.com/", "http://m.google.com/", false},
{L"g", "http://www.google.com/", "http://www.google.com/?foo", false},
// Don't allow URLs with different schemes to be considered duplicates for
// certain inputs.
{L"http://g", "http://google.com/", "https://google.com/", false},
{L"http://g", "http://blah.com/", "https://blah.com/", true},
{L"http://g", "http://google.com/1", "https://google.com/1", false},
{L"http://g hello", "http://google.com/", "https://google.com/", false},
{L"hello http://g", "http://google.com/", "https://google.com/", false},
{L"hello http://g", "http://blah.com/", "https://blah.com/", true},
{L"http://b http://g", "http://google.com/", "https://google.com/",
false},
{L"http://b http://g", "http://blah.com/", "https://blah.com/", false},
// If the user types unicode that matches the beginning of a
// punycode-encoded hostname then consider that a match.
{L"x", "http://xn--1lq90ic7f1rc.cn/", "https://xn--1lq90ic7f1rc.cn/",
true},
{L"http://\x5317 x", "http://xn--1lq90ic7f1rc.cn/",
"https://xn--1lq90ic7f1rc.cn/", false},
{L"http://\x89c6 x", "http://xn--1lq90ic7f1rc.cn/",
"https://xn--1lq90ic7f1rc.cn/", true},
// URLs with hosts containing only `www.` should produce valid stripped
// urls
{L"http://www./", "http://www./", "http://google.com/", false},
};
for (const auto& caseI : cases)
CheckDuplicateCase(caseI);
}
TEST_F(AutocompleteMatchTest, DedupeDriveURLs) {
DuplicateCase cases[] = {
// Document URLs pointing to the same document, perhaps with different
// /edit points, hashes, or cgiargs, are deduped.
{L"docs", "https://docs.google.com/spreadsheets/d/the_doc-id/preview?x=1",
"https://docs.google.com/spreadsheets/d/the_doc-id/edit?x=2#y=3", true},
{L"report", "https://drive.google.com/open?id=the-doc-id",
"https://docs.google.com/spreadsheets/d/the-doc-id/edit?x=2#y=3", true},
// Similar but different URLs should not be deduped.
{L"docs", "https://docs.google.com/spreadsheets/d/the_doc-id/preview",
"https://docs.google.com/spreadsheets/d/another_doc-id/preview", false},
{L"report", "https://drive.google.com/open?id=the-doc-id",
"https://drive.google.com/open?id=another-doc-id", false},
};
for (const auto& caseI : cases)
CheckDuplicateCase(caseI);
}
TEST_F(AutocompleteMatchTest, UpgradeMatchWithPropertiesFrom) {
scoped_refptr<FakeAutocompleteProvider> bookmark_provider =
new FakeAutocompleteProvider(AutocompleteProvider::Type::TYPE_BOOKMARK);
scoped_refptr<FakeAutocompleteProvider> history_provider =
new FakeAutocompleteProvider(
AutocompleteProvider::Type::TYPE_HISTORY_QUICK);
scoped_refptr<FakeAutocompleteProvider> search_provider =
new FakeAutocompleteProvider(AutocompleteProvider::Type::TYPE_SEARCH);
AutocompleteMatch search_history_match(search_provider.get(), 500, true,
AutocompleteMatchType::SEARCH_HISTORY);
// Entity match should get the increased score, but not change types.
AutocompleteMatch entity_match(search_provider.get(), 400, false,
AutocompleteMatchType::SEARCH_SUGGEST_ENTITY);
entity_match.UpgradeMatchWithPropertiesFrom(search_history_match);
EXPECT_EQ(entity_match.relevance, 500);
EXPECT_EQ(entity_match.type, AutocompleteMatchType::SEARCH_SUGGEST_ENTITY);
// Suggest and search-what-typed matches should get the search history type.
AutocompleteMatch suggest_match(search_provider.get(), 400, true,
AutocompleteMatchType::SEARCH_SUGGEST);
AutocompleteMatch search_what_you_typed(
search_provider.get(), 400, true,
AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED);
suggest_match.UpgradeMatchWithPropertiesFrom(search_history_match);
search_what_you_typed.UpgradeMatchWithPropertiesFrom(search_history_match);
EXPECT_EQ(suggest_match.relevance, 500);
EXPECT_EQ(search_what_you_typed.relevance, 500);
EXPECT_EQ(suggest_match.type, AutocompleteMatchType::SEARCH_HISTORY);
EXPECT_EQ(search_what_you_typed.type, AutocompleteMatchType::SEARCH_HISTORY);
// Some providers should bestow their suggestion texts even if not the primary
// duplicate.
AutocompleteMatch history_match(history_provider.get(), 800, true,
AutocompleteMatchType::HISTORY_TITLE);
AutocompleteMatch bookmark_match(bookmark_provider.get(), 400, true,
AutocompleteMatchType::BOOKMARK_TITLE);
history_match.contents = u"overwrite";
history_match.inline_autocompletion = u"preserve";
bookmark_match.contents = u"propagate";
bookmark_match.inline_autocompletion = u"discard";
history_match.UpgradeMatchWithPropertiesFrom(bookmark_match);
EXPECT_EQ(history_match.type, AutocompleteMatchType::HISTORY_TITLE);
EXPECT_EQ(history_match.contents, u"propagate");
EXPECT_EQ(history_match.inline_autocompletion, u"preserve");
omnibox::RichAnswerTemplate answer_template;
omnibox::SuggestionEnhancement* enhancement =
answer_template.mutable_enhancements()->add_enhancements();
enhancement->set_display_text("Similar and opposite words");
AutocompleteMatch match_with_answer_actions(
search_provider.get(), 400, true, AutocompleteMatchType::SEARCH_SUGGEST);
match_with_answer_actions.actions.push_back(
base::MakeRefCounted<OmniboxAnswerAction>(
std::move(*enhancement), TemplateURLRef::SearchTermsArgs(),
omnibox::ANSWER_TYPE_DICTIONARY));
AutocompleteMatch match_with_no_answer_actions(
search_provider.get(), 400, true,
AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED);
match_with_no_answer_actions.UpgradeMatchWithPropertiesFrom(
match_with_answer_actions);
EXPECT_EQ(0u, match_with_no_answer_actions.actions.size());
}
TEST_F(AutocompleteMatchTest, MergeScoringSignals) {
AutocompleteMatch match = CreateACMatchWithScoringSignals(
/*typed_count*/ 3, /*visit_count*/ 10,
/*elapsed_time_last_visit_secs*/ 100, /*shortcut_visit_count*/ 5,
/*shortest_shortcut_len*/ 3, /*is_host_only*/ true,
/*num_bookmarks_of_url*/ 5, /*first_bookmark_title_match_position*/ 1,
/*total_bookmark_title_match_length*/ 8,
/*num_input_terms_matched_by_bookmark_title*/ 2,
/*first_url_match_position*/ 2, /*total_url_match_length*/ 5,
/*host_match_at_word_boundary*/ true, /*total_path_match_length*/ 0,
/*total_query_or_ref_match_length*/ 0,
/*total_title_match_length*/ 5, /*has_non_scheme_www_match*/ true,
/*num_input_terms_matched_by_title*/ 2,
/*num_input_terms_matched_by_url*/ 2, /*length_of_url*/ 10,
/*site_engagement*/ 0.6, /*allowed_to_be_default_match*/ true);
AutocompleteMatch other_match = CreateACMatchWithScoringSignals(
/*typed_count*/ 1, /*visit_count*/ 2, /*elapsed_time_last_visit_secs*/ 50,
/*shortcut_visit_count*/ 1,
/*shortest_shortcut_len*/ 2, /*is_host_only*/ false,
/*num_bookmarks_of_url*/ 1, /*first_bookmark_title_match_position*/ 2,
/*total_bookmark_title_match_length*/ 6,
/*num_input_terms_matched_by_bookmark_title*/ 3,
/*first_url_match_position*/ 5, /*total_url_match_length*/ 3,
/*host_match_at_word_boundary*/ false, /*total_path_match_length*/ 1,
/*total_query_or_ref_match_length*/ 2,
/*total_title_match_length*/ 3, /*has_non_scheme_www_match*/ false,
/*num_input_terms_matched_by_title*/ 0,
/*num_input_terms_matched_by_url*/ 1, /*length_of_url*/ 12,
/*site_engagement*/ 0.5, /*allowed_to_be_default_match*/ false);
match.MergeScoringSignals(other_match);
EXPECT_EQ(match.scoring_signals->typed_count(), 3);
EXPECT_EQ(match.scoring_signals->visit_count(), 10);
EXPECT_EQ(match.scoring_signals->elapsed_time_last_visit_secs(), 50);
EXPECT_EQ(match.scoring_signals->shortcut_visit_count(), 5);
EXPECT_EQ(match.scoring_signals->shortest_shortcut_len(), 2);
EXPECT_TRUE(match.scoring_signals->is_host_only());
EXPECT_EQ(match.scoring_signals->num_bookmarks_of_url(), 5);
EXPECT_EQ(match.scoring_signals->first_bookmark_title_match_position(), 1);
EXPECT_EQ(match.scoring_signals->total_bookmark_title_match_length(), 8);
EXPECT_EQ(match.scoring_signals->num_input_terms_matched_by_bookmark_title(),
3);
EXPECT_EQ(match.scoring_signals->first_url_match_position(), 2);
EXPECT_EQ(match.scoring_signals->total_url_match_length(), 5);
EXPECT_TRUE(match.scoring_signals->host_match_at_word_boundary());
EXPECT_EQ(match.scoring_signals->total_path_match_length(), 1);
EXPECT_EQ(match.scoring_signals->total_query_or_ref_match_length(), 2);
EXPECT_EQ(match.scoring_signals->total_title_match_length(), 5);
EXPECT_TRUE(match.scoring_signals->has_non_scheme_www_match());
EXPECT_EQ(match.scoring_signals->num_input_terms_matched_by_title(), 2);
EXPECT_EQ(match.scoring_signals->num_input_terms_matched_by_url(), 2);
EXPECT_EQ(match.scoring_signals->length_of_url(), 10);
EXPECT_EQ(match.scoring_signals->site_engagement(), 0.6f);
EXPECT_TRUE(match.scoring_signals->allowed_to_be_default_match());
}
TEST_F(AutocompleteMatchTest, SetAllowedToBeDefault) {
// Test all combinations of:
// 1) input text in ["goo", "goo ", "goo "]
// 2) input prevent_inline_autocomplete in [false, true]
// 3) match inline_autocompletion in ["", "gle.com", " gle.com", " gle.com"]
TestSetAllowedToBeDefault(1, "goo", false, "", "", true);
TestSetAllowedToBeDefault(2, "goo", false, "gle.com", "gle.com", true);
TestSetAllowedToBeDefault(3, "goo", false, " gle.com", " gle.com", true);
TestSetAllowedToBeDefault(4, "goo", false, " gle.com", " gle.com", true);
TestSetAllowedToBeDefault(5, "goo ", false, "", "", true);
TestSetAllowedToBeDefault(6, "goo ", false, "gle.com", "gle.com", false);
TestSetAllowedToBeDefault(7, "goo ", false, " gle.com", "gle.com", true);
TestSetAllowedToBeDefault(8, "goo ", false, " gle.com", " gle.com", true);
TestSetAllowedToBeDefault(9, "goo ", false, "", "", true);
TestSetAllowedToBeDefault(10, "goo ", false, "gle.com", "gle.com", false);
TestSetAllowedToBeDefault(11, "goo ", false, " gle.com", " gle.com", false);
TestSetAllowedToBeDefault(12, "goo ", false, " gle.com", "gle.com", true);
TestSetAllowedToBeDefault(13, "goo", true, "", "", true);
TestSetAllowedToBeDefault(14, "goo", true, "gle.com", "gle.com", false);
TestSetAllowedToBeDefault(15, "goo", true, " gle.com", " gle.com", false);
TestSetAllowedToBeDefault(16, "goo", true, " gle.com", " gle.com", false);
TestSetAllowedToBeDefault(17, "goo ", true, "", "", true);
TestSetAllowedToBeDefault(18, "goo ", true, "gle.com", "gle.com", false);
TestSetAllowedToBeDefault(19, "goo ", true, " gle.com", " gle.com", false);
TestSetAllowedToBeDefault(20, "goo ", true, " gle.com", " gle.com", false);
TestSetAllowedToBeDefault(21, "goo ", true, "", "", true);
TestSetAllowedToBeDefault(22, "goo ", true, "gle.com", "gle.com", false);
TestSetAllowedToBeDefault(23, "goo ", true, " gle.com", " gle.com", false);
TestSetAllowedToBeDefault(24, "goo ", true, " gle.com", " gle.com", false);
}
TEST_F(AutocompleteMatchTest, TryRichAutocompletion) {
auto test = [](const std::string input_text,
bool input_prevent_inline_autocomplete,
const std::string primary_text,
const std::string secondary_text,
const std::string shortcut_text, bool expected_return,
AutocompleteMatch::RichAutocompletionType
expected_rich_autocompletion_triggered,
const std::string expected_inline_autocompletion,
const std::string expected_additional_text,
bool expected_allowed_to_be_default_match) {
AutocompleteInput input(base::UTF8ToUTF16(input_text),
metrics::OmniboxEventProto::OTHER,
TestSchemeClassifier());
input.set_prevent_inline_autocomplete(input_prevent_inline_autocomplete);
AutocompleteMatch match;
EXPECT_EQ(
match.TryRichAutocompletion(input, base::UTF8ToUTF16(primary_text),
base::UTF8ToUTF16(secondary_text),
base::UTF8ToUTF16(shortcut_text)),
expected_return);
EXPECT_EQ(match.rich_autocompletion_triggered,
expected_rich_autocompletion_triggered);
EXPECT_EQ(base::UTF16ToUTF8(match.inline_autocompletion).c_str(),
expected_inline_autocompletion);
EXPECT_EQ(base::UTF16ToUTF8(match.additional_text).c_str(),
expected_additional_text);
EXPECT_EQ(match.allowed_to_be_default_match,
expected_allowed_to_be_default_match);
};
{
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeatureWithParameters(
omnibox::kRichAutocompletion,
{
{"RichAutocompletionAutocompleteTitlesMinChar", "0"},
{"RichAutocompletionAutocompleteShortcutTextMinChar", "0"},
});
RichAutocompletionParams::ClearParamsForTesting();
// The combinations that are allowed to be autocompleted, in order of
// preference:
// - Non-shortcut, prefix, primary - autocomplete
// - Non-shortcut, prefix, secondary - no
// - Non-shortcut, non-prefix, primary - no
// - Non-shortcut, non-prefix, secondary - no
// - Shortcut, prefix, primary - autocomplete
// - Shortcut, prefix, secondary - autocomplete
// - Shortcut, prefix, shortcut - autocomplete
// - Shortcut, non-prefix, primary - no
// - Shortcut, non-prefix, secondary - no
{
SCOPED_TRACE("non-shortcut, prefix, primary text");
// This case shouldn't set `rich_autocompletion_triggered`.
test("x", false, "x_mixd_x_primary", "x_mixd_x_secondary", "", true,
AutocompleteMatch::RichAutocompletionType::kNone, "_mixd_x_primary",
"", true);
}
{
SCOPED_TRACE("non-shortcut, all other cases text");
test("x", false, "y_mixd_x_primary", "x_mixd_x_secondary", "", false,
AutocompleteMatch::RichAutocompletionType::kNone, "", "", false);
}
{
SCOPED_TRACE("shortcut, prefix, primary text");
test("x", false, "x_mixd_x_primary", "x_mixd_x_secondary",
"x_mixd_x_shortcut", true,
AutocompleteMatch::RichAutocompletionType::kNone, "_mixd_x_primary",
"", true);
}
{
SCOPED_TRACE("shortcut, prefix, secondary text");
test("x", false, "y_mixd_x_primary", "x_mixd_x_secondary",
"x_mixd_x_shortcut", true,
AutocompleteMatch::RichAutocompletionType::kTitlePrefix,
"_mixd_x_secondary", "y_mixd_x_primary", true);
}
{
SCOPED_TRACE("shortcut, prefix, shortcut text");
test("x", false, "y_mixd_x_primary", "y_mixd_x_secondary",
"x_mixd_x_shortcut", true,
AutocompleteMatch::RichAutocompletionType::kShortcutTextPrefix,
"_mixd_x_shortcut", "y_mixd_x_primary", true);
}
{
SCOPED_TRACE("shortcut, all other cases");
test("x", false, "y_mixd_x_primary", "y_mixd_x_secondary",
"y_mixd_x_shortcut", false,
AutocompleteMatch::RichAutocompletionType::kNone, "", "", false);
}
{
SCOPED_TRACE("Autocomplete input with spaces");
test("x x", false, "primary", "x x secondary", "x x shortcut", true,
AutocompleteMatch::RichAutocompletionType::kTitlePrefix,
" secondary", "primary", true);
}
}
{
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeatureWithParameters(
omnibox::kRichAutocompletion,
{
{"RichAutocompletionAutocompleteTitlesMinChar", "3"},
{"RichAutocompletionAutocompleteShortcutTextMinChar", "3"},
});
RichAutocompletionParams::ClearParamsForTesting();
{
SCOPED_TRACE("Do autocomplete title if input is greater than limits");
test("x_mixd", false, "y_mixd_x_primary", "x_mixd_x_secondary",
"x_mixd_x_shortcut", true,
AutocompleteMatch::RichAutocompletionType::kTitlePrefix,
"_x_secondary", "y_mixd_x_primary", true);
}
{
SCOPED_TRACE("Do autocomplete shortcut if input is greater than limits");
test("x_mixd", false, "y_mixd_x_primary", "y_mixd_x_secondary",
"x_mixd_x_shortcut", true,
AutocompleteMatch::RichAutocompletionType::kShortcutTextPrefix,
"_x_shortcut", "y_mixd_x_primary", true);
}
{
SCOPED_TRACE(
"Don't autocomplete title or shortcut if input is shorter than "
"limits");
test("x", false, "y_mixd_x_primary", "y_mixd_x_secondary",
"x_mixd_x_shortcut", false,
AutocompleteMatch::RichAutocompletionType::kNone, "", "", false);
}
}
}
TEST_F(AutocompleteMatchTest, BetterDuplicate) {
const auto create_match = [](scoped_refptr<FakeAutocompleteProvider> provider,
int relevance,
AutocompleteMatchType::Type match_type =
AutocompleteMatchType::URL_WHAT_YOU_TYPED) {
return AutocompleteMatch{provider.get(), relevance, false, match_type};
};
scoped_refptr<FakeAutocompleteProvider> document_provider =
new FakeAutocompleteProvider(AutocompleteProvider::Type::TYPE_DOCUMENT);
scoped_refptr<FakeAutocompleteProvider> bookmark_provider =
new FakeAutocompleteProvider(AutocompleteProvider::Type::TYPE_BOOKMARK);
scoped_refptr<FakeAutocompleteProvider> history_provider =
new FakeAutocompleteProvider(
AutocompleteProvider::Type::TYPE_HISTORY_QUICK);
scoped_refptr<FakeAutocompleteProvider> shortcuts_provider =
new FakeAutocompleteProvider(AutocompleteProvider::Type::TYPE_SHORTCUTS);
scoped_refptr<FakeAutocompleteProvider> featured_search_provider =
new FakeAutocompleteProvider(
AutocompleteProvider::Type::TYPE_FEATURED_SEARCH);
// Prefer document provider matches over other providers, even if scored
// lower.
EXPECT_TRUE(
AutocompleteMatch::BetterDuplicate(create_match(document_provider, 0),
create_match(history_provider, 1000)));
// Prefer bookmark provider matches over other providers, even if scored
// lower.
EXPECT_TRUE(
AutocompleteMatch::BetterDuplicate(create_match(bookmark_provider, 0),
create_match(history_provider, 1000)));
// Prefer document provider matches over bookmark provider matches.
EXPECT_TRUE(AutocompleteMatch::BetterDuplicate(
create_match(document_provider, 0),
create_match(bookmark_provider, 1000)));
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
// Prefer non-shortcuts provider matches over shortcuts provider matches.
EXPECT_TRUE(AutocompleteMatch::BetterDuplicate(
create_match(history_provider, 0),
create_match(shortcuts_provider, 1000)));
// Prefer featured enterprise search over other matches.
EXPECT_TRUE(AutocompleteMatch::BetterDuplicate(
create_match(featured_search_provider, 100,
AutocompleteMatchType::FEATURED_ENTERPRISE_SEARCH),
create_match(featured_search_provider, 500,
AutocompleteMatchType::STARTER_PACK)));
EXPECT_FALSE(AutocompleteMatch::BetterDuplicate(
create_match(featured_search_provider, 500,
AutocompleteMatchType::STARTER_PACK),
create_match(featured_search_provider, 100,
AutocompleteMatchType::FEATURED_ENTERPRISE_SEARCH)));
EXPECT_TRUE(AutocompleteMatch::BetterDuplicate(
create_match(featured_search_provider, 100,
AutocompleteMatchType::FEATURED_ENTERPRISE_SEARCH),
create_match(bookmark_provider, 500)));
EXPECT_FALSE(AutocompleteMatch::BetterDuplicate(
create_match(bookmark_provider, 500),
create_match(featured_search_provider, 100,
AutocompleteMatchType::FEATURED_ENTERPRISE_SEARCH)));
// Prefer stater pack matches over other matches.
EXPECT_TRUE(AutocompleteMatch::BetterDuplicate(
create_match(featured_search_provider, 100,
AutocompleteMatchType::STARTER_PACK),
create_match(bookmark_provider, 500)));
EXPECT_FALSE(AutocompleteMatch::BetterDuplicate(
create_match(bookmark_provider, 500),
create_match(featured_search_provider, 100,
AutocompleteMatchType::STARTER_PACK)));
#endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
// Prefer more relevant matches.
EXPECT_FALSE(
AutocompleteMatch::BetterDuplicate(create_match(history_provider, 500),
create_match(history_provider, 510)));
}
TEST_F(AutocompleteMatchTest, FilterOmniboxActions) {
scoped_refptr<FakeAutocompleteProvider> provider =
new FakeAutocompleteProvider(AutocompleteProvider::Type::TYPE_SEARCH);
const OmniboxAction::LabelStrings dummy_labels(u"", u"", u"", u"");
using OmniboxActionId::ACTION_IN_SUGGEST;
using OmniboxActionId::HISTORY_CLUSTERS;
using OmniboxActionId::PEDAL;
struct FilterOmniboxActionsTestData {
std::string test_name;
// This is what will get added to the AutocompleteMatch.
std::vector<OmniboxActionId> actions_attached_to_match;
// This is the filter. Order of elements specifies the preference.
std::vector<OmniboxActionId> allowed_actions;
// This is the expected result.
std::vector<OmniboxActionId> resulting_actions;
} test_cases[]{
{"have nothing, want nothing", {}, {}, {}},
{"have nothing, want Pedals", {}, {PEDAL}, {}},
{"have Pedals, want nothing", {PEDAL}, {}, {}},
{"have Pedals, want Pedals", {PEDAL}, {PEDAL}, {PEDAL}},
{"have Pedals, want History Clusters", {PEDAL}, {HISTORY_CLUSTERS}, {}},
{"have Pedals, want History Clusters, then Pedals",
{PEDAL},
{HISTORY_CLUSTERS, PEDAL},
{PEDAL}},
{"have Pedals and History Clusters, want History Clusters, then Pedals",
{PEDAL, HISTORY_CLUSTERS},
{HISTORY_CLUSTERS, PEDAL},
{HISTORY_CLUSTERS}},
{"have Pedals and History Clusters, want Pedals, then History Clusters",
{PEDAL, HISTORY_CLUSTERS},
{PEDAL, HISTORY_CLUSTERS},
{PEDAL}},
{"have Pedals and History Clusters, want Actions in Suggest",
{PEDAL, HISTORY_CLUSTERS},
{ACTION_IN_SUGGEST},
{}},
{"have Pedals and History Clusters, want Actions in Suggest, then Pedals",
{PEDAL, HISTORY_CLUSTERS},
{ACTION_IN_SUGGEST, PEDAL},
{PEDAL}},
{"have Pedals, Actions and History Clusters, want Pedals",
{ACTION_IN_SUGGEST, PEDAL, HISTORY_CLUSTERS},
{PEDAL},
{PEDAL}},
{"have multiple, want Actions, then History Clusters, then Pedals",
// Mix: 4 pedals, 3 history clusters, 2 actions.
{PEDAL, ACTION_IN_SUGGEST, HISTORY_CLUSTERS, PEDAL, ACTION_IN_SUGGEST,
HISTORY_CLUSTERS, PEDAL, HISTORY_CLUSTERS, PEDAL},
{ACTION_IN_SUGGEST, HISTORY_CLUSTERS, PEDAL},
{ACTION_IN_SUGGEST, ACTION_IN_SUGGEST}},
{"have multiple, want History Clusters, then Actions, then Pedals",
// Mix: 4 pedals, 3 history clusters, 2 actions.
{PEDAL, ACTION_IN_SUGGEST, HISTORY_CLUSTERS, PEDAL, ACTION_IN_SUGGEST,
HISTORY_CLUSTERS, PEDAL, HISTORY_CLUSTERS, PEDAL},
{HISTORY_CLUSTERS, ACTION_IN_SUGGEST, PEDAL},
{HISTORY_CLUSTERS, HISTORY_CLUSTERS, HISTORY_CLUSTERS}},
{"have multiple, want Pedals, then History Clusters, then Actions",
// Mix: 4 pedals, 3 history clusters, 2 actions.
{PEDAL, ACTION_IN_SUGGEST, HISTORY_CLUSTERS, PEDAL, ACTION_IN_SUGGEST,
HISTORY_CLUSTERS, PEDAL, HISTORY_CLUSTERS, PEDAL},
{PEDAL, HISTORY_CLUSTERS, ACTION_IN_SUGGEST},
{PEDAL, PEDAL, PEDAL, PEDAL}},
{"have multiple, want nothing",
// Mix: 4 pedals, 3 history clusters, 2 actions.
{PEDAL, ACTION_IN_SUGGEST, HISTORY_CLUSTERS, PEDAL, ACTION_IN_SUGGEST,
HISTORY_CLUSTERS, PEDAL, HISTORY_CLUSTERS, PEDAL},
{},
{}}};
for (const auto& test_case : test_cases) {
AutocompleteMatch match(provider.get(), 1, false,
AutocompleteMatchType::SEARCH_SUGGEST_ENTITY);
// Populate match with requested actions.
for (auto& action_id : test_case.actions_attached_to_match) {
match.actions.push_back(
base::MakeRefCounted<FakeOmniboxAction>(action_id));
}
match.FilterOmniboxActions(test_case.allowed_actions);
EXPECT_EQ(match.actions.size(), test_case.resulting_actions.size())
<< "while testing variant: " << test_case.test_name;
for (size_t index = 0u; index < match.actions.size(); ++index) {
EXPECT_EQ(match.actions[index]->ActionId(),
test_case.resulting_actions[index])
<< "while testing variant: " << test_case.test_name;
}
}
}
TEST_F(AutocompleteMatchTest, RearrangeActionsInSuggest) {
scoped_refptr<FakeAutocompleteProvider> provider =
new FakeAutocompleteProvider(AutocompleteProvider::Type::TYPE_SEARCH);
const OmniboxAction::LabelStrings dummy_labels(u"", u"", u"", u"");
using ActionType = omnibox::SuggestTemplateInfo::TemplateAction::ActionType;
constexpr auto CALL =
omnibox::SuggestTemplateInfo_TemplateAction_ActionType_CALL;
constexpr auto NAV =
omnibox::SuggestTemplateInfo_TemplateAction_ActionType_DIRECTIONS;
constexpr auto REVS =
omnibox::SuggestTemplateInfo_TemplateAction_ActionType_REVIEWS;
struct FilterOmniboxActionsTestData {
std::string test_name;
// This is what will get added to the AutocompleteMatch.
std::vector<ActionType> types_to_add;
// This is the expected result (and order).
std::vector<ActionType> types_to_expect;
} test_cases[]{
// clang-format off
// Retain all
{"retain all - no actions, promote calls", {}, {}},
{"retain all - have no reviews, promote reviews",
{CALL, CALL, CALL}, {CALL, CALL, CALL}},
{"retain all - have reviews",
{CALL, CALL, REVS}, {CALL, CALL, REVS}},
{"retain all - have all types",
{CALL, NAV, REVS}, {CALL, NAV, REVS}},
{"retain all - have all types, sort",
{REVS, CALL, NAV}, {CALL, NAV, REVS}},
{"retain all - have multiple reviews, sort",
{REVS, NAV, REVS}, {NAV, REVS, REVS}},
// clang-format on
};
for (const auto& test_case : test_cases) {
AutocompleteMatch match(provider.get(), 1, false,
AutocompleteMatchType::SEARCH_SUGGEST_ENTITY);
// Populate match with requested actions.
for (auto& action_type : test_case.types_to_add) {
omnibox::SuggestTemplateInfo::TemplateAction action;
action.set_action_type(action_type);
match.actions.push_back(base::MakeRefCounted<OmniboxActionInSuggest>(
std::move(action), std::nullopt));
}
match.FilterAndSortActionsInSuggest();
EXPECT_EQ(match.actions.size(), test_case.types_to_expect.size())
<< "while testing variant: " << test_case.test_name;
for (size_t index = 0u; index < match.actions.size(); ++index) {
const auto* action =
OmniboxActionInSuggest::FromAction(match.actions[index].get());
EXPECT_NE(nullptr, action)
<< "while testing variant: " << test_case.test_name;
EXPECT_EQ(action->Type(), test_case.types_to_expect[index])
<< "at position " << index
<< " while testing variant: " << test_case.test_name;
}
}
}
#if (!BUILDFLAG(IS_ANDROID) || BUILDFLAG(ENABLE_VR)) && !BUILDFLAG(IS_IOS)
TEST_F(AutocompleteMatchTest, ValidateGetVectorIcons) {
AutocompleteMatch match;
// Irrespective of match type, bookmark suggestions should have a non-empty
// icon.
EXPECT_FALSE(match.GetVectorIcon(/*is_bookmark=*/true).is_empty());
for (int type = AutocompleteMatchType::URL_WHAT_YOU_TYPED;
type != AutocompleteMatchType::NUM_TYPES; type++) {
match.type = static_cast<AutocompleteMatchType::Type>(type);
if (match.type == AutocompleteMatchType::STARTER_PACK) {
// All STARTER_PACK suggestions should have non-empty vector icons.
for (int starter_pack_id = template_url_starter_pack_data::kBookmarks;
starter_pack_id != template_url_starter_pack_data::kMaxStarterPackId;
starter_pack_id++) {
TemplateURLData turl_data;
turl_data.starter_pack_id = starter_pack_id;
TemplateURL turl(turl_data);
EXPECT_FALSE(
match.GetVectorIcon(/*is_bookmark=*/false, &turl).is_empty());
}
} else if (match.type == AutocompleteMatchType::SEARCH_SUGGEST_TAIL ||
match.type == AutocompleteMatchType::HISTORY_EMBEDDINGS_ANSWER ||
(match.type == AutocompleteMatchType::NULL_RESULT_MESSAGE &&
!match.IsIphSuggestion())) {
// SEARCH_SUGGEST_TAIL and non-IPH NULL_RESULT_MESSAGE suggestions use an
// empty vector icon.
EXPECT_TRUE(match.GetVectorIcon(/*is_bookmark=*/false).is_empty());
} else {
// All other suggestion types should result in non-empty vector icons.
EXPECT_FALSE(match.GetVectorIcon(/*is_bookmark=*/false).is_empty());
}
}
// When the match has a SuggestTemplateInfo, its icon should be set.
// This checks the full range to ensure any new additions get mapped.
match.suggest_template = omnibox::SuggestTemplateInfo();
for (int i = omnibox::SuggestTemplateInfo::IconType_MIN;
i <= omnibox::SuggestTemplateInfo::IconType_MAX; i++) {
match.suggest_template->set_type_icon(
static_cast<omnibox::SuggestTemplateInfo::IconType>(i));
EXPECT_FALSE(match.GetVectorIcon(false).is_empty());
}
}
#endif
TEST_F(AutocompleteMatchTest, IsClipboardType) {
std::set<int> clipboard_types{AutocompleteMatchType::CLIPBOARD_TEXT,
AutocompleteMatchType::CLIPBOARD_URL,
AutocompleteMatchType::CLIPBOARD_IMAGE};
for (int type = 0; type < AutocompleteMatchType::NUM_TYPES; type++) {
EXPECT_EQ(
AutocompleteMatch::IsClipboardType((AutocompleteMatchType::Type)type),
clipboard_types.contains(type));
}
}
TEST_F(AutocompleteMatchTest, HasLensSearchAction) {
AutocompleteMatch match;
EXPECT_FALSE(match.HasLensSearchAction());
match.suggest_template = omnibox::SuggestTemplateInfo();
EXPECT_FALSE(match.HasLensSearchAction());
auto* action = match.suggest_template->add_action_suggestions();
action->set_action_type(
omnibox::SuggestTemplateInfo_TemplateAction_ActionType_DIRECTIONS);
EXPECT_FALSE(match.HasLensSearchAction());
action = match.suggest_template->add_action_suggestions();
action->set_action_type(
omnibox::SuggestTemplateInfo_TemplateAction_ActionType_CHROME_LENS);
EXPECT_TRUE(match.HasLensSearchAction());
}