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 "chrome/browser/autocomplete/autocomplete_result.h" | |
6 | |
7 #include <vector> | |
8 | |
9 #include "base/memory/scoped_ptr.h" | |
10 #include "base/metrics/field_trial.h" | |
11 #include "base/strings/string_number_conversions.h" | |
12 #include "base/strings/string_util.h" | |
13 #include "base/strings/utf_string_conversions.h" | |
14 #include "components/metrics/proto/omnibox_event.pb.h" | |
15 #include "components/omnibox/autocomplete_input.h" | |
16 #include "components/omnibox/autocomplete_match.h" | |
17 #include "components/omnibox/autocomplete_match_type.h" | |
18 #include "components/omnibox/autocomplete_provider.h" | |
19 #include "components/omnibox/omnibox_field_trial.h" | |
20 #include "components/omnibox/test_scheme_classifier.h" | |
21 #include "components/search_engines/template_url_prepopulate_data.h" | |
22 #include "components/search_engines/template_url_service.h" | |
23 #include "components/variations/entropy_provider.h" | |
24 #include "components/variations/variations_associated_data.h" | |
25 #include "testing/gtest/include/gtest/gtest.h" | |
26 | |
27 using metrics::OmniboxEventProto; | |
28 | |
29 namespace { | |
30 | |
31 struct AutocompleteMatchTestData { | |
32 std::string destination_url; | |
33 AutocompleteMatch::Type type; | |
34 }; | |
35 | |
36 const AutocompleteMatchTestData kVerbatimMatches[] = { | |
37 { "http://search-what-you-typed/", | |
38 AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED }, | |
39 { "http://url-what-you-typed/", AutocompleteMatchType::URL_WHAT_YOU_TYPED }, | |
40 }; | |
41 | |
42 const AutocompleteMatchTestData kNonVerbatimMatches[] = { | |
43 { "http://search-history/", AutocompleteMatchType::SEARCH_HISTORY }, | |
44 { "http://history-title/", AutocompleteMatchType::HISTORY_TITLE }, | |
45 }; | |
46 | |
47 // Adds |count| AutocompleteMatches to |matches|. | |
48 void PopulateAutocompleteMatchesFromTestData( | |
49 const AutocompleteMatchTestData* data, | |
50 size_t count, | |
51 ACMatches* matches) { | |
52 ASSERT_TRUE(matches != NULL); | |
53 for (size_t i = 0; i < count; ++i) { | |
54 AutocompleteMatch match; | |
55 match.destination_url = GURL(data[i].destination_url); | |
56 match.relevance = | |
57 matches->empty() ? 1300 : (matches->back().relevance - 100); | |
58 match.allowed_to_be_default_match = true; | |
59 match.type = data[i].type; | |
60 matches->push_back(match); | |
61 } | |
62 } | |
63 | |
64 } // namespace | |
65 | |
66 class AutocompleteResultTest : public testing::Test { | |
67 public: | |
68 struct TestData { | |
69 // Used to build a url for the AutocompleteMatch. The URL becomes | |
70 // "http://" + ('a' + |url_id|) (e.g. an ID of 2 yields "http://b"). | |
71 int url_id; | |
72 | |
73 // ID of the provider. | |
74 int provider_id; | |
75 | |
76 // Relevance score. | |
77 int relevance; | |
78 | |
79 // Duplicate matches. | |
80 std::vector<AutocompleteMatch> duplicate_matches; | |
81 }; | |
82 | |
83 AutocompleteResultTest() { | |
84 // Destroy the existing FieldTrialList before creating a new one to avoid | |
85 // a DCHECK. | |
86 field_trial_list_.reset(); | |
87 field_trial_list_.reset(new base::FieldTrialList( | |
88 new metrics::SHA1EntropyProvider("foo"))); | |
89 variations::testing::ClearAllVariationParams(); | |
90 } | |
91 | |
92 virtual void SetUp() OVERRIDE { | |
93 #if defined(OS_ANDROID) | |
94 TemplateURLPrepopulateData::InitCountryCode( | |
95 std::string() /* unknown country code */); | |
96 #endif | |
97 template_url_service_.reset(new TemplateURLService(NULL, 0)); | |
98 template_url_service_->Load(); | |
99 } | |
100 | |
101 // Configures |match| from |data|. | |
102 static void PopulateAutocompleteMatch(const TestData& data, | |
103 AutocompleteMatch* match); | |
104 | |
105 // Adds |count| AutocompleteMatches to |matches|. | |
106 static void PopulateAutocompleteMatches(const TestData* data, | |
107 size_t count, | |
108 ACMatches* matches); | |
109 | |
110 // Asserts that |result| has |expected_count| matches matching |expected|. | |
111 void AssertResultMatches(const AutocompleteResult& result, | |
112 const TestData* expected, | |
113 size_t expected_count); | |
114 | |
115 // Creates an AutocompleteResult from |last| and |current|. The two are | |
116 // merged by |CopyOldMatches| and compared by |AssertResultMatches|. | |
117 void RunCopyOldMatchesTest(const TestData* last, size_t last_size, | |
118 const TestData* current, size_t current_size, | |
119 const TestData* expected, size_t expected_size); | |
120 | |
121 protected: | |
122 scoped_ptr<TemplateURLService> template_url_service_; | |
123 | |
124 private: | |
125 scoped_ptr<base::FieldTrialList> field_trial_list_; | |
126 | |
127 DISALLOW_COPY_AND_ASSIGN(AutocompleteResultTest); | |
128 }; | |
129 | |
130 // static | |
131 void AutocompleteResultTest::PopulateAutocompleteMatch( | |
132 const TestData& data, | |
133 AutocompleteMatch* match) { | |
134 match->provider = reinterpret_cast<AutocompleteProvider*>(data.provider_id); | |
135 match->fill_into_edit = base::IntToString16(data.url_id); | |
136 std::string url_id(1, data.url_id + 'a'); | |
137 match->destination_url = GURL("http://" + url_id); | |
138 match->relevance = data.relevance; | |
139 match->allowed_to_be_default_match = true; | |
140 match->duplicate_matches = data.duplicate_matches; | |
141 } | |
142 | |
143 // static | |
144 void AutocompleteResultTest::PopulateAutocompleteMatches( | |
145 const TestData* data, | |
146 size_t count, | |
147 ACMatches* matches) { | |
148 for (size_t i = 0; i < count; ++i) { | |
149 AutocompleteMatch match; | |
150 PopulateAutocompleteMatch(data[i], &match); | |
151 matches->push_back(match); | |
152 } | |
153 } | |
154 | |
155 void AutocompleteResultTest::AssertResultMatches( | |
156 const AutocompleteResult& result, | |
157 const TestData* expected, | |
158 size_t expected_count) { | |
159 ASSERT_EQ(expected_count, result.size()); | |
160 for (size_t i = 0; i < expected_count; ++i) { | |
161 AutocompleteMatch expected_match; | |
162 PopulateAutocompleteMatch(expected[i], &expected_match); | |
163 const AutocompleteMatch& match = *(result.begin() + i); | |
164 EXPECT_EQ(expected_match.provider, match.provider) << i; | |
165 EXPECT_EQ(expected_match.relevance, match.relevance) << i; | |
166 EXPECT_EQ(expected_match.destination_url.spec(), | |
167 match.destination_url.spec()) << i; | |
168 } | |
169 } | |
170 | |
171 void AutocompleteResultTest::RunCopyOldMatchesTest( | |
172 const TestData* last, size_t last_size, | |
173 const TestData* current, size_t current_size, | |
174 const TestData* expected, size_t expected_size) { | |
175 AutocompleteInput input(base::ASCIIToUTF16("a"), base::string16::npos, | |
176 base::string16(), GURL(), | |
177 OmniboxEventProto::INVALID_SPEC, false, false, false, | |
178 true, | |
179 TestSchemeClassifier()); | |
180 | |
181 ACMatches last_matches; | |
182 PopulateAutocompleteMatches(last, last_size, &last_matches); | |
183 AutocompleteResult last_result; | |
184 last_result.AppendMatches(last_matches); | |
185 last_result.SortAndCull(input, template_url_service_.get()); | |
186 | |
187 ACMatches current_matches; | |
188 PopulateAutocompleteMatches(current, current_size, ¤t_matches); | |
189 AutocompleteResult current_result; | |
190 current_result.AppendMatches(current_matches); | |
191 current_result.SortAndCull(input, template_url_service_.get()); | |
192 current_result.CopyOldMatches( | |
193 input, last_result, template_url_service_.get()); | |
194 | |
195 AssertResultMatches(current_result, expected, expected_size); | |
196 } | |
197 | |
198 // Assertion testing for AutocompleteResult::Swap. | |
199 TEST_F(AutocompleteResultTest, Swap) { | |
200 AutocompleteResult r1; | |
201 AutocompleteResult r2; | |
202 | |
203 // Swap with empty shouldn't do anything interesting. | |
204 r1.Swap(&r2); | |
205 EXPECT_EQ(r1.end(), r1.default_match()); | |
206 EXPECT_EQ(r2.end(), r2.default_match()); | |
207 | |
208 // Swap with a single match. | |
209 ACMatches matches; | |
210 AutocompleteMatch match; | |
211 match.relevance = 1; | |
212 match.allowed_to_be_default_match = true; | |
213 AutocompleteInput input(base::ASCIIToUTF16("a"), base::string16::npos, | |
214 base::string16(), GURL(), | |
215 OmniboxEventProto::INVALID_SPEC, false, false, false, | |
216 true, TestSchemeClassifier()); | |
217 matches.push_back(match); | |
218 r1.AppendMatches(matches); | |
219 r1.SortAndCull(input, template_url_service_.get()); | |
220 EXPECT_EQ(r1.begin(), r1.default_match()); | |
221 EXPECT_EQ("http://a/", r1.alternate_nav_url().spec()); | |
222 r1.Swap(&r2); | |
223 EXPECT_TRUE(r1.empty()); | |
224 EXPECT_EQ(r1.end(), r1.default_match()); | |
225 EXPECT_TRUE(r1.alternate_nav_url().is_empty()); | |
226 ASSERT_FALSE(r2.empty()); | |
227 EXPECT_EQ(r2.begin(), r2.default_match()); | |
228 EXPECT_EQ("http://a/", r2.alternate_nav_url().spec()); | |
229 } | |
230 | |
231 // Tests that if the new results have a lower max relevance score than last, | |
232 // any copied results have their relevance shifted down. | |
233 TEST_F(AutocompleteResultTest, CopyOldMatches) { | |
234 TestData last[] = { | |
235 { 0, 0, 1000 }, | |
236 { 1, 0, 500 }, | |
237 }; | |
238 TestData current[] = { | |
239 { 2, 0, 400 }, | |
240 }; | |
241 TestData result[] = { | |
242 { 2, 0, 400 }, | |
243 { 1, 0, 399 }, | |
244 }; | |
245 | |
246 ASSERT_NO_FATAL_FAILURE( | |
247 RunCopyOldMatchesTest(last, ARRAYSIZE_UNSAFE(last), | |
248 current, ARRAYSIZE_UNSAFE(current), | |
249 result, ARRAYSIZE_UNSAFE(result))); | |
250 } | |
251 | |
252 // Tests that matches are copied correctly from two distinct providers. | |
253 TEST_F(AutocompleteResultTest, CopyOldMatches2) { | |
254 TestData last[] = { | |
255 { 0, 0, 1000 }, | |
256 { 1, 1, 500 }, | |
257 { 2, 0, 400 }, | |
258 { 3, 1, 300 }, | |
259 }; | |
260 TestData current[] = { | |
261 { 4, 0, 1100 }, | |
262 { 5, 1, 550 }, | |
263 }; | |
264 TestData result[] = { | |
265 { 4, 0, 1100 }, | |
266 { 5, 1, 550 }, | |
267 { 2, 0, 400 }, | |
268 { 3, 1, 300 }, | |
269 }; | |
270 | |
271 ASSERT_NO_FATAL_FAILURE( | |
272 RunCopyOldMatchesTest(last, ARRAYSIZE_UNSAFE(last), | |
273 current, ARRAYSIZE_UNSAFE(current), | |
274 result, ARRAYSIZE_UNSAFE(result))); | |
275 } | |
276 | |
277 // Tests that matches with empty destination URLs aren't treated as duplicates | |
278 // and culled. | |
279 TEST_F(AutocompleteResultTest, SortAndCullEmptyDestinationURLs) { | |
280 TestData data[] = { | |
281 { 1, 0, 500 }, | |
282 { 0, 0, 1100 }, | |
283 { 1, 0, 1000 }, | |
284 { 0, 0, 1300 }, | |
285 { 0, 0, 1200 }, | |
286 }; | |
287 | |
288 ACMatches matches; | |
289 PopulateAutocompleteMatches(data, arraysize(data), &matches); | |
290 matches[1].destination_url = GURL(); | |
291 matches[3].destination_url = GURL(); | |
292 matches[4].destination_url = GURL(); | |
293 | |
294 AutocompleteResult result; | |
295 result.AppendMatches(matches); | |
296 AutocompleteInput input(base::string16(), base::string16::npos, | |
297 base::string16(), GURL(), | |
298 OmniboxEventProto::INVALID_SPEC, false, false, false, | |
299 true, | |
300 TestSchemeClassifier()); | |
301 result.SortAndCull(input, template_url_service_.get()); | |
302 | |
303 // Of the two results with the same non-empty destination URL, the | |
304 // lower-relevance one should be dropped. All of the results with empty URLs | |
305 // should be kept. | |
306 ASSERT_EQ(4U, result.size()); | |
307 EXPECT_TRUE(result.match_at(0)->destination_url.is_empty()); | |
308 EXPECT_EQ(1300, result.match_at(0)->relevance); | |
309 EXPECT_TRUE(result.match_at(1)->destination_url.is_empty()); | |
310 EXPECT_EQ(1200, result.match_at(1)->relevance); | |
311 EXPECT_TRUE(result.match_at(2)->destination_url.is_empty()); | |
312 EXPECT_EQ(1100, result.match_at(2)->relevance); | |
313 EXPECT_EQ("http://b/", result.match_at(3)->destination_url.spec()); | |
314 EXPECT_EQ(1000, result.match_at(3)->relevance); | |
315 } | |
316 | |
317 TEST_F(AutocompleteResultTest, SortAndCullDuplicateSearchURLs) { | |
318 // Register a template URL that corresponds to 'foo' search engine. | |
319 TemplateURLData url_data; | |
320 url_data.short_name = base::ASCIIToUTF16("unittest"); | |
321 url_data.SetKeyword(base::ASCIIToUTF16("foo")); | |
322 url_data.SetURL("http://www.foo.com/s?q={searchTerms}"); | |
323 template_url_service_.get()->Add(new TemplateURL(url_data)); | |
324 | |
325 TestData data[] = { | |
326 { 0, 0, 1300 }, | |
327 { 1, 0, 1200 }, | |
328 { 2, 0, 1100 }, | |
329 { 3, 0, 1000 }, | |
330 { 4, 1, 900 }, | |
331 }; | |
332 | |
333 ACMatches matches; | |
334 PopulateAutocompleteMatches(data, arraysize(data), &matches); | |
335 matches[0].destination_url = GURL("http://www.foo.com/s?q=foo"); | |
336 matches[1].destination_url = GURL("http://www.foo.com/s?q=foo2"); | |
337 matches[2].destination_url = GURL("http://www.foo.com/s?q=foo&oq=f"); | |
338 matches[3].destination_url = GURL("http://www.foo.com/s?q=foo&aqs=0"); | |
339 matches[4].destination_url = GURL("http://www.foo.com/"); | |
340 | |
341 AutocompleteResult result; | |
342 result.AppendMatches(matches); | |
343 AutocompleteInput input(base::string16(), base::string16::npos, | |
344 base::string16(), GURL(), | |
345 OmniboxEventProto::INVALID_SPEC, false, false, false, | |
346 true, | |
347 TestSchemeClassifier()); | |
348 result.SortAndCull(input, template_url_service_.get()); | |
349 | |
350 // We expect the 3rd and 4th results to be removed. | |
351 ASSERT_EQ(3U, result.size()); | |
352 EXPECT_EQ("http://www.foo.com/s?q=foo", | |
353 result.match_at(0)->destination_url.spec()); | |
354 EXPECT_EQ(1300, result.match_at(0)->relevance); | |
355 EXPECT_EQ("http://www.foo.com/s?q=foo2", | |
356 result.match_at(1)->destination_url.spec()); | |
357 EXPECT_EQ(1200, result.match_at(1)->relevance); | |
358 EXPECT_EQ("http://www.foo.com/", | |
359 result.match_at(2)->destination_url.spec()); | |
360 EXPECT_EQ(900, result.match_at(2)->relevance); | |
361 } | |
362 | |
363 TEST_F(AutocompleteResultTest, SortAndCullWithMatchDups) { | |
364 // Register a template URL that corresponds to 'foo' search engine. | |
365 TemplateURLData url_data; | |
366 url_data.short_name = base::ASCIIToUTF16("unittest"); | |
367 url_data.SetKeyword(base::ASCIIToUTF16("foo")); | |
368 url_data.SetURL("http://www.foo.com/s?q={searchTerms}"); | |
369 template_url_service_.get()->Add(new TemplateURL(url_data)); | |
370 | |
371 AutocompleteMatch dup_match; | |
372 dup_match.destination_url = GURL("http://www.foo.com/s?q=foo&oq=dup"); | |
373 std::vector<AutocompleteMatch> dups; | |
374 dups.push_back(dup_match); | |
375 | |
376 TestData data[] = { | |
377 { 0, 0, 1300, dups }, | |
378 { 1, 0, 1200 }, | |
379 { 2, 0, 1100 }, | |
380 { 3, 0, 1000, dups }, | |
381 { 4, 1, 900 }, | |
382 { 5, 0, 800 }, | |
383 }; | |
384 | |
385 ACMatches matches; | |
386 PopulateAutocompleteMatches(data, arraysize(data), &matches); | |
387 matches[0].destination_url = GURL("http://www.foo.com/s?q=foo"); | |
388 matches[1].destination_url = GURL("http://www.foo.com/s?q=foo2"); | |
389 matches[2].destination_url = GURL("http://www.foo.com/s?q=foo&oq=f"); | |
390 matches[3].destination_url = GURL("http://www.foo.com/s?q=foo&aqs=0"); | |
391 matches[4].destination_url = GURL("http://www.foo.com/"); | |
392 matches[5].destination_url = GURL("http://www.foo.com/s?q=foo2&oq=f"); | |
393 | |
394 AutocompleteResult result; | |
395 result.AppendMatches(matches); | |
396 AutocompleteInput input(base::string16(), base::string16::npos, | |
397 base::string16(), GURL(), | |
398 OmniboxEventProto::INVALID_SPEC, false, false, false, | |
399 true, | |
400 TestSchemeClassifier()); | |
401 result.SortAndCull(input, template_url_service_.get()); | |
402 | |
403 // Expect 3 unique results after SortAndCull(). | |
404 ASSERT_EQ(3U, result.size()); | |
405 | |
406 // Check that 3rd and 4th result got added to the first result as dups | |
407 // and also duplicates of the 4th match got copied. | |
408 ASSERT_EQ(4U, result.match_at(0)->duplicate_matches.size()); | |
409 const AutocompleteMatch* first_match = result.match_at(0); | |
410 EXPECT_EQ(matches[2].destination_url, | |
411 first_match->duplicate_matches.at(1).destination_url); | |
412 EXPECT_EQ(dup_match.destination_url, | |
413 first_match->duplicate_matches.at(2).destination_url); | |
414 EXPECT_EQ(matches[3].destination_url, | |
415 first_match->duplicate_matches.at(3).destination_url); | |
416 | |
417 // Check that 6th result started a new list of dups for the second result. | |
418 ASSERT_EQ(1U, result.match_at(1)->duplicate_matches.size()); | |
419 EXPECT_EQ(matches[5].destination_url, | |
420 result.match_at(1)->duplicate_matches.at(0).destination_url); | |
421 } | |
422 | |
423 TEST_F(AutocompleteResultTest, SortAndCullWithDemotionsByType) { | |
424 // Add some matches. | |
425 ACMatches matches; | |
426 const AutocompleteMatchTestData data[] = { | |
427 { "http://history-url/", AutocompleteMatchType::HISTORY_URL }, | |
428 { "http://search-what-you-typed/", | |
429 AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED }, | |
430 { "http://history-title/", AutocompleteMatchType::HISTORY_TITLE }, | |
431 { "http://search-history/", AutocompleteMatchType::SEARCH_HISTORY }, | |
432 }; | |
433 PopulateAutocompleteMatchesFromTestData(data, arraysize(data), &matches); | |
434 | |
435 // Demote the search history match relevance score. | |
436 matches.back().relevance = 500; | |
437 | |
438 // Add a rule demoting history-url and killing history-title. | |
439 { | |
440 std::map<std::string, std::string> params; | |
441 params[std::string(OmniboxFieldTrial::kDemoteByTypeRule) + ":3:*"] = | |
442 "1:50,7:100,2:0"; // 3 == HOME_PAGE | |
443 ASSERT_TRUE(variations::AssociateVariationParams( | |
444 OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A", params)); | |
445 } | |
446 base::FieldTrialList::CreateFieldTrial( | |
447 OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A"); | |
448 | |
449 AutocompleteResult result; | |
450 result.AppendMatches(matches); | |
451 AutocompleteInput input(base::string16(), base::string16::npos, | |
452 base::string16(), GURL(), | |
453 OmniboxEventProto::HOME_PAGE, false, false, false, | |
454 true, | |
455 TestSchemeClassifier()); | |
456 result.SortAndCull(input, template_url_service_.get()); | |
457 | |
458 // Check the new ordering. The history-title results should be omitted. | |
459 // We cannot check relevance scores because the matches are sorted by | |
460 // demoted relevance but the actual relevance scores are not modified. | |
461 ASSERT_EQ(3u, result.size()); | |
462 EXPECT_EQ("http://search-what-you-typed/", | |
463 result.match_at(0)->destination_url.spec()); | |
464 EXPECT_EQ("http://history-url/", | |
465 result.match_at(1)->destination_url.spec()); | |
466 EXPECT_EQ("http://search-history/", | |
467 result.match_at(2)->destination_url.spec()); | |
468 } | |
469 | |
470 TEST_F(AutocompleteResultTest, SortAndCullWithMatchDupsAndDemotionsByType) { | |
471 // Add some matches. | |
472 ACMatches matches; | |
473 const AutocompleteMatchTestData data[] = { | |
474 { "http://search-what-you-typed/", | |
475 AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED }, | |
476 { "http://dup-url/", AutocompleteMatchType::HISTORY_URL }, | |
477 { "http://dup-url/", AutocompleteMatchType::NAVSUGGEST }, | |
478 { "http://search-url/", AutocompleteMatchType::SEARCH_SUGGEST }, | |
479 { "http://history-url/", AutocompleteMatchType::HISTORY_URL }, | |
480 }; | |
481 PopulateAutocompleteMatchesFromTestData(data, arraysize(data), &matches); | |
482 | |
483 // Add a rule demoting HISTORY_URL. | |
484 { | |
485 std::map<std::string, std::string> params; | |
486 params[std::string(OmniboxFieldTrial::kDemoteByTypeRule) + ":8:*"] = | |
487 "1:50"; // 8 == INSTANT_NTP_WITH_FAKEBOX_AS_STARTING_FOCUS | |
488 ASSERT_TRUE(variations::AssociateVariationParams( | |
489 OmniboxFieldTrial::kBundledExperimentFieldTrialName, "C", params)); | |
490 } | |
491 base::FieldTrialList::CreateFieldTrial( | |
492 OmniboxFieldTrial::kBundledExperimentFieldTrialName, "C"); | |
493 | |
494 { | |
495 AutocompleteResult result; | |
496 result.AppendMatches(matches); | |
497 AutocompleteInput input( | |
498 base::string16(), base::string16::npos, base::string16(), GURL(), | |
499 OmniboxEventProto::INSTANT_NTP_WITH_FAKEBOX_AS_STARTING_FOCUS, false, | |
500 false, false, true, | |
501 TestSchemeClassifier()); | |
502 result.SortAndCull(input, template_url_service_.get()); | |
503 | |
504 // The NAVSUGGEST dup-url stay above search-url since the navsuggest | |
505 // variant should not be demoted. | |
506 ASSERT_EQ(4u, result.size()); | |
507 EXPECT_EQ("http://search-what-you-typed/", | |
508 result.match_at(0)->destination_url.spec()); | |
509 EXPECT_EQ("http://dup-url/", | |
510 result.match_at(1)->destination_url.spec()); | |
511 EXPECT_EQ(AutocompleteMatchType::NAVSUGGEST, | |
512 result.match_at(1)->type); | |
513 EXPECT_EQ("http://search-url/", | |
514 result.match_at(2)->destination_url.spec()); | |
515 EXPECT_EQ("http://history-url/", | |
516 result.match_at(3)->destination_url.spec()); | |
517 } | |
518 } | |
519 | |
520 TEST_F(AutocompleteResultTest, SortAndCullReorderForDefaultMatch) { | |
521 TestData data[] = { | |
522 { 0, 0, 1300 }, | |
523 { 1, 0, 1200 }, | |
524 { 2, 0, 1100 }, | |
525 { 3, 0, 1000 } | |
526 }; | |
527 | |
528 { | |
529 // Check that reorder doesn't do anything if the top result | |
530 // is already a legal default match (which is the default from | |
531 // PopulateAutocompleteMatches()). | |
532 ACMatches matches; | |
533 PopulateAutocompleteMatches(data, arraysize(data), &matches); | |
534 AutocompleteResult result; | |
535 result.AppendMatches(matches); | |
536 AutocompleteInput input(base::string16(), base::string16::npos, | |
537 base::string16(), GURL(), | |
538 OmniboxEventProto::HOME_PAGE, false, false, false, | |
539 true, | |
540 TestSchemeClassifier()); | |
541 result.SortAndCull(input, template_url_service_.get()); | |
542 AssertResultMatches(result, data, 4); | |
543 } | |
544 | |
545 { | |
546 // Check that reorder swaps up a result appropriately. | |
547 ACMatches matches; | |
548 PopulateAutocompleteMatches(data, arraysize(data), &matches); | |
549 matches[0].allowed_to_be_default_match = false; | |
550 matches[1].allowed_to_be_default_match = false; | |
551 AutocompleteResult result; | |
552 result.AppendMatches(matches); | |
553 AutocompleteInput input(base::string16(), base::string16::npos, | |
554 base::string16(), GURL(), | |
555 OmniboxEventProto::HOME_PAGE, false, false, false, | |
556 true, | |
557 TestSchemeClassifier()); | |
558 result.SortAndCull(input, template_url_service_.get()); | |
559 ASSERT_EQ(4U, result.size()); | |
560 EXPECT_EQ("http://c/", result.match_at(0)->destination_url.spec()); | |
561 EXPECT_EQ("http://a/", result.match_at(1)->destination_url.spec()); | |
562 EXPECT_EQ("http://b/", result.match_at(2)->destination_url.spec()); | |
563 EXPECT_EQ("http://d/", result.match_at(3)->destination_url.spec()); | |
564 } | |
565 } | |
566 | |
567 | |
568 | |
569 TEST_F(AutocompleteResultTest, SortAndCullWithDisableInlining) { | |
570 TestData data[] = { | |
571 { 0, 0, 1300 }, | |
572 { 1, 0, 1200 }, | |
573 { 2, 0, 1100 }, | |
574 { 3, 0, 1000 } | |
575 }; | |
576 | |
577 { | |
578 // Check that with the field trial disabled, we keep keep the first match | |
579 // first even if it has an inline autocompletion. | |
580 ACMatches matches; | |
581 PopulateAutocompleteMatches(data, arraysize(data), &matches); | |
582 matches[0].inline_autocompletion = base::ASCIIToUTF16("completion"); | |
583 AutocompleteResult result; | |
584 result.AppendMatches(matches); | |
585 AutocompleteInput input(base::string16(), base::string16::npos, | |
586 base::string16(), GURL(), | |
587 OmniboxEventProto::HOME_PAGE, false, false, false, | |
588 true, | |
589 TestSchemeClassifier()); | |
590 result.SortAndCull(input, template_url_service_.get()); | |
591 AssertResultMatches(result, data, 4); | |
592 } | |
593 | |
594 // Enable the field trial to disable inlining. | |
595 { | |
596 std::map<std::string, std::string> params; | |
597 params[OmniboxFieldTrial::kDisableInliningRule] = "true"; | |
598 ASSERT_TRUE(variations::AssociateVariationParams( | |
599 OmniboxFieldTrial::kBundledExperimentFieldTrialName, "D", params)); | |
600 } | |
601 base::FieldTrialList::CreateFieldTrial( | |
602 OmniboxFieldTrial::kBundledExperimentFieldTrialName, "D"); | |
603 | |
604 { | |
605 // Now the first match should be demoted past the second. | |
606 ACMatches matches; | |
607 PopulateAutocompleteMatches(data, arraysize(data), &matches); | |
608 matches[0].inline_autocompletion = base::ASCIIToUTF16("completion"); | |
609 AutocompleteResult result; | |
610 result.AppendMatches(matches); | |
611 AutocompleteInput input(base::string16(), base::string16::npos, | |
612 base::string16(), GURL(), | |
613 OmniboxEventProto::HOME_PAGE, false, false, false, | |
614 true, | |
615 TestSchemeClassifier()); | |
616 result.SortAndCull(input, template_url_service_.get()); | |
617 ASSERT_EQ(4U, result.size()); | |
618 EXPECT_EQ("http://b/", result.match_at(0)->destination_url.spec()); | |
619 EXPECT_EQ("http://a/", result.match_at(1)->destination_url.spec()); | |
620 EXPECT_EQ("http://c/", result.match_at(2)->destination_url.spec()); | |
621 EXPECT_EQ("http://d/", result.match_at(3)->destination_url.spec()); | |
622 } | |
623 | |
624 { | |
625 // But if there was no inline autocompletion on the first match, then | |
626 // the order should stay the same. This is true even if there are | |
627 // inline autocompletions elsewhere. | |
628 ACMatches matches; | |
629 PopulateAutocompleteMatches(data, arraysize(data), &matches); | |
630 matches[2].inline_autocompletion = base::ASCIIToUTF16("completion"); | |
631 AutocompleteResult result; | |
632 result.AppendMatches(matches); | |
633 AutocompleteInput input(base::string16(), base::string16::npos, | |
634 base::string16(), GURL(), | |
635 OmniboxEventProto::HOME_PAGE, false, false, false, | |
636 true, | |
637 TestSchemeClassifier()); | |
638 result.SortAndCull(input, template_url_service_.get()); | |
639 AssertResultMatches(result, data, 4); | |
640 } | |
641 | |
642 { | |
643 // Try a more complicated situation. | |
644 ACMatches matches; | |
645 PopulateAutocompleteMatches(data, arraysize(data), &matches); | |
646 matches[0].allowed_to_be_default_match = false; | |
647 matches[1].inline_autocompletion = base::ASCIIToUTF16("completion"); | |
648 AutocompleteResult result; | |
649 result.AppendMatches(matches); | |
650 AutocompleteInput input(base::string16(), base::string16::npos, | |
651 base::string16(), GURL(), | |
652 OmniboxEventProto::HOME_PAGE, false, false, false, | |
653 true, | |
654 TestSchemeClassifier()); | |
655 result.SortAndCull(input, template_url_service_.get()); | |
656 ASSERT_EQ(4U, result.size()); | |
657 EXPECT_EQ("http://c/", result.match_at(0)->destination_url.spec()); | |
658 EXPECT_EQ("http://a/", result.match_at(1)->destination_url.spec()); | |
659 EXPECT_EQ("http://b/", result.match_at(2)->destination_url.spec()); | |
660 EXPECT_EQ("http://d/", result.match_at(3)->destination_url.spec()); | |
661 } | |
662 | |
663 { | |
664 // Try another complicated situation. | |
665 ACMatches matches; | |
666 PopulateAutocompleteMatches(data, arraysize(data), &matches); | |
667 matches[0].inline_autocompletion = base::ASCIIToUTF16("completion"); | |
668 matches[1].allowed_to_be_default_match = false; | |
669 AutocompleteResult result; | |
670 result.AppendMatches(matches); | |
671 AutocompleteInput input(base::string16(), base::string16::npos, | |
672 base::string16(), GURL(), | |
673 OmniboxEventProto::HOME_PAGE, false, false, false, | |
674 true, | |
675 TestSchemeClassifier()); | |
676 result.SortAndCull(input, template_url_service_.get()); | |
677 ASSERT_EQ(4U, result.size()); | |
678 EXPECT_EQ("http://c/", result.match_at(0)->destination_url.spec()); | |
679 EXPECT_EQ("http://a/", result.match_at(1)->destination_url.spec()); | |
680 EXPECT_EQ("http://b/", result.match_at(2)->destination_url.spec()); | |
681 EXPECT_EQ("http://d/", result.match_at(3)->destination_url.spec()); | |
682 } | |
683 | |
684 { | |
685 // Check that disaster doesn't strike if we can't demote the top inline | |
686 // autocompletion because every match either has a completion or isn't | |
687 // allowed to be the default match. In this case, we should leave | |
688 // everything untouched. | |
689 ACMatches matches; | |
690 PopulateAutocompleteMatches(data, arraysize(data), &matches); | |
691 matches[0].inline_autocompletion = base::ASCIIToUTF16("completion"); | |
692 matches[1].allowed_to_be_default_match = false; | |
693 matches[2].allowed_to_be_default_match = false; | |
694 matches[3].inline_autocompletion = base::ASCIIToUTF16("completion"); | |
695 AutocompleteResult result; | |
696 result.AppendMatches(matches); | |
697 AutocompleteInput input(base::string16(), base::string16::npos, | |
698 base::string16(), GURL(), | |
699 OmniboxEventProto::HOME_PAGE, false, false, false, | |
700 true, | |
701 TestSchemeClassifier()); | |
702 result.SortAndCull(input, template_url_service_.get()); | |
703 AssertResultMatches(result, data, 4); | |
704 } | |
705 | |
706 { | |
707 // Check a similar situation, except in this case the top match is not | |
708 // allowed to the default match, so it still needs to be demoted so we | |
709 // get a legal default match first. That match will have an inline | |
710 // autocompletion because we don't have any better options. | |
711 ACMatches matches; | |
712 PopulateAutocompleteMatches(data, arraysize(data), &matches); | |
713 matches[0].allowed_to_be_default_match = false; | |
714 matches[1].inline_autocompletion = base::ASCIIToUTF16("completion"); | |
715 matches[2].allowed_to_be_default_match = false; | |
716 matches[3].inline_autocompletion = base::ASCIIToUTF16("completion"); | |
717 AutocompleteResult result; | |
718 result.AppendMatches(matches); | |
719 AutocompleteInput input(base::string16(), base::string16::npos, | |
720 base::string16(), GURL(), | |
721 OmniboxEventProto::HOME_PAGE, false, false, false, | |
722 true, | |
723 TestSchemeClassifier()); | |
724 result.SortAndCull(input, template_url_service_.get()); | |
725 ASSERT_EQ(4U, result.size()); | |
726 EXPECT_EQ("http://b/", result.match_at(0)->destination_url.spec()); | |
727 EXPECT_EQ("http://a/", result.match_at(1)->destination_url.spec()); | |
728 EXPECT_EQ("http://c/", result.match_at(2)->destination_url.spec()); | |
729 EXPECT_EQ("http://d/", result.match_at(3)->destination_url.spec()); | |
730 } | |
731 } | |
732 | |
733 TEST_F(AutocompleteResultTest, ShouldHideTopMatch) { | |
734 base::FieldTrialList::CreateFieldTrial("InstantExtended", | |
735 "Group1 hide_verbatim:1"); | |
736 ACMatches matches; | |
737 | |
738 // Case 1: Top match is a verbatim match. | |
739 PopulateAutocompleteMatchesFromTestData(kVerbatimMatches, 1, &matches); | |
740 AutocompleteResult result; | |
741 result.AppendMatches(matches); | |
742 EXPECT_TRUE(result.ShouldHideTopMatch()); | |
743 matches.clear(); | |
744 result.Reset(); | |
745 | |
746 // Case 2: If the verbatim first match is followed by another verbatim match, | |
747 // don't hide the top verbatim match. | |
748 PopulateAutocompleteMatchesFromTestData(kVerbatimMatches, | |
749 arraysize(kVerbatimMatches), | |
750 &matches); | |
751 result.AppendMatches(matches); | |
752 EXPECT_FALSE(result.ShouldHideTopMatch()); | |
753 matches.clear(); | |
754 result.Reset(); | |
755 | |
756 // Case 3: Top match is not a verbatim match. Do not hide the top match. | |
757 PopulateAutocompleteMatchesFromTestData(kNonVerbatimMatches, 1, &matches); | |
758 PopulateAutocompleteMatchesFromTestData(kVerbatimMatches, | |
759 arraysize(kVerbatimMatches), | |
760 &matches); | |
761 result.AppendMatches(matches); | |
762 EXPECT_FALSE(result.ShouldHideTopMatch()); | |
763 } | |
764 | |
765 TEST_F(AutocompleteResultTest, ShouldHideTopMatchAfterCopy) { | |
766 base::FieldTrialList::CreateFieldTrial("InstantExtended", | |
767 "Group1 hide_verbatim:1"); | |
768 ACMatches matches; | |
769 | |
770 // Case 1: Top match is a verbatim match followed by only copied matches. | |
771 PopulateAutocompleteMatchesFromTestData(kVerbatimMatches, | |
772 arraysize(kVerbatimMatches), | |
773 &matches); | |
774 for (size_t i = 1; i < arraysize(kVerbatimMatches); ++i) | |
775 matches[i].from_previous = true; | |
776 AutocompleteResult result; | |
777 result.AppendMatches(matches); | |
778 EXPECT_TRUE(result.ShouldHideTopMatch()); | |
779 result.Reset(); | |
780 | |
781 // Case 2: The copied matches are then followed by a non-verbatim match. | |
782 PopulateAutocompleteMatchesFromTestData(kNonVerbatimMatches, 1, &matches); | |
783 result.AppendMatches(matches); | |
784 EXPECT_TRUE(result.ShouldHideTopMatch()); | |
785 result.Reset(); | |
786 | |
787 // Case 3: The copied matches are instead followed by a verbatim match. | |
788 matches.back().from_previous = true; | |
789 PopulateAutocompleteMatchesFromTestData(kVerbatimMatches, 1, &matches); | |
790 result.AppendMatches(matches); | |
791 EXPECT_FALSE(result.ShouldHideTopMatch()); | |
792 } | |
793 | |
794 TEST_F(AutocompleteResultTest, DoNotHideTopMatch_FieldTrialFlagDisabled) { | |
795 // This test config is identical to ShouldHideTopMatch test ("Case 1") except | |
796 // that the "hide_verbatim" flag is disabled in the field trials. | |
797 base::FieldTrialList::CreateFieldTrial("InstantExtended", | |
798 "Group1 hide_verbatim:0"); | |
799 ACMatches matches; | |
800 PopulateAutocompleteMatchesFromTestData(kVerbatimMatches, 1, &matches); | |
801 AutocompleteResult result; | |
802 result.AppendMatches(matches); | |
803 // Field trial flag "hide_verbatim" is disabled. Do not hide top match. | |
804 EXPECT_FALSE(result.ShouldHideTopMatch()); | |
805 } | |
806 | |
807 TEST_F(AutocompleteResultTest, TopMatchIsStandaloneVerbatimMatch) { | |
808 ACMatches matches; | |
809 AutocompleteResult result; | |
810 result.AppendMatches(matches); | |
811 | |
812 // Case 1: Result set is empty. | |
813 EXPECT_FALSE(result.TopMatchIsStandaloneVerbatimMatch()); | |
814 | |
815 // Case 2: Top match is not a verbatim match. | |
816 PopulateAutocompleteMatchesFromTestData(kNonVerbatimMatches, 1, &matches); | |
817 result.AppendMatches(matches); | |
818 EXPECT_FALSE(result.TopMatchIsStandaloneVerbatimMatch()); | |
819 result.Reset(); | |
820 matches.clear(); | |
821 | |
822 // Case 3: Top match is a verbatim match. | |
823 PopulateAutocompleteMatchesFromTestData(kVerbatimMatches, 1, &matches); | |
824 result.AppendMatches(matches); | |
825 EXPECT_TRUE(result.TopMatchIsStandaloneVerbatimMatch()); | |
826 result.Reset(); | |
827 matches.clear(); | |
828 | |
829 // Case 4: Standalone verbatim match found in AutocompleteResult. | |
830 PopulateAutocompleteMatchesFromTestData(kVerbatimMatches, 1, &matches); | |
831 PopulateAutocompleteMatchesFromTestData(kNonVerbatimMatches, 1, &matches); | |
832 result.AppendMatches(matches); | |
833 EXPECT_TRUE(result.TopMatchIsStandaloneVerbatimMatch()); | |
834 result.Reset(); | |
835 matches.clear(); | |
836 | |
837 // Case 5: Multiple verbatim matches found in AutocompleteResult. | |
838 PopulateAutocompleteMatchesFromTestData(kVerbatimMatches, | |
839 arraysize(kVerbatimMatches), | |
840 &matches); | |
841 result.AppendMatches(matches); | |
842 EXPECT_FALSE(result.ShouldHideTopMatch()); | |
843 } | |
OLD | NEW |