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.
5 #include "chrome/browser/autocomplete/shortcuts_provider.h"
15 #include "base/memory/ref_counted.h"
16 #include "base/message_loop/message_loop.h"
17 #include "base/prefs/pref_service.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "chrome/browser/autocomplete/autocomplete_input.h"
21 #include "chrome/browser/autocomplete/autocomplete_match.h"
22 #include "chrome/browser/autocomplete/autocomplete_provider.h"
23 #include "chrome/browser/autocomplete/autocomplete_provider_listener.h"
24 #include "chrome/browser/autocomplete/autocomplete_result.h"
25 #include "chrome/browser/chrome_notification_types.h"
26 #include "chrome/browser/history/history_service.h"
27 #include "chrome/browser/history/in_memory_url_index.h"
28 #include "chrome/browser/history/shortcuts_backend.h"
29 #include "chrome/browser/history/shortcuts_backend_factory.h"
30 #include "chrome/browser/history/url_database.h"
31 #include "chrome/common/pref_names.h"
32 #include "chrome/test/base/testing_profile.h"
33 #include "content/public/browser/notification_service.h"
34 #include "content/public/test/test_browser_thread.h"
35 #include "extensions/common/extension.h"
36 #include "extensions/common/extension_builder.h"
37 #include "extensions/common/value_builder.h"
38 #include "testing/gtest/include/gtest/gtest.h"
40 using base::ASCIIToUTF16;
42 // TestShortcutInfo -----------------------------------------------------------
46 struct TestShortcutInfo {
49 std::string fill_into_edit;
50 std::string destination_url;
52 std::string contents_class;
53 std::string description;
54 std::string description_class;
55 content::PageTransition transition;
56 AutocompleteMatch::Type type;
60 } shortcut_test_db[] = {
61 { "BD85DBA2-8C29-49F9-84AE-48E1E90880E0", "goog", "www.google.com",
62 "http://www.google.com/", "Google", "0,1,4,0", "Google", "0,3,4,1",
63 content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL, "", 1,
65 { "BD85DBA2-8C29-49F9-84AE-48E1E90880E1", "slash", "slashdot.org",
66 "http://slashdot.org/", "slashdot.org", "0,3,5,1",
67 "Slashdot - News for nerds, stuff that matters", "0,2,5,0",
68 content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL, "", 0,
70 { "BD85DBA2-8C29-49F9-84AE-48E1E90880E2", "news", "slashdot.org",
71 "http://slashdot.org/", "slashdot.org", "0,1",
72 "Slashdot - News for nerds, stuff that matters", "0,0,11,2,15,0",
73 content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_TITLE, "", 0,
75 { "BD85DBA2-8C29-49F9-84AE-48E1E90880E3", "news", "sports.yahoo.com",
76 "http://sports.yahoo.com/", "sports.yahoo.com", "0,1",
77 "Yahoo! Sports - Sports News, Scores, Rumors, Fantasy Games, and more",
78 "0,0,23,2,27,0", content::PAGE_TRANSITION_TYPED,
79 AutocompleteMatchType::HISTORY_TITLE, "", 2, 5 },
80 { "BD85DBA2-8C29-49F9-84AE-48E1E90880E4", "news weather",
81 "www.cnn.com/index.html", "http://www.cnn.com/index.html",
82 "www.cnn.com/index.html", "0,1",
83 "CNN.com - Breaking News, U.S., World, Weather, Entertainment & Video",
84 "0,0,19,2,23,0,38,2,45,0", content::PAGE_TRANSITION_TYPED,
85 AutocompleteMatchType::HISTORY_TITLE, "", 1, 10 },
86 { "BD85DBA2-8C29-49F9-84AE-48E1E90880E5", "nhl scores", "sports.yahoo.com",
87 "http://sports.yahoo.com/", "sports.yahoo.com", "0,1",
88 "Yahoo! Sports - Sports News, Scores, Rumors, Fantasy Games, and more",
89 "0,0,29,2,35,0", content::PAGE_TRANSITION_TYPED,
90 AutocompleteMatchType::HISTORY_BODY, "", 1, 10 },
91 { "BD85DBA2-8C29-49F9-84AE-48E1E90880E6", "nhl scores",
92 "www.nhl.com/scores/index.html", "http://www.nhl.com/scores/index.html",
93 "www.nhl.com/scores/index.html", "0,1,4,3,7,1",
94 "January 13, 2010 - NHL.com - Scores", "0,0,19,2,22,0,29,2,35,0",
95 content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL, "", 5,
97 { "BD85DBA2-8C29-49F9-84AE-48E1E90880E7", "just", "www.testsite.com/a.html",
98 "http://www.testsite.com/a.html", "www.testsite.com/a.html", "0,1",
99 "Test - site - just a test", "0,0,14,2,18,0",
100 content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_TITLE, "", 5,
102 { "BD85DBA2-8C29-49F9-84AE-48E1E90880E8", "just", "www.testsite.com/b.html",
103 "http://www.testsite.com/b.html", "www.testsite.com/b.html", "0,1",
104 "Test - site - just a test", "0,0,14,2,18,0",
105 content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_TITLE, "", 5,
107 { "BD85DBA2-8C29-49F9-84AE-48E1E90880E9", "just", "www.testsite.com/c.html",
108 "http://www.testsite.com/c.html", "www.testsite.com/c.html", "0,1",
109 "Test - site - just a test", "0,0,14,2,18,0",
110 content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_TITLE, "", 8,
112 { "BD85DBA2-8C29-49F9-84AE-48E1E90880EA", "just a", "www.testsite.com/d.html",
113 "http://www.testsite.com/d.html", "www.testsite.com/d.html", "0,1",
114 "Test - site - just a test", "0,0,14,2,18,0",
115 content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_TITLE, "",
117 { "BD85DBA2-8C29-49F9-84AE-48E1E90880EB", "just a t",
118 "www.testsite.com/e.html", "http://www.testsite.com/e.html",
119 "www.testsite.com/e.html", "0,1", "Test - site - just a test",
120 "0,0,14,2,18,0", content::PAGE_TRANSITION_TYPED,
121 AutocompleteMatchType::HISTORY_TITLE, "", 12, 1 },
122 { "BD85DBA2-8C29-49F9-84AE-48E1E90880EC", "just a te",
123 "www.testsite.com/f.html", "http://www.testsite.com/f.html",
124 "www.testsite.com/f.html", "0,1", "Test - site - just a test",
125 "0,0,14,2,18,0", content::PAGE_TRANSITION_TYPED,
126 AutocompleteMatchType::HISTORY_TITLE, "", 12, 1 },
127 { "BD85DBA2-8C29-49F9-84AE-48E1E90880ED", "ago", "www.daysagotest.com/a.html",
128 "http://www.daysagotest.com/a.html", "www.daysagotest.com/a.html",
129 "0,1,8,3,11,1", "Test - site", "0,0", content::PAGE_TRANSITION_TYPED,
130 AutocompleteMatchType::HISTORY_URL, "", 1, 1 },
131 { "BD85DBA2-8C29-49F9-84AE-48E1E90880EE", "ago", "www.daysagotest.com/b.html",
132 "http://www.daysagotest.com/b.html", "www.daysagotest.com/b.html",
133 "0,1,8,3,11,1", "Test - site", "0,0", content::PAGE_TRANSITION_TYPED,
134 AutocompleteMatchType::HISTORY_URL, "", 2, 1 },
135 { "BD85DBA2-8C29-49F9-84AE-48E1E90880EF", "ago", "www.daysagotest.com/c.html",
136 "http://www.daysagotest.com/c.html", "www.daysagotest.com/c.html",
137 "0,1,8,3,11,1", "Test - site", "0,0", content::PAGE_TRANSITION_TYPED,
138 AutocompleteMatchType::HISTORY_URL, "", 3, 1 },
139 { "BD85DBA2-8C29-49F9-84AE-48E1E90880F0", "ago", "www.daysagotest.com/d.html",
140 "http://www.daysagotest.com/d.html", "www.daysagotest.com/d.html",
141 "0,1,8,3,11,1", "Test - site", "0,0", content::PAGE_TRANSITION_TYPED,
142 AutocompleteMatchType::HISTORY_URL, "", 4, 1 },
143 { "BD85DBA2-8C29-49F9-84AE-48E1E90880F1", "echo echo", "echo echo",
144 "chrome-extension://cedabbhfglmiikkmdgcpjdkocfcmbkee/?q=echo",
145 "Run Echo command: echo", "0,0", "Echo", "0,4",
146 content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::EXTENSION_APP,
148 { "BD85DBA2-8C29-49F9-84AE-48E1E90880F2", "abcdef.com", "http://abcdef.com",
149 "http://abcdef.com/", "Abcdef", "0,1,4,0", "Abcdef", "0,3,4,1",
150 content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL, "", 1,
152 { "BD85DBA2-8C29-49F9-84AE-48E1E90880F3", "query", "query",
153 "https://www.google.com/search?q=query", "query", "0,0",
154 "Google Search", "0,4", content::PAGE_TRANSITION_GENERATED,
155 AutocompleteMatchType::SEARCH_HISTORY, "", 1, 100 },
156 { "BD85DBA2-8C29-49F9-84AE-48E1E90880F4", "word", "www.word",
157 "https://www.google.com/search?q=www.word", "www.word", "0,0",
158 "Google Search", "0,4", content::PAGE_TRANSITION_GENERATED,
159 AutocompleteMatchType::SEARCH_HISTORY, "", 1, 100 },
160 { "BD85DBA2-8C29-49F9-84AE-48E1E90880F5", "about:o", "chrome://omnibox",
161 "chrome://omnibox/", "about:omnibox", "0,3,10,1", "", "",
162 content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::NAVSUGGEST, "",
164 { "BD85DBA2-8C29-49F9-84AE-48E1E90880F6", "www/real sp",
165 "http://www/real space/long-url-with-space.html",
166 "http://www/real%20space/long-url-with-space.html",
167 "www/real space/long-url-with-space.html", "0,3,11,1",
168 "Page With Space; Input with Space", "0,0",
169 content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL, "",
171 { "BD85DBA2-8C29-49F9-84AE-48E1E90880F7", "duplicate", "http://duplicate.com",
172 "http://duplicate.com/", "Duplicate", "0,1", "Duplicate", "0,1",
173 content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL, "", 1,
175 { "BD85DBA2-8C29-49F9-84AE-48E1E90880F8", "dupl", "http://duplicate.com",
176 "http://duplicate.com/", "Duplicate", "0,1", "Duplicate", "0,1",
177 content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL, "", 1,
184 // ClassifyTest ---------------------------------------------------------------
186 // Helper class to make running tests of ClassifyAllMatchesInString() more
190 ClassifyTest(const base::string16& text, ACMatchClassifications matches);
193 ACMatchClassifications RunTest(const base::string16& find_text);
196 const base::string16 text_;
197 const ACMatchClassifications matches_;
200 ClassifyTest::ClassifyTest(const base::string16& text,
201 ACMatchClassifications matches)
206 ClassifyTest::~ClassifyTest() {
209 ACMatchClassifications ClassifyTest::RunTest(const base::string16& find_text) {
210 return ShortcutsProvider::ClassifyAllMatchesInString(find_text,
211 ShortcutsProvider::CreateWordMapForString(find_text), text_, matches_);
217 // ShortcutsProviderTest ------------------------------------------------------
219 class ShortcutsProviderTest : public testing::Test,
220 public AutocompleteProviderListener {
222 ShortcutsProviderTest();
224 // AutocompleteProviderListener:
225 virtual void OnProviderUpdate(bool updated_matches) OVERRIDE;
228 typedef std::pair<std::string, bool> ExpectedURLAndAllowedToBeDefault;
229 typedef std::vector<ExpectedURLAndAllowedToBeDefault> ExpectedURLs;
231 class SetShouldContain
232 : public std::unary_function<const ExpectedURLAndAllowedToBeDefault&,
233 std::set<std::string> > {
235 explicit SetShouldContain(const ACMatches& matched_urls);
237 void operator()(const ExpectedURLAndAllowedToBeDefault& expected);
238 std::set<ExpectedURLAndAllowedToBeDefault> Leftovers() const {
243 std::set<ExpectedURLAndAllowedToBeDefault> matches_;
246 virtual void SetUp();
247 virtual void TearDown();
249 // Fills test data into the provider.
250 void FillData(TestShortcutInfo* db, size_t db_size);
252 // Runs an autocomplete query on |text| with the provided
253 // |prevent_inline_autocomplete| setting and checks to see that the returned
254 // results' destination URLs match those provided. |expected_urls| does not
255 // need to be in sorted order, but |expected_top_result| should be the top
256 // match, and it should have inline autocompletion
257 // |top_result_inline_autocompletion|.
258 void RunTest(const base::string16 text,
259 bool prevent_inline_autocomplete,
260 const ExpectedURLs& expected_urls,
261 std::string expected_top_result,
262 base::string16 top_result_inline_autocompletion);
264 // Passthrough to the private function in provider_.
265 int CalculateScore(const std::string& terms,
266 const ShortcutsBackend::Shortcut& shortcut,
269 base::MessageLoopForUI message_loop_;
270 content::TestBrowserThread ui_thread_;
271 content::TestBrowserThread file_thread_;
273 TestingProfile profile_;
275 ACMatches ac_matches_; // The resulting matches after running RunTest.
277 scoped_refptr<ShortcutsBackend> backend_;
278 scoped_refptr<ShortcutsProvider> provider_;
281 ShortcutsProviderTest::ShortcutsProviderTest()
282 : ui_thread_(content::BrowserThread::UI, &message_loop_),
283 file_thread_(content::BrowserThread::FILE, &message_loop_) {
286 void ShortcutsProviderTest::OnProviderUpdate(bool updated_matches) {}
288 void ShortcutsProviderTest::SetUp() {
289 ShortcutsBackendFactory::GetInstance()->SetTestingFactoryAndUse(
290 &profile_, &ShortcutsBackendFactory::BuildProfileNoDatabaseForTesting);
291 backend_ = ShortcutsBackendFactory::GetForProfile(&profile_);
292 ASSERT_TRUE(backend_.get());
293 ASSERT_TRUE(profile_.CreateHistoryService(true, false));
294 provider_ = new ShortcutsProvider(this, &profile_);
295 FillData(shortcut_test_db, arraysize(shortcut_test_db));
298 void ShortcutsProviderTest::TearDown() {
299 // Run all pending tasks or else some threads hold on to the message loop
300 // and prevent it from being deleted.
301 message_loop_.RunUntilIdle();
305 void ShortcutsProviderTest::FillData(TestShortcutInfo* db, size_t db_size) {
306 DCHECK(provider_.get());
307 size_t expected_size = backend_->shortcuts_map().size() + db_size;
308 for (size_t i = 0; i < db_size; ++i) {
309 const TestShortcutInfo& cur = db[i];
310 ShortcutsBackend::Shortcut shortcut(
311 cur.guid, ASCIIToUTF16(cur.text),
312 ShortcutsBackend::Shortcut::MatchCore(
313 ASCIIToUTF16(cur.fill_into_edit), GURL(cur.destination_url),
314 ASCIIToUTF16(cur.contents),
315 AutocompleteMatch::ClassificationsFromString(cur.contents_class),
316 ASCIIToUTF16(cur.description),
317 AutocompleteMatch::ClassificationsFromString(cur.description_class),
318 cur.transition, cur.type, ASCIIToUTF16(cur.keyword)),
319 base::Time::Now() - base::TimeDelta::FromDays(cur.days_from_now),
321 backend_->AddShortcut(shortcut);
323 EXPECT_EQ(expected_size, backend_->shortcuts_map().size());
326 ShortcutsProviderTest::SetShouldContain::SetShouldContain(
327 const ACMatches& matched_urls) {
328 for (ACMatches::const_iterator iter = matched_urls.begin();
329 iter != matched_urls.end(); ++iter)
330 matches_.insert(ExpectedURLAndAllowedToBeDefault(
331 iter->destination_url.spec(), iter->allowed_to_be_default_match));
334 void ShortcutsProviderTest::SetShouldContain::operator()(
335 const ExpectedURLAndAllowedToBeDefault& expected) {
336 EXPECT_EQ(1U, matches_.erase(expected));
339 void ShortcutsProviderTest::RunTest(
340 const base::string16 text,
341 bool prevent_inline_autocomplete,
342 const ExpectedURLs& expected_urls,
343 std::string expected_top_result,
344 base::string16 top_result_inline_autocompletion) {
345 base::MessageLoop::current()->RunUntilIdle();
346 AutocompleteInput input(text, base::string16::npos, base::string16(), GURL(),
347 AutocompleteInput::INVALID_SPEC,
348 prevent_inline_autocomplete, false, true,
349 AutocompleteInput::ALL_MATCHES);
350 provider_->Start(input, false);
351 EXPECT_TRUE(provider_->done());
353 ac_matches_ = provider_->matches();
355 // We should have gotten back at most AutocompleteProvider::kMaxMatches.
356 EXPECT_LE(ac_matches_.size(), AutocompleteProvider::kMaxMatches);
358 // If the number of expected and actual matches aren't equal then we need
359 // test no further, but let's do anyway so that we know which URLs failed.
360 EXPECT_EQ(expected_urls.size(), ac_matches_.size());
362 // Verify that all expected URLs were found and that all found URLs
364 std::set<ExpectedURLAndAllowedToBeDefault> Leftovers =
365 for_each(expected_urls.begin(), expected_urls.end(),
366 SetShouldContain(ac_matches_)).Leftovers();
367 EXPECT_EQ(0U, Leftovers.size());
369 // See if we got the expected top scorer.
370 if (!ac_matches_.empty()) {
371 std::partial_sort(ac_matches_.begin(), ac_matches_.begin() + 1,
372 ac_matches_.end(), AutocompleteMatch::MoreRelevant);
373 EXPECT_EQ(expected_top_result, ac_matches_[0].destination_url.spec());
374 EXPECT_EQ(top_result_inline_autocompletion,
375 ac_matches_[0].inline_autocompletion);
379 int ShortcutsProviderTest::CalculateScore(
380 const std::string& terms,
381 const ShortcutsBackend::Shortcut& shortcut,
383 return provider_->CalculateScore(ASCIIToUTF16(terms), shortcut,
388 // Actual tests ---------------------------------------------------------------
390 TEST_F(ShortcutsProviderTest, SimpleSingleMatch) {
391 base::string16 text(ASCIIToUTF16("go"));
392 std::string expected_url("http://www.google.com/");
393 ExpectedURLs expected_urls;
394 expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(expected_url, true));
395 RunTest(text, false, expected_urls, expected_url, ASCIIToUTF16("ogle.com"));
397 // Same test with prevent inline autocomplete.
398 expected_urls.clear();
399 expected_urls.push_back(
400 ExpectedURLAndAllowedToBeDefault(expected_url, false));
401 // The match will have an |inline_autocompletion| set, but the value will not
402 // be used because |allowed_to_be_default_match| will be false.
403 RunTest(text, true, expected_urls, expected_url, ASCIIToUTF16("ogle.com"));
405 // A pair of analogous tests where the shortcut ends at the end of
406 // |fill_into_edit|. This exercises the inline autocompletion and default
408 text = ASCIIToUTF16("abcdef.com");
409 expected_url = "http://abcdef.com/";
410 expected_urls.clear();
411 expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(expected_url, true));
412 RunTest(text, false, expected_urls, expected_url, base::string16());
413 // With prevent inline autocomplete, the suggestion should be the same
414 // (because there is no completion).
415 RunTest(text, true, expected_urls, expected_url, base::string16());
417 // Another test, simply for a query match type, not a navigation URL match
419 text = ASCIIToUTF16("que");
420 expected_url = "https://www.google.com/search?q=query";
421 expected_urls.clear();
422 expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(expected_url, true));
423 RunTest(text, false, expected_urls, expected_url, ASCIIToUTF16("ry"));
425 // Same test with prevent inline autocomplete.
426 expected_urls.clear();
427 expected_urls.push_back(
428 ExpectedURLAndAllowedToBeDefault(expected_url, false));
429 // The match will have an |inline_autocompletion| set, but the value will not
430 // be used because |allowed_to_be_default_match| will be false.
431 RunTest(text, true, expected_urls, expected_url, ASCIIToUTF16("ry"));
433 // A pair of analogous tests where the shortcut ends at the end of
434 // |fill_into_edit|. This exercises the inline autocompletion and default
436 text = ASCIIToUTF16("query");
437 expected_urls.clear();
438 expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(expected_url, true));
439 RunTest(text, false, expected_urls, expected_url, base::string16());
440 // With prevent inline autocomplete, the suggestion should be the same
441 // (because there is no completion).
442 RunTest(text, true, expected_urls, expected_url, base::string16());
444 // Now the shortcut ends at the end of |fill_into_edit| but has a
445 // non-droppable prefix. ("www.", for instance, is not droppable for
447 text = ASCIIToUTF16("word");
448 expected_url = "https://www.google.com/search?q=www.word";
449 expected_urls.clear();
450 expected_urls.push_back(
451 ExpectedURLAndAllowedToBeDefault(expected_url, false));
452 RunTest(text, false, expected_urls, expected_url, base::string16());
455 // These tests are like those in SimpleSingleMatch but more complex,
456 // involving URLs that need to be fixed up to match properly.
457 TEST_F(ShortcutsProviderTest, TrickySingleMatch) {
458 // Test that about: URLs are fixed up/transformed to chrome:// URLs.
459 base::string16 text(ASCIIToUTF16("about:o"));
460 std::string expected_url("chrome://omnibox/");
461 ExpectedURLs expected_urls;
462 expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(expected_url, true));
463 RunTest(text, false, expected_urls, expected_url, ASCIIToUTF16("mnibox"));
465 // Same test with prevent inline autocomplete.
466 expected_urls.clear();
467 expected_urls.push_back(
468 ExpectedURLAndAllowedToBeDefault(expected_url, false));
469 // The match will have an |inline_autocompletion| set, but the value will not
470 // be used because |allowed_to_be_default_match| will be false.
471 RunTest(text, true, expected_urls, expected_url, ASCIIToUTF16("mnibox"));
473 // Test that an input with a space can match URLs with a (escaped) space.
474 // This would fail if we didn't try to lookup the un-fixed-up string.
475 text = ASCIIToUTF16("www/real sp");
476 expected_url = "http://www/real%20space/long-url-with-space.html";
477 expected_urls.clear();
478 expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(expected_url, true));
479 RunTest(text, false, expected_urls, expected_url,
480 ASCIIToUTF16("ace/long-url-with-space.html"));
482 // Same test with prevent inline autocomplete.
483 expected_urls.clear();
484 expected_urls.push_back(
485 ExpectedURLAndAllowedToBeDefault(expected_url, false));
486 // The match will have an |inline_autocompletion| set, but the value will not
487 // be used because |allowed_to_be_default_match| will be false.
488 RunTest(text, true, expected_urls, expected_url,
489 ASCIIToUTF16("ace/long-url-with-space.html"));
492 TEST_F(ShortcutsProviderTest, MultiMatch) {
493 base::string16 text(ASCIIToUTF16("NEWS"));
494 ExpectedURLs expected_urls;
495 // Scores high because of completion length.
496 expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
497 "http://slashdot.org/", false));
498 // Scores high because of visit count.
499 expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
500 "http://sports.yahoo.com/", false));
501 // Scores high because of visit count but less match span,
502 // which is more important.
503 expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
504 "http://www.cnn.com/index.html", false));
505 RunTest(text, false, expected_urls, "http://slashdot.org/", base::string16());
508 TEST_F(ShortcutsProviderTest, RemoveDuplicates) {
509 base::string16 text(ASCIIToUTF16("dupl"));
510 ExpectedURLs expected_urls;
511 expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
512 "http://duplicate.com/", true));
513 // Make sure the URL only appears once in the output list.
514 RunTest(text, false, expected_urls, "http://duplicate.com/",
515 ASCIIToUTF16("icate.com"));
518 TEST_F(ShortcutsProviderTest, TypedCountMatches) {
519 base::string16 text(ASCIIToUTF16("just"));
520 ExpectedURLs expected_urls;
521 expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
522 "http://www.testsite.com/b.html", false));
523 expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
524 "http://www.testsite.com/a.html", false));
525 expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
526 "http://www.testsite.com/c.html", false));
527 RunTest(text, false, expected_urls, "http://www.testsite.com/b.html",
531 TEST_F(ShortcutsProviderTest, FragmentLengthMatches) {
532 base::string16 text(ASCIIToUTF16("just a"));
533 ExpectedURLs expected_urls;
534 expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
535 "http://www.testsite.com/d.html", false));
536 expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
537 "http://www.testsite.com/e.html", false));
538 expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
539 "http://www.testsite.com/f.html", false));
540 RunTest(text, false, expected_urls, "http://www.testsite.com/d.html",
544 TEST_F(ShortcutsProviderTest, DaysAgoMatches) {
545 base::string16 text(ASCIIToUTF16("ago"));
546 ExpectedURLs expected_urls;
547 expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
548 "http://www.daysagotest.com/a.html", false));
549 expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
550 "http://www.daysagotest.com/b.html", false));
551 expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
552 "http://www.daysagotest.com/c.html", false));
553 RunTest(text, false, expected_urls, "http://www.daysagotest.com/a.html",
557 TEST_F(ShortcutsProviderTest, ClassifyAllMatchesInString) {
558 ACMatchClassifications matches =
559 AutocompleteMatch::ClassificationsFromString("0,0");
560 ClassifyTest classify_test(ASCIIToUTF16("A man, a plan, a canal Panama"),
563 ACMatchClassifications spans_a = classify_test.RunTest(ASCIIToUTF16("man"));
564 // ACMatch spans should be: '--MMM------------------------'
565 EXPECT_EQ("0,0,2,2,5,0", AutocompleteMatch::ClassificationsToString(spans_a));
567 ACMatchClassifications spans_b = classify_test.RunTest(ASCIIToUTF16("man p"));
568 // ACMatch spans should be: '--MMM----M-------------M-----'
569 EXPECT_EQ("0,0,2,2,5,0,9,2,10,0,23,2,24,0",
570 AutocompleteMatch::ClassificationsToString(spans_b));
572 ACMatchClassifications spans_c =
573 classify_test.RunTest(ASCIIToUTF16("man plan panama"));
574 // ACMatch spans should be:'--MMM----MMMM----------MMMMMM'
575 EXPECT_EQ("0,0,2,2,5,0,9,2,13,0,23,2",
576 AutocompleteMatch::ClassificationsToString(spans_c));
578 ClassifyTest classify_test2(ASCIIToUTF16("Yahoo! Sports - Sports News, "
579 "Scores, Rumors, Fantasy Games, and more"), matches);
581 ACMatchClassifications spans_d = classify_test2.RunTest(ASCIIToUTF16("ne"));
582 // ACMatch spans should match first two letters of the "news".
583 EXPECT_EQ("0,0,23,2,25,0",
584 AutocompleteMatch::ClassificationsToString(spans_d));
586 ACMatchClassifications spans_e =
587 classify_test2.RunTest(ASCIIToUTF16("news r"));
588 EXPECT_EQ("0,0,10,2,11,0,19,2,20,0,23,2,27,0,32,2,33,0,37,2,38,0,41,2,42,0,"
589 "66,2,67,0", AutocompleteMatch::ClassificationsToString(spans_e));
591 matches = AutocompleteMatch::ClassificationsFromString("0,1");
592 ClassifyTest classify_test3(ASCIIToUTF16("livescore.goal.com"), matches);
594 ACMatchClassifications spans_f = classify_test3.RunTest(ASCIIToUTF16("go"));
595 // ACMatch spans should match first two letters of the "goal".
596 EXPECT_EQ("0,1,10,3,12,1",
597 AutocompleteMatch::ClassificationsToString(spans_f));
599 matches = AutocompleteMatch::ClassificationsFromString("0,0,13,1");
600 ClassifyTest classify_test4(ASCIIToUTF16("Email login: mail.somecorp.com"),
603 ACMatchClassifications spans_g = classify_test4.RunTest(ASCIIToUTF16("ail"));
604 EXPECT_EQ("0,0,2,2,5,0,13,1,14,3,17,1",
605 AutocompleteMatch::ClassificationsToString(spans_g));
607 ACMatchClassifications spans_h =
608 classify_test4.RunTest(ASCIIToUTF16("lo log"));
609 EXPECT_EQ("0,0,6,2,9,0,13,1",
610 AutocompleteMatch::ClassificationsToString(spans_h));
612 ACMatchClassifications spans_i =
613 classify_test4.RunTest(ASCIIToUTF16("ail em"));
614 // 'Email' and 'ail' should be matched.
615 EXPECT_EQ("0,2,5,0,13,1,14,3,17,1",
616 AutocompleteMatch::ClassificationsToString(spans_i));
618 // Some web sites do not have a description. If the string being searched is
619 // empty, the classifications must also be empty: http://crbug.com/148647
620 // Extra parens in the next line hack around C++03's "most vexing parse".
621 class ClassifyTest classify_test5((base::string16()),
622 ACMatchClassifications());
623 ACMatchClassifications spans_j = classify_test5.RunTest(ASCIIToUTF16("man"));
624 ASSERT_EQ(0U, spans_j.size());
626 // Matches which end at beginning of classification merge properly.
627 matches = AutocompleteMatch::ClassificationsFromString("0,4,9,0");
628 ClassifyTest classify_test6(ASCIIToUTF16("html password example"), matches);
630 // Extra space in the next string avoids having the string be a prefix of the
631 // text above, which would allow for two different valid classification sets,
632 // one of which uses two spans (the first of which would mark all of "html
633 // pass" as a match) and one which uses four (which marks the individual words
634 // as matches but not the space between them). This way only the latter is
636 ACMatchClassifications spans_k =
637 classify_test6.RunTest(ASCIIToUTF16("html pass"));
638 EXPECT_EQ("0,6,4,4,5,6,9,0",
639 AutocompleteMatch::ClassificationsToString(spans_k));
641 // Multiple matches with both beginning and end at beginning of
642 // classifications merge properly.
643 matches = AutocompleteMatch::ClassificationsFromString("0,1,11,0");
644 ClassifyTest classify_test7(ASCIIToUTF16("http://a.co is great"), matches);
646 ACMatchClassifications spans_l =
647 classify_test7.RunTest(ASCIIToUTF16("ht co"));
648 EXPECT_EQ("0,3,2,1,9,3,11,0",
649 AutocompleteMatch::ClassificationsToString(spans_l));
652 TEST_F(ShortcutsProviderTest, CalculateScore) {
653 ShortcutsBackend::Shortcut shortcut(
654 std::string(), ASCIIToUTF16("test"),
655 ShortcutsBackend::Shortcut::MatchCore(
656 ASCIIToUTF16("www.test.com"), GURL("http://www.test.com"),
657 ASCIIToUTF16("www.test.com"),
658 AutocompleteMatch::ClassificationsFromString("0,1,4,3,8,1"),
659 ASCIIToUTF16("A test"),
660 AutocompleteMatch::ClassificationsFromString("0,0,2,2"),
661 content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL,
663 base::Time::Now(), 1);
666 const int max_relevance = AutocompleteResult::kLowestDefaultScore - 1;
667 const int kMaxScore = CalculateScore("test", shortcut, max_relevance);
669 // Score decreases as percent of the match is decreased.
670 int score_three_quarters = CalculateScore("tes", shortcut, max_relevance);
671 EXPECT_LT(score_three_quarters, kMaxScore);
672 int score_one_half = CalculateScore("te", shortcut, max_relevance);
673 EXPECT_LT(score_one_half, score_three_quarters);
674 int score_one_quarter = CalculateScore("t", shortcut, max_relevance);
675 EXPECT_LT(score_one_quarter, score_one_half);
677 // Should decay with time - one week.
678 shortcut.last_access_time = base::Time::Now() - base::TimeDelta::FromDays(7);
679 int score_week_old = CalculateScore("test", shortcut, max_relevance);
680 EXPECT_LT(score_week_old, kMaxScore);
682 // Should decay more in two weeks.
683 shortcut.last_access_time = base::Time::Now() - base::TimeDelta::FromDays(14);
684 int score_two_weeks_old = CalculateScore("test", shortcut, max_relevance);
685 EXPECT_LT(score_two_weeks_old, score_week_old);
687 // But not if it was activly clicked on. 2 hits slow decaying power.
688 shortcut.number_of_hits = 2;
689 shortcut.last_access_time = base::Time::Now() - base::TimeDelta::FromDays(14);
690 int score_popular_two_weeks_old =
691 CalculateScore("test", shortcut, max_relevance);
692 EXPECT_LT(score_two_weeks_old, score_popular_two_weeks_old);
693 // But still decayed.
694 EXPECT_LT(score_popular_two_weeks_old, kMaxScore);
696 // 3 hits slow decaying power even more.
697 shortcut.number_of_hits = 3;
698 shortcut.last_access_time = base::Time::Now() - base::TimeDelta::FromDays(14);
699 int score_more_popular_two_weeks_old =
700 CalculateScore("test", shortcut, max_relevance);
701 EXPECT_LT(score_two_weeks_old, score_more_popular_two_weeks_old);
702 EXPECT_LT(score_popular_two_weeks_old, score_more_popular_two_weeks_old);
703 // But still decayed.
704 EXPECT_LT(score_more_popular_two_weeks_old, kMaxScore);
707 TEST_F(ShortcutsProviderTest, DeleteMatch) {
708 TestShortcutInfo shortcuts_to_test_delete[] = {
709 { "BD85DBA2-8C29-49F9-84AE-48E1E90881F1", "delete", "www.deletetest.com/1",
710 "http://www.deletetest.com/1", "http://www.deletetest.com/1", "0,2",
711 "Erase this shortcut!", "0,0", content::PAGE_TRANSITION_TYPED,
712 AutocompleteMatchType::HISTORY_URL, "", 1, 1},
713 { "BD85DBA2-8C29-49F9-84AE-48E1E90881F2", "erase", "www.deletetest.com/1",
714 "http://www.deletetest.com/1", "http://www.deletetest.com/1", "0,2",
715 "Erase this shortcut!", "0,0", content::PAGE_TRANSITION_TYPED,
716 AutocompleteMatchType::HISTORY_TITLE, "", 1, 1},
717 { "BD85DBA2-8C29-49F9-84AE-48E1E90881F3", "keep", "www.deletetest.com/1/2",
718 "http://www.deletetest.com/1/2", "http://www.deletetest.com/1/2", "0,2",
719 "Keep this shortcut!", "0,0", content::PAGE_TRANSITION_TYPED,
720 AutocompleteMatchType::HISTORY_TITLE, "", 1, 1},
721 { "BD85DBA2-8C29-49F9-84AE-48E1E90881F4", "delete", "www.deletetest.com/2",
722 "http://www.deletetest.com/2", "http://www.deletetest.com/2", "0,2",
723 "Erase this shortcut!", "0,0", content::PAGE_TRANSITION_TYPED,
724 AutocompleteMatchType::HISTORY_URL, "", 1, 1},
727 size_t original_shortcuts_count = backend_->shortcuts_map().size();
729 FillData(shortcuts_to_test_delete, arraysize(shortcuts_to_test_delete));
731 EXPECT_EQ(original_shortcuts_count + 4, backend_->shortcuts_map().size());
732 EXPECT_FALSE(backend_->shortcuts_map().end() ==
733 backend_->shortcuts_map().find(ASCIIToUTF16("delete")));
734 EXPECT_FALSE(backend_->shortcuts_map().end() ==
735 backend_->shortcuts_map().find(ASCIIToUTF16("erase")));
737 AutocompleteMatch match(
738 provider_.get(), 1200, true, AutocompleteMatchType::HISTORY_TITLE);
740 match.destination_url = GURL(shortcuts_to_test_delete[0].destination_url);
741 match.contents = ASCIIToUTF16(shortcuts_to_test_delete[0].contents);
742 match.description = ASCIIToUTF16(shortcuts_to_test_delete[0].description);
744 provider_->DeleteMatch(match);
746 // shortcuts_to_test_delete[0] and shortcuts_to_test_delete[1] should be
747 // deleted, but not shortcuts_to_test_delete[2] or
748 // shortcuts_to_test_delete[3], which have different URLs.
749 EXPECT_EQ(original_shortcuts_count + 2, backend_->shortcuts_map().size());
750 EXPECT_FALSE(backend_->shortcuts_map().end() ==
751 backend_->shortcuts_map().find(ASCIIToUTF16("delete")));
752 EXPECT_TRUE(backend_->shortcuts_map().end() ==
753 backend_->shortcuts_map().find(ASCIIToUTF16("erase")));
755 match.destination_url = GURL(shortcuts_to_test_delete[3].destination_url);
756 match.contents = ASCIIToUTF16(shortcuts_to_test_delete[3].contents);
757 match.description = ASCIIToUTF16(shortcuts_to_test_delete[3].description);
759 provider_->DeleteMatch(match);
760 EXPECT_EQ(original_shortcuts_count + 1, backend_->shortcuts_map().size());
761 EXPECT_TRUE(backend_->shortcuts_map().end() ==
762 backend_->shortcuts_map().find(ASCIIToUTF16("delete")));
765 TEST_F(ShortcutsProviderTest, Extension) {
766 // Try an input string that matches an extension URL.
767 base::string16 text(ASCIIToUTF16("echo"));
768 std::string expected_url(
769 "chrome-extension://cedabbhfglmiikkmdgcpjdkocfcmbkee/?q=echo");
770 ExpectedURLs expected_urls;
771 expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
772 expected_url, true));
773 RunTest(text, false, expected_urls, expected_url, ASCIIToUTF16(" echo"));
775 // Claim the extension has been unloaded.
776 scoped_refptr<const extensions::Extension> extension =
777 extensions::ExtensionBuilder()
778 .SetManifest(extensions::DictionaryBuilder()
780 .Set("version", "1.0"))
781 .SetID("cedabbhfglmiikkmdgcpjdkocfcmbkee")
783 extensions::UnloadedExtensionInfo details(
784 extension.get(), extensions::UnloadedExtensionInfo::REASON_UNINSTALL);
785 content::NotificationService::current()->Notify(
786 chrome::NOTIFICATION_EXTENSION_UNLOADED,
787 content::Source<Profile>(&profile_),
788 content::Details<extensions::UnloadedExtensionInfo>(&details));
790 // Now the URL should have disappeared.
791 RunTest(text, false, ExpectedURLs(), std::string(), base::string16());
794 } // namespace history