Update Polymer components and re-run reproduce.sh
[chromium-blink-merge.git] / components / omnibox / keyword_provider_unittest.cc
blob6e05f292c880d211dcbbc9e84e59fb64bca98c18
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "base/command_line.h"
6 #include "base/message_loop/message_loop.h"
7 #include "base/strings/utf_string_conversions.h"
8 #include "components/metrics/proto/omnibox_event.pb.h"
9 #include "components/omnibox/autocomplete_match.h"
10 #include "components/omnibox/autocomplete_scheme_classifier.h"
11 #include "components/omnibox/keyword_provider.h"
12 #include "components/search_engines/search_engines_switches.h"
13 #include "components/search_engines/template_url.h"
14 #include "components/search_engines/template_url_service.h"
15 #include "testing/gtest/include/gtest/gtest.h"
16 #include "url/gurl.h"
18 using base::ASCIIToUTF16;
20 namespace {
22 class TestingSchemeClassifier : public AutocompleteSchemeClassifier {
23 public:
24 metrics::OmniboxInputType::Type GetInputTypeForScheme(
25 const std::string& scheme) const override {
26 if (net::URLRequest::IsHandledProtocol(scheme))
27 return metrics::OmniboxInputType::URL;
28 return metrics::OmniboxInputType::INVALID;
32 } // namespace
34 class KeywordProviderTest : public testing::Test {
35 protected:
36 template<class ResultType>
37 struct MatchType {
38 const ResultType member;
39 bool allowed_to_be_default_match;
42 template<class ResultType>
43 struct TestData {
44 const base::string16 input;
45 const size_t num_results;
46 const MatchType<ResultType> output[3];
49 KeywordProviderTest() : kw_provider_(NULL) { }
50 ~KeywordProviderTest() override {}
52 void SetUp() override;
53 void TearDown() override;
55 template<class ResultType>
56 void RunTest(TestData<ResultType>* keyword_cases,
57 int num_cases,
58 ResultType AutocompleteMatch::* member);
60 protected:
61 static const TemplateURLService::Initializer kTestData[];
63 scoped_refptr<KeywordProvider> kw_provider_;
64 scoped_ptr<TemplateURLService> model_;
67 // static
68 const TemplateURLService::Initializer KeywordProviderTest::kTestData[] = {
69 { "aa", "aa.com?foo={searchTerms}", "aa" },
70 { "aaaa", "http://aaaa/?aaaa=1&b={searchTerms}&c", "aaaa" },
71 { "aaaaa", "{searchTerms}", "aaaaa" },
72 { "ab", "bogus URL {searchTerms}", "ab" },
73 { "weasel", "weasel{searchTerms}weasel", "weasel" },
74 { "www", " +%2B?={searchTerms}foo ", "www" },
75 { "nonsub", "http://nonsubstituting-keyword.com/", "nonsub" },
76 { "z", "{searchTerms}=z", "z" },
79 void KeywordProviderTest::SetUp() {
80 model_.reset(new TemplateURLService(kTestData, arraysize(kTestData)));
81 kw_provider_ = new KeywordProvider(NULL, model_.get());
84 void KeywordProviderTest::TearDown() {
85 model_.reset();
86 kw_provider_ = NULL;
89 template<class ResultType>
90 void KeywordProviderTest::RunTest(TestData<ResultType>* keyword_cases,
91 int num_cases,
92 ResultType AutocompleteMatch::* member) {
93 ACMatches matches;
94 for (int i = 0; i < num_cases; ++i) {
95 SCOPED_TRACE(keyword_cases[i].input);
96 AutocompleteInput input(
97 keyword_cases[i].input, base::string16::npos, std::string(), GURL(),
98 metrics::OmniboxEventProto::INVALID_SPEC, true, false, true, true,
99 false, TestingSchemeClassifier());
100 kw_provider_->Start(input, false);
101 EXPECT_TRUE(kw_provider_->done());
102 matches = kw_provider_->matches();
103 ASSERT_EQ(keyword_cases[i].num_results, matches.size());
104 for (size_t j = 0; j < matches.size(); ++j) {
105 EXPECT_EQ(keyword_cases[i].output[j].member, matches[j].*member);
106 EXPECT_EQ(keyword_cases[i].output[j].allowed_to_be_default_match,
107 matches[j].allowed_to_be_default_match);
112 TEST_F(KeywordProviderTest, Edit) {
113 const MatchType<base::string16> kEmptyMatch = { base::string16(), false };
114 TestData<base::string16> edit_cases[] = {
115 // Searching for a nonexistent prefix should give nothing.
116 { ASCIIToUTF16("Not Found"), 0,
117 { kEmptyMatch, kEmptyMatch, kEmptyMatch } },
118 { ASCIIToUTF16("aaaaaNot Found"), 0,
119 { kEmptyMatch, kEmptyMatch, kEmptyMatch } },
121 // Check that tokenization only collapses whitespace between first tokens,
122 // no-query-input cases have a space appended, and action is not escaped.
123 { ASCIIToUTF16("z"), 1,
124 { { ASCIIToUTF16("z "), true }, kEmptyMatch, kEmptyMatch } },
125 { ASCIIToUTF16("z \t"), 1,
126 { { ASCIIToUTF16("z "), true }, kEmptyMatch, kEmptyMatch } },
128 // Check that exact, substituting keywords with a verbatim search term
129 // don't generate a result. (These are handled by SearchProvider.)
130 { ASCIIToUTF16("z foo"), 0,
131 { kEmptyMatch, kEmptyMatch, kEmptyMatch } },
132 { ASCIIToUTF16("z a b c++"), 0,
133 { kEmptyMatch, kEmptyMatch, kEmptyMatch } },
135 // Matches should be limited to three, and sorted in quality order, not
136 // alphabetical.
137 { ASCIIToUTF16("aaa"), 2,
138 { { ASCIIToUTF16("aaaa "), false },
139 { ASCIIToUTF16("aaaaa "), false },
140 kEmptyMatch } },
141 { ASCIIToUTF16("a 1 2 3"), 3,
142 { { ASCIIToUTF16("aa 1 2 3"), false },
143 { ASCIIToUTF16("ab 1 2 3"), false },
144 { ASCIIToUTF16("aaaa 1 2 3"), false } } },
145 { ASCIIToUTF16("www.a"), 3,
146 { { ASCIIToUTF16("aa "), false },
147 { ASCIIToUTF16("ab "), false },
148 { ASCIIToUTF16("aaaa "), false } } },
149 // Exact matches should prevent returning inexact matches. Also, the
150 // verbatim query for this keyword match should not be returned. (It's
151 // returned by SearchProvider.)
152 { ASCIIToUTF16("aaaa foo"), 0,
153 { kEmptyMatch, kEmptyMatch, kEmptyMatch } },
154 { ASCIIToUTF16("www.aaaa foo"), 0,
155 { kEmptyMatch, kEmptyMatch, kEmptyMatch } },
157 // Clean up keyword input properly. "http" and "https" are the only
158 // allowed schemes.
159 { ASCIIToUTF16("www"), 1,
160 { { ASCIIToUTF16("www "), true }, kEmptyMatch, kEmptyMatch }},
161 { ASCIIToUTF16("www."), 0,
162 { kEmptyMatch, kEmptyMatch, kEmptyMatch } },
163 { ASCIIToUTF16("www.w w"), 2,
164 { { ASCIIToUTF16("www w"), false },
165 { ASCIIToUTF16("weasel w"), false },
166 kEmptyMatch } },
167 { ASCIIToUTF16("http://www"), 1,
168 { { ASCIIToUTF16("www "), true }, kEmptyMatch, kEmptyMatch } },
169 { ASCIIToUTF16("http://www."), 0,
170 { kEmptyMatch, kEmptyMatch, kEmptyMatch } },
171 { ASCIIToUTF16("ftp: blah"), 0,
172 { kEmptyMatch, kEmptyMatch, kEmptyMatch } },
173 { ASCIIToUTF16("mailto:z"), 0,
174 { kEmptyMatch, kEmptyMatch, kEmptyMatch } },
175 { ASCIIToUTF16("ftp://z"), 0,
176 { kEmptyMatch, kEmptyMatch, kEmptyMatch } },
177 { ASCIIToUTF16("https://z"), 1,
178 { { ASCIIToUTF16("z "), true }, kEmptyMatch, kEmptyMatch } },
180 // Non-substituting keywords, whether typed fully or not
181 // should not add a space.
182 { ASCIIToUTF16("nonsu"), 1,
183 { { ASCIIToUTF16("nonsub"), false }, kEmptyMatch, kEmptyMatch } },
184 { ASCIIToUTF16("nonsub"), 1,
185 { { ASCIIToUTF16("nonsub"), true }, kEmptyMatch, kEmptyMatch } },
188 RunTest<base::string16>(edit_cases, arraysize(edit_cases),
189 &AutocompleteMatch::fill_into_edit);
192 TEST_F(KeywordProviderTest, URL) {
193 const MatchType<GURL> kEmptyMatch = { GURL(), false };
194 TestData<GURL> url_cases[] = {
195 // No query input -> empty destination URL.
196 { ASCIIToUTF16("z"), 1,
197 { { GURL(), true }, kEmptyMatch, kEmptyMatch } },
198 { ASCIIToUTF16("z \t"), 1,
199 { { GURL(), true }, kEmptyMatch, kEmptyMatch } },
201 // Check that tokenization only collapses whitespace between first tokens
202 // and query input, but not rest of URL, is escaped.
203 { ASCIIToUTF16("w bar +baz"), 2,
204 { { GURL(" +%2B?=bar+%2Bbazfoo "), false },
205 { GURL("bar+%2Bbaz=z"), false },
206 kEmptyMatch } },
208 // Substitution should work with various locations of the "%s".
209 { ASCIIToUTF16("aaa 1a2b"), 2,
210 { { GURL("http://aaaa/?aaaa=1&b=1a2b&c"), false },
211 { GURL("1a2b"), false },
212 kEmptyMatch } },
213 { ASCIIToUTF16("a 1 2 3"), 3,
214 { { GURL("aa.com?foo=1+2+3"), false },
215 { GURL("bogus URL 1+2+3"), false },
216 { GURL("http://aaaa/?aaaa=1&b=1+2+3&c"), false } } },
217 { ASCIIToUTF16("www.w w"), 2,
218 { { GURL(" +%2B?=wfoo "), false },
219 { GURL("weaselwweasel"), false },
220 kEmptyMatch } },
223 RunTest<GURL>(url_cases, arraysize(url_cases),
224 &AutocompleteMatch::destination_url);
227 TEST_F(KeywordProviderTest, Contents) {
228 const MatchType<base::string16> kEmptyMatch = { base::string16(), false };
229 TestData<base::string16> contents_cases[] = {
230 // No query input -> substitute "<enter query>" into contents.
231 { ASCIIToUTF16("z"), 1,
232 { { ASCIIToUTF16("Search z for <enter query>"), true },
233 kEmptyMatch, kEmptyMatch } },
234 { ASCIIToUTF16("z \t"), 1,
235 { { ASCIIToUTF16("Search z for <enter query>"), true },
236 kEmptyMatch, kEmptyMatch } },
238 // Exact keyword matches with remaining text should return nothing.
239 { ASCIIToUTF16("www.www www"), 0,
240 { kEmptyMatch, kEmptyMatch, kEmptyMatch } },
241 { ASCIIToUTF16("z a b c++"), 0,
242 { kEmptyMatch, kEmptyMatch, kEmptyMatch } },
244 // Exact keyword matches with remaining text when the keyword is an
245 // extension keyword should return something. This is tested in
246 // chrome/browser/extensions/api/omnibox/omnibox_apitest.cc's
247 // in OmniboxApiTest's Basic test.
249 // Substitution should work with various locations of the "%s".
250 { ASCIIToUTF16("aaa"), 2,
251 { { ASCIIToUTF16("Search aaaa for <enter query>"), false },
252 { ASCIIToUTF16("Search aaaaa for <enter query>"), false },
253 kEmptyMatch} },
254 { ASCIIToUTF16("www.w w"), 2,
255 { { ASCIIToUTF16("Search www for w"), false },
256 { ASCIIToUTF16("Search weasel for w"), false },
257 kEmptyMatch } },
258 // Also, check that tokenization only collapses whitespace between first
259 // tokens and contents are not escaped or unescaped.
260 { ASCIIToUTF16("a 1 2+ 3"), 3,
261 { { ASCIIToUTF16("Search aa for 1 2+ 3"), false },
262 { ASCIIToUTF16("Search ab for 1 2+ 3"), false },
263 { ASCIIToUTF16("Search aaaa for 1 2+ 3"), false } } },
266 RunTest<base::string16>(contents_cases, arraysize(contents_cases),
267 &AutocompleteMatch::contents);
270 TEST_F(KeywordProviderTest, AddKeyword) {
271 TemplateURLData data;
272 data.SetShortName(ASCIIToUTF16("Test"));
273 base::string16 keyword(ASCIIToUTF16("foo"));
274 data.SetKeyword(keyword);
275 data.SetURL("http://www.google.com/foo?q={searchTerms}");
276 TemplateURL* template_url = new TemplateURL(data);
277 model_->Add(template_url);
278 ASSERT_TRUE(template_url == model_->GetTemplateURLForKeyword(keyword));
281 TEST_F(KeywordProviderTest, RemoveKeyword) {
282 base::string16 url(ASCIIToUTF16("http://aaaa/?aaaa=1&b={searchTerms}&c"));
283 model_->Remove(model_->GetTemplateURLForKeyword(ASCIIToUTF16("aaaa")));
284 ASSERT_TRUE(model_->GetTemplateURLForKeyword(ASCIIToUTF16("aaaa")) == NULL);
287 TEST_F(KeywordProviderTest, GetKeywordForInput) {
288 EXPECT_EQ(ASCIIToUTF16("aa"),
289 kw_provider_->GetKeywordForText(ASCIIToUTF16("aa")));
290 EXPECT_EQ(base::string16(),
291 kw_provider_->GetKeywordForText(ASCIIToUTF16("aafoo")));
292 EXPECT_EQ(base::string16(),
293 kw_provider_->GetKeywordForText(ASCIIToUTF16("aa foo")));
296 TEST_F(KeywordProviderTest, GetSubstitutingTemplateURLForInput) {
297 struct {
298 const std::string text;
299 const size_t cursor_position;
300 const bool allow_exact_keyword_match;
301 const std::string expected_url;
302 const std::string updated_text;
303 const size_t updated_cursor_position;
304 } cases[] = {
305 { "foo", base::string16::npos, true, "", "foo", base::string16::npos },
306 { "aa foo", base::string16::npos, true, "aa.com?foo={searchTerms}", "foo",
307 base::string16::npos },
309 // Cursor adjustment.
310 { "aa foo", base::string16::npos, true, "aa.com?foo={searchTerms}", "foo",
311 base::string16::npos },
312 { "aa foo", 4u, true, "aa.com?foo={searchTerms}", "foo", 1u },
313 // Cursor at the end.
314 { "aa foo", 6u, true, "aa.com?foo={searchTerms}", "foo", 3u },
315 // Cursor before the first character of the remaining text.
316 { "aa foo", 3u, true, "aa.com?foo={searchTerms}", "foo", 0u },
318 // Trailing space.
319 { "aa foo ", 7u, true, "aa.com?foo={searchTerms}", "foo ", 4u },
320 // Trailing space without remaining text, cursor in the middle.
321 { "aa ", 3u, true, "aa.com?foo={searchTerms}", "", base::string16::npos },
322 // Trailing space without remaining text, cursor at the end.
323 { "aa ", 4u, true, "aa.com?foo={searchTerms}", "", base::string16::npos },
324 // Extra space after keyword, cursor at the end.
325 { "aa foo ", 8u, true, "aa.com?foo={searchTerms}", "foo ", 4u },
326 // Extra space after keyword, cursor in the middle.
327 { "aa foo ", 3u, true, "aa.com?foo={searchTerms}", "foo ", 0 },
328 // Extra space after keyword, no trailing space, cursor at the end.
329 { "aa foo", 7u, true, "aa.com?foo={searchTerms}", "foo", 3u },
330 // Extra space after keyword, no trailing space, cursor in the middle.
331 { "aa foo", 5u, true, "aa.com?foo={searchTerms}", "foo", 1u },
333 // Disallow exact keyword match.
334 { "aa foo", base::string16::npos, false, "", "aa foo",
335 base::string16::npos },
337 for (size_t i = 0; i < arraysize(cases); i++) {
338 AutocompleteInput input(ASCIIToUTF16(cases[i].text),
339 cases[i].cursor_position, std::string(), GURL(),
340 metrics::OmniboxEventProto::INVALID_SPEC, false,
341 false, cases[i].allow_exact_keyword_match, true,
342 false, TestingSchemeClassifier());
343 const TemplateURL* url =
344 KeywordProvider::GetSubstitutingTemplateURLForInput(model_.get(),
345 &input);
346 if (cases[i].expected_url.empty())
347 EXPECT_FALSE(url);
348 else
349 EXPECT_EQ(cases[i].expected_url, url->url());
350 EXPECT_EQ(ASCIIToUTF16(cases[i].updated_text), input.text());
351 EXPECT_EQ(cases[i].updated_cursor_position, input.cursor_position());
355 // If extra query params are specified on the command line, they should be
356 // reflected (only) in the default search provider's destination URL.
357 TEST_F(KeywordProviderTest, ExtraQueryParams) {
358 base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
359 switches::kExtraSearchQueryParams, "a=b");
361 TestData<GURL> url_cases[] = {
362 { ASCIIToUTF16("a 1 2 3"), 3,
363 { { GURL("aa.com?a=b&foo=1+2+3"), false },
364 { GURL("bogus URL 1+2+3"), false },
365 { GURL("http://aaaa/?aaaa=1&b=1+2+3&c"), false } } },
368 RunTest<GURL>(url_cases, arraysize(url_cases),
369 &AutocompleteMatch::destination_url);
372 TEST_F(KeywordProviderTest, DoesNotProvideMatchesOnFocus) {
373 AutocompleteInput input(ASCIIToUTF16("aaa"), base::string16::npos,
374 std::string(), GURL(),
375 metrics::OmniboxEventProto::INVALID_SPEC, true, false,
376 true, true, true, TestingSchemeClassifier());
377 kw_provider_->Start(input, false);
378 ASSERT_TRUE(kw_provider_->matches().empty());