Roll src/third_party/WebKit 3aea697:d9c6159 (svn 201973:201974)
[chromium-blink-merge.git] / components / omnibox / browser / keyword_provider_unittest.cc
blob15be17c5114a0d6b7ff920482b47cae2938d4a61
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/browser/autocomplete_match.h"
10 #include "components/omnibox/browser/autocomplete_scheme_classifier.h"
11 #include "components/omnibox/browser/keyword_provider.h"
12 #include "components/omnibox/browser/mock_autocomplete_provider_client.h"
13 #include "components/search_engines/search_engines_switches.h"
14 #include "components/search_engines/template_url.h"
15 #include "components/search_engines/template_url_service.h"
16 #include "testing/gmock/include/gmock/gmock.h"
17 #include "testing/gtest/include/gtest/gtest.h"
18 #include "url/gurl.h"
20 using base::ASCIIToUTF16;
22 namespace {
24 class TestingSchemeClassifier : public AutocompleteSchemeClassifier {
25 public:
26 metrics::OmniboxInputType::Type GetInputTypeForScheme(
27 const std::string& scheme) const override {
28 if (net::URLRequest::IsHandledProtocol(scheme))
29 return metrics::OmniboxInputType::URL;
30 return metrics::OmniboxInputType::INVALID;
34 } // namespace
36 class KeywordProviderTest : public testing::Test {
37 protected:
38 template<class ResultType>
39 struct MatchType {
40 const ResultType member;
41 bool allowed_to_be_default_match;
44 template<class ResultType>
45 struct TestData {
46 const base::string16 input;
47 const size_t num_results;
48 const MatchType<ResultType> output[3];
51 KeywordProviderTest() : kw_provider_(NULL) { }
52 ~KeywordProviderTest() override {}
54 void SetUp() override;
55 void TearDown() override;
57 template<class ResultType>
58 void RunTest(TestData<ResultType>* keyword_cases,
59 int num_cases,
60 ResultType AutocompleteMatch::* member);
62 protected:
63 static const TemplateURLService::Initializer kTestData[];
65 scoped_refptr<KeywordProvider> kw_provider_;
66 scoped_ptr<MockAutocompleteProviderClient> client_;
69 // static
70 const TemplateURLService::Initializer KeywordProviderTest::kTestData[] = {
71 { "aa", "aa.com?foo={searchTerms}", "aa" },
72 { "aaaa", "http://aaaa/?aaaa=1&b={searchTerms}&c", "aaaa" },
73 { "aaaaa", "{searchTerms}", "aaaaa" },
74 { "ab", "bogus URL {searchTerms}", "ab" },
75 { "weasel", "weasel{searchTerms}weasel", "weasel" },
76 { "www", " +%2B?={searchTerms}foo ", "www" },
77 { "nonsub", "http://nonsubstituting-keyword.com/", "nonsub" },
78 { "z", "{searchTerms}=z", "z" },
81 void KeywordProviderTest::SetUp() {
82 scoped_ptr<TemplateURLService> template_url_service(
83 new TemplateURLService(kTestData, arraysize(kTestData)));
84 client_.reset(new MockAutocompleteProviderClient());
85 client_->set_template_url_service(template_url_service.Pass());
86 kw_provider_ = new KeywordProvider(client_.get(), nullptr);
89 void KeywordProviderTest::TearDown() {
90 client_.reset();
91 kw_provider_ = NULL;
94 template<class ResultType>
95 void KeywordProviderTest::RunTest(TestData<ResultType>* keyword_cases,
96 int num_cases,
97 ResultType AutocompleteMatch::* member) {
98 ACMatches matches;
99 for (int i = 0; i < num_cases; ++i) {
100 SCOPED_TRACE(keyword_cases[i].input);
101 AutocompleteInput input(
102 keyword_cases[i].input, base::string16::npos, std::string(), GURL(),
103 metrics::OmniboxEventProto::INVALID_SPEC, true, false, true, true,
104 false, TestingSchemeClassifier());
105 kw_provider_->Start(input, false);
106 EXPECT_TRUE(kw_provider_->done());
107 matches = kw_provider_->matches();
108 ASSERT_EQ(keyword_cases[i].num_results, matches.size());
109 for (size_t j = 0; j < matches.size(); ++j) {
110 EXPECT_EQ(keyword_cases[i].output[j].member, matches[j].*member);
111 EXPECT_EQ(keyword_cases[i].output[j].allowed_to_be_default_match,
112 matches[j].allowed_to_be_default_match);
117 TEST_F(KeywordProviderTest, Edit) {
118 const MatchType<base::string16> kEmptyMatch = { base::string16(), false };
119 TestData<base::string16> edit_cases[] = {
120 // Searching for a nonexistent prefix should give nothing.
121 { ASCIIToUTF16("Not Found"), 0,
122 { kEmptyMatch, kEmptyMatch, kEmptyMatch } },
123 { ASCIIToUTF16("aaaaaNot Found"), 0,
124 { kEmptyMatch, kEmptyMatch, kEmptyMatch } },
126 // Check that tokenization only collapses whitespace between first tokens,
127 // no-query-input cases have a space appended, and action is not escaped.
128 { ASCIIToUTF16("z"), 1,
129 { { ASCIIToUTF16("z "), true }, kEmptyMatch, kEmptyMatch } },
130 { ASCIIToUTF16("z \t"), 1,
131 { { ASCIIToUTF16("z "), true }, kEmptyMatch, kEmptyMatch } },
133 // Check that exact, substituting keywords with a verbatim search term
134 // don't generate a result. (These are handled by SearchProvider.)
135 { ASCIIToUTF16("z foo"), 0,
136 { kEmptyMatch, kEmptyMatch, kEmptyMatch } },
137 { ASCIIToUTF16("z a b c++"), 0,
138 { kEmptyMatch, kEmptyMatch, kEmptyMatch } },
140 // Matches should be limited to three, and sorted in quality order, not
141 // alphabetical.
142 { ASCIIToUTF16("aaa"), 2,
143 { { ASCIIToUTF16("aaaa "), false },
144 { ASCIIToUTF16("aaaaa "), false },
145 kEmptyMatch } },
146 { ASCIIToUTF16("a 1 2 3"), 3,
147 { { ASCIIToUTF16("aa 1 2 3"), false },
148 { ASCIIToUTF16("ab 1 2 3"), false },
149 { ASCIIToUTF16("aaaa 1 2 3"), false } } },
150 { ASCIIToUTF16("www.a"), 3,
151 { { ASCIIToUTF16("aa "), false },
152 { ASCIIToUTF16("ab "), false },
153 { ASCIIToUTF16("aaaa "), false } } },
154 // Exact matches should prevent returning inexact matches. Also, the
155 // verbatim query for this keyword match should not be returned. (It's
156 // returned by SearchProvider.)
157 { ASCIIToUTF16("aaaa foo"), 0,
158 { kEmptyMatch, kEmptyMatch, kEmptyMatch } },
159 { ASCIIToUTF16("www.aaaa foo"), 0,
160 { kEmptyMatch, kEmptyMatch, kEmptyMatch } },
162 // Clean up keyword input properly. "http" and "https" are the only
163 // allowed schemes.
164 { ASCIIToUTF16("www"), 1,
165 { { ASCIIToUTF16("www "), true }, kEmptyMatch, kEmptyMatch }},
166 { ASCIIToUTF16("www."), 0,
167 { kEmptyMatch, kEmptyMatch, kEmptyMatch } },
168 { ASCIIToUTF16("www.w w"), 2,
169 { { ASCIIToUTF16("www w"), false },
170 { ASCIIToUTF16("weasel w"), false },
171 kEmptyMatch } },
172 { ASCIIToUTF16("http://www"), 1,
173 { { ASCIIToUTF16("www "), true }, kEmptyMatch, kEmptyMatch } },
174 { ASCIIToUTF16("http://www."), 0,
175 { kEmptyMatch, kEmptyMatch, kEmptyMatch } },
176 { ASCIIToUTF16("ftp: blah"), 0,
177 { kEmptyMatch, kEmptyMatch, kEmptyMatch } },
178 { ASCIIToUTF16("mailto:z"), 0,
179 { kEmptyMatch, kEmptyMatch, kEmptyMatch } },
180 { ASCIIToUTF16("ftp://z"), 0,
181 { kEmptyMatch, kEmptyMatch, kEmptyMatch } },
182 { ASCIIToUTF16("https://z"), 1,
183 { { ASCIIToUTF16("z "), true }, kEmptyMatch, kEmptyMatch } },
185 // Non-substituting keywords, whether typed fully or not
186 // should not add a space.
187 { ASCIIToUTF16("nonsu"), 1,
188 { { ASCIIToUTF16("nonsub"), false }, kEmptyMatch, kEmptyMatch } },
189 { ASCIIToUTF16("nonsub"), 1,
190 { { ASCIIToUTF16("nonsub"), true }, kEmptyMatch, kEmptyMatch } },
193 RunTest<base::string16>(edit_cases, arraysize(edit_cases),
194 &AutocompleteMatch::fill_into_edit);
197 TEST_F(KeywordProviderTest, URL) {
198 const MatchType<GURL> kEmptyMatch = { GURL(), false };
199 TestData<GURL> url_cases[] = {
200 // No query input -> empty destination URL.
201 { ASCIIToUTF16("z"), 1,
202 { { GURL(), true }, kEmptyMatch, kEmptyMatch } },
203 { ASCIIToUTF16("z \t"), 1,
204 { { GURL(), true }, kEmptyMatch, kEmptyMatch } },
206 // Check that tokenization only collapses whitespace between first tokens
207 // and query input, but not rest of URL, is escaped.
208 { ASCIIToUTF16("w bar +baz"), 2,
209 { { GURL(" +%2B?=bar+%2Bbazfoo "), false },
210 { GURL("bar+%2Bbaz=z"), false },
211 kEmptyMatch } },
213 // Substitution should work with various locations of the "%s".
214 { ASCIIToUTF16("aaa 1a2b"), 2,
215 { { GURL("http://aaaa/?aaaa=1&b=1a2b&c"), false },
216 { GURL("1a2b"), false },
217 kEmptyMatch } },
218 { ASCIIToUTF16("a 1 2 3"), 3,
219 { { GURL("aa.com?foo=1+2+3"), false },
220 { GURL("bogus URL 1+2+3"), false },
221 { GURL("http://aaaa/?aaaa=1&b=1+2+3&c"), false } } },
222 { ASCIIToUTF16("www.w w"), 2,
223 { { GURL(" +%2B?=wfoo "), false },
224 { GURL("weaselwweasel"), false },
225 kEmptyMatch } },
228 RunTest<GURL>(url_cases, arraysize(url_cases),
229 &AutocompleteMatch::destination_url);
232 TEST_F(KeywordProviderTest, Contents) {
233 const MatchType<base::string16> kEmptyMatch = { base::string16(), false };
234 TestData<base::string16> contents_cases[] = {
235 // No query input -> substitute "<enter query>" into contents.
236 { ASCIIToUTF16("z"), 1,
237 { { ASCIIToUTF16("Search z for <enter query>"), true },
238 kEmptyMatch, kEmptyMatch } },
239 { ASCIIToUTF16("z \t"), 1,
240 { { ASCIIToUTF16("Search z for <enter query>"), true },
241 kEmptyMatch, kEmptyMatch } },
243 // Exact keyword matches with remaining text should return nothing.
244 { ASCIIToUTF16("www.www www"), 0,
245 { kEmptyMatch, kEmptyMatch, kEmptyMatch } },
246 { ASCIIToUTF16("z a b c++"), 0,
247 { kEmptyMatch, kEmptyMatch, kEmptyMatch } },
249 // Exact keyword matches with remaining text when the keyword is an
250 // extension keyword should return something. This is tested in
251 // chrome/browser/extensions/api/omnibox/omnibox_apitest.cc's
252 // in OmniboxApiTest's Basic test.
254 // Substitution should work with various locations of the "%s".
255 { ASCIIToUTF16("aaa"), 2,
256 { { ASCIIToUTF16("Search aaaa for <enter query>"), false },
257 { ASCIIToUTF16("Search aaaaa for <enter query>"), false },
258 kEmptyMatch} },
259 { ASCIIToUTF16("www.w w"), 2,
260 { { ASCIIToUTF16("Search www for w"), false },
261 { ASCIIToUTF16("Search weasel for w"), false },
262 kEmptyMatch } },
263 // Also, check that tokenization only collapses whitespace between first
264 // tokens and contents are not escaped or unescaped.
265 { ASCIIToUTF16("a 1 2+ 3"), 3,
266 { { ASCIIToUTF16("Search aa for 1 2+ 3"), false },
267 { ASCIIToUTF16("Search ab for 1 2+ 3"), false },
268 { ASCIIToUTF16("Search aaaa for 1 2+ 3"), false } } },
271 RunTest<base::string16>(contents_cases, arraysize(contents_cases),
272 &AutocompleteMatch::contents);
275 TEST_F(KeywordProviderTest, AddKeyword) {
276 TemplateURLData data;
277 data.SetShortName(ASCIIToUTF16("Test"));
278 base::string16 keyword(ASCIIToUTF16("foo"));
279 data.SetKeyword(keyword);
280 data.SetURL("http://www.google.com/foo?q={searchTerms}");
281 TemplateURL* template_url = new TemplateURL(data);
282 client_->GetTemplateURLService()->Add(template_url);
283 ASSERT_TRUE(
284 template_url ==
285 client_->GetTemplateURLService()->GetTemplateURLForKeyword(keyword));
288 TEST_F(KeywordProviderTest, RemoveKeyword) {
289 TemplateURLService* template_url_service = client_->GetTemplateURLService();
290 base::string16 url(ASCIIToUTF16("http://aaaa/?aaaa=1&b={searchTerms}&c"));
291 template_url_service->Remove(
292 template_url_service->GetTemplateURLForKeyword(ASCIIToUTF16("aaaa")));
293 ASSERT_TRUE(template_url_service->GetTemplateURLForKeyword(
294 ASCIIToUTF16("aaaa")) == NULL);
297 TEST_F(KeywordProviderTest, GetKeywordForInput) {
298 EXPECT_EQ(ASCIIToUTF16("aa"),
299 kw_provider_->GetKeywordForText(ASCIIToUTF16("aa")));
300 EXPECT_EQ(base::string16(),
301 kw_provider_->GetKeywordForText(ASCIIToUTF16("aafoo")));
302 EXPECT_EQ(base::string16(),
303 kw_provider_->GetKeywordForText(ASCIIToUTF16("aa foo")));
306 TEST_F(KeywordProviderTest, GetSubstitutingTemplateURLForInput) {
307 struct {
308 const std::string text;
309 const size_t cursor_position;
310 const bool allow_exact_keyword_match;
311 const std::string expected_url;
312 const std::string updated_text;
313 const size_t updated_cursor_position;
314 } cases[] = {
315 { "foo", base::string16::npos, true, "", "foo", base::string16::npos },
316 { "aa foo", base::string16::npos, true, "aa.com?foo={searchTerms}", "foo",
317 base::string16::npos },
319 // Cursor adjustment.
320 { "aa foo", base::string16::npos, true, "aa.com?foo={searchTerms}", "foo",
321 base::string16::npos },
322 { "aa foo", 4u, true, "aa.com?foo={searchTerms}", "foo", 1u },
323 // Cursor at the end.
324 { "aa foo", 6u, true, "aa.com?foo={searchTerms}", "foo", 3u },
325 // Cursor before the first character of the remaining text.
326 { "aa foo", 3u, true, "aa.com?foo={searchTerms}", "foo", 0u },
328 // Trailing space.
329 { "aa foo ", 7u, true, "aa.com?foo={searchTerms}", "foo ", 4u },
330 // Trailing space without remaining text, cursor in the middle.
331 { "aa ", 3u, true, "aa.com?foo={searchTerms}", "", base::string16::npos },
332 // Trailing space without remaining text, cursor at the end.
333 { "aa ", 4u, true, "aa.com?foo={searchTerms}", "", base::string16::npos },
334 // Extra space after keyword, cursor at the end.
335 { "aa foo ", 8u, true, "aa.com?foo={searchTerms}", "foo ", 4u },
336 // Extra space after keyword, cursor in the middle.
337 { "aa foo ", 3u, true, "aa.com?foo={searchTerms}", "foo ", 0 },
338 // Extra space after keyword, no trailing space, cursor at the end.
339 { "aa foo", 7u, true, "aa.com?foo={searchTerms}", "foo", 3u },
340 // Extra space after keyword, no trailing space, cursor in the middle.
341 { "aa foo", 5u, true, "aa.com?foo={searchTerms}", "foo", 1u },
343 // Disallow exact keyword match.
344 { "aa foo", base::string16::npos, false, "", "aa foo",
345 base::string16::npos },
347 for (size_t i = 0; i < arraysize(cases); i++) {
348 AutocompleteInput input(ASCIIToUTF16(cases[i].text),
349 cases[i].cursor_position, std::string(), GURL(),
350 metrics::OmniboxEventProto::INVALID_SPEC, false,
351 false, cases[i].allow_exact_keyword_match, true,
352 false, TestingSchemeClassifier());
353 const TemplateURL* url =
354 KeywordProvider::GetSubstitutingTemplateURLForInput(
355 client_->GetTemplateURLService(), &input);
356 if (cases[i].expected_url.empty())
357 EXPECT_FALSE(url);
358 else
359 EXPECT_EQ(cases[i].expected_url, url->url());
360 EXPECT_EQ(ASCIIToUTF16(cases[i].updated_text), input.text());
361 EXPECT_EQ(cases[i].updated_cursor_position, input.cursor_position());
365 // If extra query params are specified on the command line, they should be
366 // reflected (only) in the default search provider's destination URL.
367 TEST_F(KeywordProviderTest, ExtraQueryParams) {
368 base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
369 switches::kExtraSearchQueryParams, "a=b");
371 TestData<GURL> url_cases[] = {
372 { ASCIIToUTF16("a 1 2 3"), 3,
373 { { GURL("aa.com?a=b&foo=1+2+3"), false },
374 { GURL("bogus URL 1+2+3"), false },
375 { GURL("http://aaaa/?aaaa=1&b=1+2+3&c"), false } } },
378 RunTest<GURL>(url_cases, arraysize(url_cases),
379 &AutocompleteMatch::destination_url);
382 TEST_F(KeywordProviderTest, DoesNotProvideMatchesOnFocus) {
383 AutocompleteInput input(ASCIIToUTF16("aaa"), base::string16::npos,
384 std::string(), GURL(),
385 metrics::OmniboxEventProto::INVALID_SPEC, true, false,
386 true, true, true, TestingSchemeClassifier());
387 kw_provider_->Start(input, false);
388 ASSERT_TRUE(kw_provider_->matches().empty());