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