// 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 "chrome/browser/chromeos/launcher_search/search_util.h"
#include <optional>
#include <string>
#include "base/json/json_reader.h"
#include "base/values.h"
#include "components/omnibox/browser/autocomplete_classifier.h"
#include "components/omnibox/browser/autocomplete_match_type.h"
#include "components/omnibox/browser/autocomplete_provider.h"
#include "components/omnibox/browser/suggestion_answer.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/omnibox_proto/answer_type.pb.h"
#include "third_party/omnibox_proto/rich_answer_template.pb.h"
#include "url/gurl.h"
namespace crosapi {
namespace {
TEST(SearchUtilTest, ProviderTypes) {
const int types = ProviderTypes();
EXPECT_FALSE(types & AutocompleteProvider::TYPE_DOCUMENT);
EXPECT_TRUE(types & AutocompleteProvider::TYPE_OPEN_TAB);
}
TEST(SearchUtilTest, ProviderTypesPickerAll) {
const int types = ProviderTypesPicker(/*bookmarks=*/true, /*history=*/true,
/*open_tabs=*/true);
EXPECT_TRUE(types & AutocompleteProvider::TYPE_BOOKMARK);
EXPECT_TRUE(types & AutocompleteProvider::TYPE_HISTORY_FUZZY);
EXPECT_TRUE(types & AutocompleteProvider::TYPE_HISTORY_QUICK);
EXPECT_TRUE(types & AutocompleteProvider::TYPE_HISTORY_URL);
EXPECT_TRUE(types & AutocompleteProvider::TYPE_OPEN_TAB);
EXPECT_FALSE(types & AutocompleteProvider::TYPE_DOCUMENT);
EXPECT_FALSE(types & AutocompleteProvider::TYPE_SEARCH);
}
TEST(SearchUtilTest, ProviderTypesPickerBookmarks) {
const int types = ProviderTypesPicker(/*bookmarks=*/true, /*history=*/false,
/*open_tabs=*/false);
EXPECT_TRUE(types & AutocompleteProvider::TYPE_BOOKMARK);
EXPECT_FALSE(types & AutocompleteProvider::TYPE_HISTORY_FUZZY);
EXPECT_FALSE(types & AutocompleteProvider::TYPE_HISTORY_QUICK);
EXPECT_FALSE(types & AutocompleteProvider::TYPE_HISTORY_URL);
EXPECT_FALSE(types & AutocompleteProvider::TYPE_OPEN_TAB);
EXPECT_FALSE(types & AutocompleteProvider::TYPE_DOCUMENT);
EXPECT_FALSE(types & AutocompleteProvider::TYPE_SEARCH);
}
TEST(SearchUtilTest, ProviderTypesPickerHistory) {
const int types = ProviderTypesPicker(/*bookmarks=*/false, /*history=*/true,
/*open_tabs=*/false);
EXPECT_FALSE(types & AutocompleteProvider::TYPE_BOOKMARK);
EXPECT_TRUE(types & AutocompleteProvider::TYPE_HISTORY_FUZZY);
EXPECT_TRUE(types & AutocompleteProvider::TYPE_HISTORY_QUICK);
EXPECT_TRUE(types & AutocompleteProvider::TYPE_HISTORY_URL);
EXPECT_FALSE(types & AutocompleteProvider::TYPE_OPEN_TAB);
EXPECT_FALSE(types & AutocompleteProvider::TYPE_DOCUMENT);
EXPECT_FALSE(types & AutocompleteProvider::TYPE_SEARCH);
}
TEST(SearchUtilTest, ProviderTypesPickerOpenTab) {
const int types = ProviderTypesPicker(/*bookmarks=*/false, /*history=*/false,
/*open_tabs=*/true);
EXPECT_FALSE(types & AutocompleteProvider::TYPE_BOOKMARK);
EXPECT_FALSE(types & AutocompleteProvider::TYPE_HISTORY_FUZZY);
EXPECT_FALSE(types & AutocompleteProvider::TYPE_HISTORY_QUICK);
EXPECT_FALSE(types & AutocompleteProvider::TYPE_HISTORY_URL);
EXPECT_TRUE(types & AutocompleteProvider::TYPE_OPEN_TAB);
EXPECT_FALSE(types & AutocompleteProvider::TYPE_DOCUMENT);
EXPECT_FALSE(types & AutocompleteProvider::TYPE_SEARCH);
}
// Tests result conversion for a default answer result.
TEST(SearchUtilTest, CreateAnswerResult) {
AutocompleteMatch match;
match.relevance = 1248;
match.destination_url = GURL("http://www.example.com/");
match.type = AutocompleteMatchType::Type::SEARCH_SUGGEST;
match.contents = u"contents";
match.description = u"description";
match.answer_type = omnibox::ANSWER_TYPE_DICTIONARY;
std::string json =
R"({ "l": [)"
R"( { "il": { "t": [{ "t": "text one", "tt": 8 }],)"
R"( "at": { "t": "additional one", "tt": 42 } } },)"
R"( { "il": { "t": [{ "t": "text two", "tt": 5 }],)"
R"( "at": { "t": "additional two", "tt": 6 },)"
R"( "al": "a11y label" } })"
R"(] })";
std::optional<base::Value> value = base::JSONReader::Read(json);
ASSERT_TRUE(value && value->is_dict());
// Create answer result using SuggestionAnswer.
{
SuggestionAnswer answer;
ASSERT_TRUE(SuggestionAnswer::ParseAnswer(value->GetDict(),
match.answer_type, &answer));
match.answer = answer;
const auto result =
CreateAnswerResult(match, nullptr, u"query", AutocompleteInput());
EXPECT_EQ(result->type, mojom::SearchResultType::kOmniboxResult);
EXPECT_EQ(result->relevance, 1248);
ASSERT_TRUE(result->destination_url.has_value());
EXPECT_EQ(result->destination_url.value(), GURL("http://www.example.com/"));
EXPECT_EQ(result->is_omnibox_search,
mojom::SearchResult::OptionalBool::kTrue);
EXPECT_EQ(result->is_answer, mojom::SearchResult::OptionalBool::kTrue);
ASSERT_TRUE(result->contents.has_value());
EXPECT_EQ(result->contents.value(), u"contents");
ASSERT_TRUE(result->additional_contents.has_value());
EXPECT_EQ(result->additional_contents.value(), u"additional one");
ASSERT_TRUE(result->description.has_value());
EXPECT_EQ(result->description.value(), u"text two");
ASSERT_TRUE(result->additional_description.has_value());
EXPECT_EQ(result->additional_description.value(), u"additional two");
}
// Create answer result using omnibox::RichAnswerTemplate.
{
omnibox::RichAnswerTemplate answer_template;
ASSERT_TRUE(omnibox::answer_data_parser::ParseJsonToAnswerData(
value->GetDict(), &answer_template));
match.answer_template = answer_template;
const auto result =
CreateAnswerResult(match, nullptr, u"query", AutocompleteInput());
EXPECT_EQ(result->type, mojom::SearchResultType::kOmniboxResult);
EXPECT_EQ(result->relevance, 1248);
ASSERT_TRUE(result->destination_url.has_value());
EXPECT_EQ(result->destination_url.value(), GURL("http://www.example.com/"));
EXPECT_EQ(result->is_omnibox_search,
mojom::SearchResult::OptionalBool::kTrue);
EXPECT_EQ(result->is_answer, mojom::SearchResult::OptionalBool::kTrue);
ASSERT_TRUE(result->contents.has_value());
EXPECT_EQ(result->contents.value(), u"contents");
ASSERT_TRUE(result->additional_contents.has_value());
EXPECT_EQ(result->additional_contents.value(), u"additional one");
ASSERT_TRUE(result->description.has_value());
EXPECT_EQ(result->description.value(), u"text two");
ASSERT_TRUE(result->additional_description.has_value());
EXPECT_EQ(result->additional_description.value(), u"additional two");
}
}
// Tests result conversion for a rich entity Omnibox result.
TEST(SearchUtilTest, CreateResult) {
AutocompleteMatch match;
match.relevance = 300;
match.destination_url = GURL("http://www.example.com/");
match.type = AutocompleteMatchType::Type::SEARCH_SUGGEST_ENTITY;
match.image_url = GURL("http://www.example.com/image.jpeg");
match.contents = u"contents";
match.description = u"description";
match.contents_class = {
ACMatchClassification(0, ACMatchClassification::Style::URL)};
match.description_class = {
ACMatchClassification(0, ACMatchClassification::Style::MATCH)};
const auto result =
CreateResult(match, nullptr, nullptr, nullptr, AutocompleteInput());
EXPECT_EQ(result->type, mojom::SearchResultType::kOmniboxResult);
EXPECT_EQ(result->relevance, 300);
ASSERT_TRUE(result->destination_url.has_value());
EXPECT_EQ(result->destination_url.value(), GURL("http://www.example.com/"));
EXPECT_EQ(result->is_omnibox_search,
mojom::SearchResult::OptionalBool::kTrue);
EXPECT_EQ(result->is_answer, mojom::SearchResult::OptionalBool::kFalse);
EXPECT_EQ(result->omnibox_type, mojom::SearchResult::OmniboxType::kSearch);
ASSERT_TRUE(result->image_url.has_value());
EXPECT_EQ(result->image_url.value(),
GURL("http://www.example.com/image.jpeg"));
ASSERT_TRUE(result->contents.has_value());
EXPECT_EQ(result->contents.value(), u"contents");
ASSERT_TRUE(result->description.has_value());
EXPECT_EQ(result->description.value(), u"description");
// The URL text class should be retained, but MATCH should be ignored.
EXPECT_EQ(result->contents_type, mojom::SearchResult::TextType::kUrl);
EXPECT_EQ(result->description_type, mojom::SearchResult::TextType::kUnset);
}
// Tests result conversion for a weather Omnibox result.
TEST(SearchUtilTest, CreateWeatherResult) {
AutocompleteMatch match;
match.relevance = 1200;
match.destination_url = GURL("https://www.example.com.au/weather");
match.type = AutocompleteMatchType::Type::SEARCH_SUGGEST;
match.contents = u"Weather in Perth";
match.contents_class = {
ACMatchClassification(0, ACMatchClassification::Style::MATCH)};
match.answer_type = omnibox::ANSWER_TYPE_WEATHER;
const std::string json =
R"({ "l": [)"
R"( { "il": { "t": [ { "t": "weather in perth", "tt": 8 } ] } },)"
R"( {)"
R"( "il": {)"
R"( "al": "Sunny",)"
R"( "at": { "t": "Perth WA, Australia", "tt": 19 },)"
R"( "i": { "d": "//www.weather.com.au/sunny.png", "t": 3 },)"
R"( "t": [ { "t": "16°C", "tt": 18 } ])"
R"( })"
R"( })"
R"(] })";
const std::optional<base::Value> value = base::JSONReader::Read(json);
ASSERT_TRUE(value && value->is_dict());
// Create weather result using SuggestionAnswer.
{
SuggestionAnswer answer;
ASSERT_TRUE(SuggestionAnswer::ParseAnswer(
value->GetDict(),
/* The answer type for 'weather'. */ match.answer_type, &answer));
match.answer = answer;
const auto result =
CreateAnswerResult(match, nullptr, u"query", AutocompleteInput());
EXPECT_EQ(result->type, mojom::SearchResultType::kOmniboxResult);
EXPECT_EQ(result->relevance, 1200);
ASSERT_TRUE(result->destination_url.has_value());
EXPECT_EQ(result->destination_url.value(),
GURL("https://www.example.com.au/weather"));
EXPECT_EQ(result->is_omnibox_search,
mojom::SearchResult::OptionalBool::kTrue);
EXPECT_EQ(result->is_answer, mojom::SearchResult::OptionalBool::kTrue);
ASSERT_TRUE(result->contents.has_value());
EXPECT_EQ(result->contents.value(), u"Weather in Perth");
EXPECT_FALSE(result->additional_contents.has_value());
ASSERT_TRUE(result->description.has_value());
EXPECT_EQ(result->description.value(), u"16°C");
ASSERT_TRUE(result->additional_description.has_value());
EXPECT_EQ(result->additional_description.value(), u"Perth WA, Australia");
ASSERT_TRUE(result->description_a11y_label.has_value());
EXPECT_EQ(result->description_a11y_label.value(), u"Sunny");
ASSERT_TRUE(result->image_url.has_value());
EXPECT_EQ(result->image_url.value(),
GURL("https://www.weather.com.au/sunny.png"));
}
// Create weather result using omnibox::RichAnswerTemplate.
{
omnibox::RichAnswerTemplate answer_template;
ASSERT_TRUE(omnibox::answer_data_parser::ParseJsonToAnswerData(
value->GetDict(), &answer_template));
match.answer_template = answer_template;
const auto result =
CreateAnswerResult(match, nullptr, u"query", AutocompleteInput());
EXPECT_EQ(result->type, mojom::SearchResultType::kOmniboxResult);
EXPECT_EQ(result->relevance, 1200);
ASSERT_TRUE(result->destination_url.has_value());
EXPECT_EQ(result->destination_url.value(),
GURL("https://www.example.com.au/weather"));
EXPECT_EQ(result->is_omnibox_search,
mojom::SearchResult::OptionalBool::kTrue);
EXPECT_EQ(result->is_answer, mojom::SearchResult::OptionalBool::kTrue);
ASSERT_TRUE(result->contents.has_value());
EXPECT_EQ(result->contents.value(), u"Weather in Perth");
EXPECT_FALSE(result->additional_contents.has_value());
ASSERT_TRUE(result->description.has_value());
EXPECT_EQ(result->description.value(), u"16°C");
ASSERT_TRUE(result->additional_description.has_value());
EXPECT_EQ(result->additional_description.value(), u"Perth WA, Australia");
ASSERT_TRUE(result->description_a11y_label.has_value());
EXPECT_EQ(result->description_a11y_label.value(), u"Sunny");
ASSERT_TRUE(result->image_url.has_value());
EXPECT_EQ(result->image_url.value(),
GURL("https://www.weather.com.au/sunny.png"));
}
}
// Tests result conversion for a calculator result. A calculator result can
// either have a description or no description; both possibilities are tested.
TEST(SearchUtilTest, CreateCalculatorResult) {
// A match with the input in contents and the answer in desc.
AutocompleteMatch match_desc;
match_desc.relevance = 300;
match_desc.type = AutocompleteMatchType::CALCULATOR;
match_desc.destination_url = GURL("https://www.example.com.au/calc?q=1+2");
match_desc.contents = u"1+2";
match_desc.description = u"3";
match_desc.contents_class = {
ACMatchClassification(0, ACMatchClassification::Style::MATCH)};
match_desc.description_class = {
ACMatchClassification(0, ACMatchClassification::Style::MATCH)};
// A match with the answer in content and no desc.
AutocompleteMatch match_no_desc;
match_no_desc.relevance = 300;
match_no_desc.type = AutocompleteMatchType::CALCULATOR;
match_no_desc.destination_url = GURL("https://www.example.com.au/calc?q=1+2");
match_no_desc.contents = u"3";
match_no_desc.contents_class = {
ACMatchClassification(0, ACMatchClassification::Style::MATCH)};
for (const auto& match : {match_desc, match_no_desc}) {
const auto result = CreateAnswerResult(match, /*controller=*/nullptr,
u"1+2", AutocompleteInput());
EXPECT_EQ(result->relevance, 300);
ASSERT_TRUE(result->destination_url.has_value());
EXPECT_EQ(result->destination_url.value(),
GURL("https://www.example.com.au/calc?q=1+2"));
EXPECT_EQ(result->is_omnibox_search,
mojom::SearchResult::OptionalBool::kTrue);
EXPECT_EQ(result->is_answer, mojom::SearchResult::OptionalBool::kTrue);
EXPECT_EQ(result->answer_type,
mojom::SearchResult::AnswerType::kCalculator);
ASSERT_TRUE(result->contents.has_value());
EXPECT_EQ(result->contents.value(), u"1+2");
ASSERT_TRUE(result->description.has_value());
EXPECT_EQ(result->description.value(), u"3");
EXPECT_EQ(result->contents_type, mojom::SearchResult::TextType::kUnset);
EXPECT_EQ(result->description_type, mojom::SearchResult::TextType::kUnset);
}
}
} // namespace
} // namespace crosapi