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