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