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