1// Copyright 2014 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 "components/omnibox/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 27using metrics::OmniboxEventProto; 28 29namespace { 30 31struct AutocompleteMatchTestData { 32 std::string destination_url; 33 AutocompleteMatch::Type type; 34}; 35 36const 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 42const AutocompleteMatchTestData kNonVerbatimMatches[] = { 43 { "http://search-history/", AutocompleteMatchType::SEARCH_HISTORY }, 44 { "http://history-title/", AutocompleteMatchType::HISTORY_TITLE }, 45}; 46 47// Adds |count| AutocompleteMatches to |matches|. 48void 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 66class 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 131void 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 144void 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 155void 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 171void 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. 199TEST_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. 233TEST_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. 253TEST_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. 279TEST_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 317TEST_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 363TEST_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 423TEST_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 470TEST_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 520TEST_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 569TEST_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 733TEST_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 765TEST_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 794TEST_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 807TEST_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} 844