OLD | NEW |
| (Empty) |
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 } | |
OLD | NEW |