| 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 |