- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / autocomplete / shortcuts_provider_unittest.cc
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/shortcuts_provider.h"
6
7 #include <math.h>
8
9 #include <algorithm>
10 #include <functional>
11 #include <set>
12 #include <string>
13 #include <vector>
14
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/extensions/extension.h"
32 #include "chrome/common/extensions/extension_builder.h"
33 #include "chrome/common/extensions/value_builder.h"
34 #include "chrome/common/pref_names.h"
35 #include "chrome/test/base/testing_profile.h"
36 #include "content/public/browser/notification_service.h"
37 #include "content/public/test/test_browser_thread.h"
38 #include "testing/gtest/include/gtest/gtest.h"
39
40
41 // TestShortcutInfo -----------------------------------------------------------
42
43 namespace {
44
45 struct TestShortcutInfo {
46   std::string guid;
47   std::string text;
48   std::string fill_into_edit;
49   std::string destination_url;
50   std::string contents;
51   std::string contents_class;
52   std::string description;
53   std::string description_class;
54   content::PageTransition transition;
55   AutocompleteMatch::Type type;
56   std::string keyword;
57   int days_from_now;
58   int number_of_hits;
59 } shortcut_test_db[] = {
60   { "BD85DBA2-8C29-49F9-84AE-48E1E90880E0", "goog", "www.google.com",
61     "http://www.google.com/", "Google", "0,1,4,0", "Google", "0,3,4,1",
62     content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL, "", 1,
63     100 },
64   { "BD85DBA2-8C29-49F9-84AE-48E1E90880E1", "slash", "slashdot.org",
65     "http://slashdot.org/", "slashdot.org", "0,3,5,1",
66     "Slashdot - News for nerds, stuff that matters", "0,2,5,0",
67     content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL, "", 0,
68     100 },
69   { "BD85DBA2-8C29-49F9-84AE-48E1E90880E2", "news", "slashdot.org",
70     "http://slashdot.org/", "slashdot.org", "0,1",
71     "Slashdot - News for nerds, stuff that matters", "0,0,11,2,15,0",
72     content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_TITLE, "", 0,
73     5 },
74   { "BD85DBA2-8C29-49F9-84AE-48E1E90880E3", "news", "sports.yahoo.com",
75     "http://sports.yahoo.com/", "sports.yahoo.com", "0,1",
76     "Yahoo! Sports - Sports News, Scores, Rumors, Fantasy Games, and more",
77     "0,0,23,2,27,0", content::PAGE_TRANSITION_TYPED,
78     AutocompleteMatchType::HISTORY_TITLE, "", 2, 5 },
79   { "BD85DBA2-8C29-49F9-84AE-48E1E90880E4", "news weather",
80     "www.cnn.com/index.html", "http://www.cnn.com/index.html",
81     "www.cnn.com/index.html", "0,1",
82     "CNN.com - Breaking News, U.S., World, Weather, Entertainment & Video",
83     "0,0,19,2,23,0,38,2,45,0", content::PAGE_TRANSITION_TYPED,
84     AutocompleteMatchType::HISTORY_TITLE, "", 1, 10 },
85   { "BD85DBA2-8C29-49F9-84AE-48E1E90880E5", "nhl scores", "sports.yahoo.com",
86     "http://sports.yahoo.com/", "sports.yahoo.com", "0,1",
87     "Yahoo! Sports - Sports News, Scores, Rumors, Fantasy Games, and more",
88     "0,0,29,2,35,0", content::PAGE_TRANSITION_TYPED,
89     AutocompleteMatchType::HISTORY_BODY, "", 1, 10 },
90   { "BD85DBA2-8C29-49F9-84AE-48E1E90880E6", "nhl scores",
91     "www.nhl.com/scores/index.html", "http://www.nhl.com/scores/index.html",
92     "www.nhl.com/scores/index.html", "0,1,4,3,7,1",
93     "January 13, 2010 - NHL.com - Scores", "0,0,19,2,22,0,29,2,35,0",
94     content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL, "", 5,
95     1 },
96   { "BD85DBA2-8C29-49F9-84AE-48E1E90880E7", "just", "www.testsite.com/a.html",
97     "http://www.testsite.com/a.html", "www.testsite.com/a.html", "0,1",
98     "Test - site - just a test", "0,0,14,2,18,0",
99     content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_TITLE, "", 5,
100     1 },
101   { "BD85DBA2-8C29-49F9-84AE-48E1E90880E8", "just", "www.testsite.com/b.html",
102     "http://www.testsite.com/b.html", "www.testsite.com/b.html", "0,1",
103     "Test - site - just a test", "0,0,14,2,18,0",
104     content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_TITLE, "", 5,
105     2 },
106   { "BD85DBA2-8C29-49F9-84AE-48E1E90880E9", "just", "www.testsite.com/c.html",
107     "http://www.testsite.com/c.html", "www.testsite.com/c.html", "0,1",
108     "Test - site - just a test", "0,0,14,2,18,0",
109     content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_TITLE, "", 8,
110     1 },
111   { "BD85DBA2-8C29-49F9-84AE-48E1E90880EA", "just a", "www.testsite.com/d.html",
112     "http://www.testsite.com/d.html", "www.testsite.com/d.html", "0,1",
113     "Test - site - just a test", "0,0,14,2,18,0",
114     content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_TITLE, "",
115     12, 1 },
116   { "BD85DBA2-8C29-49F9-84AE-48E1E90880EB", "just a t",
117     "www.testsite.com/e.html", "http://www.testsite.com/e.html",
118     "www.testsite.com/e.html", "0,1", "Test - site - just a test",
119     "0,0,14,2,18,0", content::PAGE_TRANSITION_TYPED,
120     AutocompleteMatchType::HISTORY_TITLE, "", 12, 1 },
121   { "BD85DBA2-8C29-49F9-84AE-48E1E90880EC", "just a te",
122     "www.testsite.com/f.html", "http://www.testsite.com/f.html",
123     "www.testsite.com/f.html", "0,1", "Test - site - just a test",
124     "0,0,14,2,18,0", content::PAGE_TRANSITION_TYPED,
125     AutocompleteMatchType::HISTORY_TITLE, "", 12, 1 },
126   { "BD85DBA2-8C29-49F9-84AE-48E1E90880ED", "ago", "www.daysagotest.com/a.html",
127     "http://www.daysagotest.com/a.html", "www.daysagotest.com/a.html",
128     "0,1,8,3,11,1", "Test - site", "0,0", content::PAGE_TRANSITION_TYPED,
129     AutocompleteMatchType::HISTORY_URL, "", 1, 1 },
130   { "BD85DBA2-8C29-49F9-84AE-48E1E90880EE", "ago", "www.daysagotest.com/b.html",
131     "http://www.daysagotest.com/b.html", "www.daysagotest.com/b.html",
132     "0,1,8,3,11,1", "Test - site", "0,0", content::PAGE_TRANSITION_TYPED,
133     AutocompleteMatchType::HISTORY_URL, "", 2, 1 },
134   { "BD85DBA2-8C29-49F9-84AE-48E1E90880EF", "ago", "www.daysagotest.com/c.html",
135     "http://www.daysagotest.com/c.html", "www.daysagotest.com/c.html",
136     "0,1,8,3,11,1", "Test - site", "0,0", content::PAGE_TRANSITION_TYPED,
137     AutocompleteMatchType::HISTORY_URL, "", 3, 1 },
138   { "BD85DBA2-8C29-49F9-84AE-48E1E90880F0", "ago", "www.daysagotest.com/d.html",
139     "http://www.daysagotest.com/d.html", "www.daysagotest.com/d.html",
140     "0,1,8,3,11,1", "Test - site", "0,0", content::PAGE_TRANSITION_TYPED,
141     AutocompleteMatchType::HISTORY_URL, "", 4, 1 },
142   { "BD85DBA2-8C29-49F9-84AE-48E1E90880F1", "echo echo", "echo echo",
143     "chrome-extension://cedabbhfglmiikkmdgcpjdkocfcmbkee/?q=echo",
144     "Run Echo command: echo", "0,0", "Echo", "0,4",
145     content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::EXTENSION_APP,
146     "echo", 1, 1 },
147 };
148
149 }  // namespace
150
151
152 // ClassifyTest ---------------------------------------------------------------
153
154 // Helper class to make running tests of ClassifyAllMatchesInString() more
155 // convenient.
156 class ClassifyTest {
157  public:
158   ClassifyTest(const string16& text, ACMatchClassifications matches);
159   ~ClassifyTest();
160
161   ACMatchClassifications RunTest(const string16& find_text);
162
163  private:
164   const string16 text_;
165   const ACMatchClassifications matches_;
166 };
167
168 ClassifyTest::ClassifyTest(const string16& text, ACMatchClassifications matches)
169     : text_(text),
170       matches_(matches) {
171 }
172
173 ClassifyTest::~ClassifyTest() {
174 }
175
176 ACMatchClassifications ClassifyTest::RunTest(const string16& find_text) {
177   return ShortcutsProvider::ClassifyAllMatchesInString(find_text,
178       ShortcutsProvider::CreateWordMapForString(find_text), text_, matches_);
179 }
180
181 namespace history {
182
183
184 // ShortcutsProviderTest ------------------------------------------------------
185
186 class ShortcutsProviderTest : public testing::Test,
187                               public AutocompleteProviderListener {
188  public:
189   ShortcutsProviderTest();
190
191   // AutocompleteProviderListener:
192   virtual void OnProviderUpdate(bool updated_matches) OVERRIDE;
193
194  protected:
195   class SetShouldContain
196       : public std::unary_function<const std::string&, std::set<std::string> > {
197    public:
198     explicit SetShouldContain(const ACMatches& matched_urls);
199
200     void operator()(const std::string& expected);
201     std::set<std::string> Leftovers() const { return matches_; }
202
203    private:
204     std::set<std::string> matches_;
205   };
206
207   typedef std::vector<std::string> URLs;
208
209   virtual void SetUp();
210   virtual void TearDown();
211
212   // Fills test data into the provider.
213   void FillData(TestShortcutInfo* db, size_t db_size);
214
215   // Runs an autocomplete query on |text| and checks to see that the returned
216   // results' destination URLs match those provided. |expected_urls| does not
217   // need to be in sorted order.
218   void RunTest(const string16 text,
219                const URLs& expected_urls,
220                std::string expected_top_result);
221
222   // Passthrough to the private function in provider_.
223   int CalculateScore(const std::string& terms,
224                      const ShortcutsBackend::Shortcut& shortcut,
225                      int max_relevance);
226
227   base::MessageLoopForUI message_loop_;
228   content::TestBrowserThread ui_thread_;
229   content::TestBrowserThread file_thread_;
230
231   TestingProfile profile_;
232
233   ACMatches ac_matches_;  // The resulting matches after running RunTest.
234
235   scoped_refptr<ShortcutsBackend> backend_;
236   scoped_refptr<ShortcutsProvider> provider_;
237 };
238
239 ShortcutsProviderTest::ShortcutsProviderTest()
240     : ui_thread_(content::BrowserThread::UI, &message_loop_),
241       file_thread_(content::BrowserThread::FILE, &message_loop_) {
242 }
243
244 void ShortcutsProviderTest::OnProviderUpdate(bool updated_matches) {}
245
246 void ShortcutsProviderTest::SetUp() {
247   ShortcutsBackendFactory::GetInstance()->SetTestingFactoryAndUse(
248       &profile_, &ShortcutsBackendFactory::BuildProfileNoDatabaseForTesting);
249   backend_ = ShortcutsBackendFactory::GetForProfile(&profile_);
250   ASSERT_TRUE(backend_.get());
251   ASSERT_TRUE(profile_.CreateHistoryService(true, false));
252   provider_ = new ShortcutsProvider(this, &profile_);
253   FillData(shortcut_test_db, arraysize(shortcut_test_db));
254 }
255
256 void ShortcutsProviderTest::TearDown() {
257   // Run all pending tasks or else some threads hold on to the message loop
258   // and prevent it from being deleted.
259   message_loop_.RunUntilIdle();
260   provider_ = NULL;
261 }
262
263 void ShortcutsProviderTest::FillData(TestShortcutInfo* db, size_t db_size) {
264   DCHECK(provider_.get());
265   size_t expected_size = backend_->shortcuts_map().size() + db_size;
266   for (size_t i = 0; i < db_size; ++i) {
267     const TestShortcutInfo& cur = db[i];
268     ShortcutsBackend::Shortcut shortcut(
269         cur.guid, ASCIIToUTF16(cur.text),
270         ShortcutsBackend::Shortcut::MatchCore(
271             ASCIIToUTF16(cur.fill_into_edit), GURL(cur.destination_url),
272             ASCIIToUTF16(cur.contents),
273             AutocompleteMatch::ClassificationsFromString(cur.contents_class),
274             ASCIIToUTF16(cur.description),
275             AutocompleteMatch::ClassificationsFromString(cur.description_class),
276             cur.transition, cur.type, ASCIIToUTF16(cur.keyword)),
277         base::Time::Now() - base::TimeDelta::FromDays(cur.days_from_now),
278         cur.number_of_hits);
279     backend_->AddShortcut(shortcut);
280   }
281   EXPECT_EQ(expected_size, backend_->shortcuts_map().size());
282 }
283
284 ShortcutsProviderTest::SetShouldContain::SetShouldContain(
285     const ACMatches& matched_urls) {
286   for (ACMatches::const_iterator iter = matched_urls.begin();
287        iter != matched_urls.end(); ++iter)
288     matches_.insert(iter->destination_url.spec());
289 }
290
291 void ShortcutsProviderTest::SetShouldContain::operator()(
292     const std::string& expected) {
293   EXPECT_EQ(1U, matches_.erase(expected));
294 }
295
296 void ShortcutsProviderTest::RunTest(const string16 text,
297                                     const URLs& expected_urls,
298                                     std::string expected_top_result) {
299   base::MessageLoop::current()->RunUntilIdle();
300   AutocompleteInput input(text, string16::npos, string16(), GURL(),
301                           AutocompleteInput::INVALID_SPEC, false, false, true,
302                           AutocompleteInput::ALL_MATCHES);
303   provider_->Start(input, false);
304   EXPECT_TRUE(provider_->done());
305
306   ac_matches_ = provider_->matches();
307
308   // We should have gotten back at most AutocompleteProvider::kMaxMatches.
309   EXPECT_LE(ac_matches_.size(), AutocompleteProvider::kMaxMatches);
310
311   // If the number of expected and actual matches aren't equal then we need
312   // test no further, but let's do anyway so that we know which URLs failed.
313   EXPECT_EQ(expected_urls.size(), ac_matches_.size());
314
315   // Verify that all expected URLs were found and that all found URLs
316   // were expected.
317   std::set<std::string> Leftovers =
318       for_each(expected_urls.begin(), expected_urls.end(),
319                SetShouldContain(ac_matches_)).Leftovers();
320   EXPECT_EQ(0U, Leftovers.size());
321
322   // See if we got the expected top scorer.
323   if (!ac_matches_.empty()) {
324     std::partial_sort(ac_matches_.begin(), ac_matches_.begin() + 1,
325                       ac_matches_.end(), AutocompleteMatch::MoreRelevant);
326     EXPECT_EQ(expected_top_result, ac_matches_[0].destination_url.spec());
327   }
328
329   // No shortcuts matches are allowed to be inlined no matter how highly
330   // they score.
331   for (ACMatches::const_iterator it = ac_matches_.begin();
332        it != ac_matches_.end(); ++it)
333     EXPECT_FALSE(it->allowed_to_be_default_match);
334 }
335
336 int ShortcutsProviderTest::CalculateScore(
337     const std::string& terms,
338     const ShortcutsBackend::Shortcut& shortcut,
339     int max_relevance) {
340   return provider_->CalculateScore(ASCIIToUTF16(terms), shortcut,
341                                    max_relevance);
342 }
343
344
345 // Actual tests ---------------------------------------------------------------
346
347 TEST_F(ShortcutsProviderTest, SimpleSingleMatch) {
348   string16 text(ASCIIToUTF16("go"));
349   std::string expected_url("http://www.google.com/");
350   URLs expected_urls;
351   expected_urls.push_back(expected_url);
352   RunTest(text, expected_urls, expected_url);
353 }
354
355 TEST_F(ShortcutsProviderTest, MultiMatch) {
356   string16 text(ASCIIToUTF16("NEWS"));
357   URLs expected_urls;
358   // Scores high because of completion length.
359   expected_urls.push_back("http://slashdot.org/");
360   // Scores high because of visit count.
361   expected_urls.push_back("http://sports.yahoo.com/");
362   // Scores high because of visit count but less match span,
363   // which is more important.
364   expected_urls.push_back("http://www.cnn.com/index.html");
365   RunTest(text, expected_urls, "http://slashdot.org/");
366 }
367
368 TEST_F(ShortcutsProviderTest, TypedCountMatches) {
369   string16 text(ASCIIToUTF16("just"));
370   URLs expected_urls;
371   expected_urls.push_back("http://www.testsite.com/b.html");
372   expected_urls.push_back("http://www.testsite.com/a.html");
373   expected_urls.push_back("http://www.testsite.com/c.html");
374   RunTest(text, expected_urls, "http://www.testsite.com/b.html");
375 }
376
377 TEST_F(ShortcutsProviderTest, FragmentLengthMatches) {
378   string16 text(ASCIIToUTF16("just a"));
379   URLs expected_urls;
380   expected_urls.push_back("http://www.testsite.com/d.html");
381   expected_urls.push_back("http://www.testsite.com/e.html");
382   expected_urls.push_back("http://www.testsite.com/f.html");
383   RunTest(text, expected_urls, "http://www.testsite.com/d.html");
384 }
385
386 TEST_F(ShortcutsProviderTest, DaysAgoMatches) {
387   string16 text(ASCIIToUTF16("ago"));
388   URLs expected_urls;
389   expected_urls.push_back("http://www.daysagotest.com/a.html");
390   expected_urls.push_back("http://www.daysagotest.com/b.html");
391   expected_urls.push_back("http://www.daysagotest.com/c.html");
392   RunTest(text, expected_urls, "http://www.daysagotest.com/a.html");
393 }
394
395 TEST_F(ShortcutsProviderTest, ClassifyAllMatchesInString) {
396   ACMatchClassifications matches =
397       AutocompleteMatch::ClassificationsFromString("0,0");
398   ClassifyTest classify_test(ASCIIToUTF16("A man, a plan, a canal Panama"),
399                              matches);
400
401   ACMatchClassifications spans_a = classify_test.RunTest(ASCIIToUTF16("man"));
402   // ACMatch spans should be: '--MMM------------------------'
403   EXPECT_EQ("0,0,2,2,5,0", AutocompleteMatch::ClassificationsToString(spans_a));
404
405   ACMatchClassifications spans_b = classify_test.RunTest(ASCIIToUTF16("man p"));
406   // ACMatch spans should be: '--MMM----M-------------M-----'
407   EXPECT_EQ("0,0,2,2,5,0,9,2,10,0,23,2,24,0",
408             AutocompleteMatch::ClassificationsToString(spans_b));
409
410   ACMatchClassifications spans_c =
411       classify_test.RunTest(ASCIIToUTF16("man plan panama"));
412   // ACMatch spans should be:'--MMM----MMMM----------MMMMMM'
413   EXPECT_EQ("0,0,2,2,5,0,9,2,13,0,23,2",
414             AutocompleteMatch::ClassificationsToString(spans_c));
415
416   ClassifyTest classify_test2(ASCIIToUTF16("Yahoo! Sports - Sports News, "
417       "Scores, Rumors, Fantasy Games, and more"), matches);
418
419   ACMatchClassifications spans_d = classify_test2.RunTest(ASCIIToUTF16("ne"));
420   // ACMatch spans should match first two letters of the "news".
421   EXPECT_EQ("0,0,23,2,25,0",
422             AutocompleteMatch::ClassificationsToString(spans_d));
423
424   ACMatchClassifications spans_e =
425       classify_test2.RunTest(ASCIIToUTF16("news r"));
426   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,"
427             "66,2,67,0", AutocompleteMatch::ClassificationsToString(spans_e));
428
429   matches = AutocompleteMatch::ClassificationsFromString("0,1");
430   ClassifyTest classify_test3(ASCIIToUTF16("livescore.goal.com"), matches);
431
432   ACMatchClassifications spans_f = classify_test3.RunTest(ASCIIToUTF16("go"));
433   // ACMatch spans should match first two letters of the "goal".
434   EXPECT_EQ("0,1,10,3,12,1",
435             AutocompleteMatch::ClassificationsToString(spans_f));
436
437   matches = AutocompleteMatch::ClassificationsFromString("0,0,13,1");
438   ClassifyTest classify_test4(ASCIIToUTF16("Email login: mail.somecorp.com"),
439                               matches);
440
441   ACMatchClassifications spans_g = classify_test4.RunTest(ASCIIToUTF16("ail"));
442   EXPECT_EQ("0,0,2,2,5,0,13,1,14,3,17,1",
443             AutocompleteMatch::ClassificationsToString(spans_g));
444
445   ACMatchClassifications spans_h =
446       classify_test4.RunTest(ASCIIToUTF16("lo log"));
447   EXPECT_EQ("0,0,6,2,9,0,13,1",
448             AutocompleteMatch::ClassificationsToString(spans_h));
449
450   ACMatchClassifications spans_i =
451       classify_test4.RunTest(ASCIIToUTF16("ail em"));
452   // 'Email' and 'ail' should be matched.
453   EXPECT_EQ("0,2,5,0,13,1,14,3,17,1",
454             AutocompleteMatch::ClassificationsToString(spans_i));
455
456   // Some web sites do not have a description.  If the string being searched is
457   // empty, the classifications must also be empty: http://crbug.com/148647
458   // Extra parens in the next line hack around C++03's "most vexing parse".
459   class ClassifyTest classify_test5((string16()), ACMatchClassifications());
460   ACMatchClassifications spans_j = classify_test5.RunTest(ASCIIToUTF16("man"));
461   ASSERT_EQ(0U, spans_j.size());
462
463   // Matches which end at beginning of classification merge properly.
464   matches = AutocompleteMatch::ClassificationsFromString("0,4,9,0");
465   ClassifyTest classify_test6(ASCIIToUTF16("html password example"), matches);
466
467   // Extra space in the next string avoids having the string be a prefix of the
468   // text above, which would allow for two different valid classification sets,
469   // one of which uses two spans (the first of which would mark all of "html
470   // pass" as a match) and one which uses four (which marks the individual words
471   // as matches but not the space between them).  This way only the latter is
472   // valid.
473   ACMatchClassifications spans_k =
474       classify_test6.RunTest(ASCIIToUTF16("html  pass"));
475   EXPECT_EQ("0,6,4,4,5,6,9,0",
476             AutocompleteMatch::ClassificationsToString(spans_k));
477
478   // Multiple matches with both beginning and end at beginning of
479   // classifications merge properly.
480   matches = AutocompleteMatch::ClassificationsFromString("0,1,11,0");
481   ClassifyTest classify_test7(ASCIIToUTF16("http://a.co is great"), matches);
482
483   ACMatchClassifications spans_l =
484       classify_test7.RunTest(ASCIIToUTF16("ht co"));
485   EXPECT_EQ("0,3,2,1,9,3,11,0",
486             AutocompleteMatch::ClassificationsToString(spans_l));
487 }
488
489 TEST_F(ShortcutsProviderTest, CalculateScore) {
490   ShortcutsBackend::Shortcut shortcut(
491       std::string(), ASCIIToUTF16("test"),
492       ShortcutsBackend::Shortcut::MatchCore(
493           ASCIIToUTF16("www.test.com"), GURL("http://www.test.com"),
494           ASCIIToUTF16("www.test.com"),
495           AutocompleteMatch::ClassificationsFromString("0,1,4,3,8,1"),
496           ASCIIToUTF16("A test"),
497           AutocompleteMatch::ClassificationsFromString("0,0,2,2"),
498           content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL,
499           string16()),
500       base::Time::Now(), 1);
501
502   // Maximal score.
503   const int max_relevance = AutocompleteResult::kLowestDefaultScore - 1;
504   const int kMaxScore = CalculateScore("test", shortcut, max_relevance);
505
506   // Score decreases as percent of the match is decreased.
507   int score_three_quarters = CalculateScore("tes", shortcut, max_relevance);
508   EXPECT_LT(score_three_quarters, kMaxScore);
509   int score_one_half = CalculateScore("te", shortcut, max_relevance);
510   EXPECT_LT(score_one_half, score_three_quarters);
511   int score_one_quarter = CalculateScore("t", shortcut, max_relevance);
512   EXPECT_LT(score_one_quarter, score_one_half);
513
514   // Should decay with time - one week.
515   shortcut.last_access_time = base::Time::Now() - base::TimeDelta::FromDays(7);
516   int score_week_old = CalculateScore("test", shortcut, max_relevance);
517   EXPECT_LT(score_week_old, kMaxScore);
518
519   // Should decay more in two weeks.
520   shortcut.last_access_time = base::Time::Now() - base::TimeDelta::FromDays(14);
521   int score_two_weeks_old = CalculateScore("test", shortcut, max_relevance);
522   EXPECT_LT(score_two_weeks_old, score_week_old);
523
524   // But not if it was activly clicked on. 2 hits slow decaying power.
525   shortcut.number_of_hits = 2;
526   shortcut.last_access_time = base::Time::Now() - base::TimeDelta::FromDays(14);
527   int score_popular_two_weeks_old =
528       CalculateScore("test", shortcut, max_relevance);
529   EXPECT_LT(score_two_weeks_old, score_popular_two_weeks_old);
530   // But still decayed.
531   EXPECT_LT(score_popular_two_weeks_old, kMaxScore);
532
533   // 3 hits slow decaying power even more.
534   shortcut.number_of_hits = 3;
535   shortcut.last_access_time = base::Time::Now() - base::TimeDelta::FromDays(14);
536   int score_more_popular_two_weeks_old =
537       CalculateScore("test", shortcut, max_relevance);
538   EXPECT_LT(score_two_weeks_old, score_more_popular_two_weeks_old);
539   EXPECT_LT(score_popular_two_weeks_old, score_more_popular_two_weeks_old);
540   // But still decayed.
541   EXPECT_LT(score_more_popular_two_weeks_old, kMaxScore);
542 }
543
544 TEST_F(ShortcutsProviderTest, DeleteMatch) {
545   TestShortcutInfo shortcuts_to_test_delete[] = {
546     { "BD85DBA2-8C29-49F9-84AE-48E1E90881F1", "delete", "www.deletetest.com/1",
547       "http://www.deletetest.com/1", "http://www.deletetest.com/1", "0,2",
548       "Erase this shortcut!", "0,0", content::PAGE_TRANSITION_TYPED,
549       AutocompleteMatchType::HISTORY_URL, "", 1, 1},
550     { "BD85DBA2-8C29-49F9-84AE-48E1E90881F2", "erase", "www.deletetest.com/1",
551       "http://www.deletetest.com/1", "http://www.deletetest.com/1", "0,2",
552       "Erase this shortcut!", "0,0", content::PAGE_TRANSITION_TYPED,
553       AutocompleteMatchType::HISTORY_TITLE, "", 1, 1},
554     { "BD85DBA2-8C29-49F9-84AE-48E1E90881F3", "keep", "www.deletetest.com/1/2",
555       "http://www.deletetest.com/1/2", "http://www.deletetest.com/1/2", "0,2",
556       "Keep this shortcut!", "0,0", content::PAGE_TRANSITION_TYPED,
557       AutocompleteMatchType::HISTORY_TITLE, "", 1, 1},
558     { "BD85DBA2-8C29-49F9-84AE-48E1E90881F4", "delete", "www.deletetest.com/2",
559       "http://www.deletetest.com/2", "http://www.deletetest.com/2", "0,2",
560       "Erase this shortcut!", "0,0", content::PAGE_TRANSITION_TYPED,
561       AutocompleteMatchType::HISTORY_URL, "", 1, 1},
562   };
563
564   size_t original_shortcuts_count = backend_->shortcuts_map().size();
565
566   FillData(shortcuts_to_test_delete, arraysize(shortcuts_to_test_delete));
567
568   EXPECT_EQ(original_shortcuts_count + 4, backend_->shortcuts_map().size());
569   EXPECT_FALSE(backend_->shortcuts_map().end() ==
570                backend_->shortcuts_map().find(ASCIIToUTF16("delete")));
571   EXPECT_FALSE(backend_->shortcuts_map().end() ==
572                backend_->shortcuts_map().find(ASCIIToUTF16("erase")));
573
574   AutocompleteMatch match(
575       provider_.get(), 1200, true, AutocompleteMatchType::HISTORY_TITLE);
576
577   match.destination_url = GURL(shortcuts_to_test_delete[0].destination_url);
578   match.contents = ASCIIToUTF16(shortcuts_to_test_delete[0].contents);
579   match.description = ASCIIToUTF16(shortcuts_to_test_delete[0].description);
580
581   provider_->DeleteMatch(match);
582
583   // shortcuts_to_test_delete[0] and shortcuts_to_test_delete[1] should be
584   // deleted, but not shortcuts_to_test_delete[2] or
585   // shortcuts_to_test_delete[3], which have different URLs.
586   EXPECT_EQ(original_shortcuts_count + 2, backend_->shortcuts_map().size());
587   EXPECT_FALSE(backend_->shortcuts_map().end() ==
588                backend_->shortcuts_map().find(ASCIIToUTF16("delete")));
589   EXPECT_TRUE(backend_->shortcuts_map().end() ==
590               backend_->shortcuts_map().find(ASCIIToUTF16("erase")));
591
592   match.destination_url = GURL(shortcuts_to_test_delete[3].destination_url);
593   match.contents = ASCIIToUTF16(shortcuts_to_test_delete[3].contents);
594   match.description = ASCIIToUTF16(shortcuts_to_test_delete[3].description);
595
596   provider_->DeleteMatch(match);
597   EXPECT_EQ(original_shortcuts_count + 1, backend_->shortcuts_map().size());
598   EXPECT_TRUE(backend_->shortcuts_map().end() ==
599               backend_->shortcuts_map().find(ASCIIToUTF16("delete")));
600 }
601
602 TEST_F(ShortcutsProviderTest, Extension) {
603   // Try an input string that matches an extension URL.
604   string16 text(ASCIIToUTF16("echo"));
605   std::string expected_url(
606       "chrome-extension://cedabbhfglmiikkmdgcpjdkocfcmbkee/?q=echo");
607   URLs expected_urls;
608   expected_urls.push_back(expected_url);
609   RunTest(text, expected_urls, expected_url);
610
611   // Claim the extension has been unloaded.
612   scoped_refptr<const extensions::Extension> extension =
613       extensions::ExtensionBuilder()
614           .SetManifest(extensions::DictionaryBuilder()
615               .Set("name", "Echo")
616               .Set("version", "1.0"))
617           .SetID("cedabbhfglmiikkmdgcpjdkocfcmbkee")
618           .Build();
619   extensions::UnloadedExtensionInfo details(
620       extension.get(), extensions::UnloadedExtensionInfo::REASON_UNINSTALL);
621   content::NotificationService::current()->Notify(
622       chrome::NOTIFICATION_EXTENSION_UNLOADED,
623       content::Source<Profile>(&profile_),
624       content::Details<extensions::UnloadedExtensionInfo>(&details));
625
626   // Now the URL should have disappeared.
627   RunTest(text, URLs(), std::string());
628 }
629
630 }  // namespace history