shortcuts_provider_unittest.cc revision 6d86b77056ed63eb6871182f42a9fd5f07550f90
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/autocomplete/shortcuts_backend.h"
26#include "chrome/browser/autocomplete/shortcuts_backend_factory.h"
27#include "chrome/browser/chrome_notification_types.h"
28#include "chrome/browser/history/history_service.h"
29#include "chrome/browser/history/in_memory_url_index.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 "components/metrics/proto/omnibox_event.pb.h"
34#include "content/public/browser/notification_service.h"
35#include "content/public/test/test_browser_thread.h"
36#include "extensions/common/extension.h"
37#include "extensions/common/extension_builder.h"
38#include "extensions/common/value_builder.h"
39#include "testing/gtest/include/gtest/gtest.h"
40
41using base::ASCIIToUTF16;
42
43// TestShortcutInfo -----------------------------------------------------------
44
45namespace {
46
47struct TestShortcutInfo {
48  std::string guid;
49  std::string text;
50  std::string fill_into_edit;
51  std::string destination_url;
52  std::string contents;
53  std::string contents_class;
54  std::string description;
55  std::string description_class;
56  content::PageTransition transition;
57  AutocompleteMatch::Type type;
58  std::string keyword;
59  int days_from_now;
60  int number_of_hits;
61} shortcut_test_db[] = {
62  { "BD85DBA2-8C29-49F9-84AE-48E1E90880E0", "goog", "www.google.com",
63    "http://www.google.com/", "Google", "0,1,4,0", "Google", "0,3,4,1",
64    content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL, "", 1,
65    100 },
66  { "BD85DBA2-8C29-49F9-84AE-48E1E90880E1", "slash", "slashdot.org",
67    "http://slashdot.org/", "slashdot.org", "0,3,5,1",
68    "Slashdot - News for nerds, stuff that matters", "0,2,5,0",
69    content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL, "", 0,
70    100 },
71  { "BD85DBA2-8C29-49F9-84AE-48E1E90880E2", "news", "slashdot.org",
72    "http://slashdot.org/", "slashdot.org", "0,1",
73    "Slashdot - News for nerds, stuff that matters", "0,0,11,2,15,0",
74    content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_TITLE, "", 0,
75    5 },
76  { "BD85DBA2-8C29-49F9-84AE-48E1E90880E3", "news", "sports.yahoo.com",
77    "http://sports.yahoo.com/", "sports.yahoo.com", "0,1",
78    "Yahoo! Sports - Sports News, Scores, Rumors, Fantasy Games, and more",
79    "0,0,23,2,27,0", content::PAGE_TRANSITION_TYPED,
80    AutocompleteMatchType::HISTORY_TITLE, "", 2, 5 },
81  { "BD85DBA2-8C29-49F9-84AE-48E1E90880E4", "news weather",
82    "www.cnn.com/index.html", "http://www.cnn.com/index.html",
83    "www.cnn.com/index.html", "0,1",
84    "CNN.com - Breaking News, U.S., World, Weather, Entertainment & Video",
85    "0,0,19,2,23,0,38,2,45,0", content::PAGE_TRANSITION_TYPED,
86    AutocompleteMatchType::HISTORY_TITLE, "", 1, 10 },
87  { "BD85DBA2-8C29-49F9-84AE-48E1E90880E5", "nhl scores", "sports.yahoo.com",
88    "http://sports.yahoo.com/", "sports.yahoo.com", "0,1",
89    "Yahoo! Sports - Sports News, Scores, Rumors, Fantasy Games, and more",
90    "0,0,29,2,35,0", content::PAGE_TRANSITION_TYPED,
91    AutocompleteMatchType::HISTORY_BODY, "", 1, 10 },
92  { "BD85DBA2-8C29-49F9-84AE-48E1E90880E6", "nhl scores",
93    "www.nhl.com/scores/index.html", "http://www.nhl.com/scores/index.html",
94    "www.nhl.com/scores/index.html", "0,1,4,3,7,1",
95    "January 13, 2010 - NHL.com - Scores", "0,0,19,2,22,0,29,2,35,0",
96    content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL, "", 5,
97    1 },
98  { "BD85DBA2-8C29-49F9-84AE-48E1E90880E7", "just", "www.testsite.com/a.html",
99    "http://www.testsite.com/a.html", "www.testsite.com/a.html", "0,1",
100    "Test - site - just a test", "0,0,14,2,18,0",
101    content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_TITLE, "", 5,
102    1 },
103  { "BD85DBA2-8C29-49F9-84AE-48E1E90880E8", "just", "www.testsite.com/b.html",
104    "http://www.testsite.com/b.html", "www.testsite.com/b.html", "0,1",
105    "Test - site - just a test", "0,0,14,2,18,0",
106    content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_TITLE, "", 5,
107    2 },
108  { "BD85DBA2-8C29-49F9-84AE-48E1E90880E9", "just", "www.testsite.com/c.html",
109    "http://www.testsite.com/c.html", "www.testsite.com/c.html", "0,1",
110    "Test - site - just a test", "0,0,14,2,18,0",
111    content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_TITLE, "", 8,
112    1 },
113  { "BD85DBA2-8C29-49F9-84AE-48E1E90880EA", "just a", "www.testsite.com/d.html",
114    "http://www.testsite.com/d.html", "www.testsite.com/d.html", "0,1",
115    "Test - site - just a test", "0,0,14,2,18,0",
116    content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_TITLE, "",
117    12, 1 },
118  { "BD85DBA2-8C29-49F9-84AE-48E1E90880EB", "just a t",
119    "www.testsite.com/e.html", "http://www.testsite.com/e.html",
120    "www.testsite.com/e.html", "0,1", "Test - site - just a test",
121    "0,0,14,2,18,0", content::PAGE_TRANSITION_TYPED,
122    AutocompleteMatchType::HISTORY_TITLE, "", 12, 1 },
123  { "BD85DBA2-8C29-49F9-84AE-48E1E90880EC", "just a te",
124    "www.testsite.com/f.html", "http://www.testsite.com/f.html",
125    "www.testsite.com/f.html", "0,1", "Test - site - just a test",
126    "0,0,14,2,18,0", content::PAGE_TRANSITION_TYPED,
127    AutocompleteMatchType::HISTORY_TITLE, "", 12, 1 },
128  { "BD85DBA2-8C29-49F9-84AE-48E1E90880ED", "ago", "www.daysagotest.com/a.html",
129    "http://www.daysagotest.com/a.html", "www.daysagotest.com/a.html",
130    "0,1,8,3,11,1", "Test - site", "0,0", content::PAGE_TRANSITION_TYPED,
131    AutocompleteMatchType::HISTORY_URL, "", 1, 1 },
132  { "BD85DBA2-8C29-49F9-84AE-48E1E90880EE", "ago", "www.daysagotest.com/b.html",
133    "http://www.daysagotest.com/b.html", "www.daysagotest.com/b.html",
134    "0,1,8,3,11,1", "Test - site", "0,0", content::PAGE_TRANSITION_TYPED,
135    AutocompleteMatchType::HISTORY_URL, "", 2, 1 },
136  { "BD85DBA2-8C29-49F9-84AE-48E1E90880EF", "ago", "www.daysagotest.com/c.html",
137    "http://www.daysagotest.com/c.html", "www.daysagotest.com/c.html",
138    "0,1,8,3,11,1", "Test - site", "0,0", content::PAGE_TRANSITION_TYPED,
139    AutocompleteMatchType::HISTORY_URL, "", 3, 1 },
140  { "BD85DBA2-8C29-49F9-84AE-48E1E90880F0", "ago", "www.daysagotest.com/d.html",
141    "http://www.daysagotest.com/d.html", "www.daysagotest.com/d.html",
142    "0,1,8,3,11,1", "Test - site", "0,0", content::PAGE_TRANSITION_TYPED,
143    AutocompleteMatchType::HISTORY_URL, "", 4, 1 },
144  { "BD85DBA2-8C29-49F9-84AE-48E1E90880F1", "echo echo", "echo echo",
145    "chrome-extension://cedabbhfglmiikkmdgcpjdkocfcmbkee/?q=echo",
146    "Run Echo command: echo", "0,0", "Echo", "0,4",
147    content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::EXTENSION_APP,
148    "echo", 1, 1 },
149  { "BD85DBA2-8C29-49F9-84AE-48E1E90880F2", "abcdef.com", "http://abcdef.com",
150    "http://abcdef.com/", "Abcdef", "0,1,4,0", "Abcdef", "0,3,4,1",
151    content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL, "", 1,
152    100 },
153  { "BD85DBA2-8C29-49F9-84AE-48E1E90880F3", "query", "query",
154    "https://www.google.com/search?q=query", "query", "0,0",
155    "Google Search", "0,4", content::PAGE_TRANSITION_GENERATED,
156    AutocompleteMatchType::SEARCH_HISTORY, "", 1, 100 },
157  { "BD85DBA2-8C29-49F9-84AE-48E1E90880F4", "word", "www.word",
158    "https://www.google.com/search?q=www.word", "www.word", "0,0",
159    "Google Search", "0,4", content::PAGE_TRANSITION_GENERATED,
160    AutocompleteMatchType::SEARCH_HISTORY, "", 1, 100 },
161  { "BD85DBA2-8C29-49F9-84AE-48E1E90880F5", "about:o", "chrome://omnibox",
162    "chrome://omnibox/", "about:omnibox", "0,3,10,1", "", "",
163    content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::NAVSUGGEST, "",
164    1, 100 },
165  { "BD85DBA2-8C29-49F9-84AE-48E1E90880F6", "www/real sp",
166    "http://www/real space/long-url-with-space.html",
167    "http://www/real%20space/long-url-with-space.html",
168    "www/real space/long-url-with-space.html", "0,3,11,1",
169    "Page With Space; Input with Space", "0,0",
170    content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL, "",
171    1, 100 },
172  { "BD85DBA2-8C29-49F9-84AE-48E1E90880F7", "duplicate", "http://duplicate.com",
173    "http://duplicate.com/", "Duplicate", "0,1", "Duplicate", "0,1",
174    content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL, "", 1,
175    100 },
176  { "BD85DBA2-8C29-49F9-84AE-48E1E90880F8", "dupl", "http://duplicate.com",
177    "http://duplicate.com/", "Duplicate", "0,1", "Duplicate", "0,1",
178    content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL, "", 1,
179    100 },
180  { "BD85DBA2-8C29-49F9-84AE-48E1E90880F9", "notrailing.com/",
181    "http://notrailing.com", "http://notrailing.com/", "No Trailing Slash",
182    "0,1", "No Trailing Slash on fill_into_edit", "0,1",
183    content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL, "",
184    1, 100 },
185  { "BD85DBA2-8C29-49F9-84AE-48E1E90880FA", "http:///foo.com",
186    "http://foo.com", "http://foo.com/", "Foo - Typo in Input",
187    "0,1", "Foo - Typo in Input Corrected in fill_into_edit", "0,1",
188    content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL, "",
189    1, 100 },
190  { "BD85DBA2-8C29-49F9-84AE-48E1E90880FB", "trailing1 ",
191    "http://trailing1.com", "http://trailing1.com/",
192    "Trailing1 - Space in Shortcut", "0,1",
193    "Trailing1 - Space in Shortcut", "0,1", content::PAGE_TRANSITION_TYPED,
194    AutocompleteMatchType::HISTORY_URL, "", 1, 100 },
195  { "BD85DBA2-8C29-49F9-84AE-48E1E90880FC", "about:trailing2 ",
196    "chrome://trailing2blah", "chrome://trailing2blah/",
197    "Trailing2 - Space in Shortcut", "0,1",
198    "Trailing2 - Space in Shortcut", "0,1", content::PAGE_TRANSITION_TYPED,
199    AutocompleteMatchType::HISTORY_URL, "", 1, 100 },
200};
201
202}  // namespace
203
204
205// ClassifyTest ---------------------------------------------------------------
206
207// Helper class to make running tests of ClassifyAllMatchesInString() more
208// convenient.
209class ClassifyTest {
210 public:
211  ClassifyTest(const base::string16& text, ACMatchClassifications matches);
212  ~ClassifyTest();
213
214  ACMatchClassifications RunTest(const base::string16& find_text);
215
216 private:
217  const base::string16 text_;
218  const ACMatchClassifications matches_;
219};
220
221ClassifyTest::ClassifyTest(const base::string16& text,
222                           ACMatchClassifications matches)
223    : text_(text),
224      matches_(matches) {
225}
226
227ClassifyTest::~ClassifyTest() {
228}
229
230ACMatchClassifications ClassifyTest::RunTest(const base::string16& find_text) {
231  return ShortcutsProvider::ClassifyAllMatchesInString(find_text,
232      ShortcutsProvider::CreateWordMapForString(find_text), text_, matches_);
233}
234
235
236// ShortcutsProviderTest ------------------------------------------------------
237
238class ShortcutsProviderTest : public testing::Test,
239                              public AutocompleteProviderListener {
240 public:
241  ShortcutsProviderTest();
242
243  // AutocompleteProviderListener:
244  virtual void OnProviderUpdate(bool updated_matches) OVERRIDE;
245
246 protected:
247  typedef std::pair<std::string, bool> ExpectedURLAndAllowedToBeDefault;
248  typedef std::vector<ExpectedURLAndAllowedToBeDefault> ExpectedURLs;
249
250  class SetShouldContain
251      : public std::unary_function<const ExpectedURLAndAllowedToBeDefault&,
252                                   std::set<std::string> > {
253   public:
254    explicit SetShouldContain(const ACMatches& matched_urls);
255
256    void operator()(const ExpectedURLAndAllowedToBeDefault& expected);
257    std::set<ExpectedURLAndAllowedToBeDefault> Leftovers() const {
258        return matches_;
259    }
260
261   private:
262    std::set<ExpectedURLAndAllowedToBeDefault> matches_;
263  };
264
265  virtual void SetUp();
266  virtual void TearDown();
267
268  // Fills test data into the provider.
269  void FillData(TestShortcutInfo* db, size_t db_size);
270
271  // Runs an autocomplete query on |text| with the provided
272  // |prevent_inline_autocomplete| setting and checks to see that the returned
273  // results' destination URLs match those provided. |expected_urls| does not
274  // need to be in sorted order, but |expected_top_result| should be the top
275  // match, and it should have inline autocompletion
276  // |top_result_inline_autocompletion|.
277  void RunTest(const base::string16 text,
278               bool prevent_inline_autocomplete,
279               const ExpectedURLs& expected_urls,
280               std::string expected_top_result,
281               base::string16 top_result_inline_autocompletion);
282
283  // Passthrough to the private function in provider_.
284  int CalculateScore(const std::string& terms,
285                     const history::ShortcutsDatabase::Shortcut& shortcut,
286                     int max_relevance);
287
288  base::MessageLoopForUI message_loop_;
289  content::TestBrowserThread ui_thread_;
290  content::TestBrowserThread file_thread_;
291
292  TestingProfile profile_;
293
294  ACMatches ac_matches_;  // The resulting matches after running RunTest.
295
296  scoped_refptr<ShortcutsBackend> backend_;
297  scoped_refptr<ShortcutsProvider> provider_;
298};
299
300ShortcutsProviderTest::ShortcutsProviderTest()
301    : ui_thread_(content::BrowserThread::UI, &message_loop_),
302      file_thread_(content::BrowserThread::FILE, &message_loop_) {
303}
304
305void ShortcutsProviderTest::OnProviderUpdate(bool updated_matches) {}
306
307void ShortcutsProviderTest::SetUp() {
308  ShortcutsBackendFactory::GetInstance()->SetTestingFactoryAndUse(
309      &profile_, &ShortcutsBackendFactory::BuildProfileNoDatabaseForTesting);
310  backend_ = ShortcutsBackendFactory::GetForProfile(&profile_);
311  ASSERT_TRUE(backend_.get());
312  ASSERT_TRUE(profile_.CreateHistoryService(true, false));
313  provider_ = new ShortcutsProvider(this, &profile_);
314  FillData(shortcut_test_db, arraysize(shortcut_test_db));
315}
316
317void ShortcutsProviderTest::TearDown() {
318  // Run all pending tasks or else some threads hold on to the message loop
319  // and prevent it from being deleted.
320  message_loop_.RunUntilIdle();
321  provider_ = NULL;
322}
323
324void ShortcutsProviderTest::FillData(TestShortcutInfo* db, size_t db_size) {
325  DCHECK(provider_.get());
326  size_t expected_size = backend_->shortcuts_map().size() + db_size;
327  for (size_t i = 0; i < db_size; ++i) {
328    const TestShortcutInfo& cur = db[i];
329    history::ShortcutsDatabase::Shortcut shortcut(
330        cur.guid, ASCIIToUTF16(cur.text),
331        history::ShortcutsDatabase::Shortcut::MatchCore(
332            ASCIIToUTF16(cur.fill_into_edit), GURL(cur.destination_url),
333            ASCIIToUTF16(cur.contents), cur.contents_class,
334            ASCIIToUTF16(cur.description), cur.description_class,
335            cur.transition, cur.type, ASCIIToUTF16(cur.keyword)),
336        base::Time::Now() - base::TimeDelta::FromDays(cur.days_from_now),
337        cur.number_of_hits);
338    backend_->AddShortcut(shortcut);
339  }
340  EXPECT_EQ(expected_size, backend_->shortcuts_map().size());
341}
342
343ShortcutsProviderTest::SetShouldContain::SetShouldContain(
344    const ACMatches& matched_urls) {
345  for (ACMatches::const_iterator iter = matched_urls.begin();
346       iter != matched_urls.end(); ++iter)
347    matches_.insert(ExpectedURLAndAllowedToBeDefault(
348        iter->destination_url.spec(), iter->allowed_to_be_default_match));
349}
350
351void ShortcutsProviderTest::SetShouldContain::operator()(
352    const ExpectedURLAndAllowedToBeDefault& expected) {
353  EXPECT_EQ(1U, matches_.erase(expected));
354}
355
356void ShortcutsProviderTest::RunTest(
357    const base::string16 text,
358    bool prevent_inline_autocomplete,
359    const ExpectedURLs& expected_urls,
360    std::string expected_top_result,
361    base::string16 top_result_inline_autocompletion) {
362  base::MessageLoop::current()->RunUntilIdle();
363  AutocompleteInput input(text, base::string16::npos, base::string16(), GURL(),
364                          metrics::OmniboxEventProto::INVALID_SPEC,
365                          prevent_inline_autocomplete, false, true, true);
366  provider_->Start(input, false);
367  EXPECT_TRUE(provider_->done());
368
369  ac_matches_ = provider_->matches();
370
371  // We should have gotten back at most AutocompleteProvider::kMaxMatches.
372  EXPECT_LE(ac_matches_.size(), AutocompleteProvider::kMaxMatches);
373
374  // If the number of expected and actual matches aren't equal then we need
375  // test no further, but let's do anyway so that we know which URLs failed.
376  EXPECT_EQ(expected_urls.size(), ac_matches_.size());
377
378  // Verify that all expected URLs were found and that all found URLs
379  // were expected.
380  std::set<ExpectedURLAndAllowedToBeDefault> Leftovers =
381      for_each(expected_urls.begin(), expected_urls.end(),
382               SetShouldContain(ac_matches_)).Leftovers();
383  EXPECT_EQ(0U, Leftovers.size());
384
385  // See if we got the expected top scorer.
386  if (!ac_matches_.empty()) {
387    std::partial_sort(ac_matches_.begin(), ac_matches_.begin() + 1,
388                      ac_matches_.end(), AutocompleteMatch::MoreRelevant);
389    EXPECT_EQ(expected_top_result, ac_matches_[0].destination_url.spec());
390    EXPECT_EQ(top_result_inline_autocompletion,
391              ac_matches_[0].inline_autocompletion);
392  }
393}
394
395int ShortcutsProviderTest::CalculateScore(
396    const std::string& terms,
397    const history::ShortcutsDatabase::Shortcut& shortcut,
398    int max_relevance) {
399  return provider_->CalculateScore(ASCIIToUTF16(terms), shortcut,
400                                   max_relevance);
401}
402
403
404// Actual tests ---------------------------------------------------------------
405
406TEST_F(ShortcutsProviderTest, SimpleSingleMatch) {
407  base::string16 text(ASCIIToUTF16("go"));
408  std::string expected_url("http://www.google.com/");
409  ExpectedURLs expected_urls;
410  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(expected_url, true));
411  RunTest(text, false, expected_urls, expected_url, ASCIIToUTF16("ogle.com"));
412
413  // Same test with prevent inline autocomplete.
414  expected_urls.clear();
415  expected_urls.push_back(
416      ExpectedURLAndAllowedToBeDefault(expected_url, false));
417  // The match will have an |inline_autocompletion| set, but the value will not
418  // be used because |allowed_to_be_default_match| will be false.
419  RunTest(text, true, expected_urls, expected_url, ASCIIToUTF16("ogle.com"));
420
421  // A pair of analogous tests where the shortcut ends at the end of
422  // |fill_into_edit|.  This exercises the inline autocompletion and default
423  // match code.
424  text = ASCIIToUTF16("abcdef.com");
425  expected_url = "http://abcdef.com/";
426  expected_urls.clear();
427  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(expected_url, true));
428  RunTest(text, false, expected_urls, expected_url, base::string16());
429  // With prevent inline autocomplete, the suggestion should be the same
430  // (because there is no completion).
431  RunTest(text, true, expected_urls, expected_url, base::string16());
432
433  // Another test, simply for a query match type, not a navigation URL match
434  // type.
435  text = ASCIIToUTF16("que");
436  expected_url = "https://www.google.com/search?q=query";
437  expected_urls.clear();
438  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(expected_url, true));
439  RunTest(text, false, expected_urls, expected_url, ASCIIToUTF16("ry"));
440
441  // Same test with prevent inline autocomplete.
442  expected_urls.clear();
443  expected_urls.push_back(
444      ExpectedURLAndAllowedToBeDefault(expected_url, false));
445  // The match will have an |inline_autocompletion| set, but the value will not
446  // be used because |allowed_to_be_default_match| will be false.
447  RunTest(text, true, expected_urls, expected_url, ASCIIToUTF16("ry"));
448
449  // A pair of analogous tests where the shortcut ends at the end of
450  // |fill_into_edit|.  This exercises the inline autocompletion and default
451  // match code.
452  text = ASCIIToUTF16("query");
453  expected_urls.clear();
454  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(expected_url, true));
455  RunTest(text, false, expected_urls, expected_url, base::string16());
456  // With prevent inline autocomplete, the suggestion should be the same
457  // (because there is no completion).
458  RunTest(text, true, expected_urls, expected_url, base::string16());
459
460  // Now the shortcut ends at the end of |fill_into_edit| but has a
461  // non-droppable prefix.  ("www.", for instance, is not droppable for
462  // queries.)
463  text = ASCIIToUTF16("word");
464  expected_url = "https://www.google.com/search?q=www.word";
465  expected_urls.clear();
466  expected_urls.push_back(
467      ExpectedURLAndAllowedToBeDefault(expected_url, false));
468  RunTest(text, false, expected_urls, expected_url, base::string16());
469}
470
471// These tests are like those in SimpleSingleMatch but more complex,
472// involving URLs that need to be fixed up to match properly.
473TEST_F(ShortcutsProviderTest, TrickySingleMatch) {
474  // Test that about: URLs are fixed up/transformed to chrome:// URLs.
475  base::string16 text(ASCIIToUTF16("about:o"));
476  std::string expected_url("chrome://omnibox/");
477  ExpectedURLs expected_urls;
478  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(expected_url, true));
479  RunTest(text, false, expected_urls, expected_url, ASCIIToUTF16("mnibox"));
480
481  // Same test with prevent inline autocomplete.
482  expected_urls.clear();
483  expected_urls.push_back(
484      ExpectedURLAndAllowedToBeDefault(expected_url, false));
485  // The match will have an |inline_autocompletion| set, but the value will not
486  // be used because |allowed_to_be_default_match| will be false.
487  RunTest(text, true, expected_urls, expected_url, ASCIIToUTF16("mnibox"));
488
489  // Test that an input with a space can match URLs with a (escaped) space.
490  // This would fail if we didn't try to lookup the un-fixed-up string.
491  text = ASCIIToUTF16("www/real sp");
492  expected_url = "http://www/real%20space/long-url-with-space.html";
493  expected_urls.clear();
494  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(expected_url, true));
495  RunTest(text, false, expected_urls, expected_url,
496          ASCIIToUTF16("ace/long-url-with-space.html"));
497
498  // Same test with prevent inline autocomplete.
499  expected_urls.clear();
500  expected_urls.push_back(
501      ExpectedURLAndAllowedToBeDefault(expected_url, false));
502  // The match will have an |inline_autocompletion| set, but the value will not
503  // be used because |allowed_to_be_default_match| will be false.
504  RunTest(text, true, expected_urls, expected_url,
505          ASCIIToUTF16("ace/long-url-with-space.html"));
506
507  // Test when the user input has a trailing slash but fill_into_edit does
508  // not.  This should still be allowed to be default.
509  text = ASCIIToUTF16("notrailing.com/");
510  expected_url = "http://notrailing.com/";
511  expected_urls.clear();
512  expected_urls.push_back(
513      ExpectedURLAndAllowedToBeDefault(expected_url, true));
514  RunTest(text, true, expected_urls, expected_url, base::string16());
515
516  // Test when the user input has a typo that can be fixed up for matching
517  // fill_into_edit.  This should still be allowed to be default.
518  text = ASCIIToUTF16("http:///foo.com");
519  expected_url = "http://foo.com/";
520  expected_urls.clear();
521  expected_urls.push_back(
522      ExpectedURLAndAllowedToBeDefault(expected_url, true));
523  RunTest(text, true, expected_urls, expected_url, base::string16());
524
525  // A foursome of tests to verify that trailing spaces prevent the shortcut
526  // from being allowed to be the default match.  For each of two tests, we
527  // first verify that the match is allowed to be default without the trailing
528  // space but is not allowed to be default with the trailing space.  In both
529  // of these with-trailing-space cases, we actually get an
530  // inline_autocompletion, though it's never used because the match is
531  // prohibited from being default.
532  text = ASCIIToUTF16("trailing1");
533  expected_url = "http://trailing1.com/";
534  expected_urls.clear();
535  expected_urls.push_back(
536      ExpectedURLAndAllowedToBeDefault(expected_url, true));
537  RunTest(text, false, expected_urls, expected_url, ASCIIToUTF16(".com"));
538  text = ASCIIToUTF16("trailing1 ");
539  expected_urls.clear();
540  expected_urls.push_back(
541      ExpectedURLAndAllowedToBeDefault(expected_url, false));
542  RunTest(text, false, expected_urls, expected_url, ASCIIToUTF16(".com"));
543  text = ASCIIToUTF16("about:trailing2");
544  expected_url = "chrome://trailing2blah/";
545  expected_urls.clear();
546  expected_urls.push_back(
547      ExpectedURLAndAllowedToBeDefault(expected_url, true));
548  RunTest(text, false, expected_urls, expected_url, ASCIIToUTF16("blah"));
549  text = ASCIIToUTF16("about:trailing2 ");
550  expected_urls.clear();
551  expected_urls.push_back(
552      ExpectedURLAndAllowedToBeDefault(expected_url, false));
553  RunTest(text, false, expected_urls, expected_url, ASCIIToUTF16("blah"));
554}
555
556TEST_F(ShortcutsProviderTest, MultiMatch) {
557  base::string16 text(ASCIIToUTF16("NEWS"));
558  ExpectedURLs expected_urls;
559  // Scores high because of completion length.
560  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
561      "http://slashdot.org/", false));
562  // Scores high because of visit count.
563  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
564      "http://sports.yahoo.com/", false));
565  // Scores high because of visit count but less match span,
566  // which is more important.
567  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
568      "http://www.cnn.com/index.html", false));
569  RunTest(text, false, expected_urls, "http://slashdot.org/", base::string16());
570}
571
572TEST_F(ShortcutsProviderTest, RemoveDuplicates) {
573  base::string16 text(ASCIIToUTF16("dupl"));
574  ExpectedURLs expected_urls;
575  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
576      "http://duplicate.com/", true));
577  // Make sure the URL only appears once in the output list.
578  RunTest(text, false, expected_urls, "http://duplicate.com/",
579          ASCIIToUTF16("icate.com"));
580}
581
582TEST_F(ShortcutsProviderTest, TypedCountMatches) {
583  base::string16 text(ASCIIToUTF16("just"));
584  ExpectedURLs expected_urls;
585  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
586      "http://www.testsite.com/b.html", false));
587  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
588      "http://www.testsite.com/a.html", false));
589  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
590      "http://www.testsite.com/c.html", false));
591  RunTest(text, false, expected_urls, "http://www.testsite.com/b.html",
592          base::string16());
593}
594
595TEST_F(ShortcutsProviderTest, FragmentLengthMatches) {
596  base::string16 text(ASCIIToUTF16("just a"));
597  ExpectedURLs expected_urls;
598  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
599      "http://www.testsite.com/d.html", false));
600  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
601      "http://www.testsite.com/e.html", false));
602  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
603      "http://www.testsite.com/f.html", false));
604  RunTest(text, false, expected_urls, "http://www.testsite.com/d.html",
605          base::string16());
606}
607
608TEST_F(ShortcutsProviderTest, DaysAgoMatches) {
609  base::string16 text(ASCIIToUTF16("ago"));
610  ExpectedURLs expected_urls;
611  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
612      "http://www.daysagotest.com/a.html", false));
613  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
614      "http://www.daysagotest.com/b.html", false));
615  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
616      "http://www.daysagotest.com/c.html", false));
617  RunTest(text, false, expected_urls, "http://www.daysagotest.com/a.html",
618          base::string16());
619}
620
621TEST_F(ShortcutsProviderTest, ClassifyAllMatchesInString) {
622  ACMatchClassifications matches =
623      AutocompleteMatch::ClassificationsFromString("0,0");
624  ClassifyTest classify_test(ASCIIToUTF16("A man, a plan, a canal Panama"),
625                             matches);
626
627  ACMatchClassifications spans_a = classify_test.RunTest(ASCIIToUTF16("man"));
628  // ACMatch spans should be: '--MMM------------------------'
629  EXPECT_EQ("0,0,2,2,5,0", AutocompleteMatch::ClassificationsToString(spans_a));
630
631  ACMatchClassifications spans_b = classify_test.RunTest(ASCIIToUTF16("man p"));
632  // ACMatch spans should be: '--MMM----M-------------M-----'
633  EXPECT_EQ("0,0,2,2,5,0,9,2,10,0,23,2,24,0",
634            AutocompleteMatch::ClassificationsToString(spans_b));
635
636  ACMatchClassifications spans_c =
637      classify_test.RunTest(ASCIIToUTF16("man plan panama"));
638  // ACMatch spans should be:'--MMM----MMMM----------MMMMMM'
639  EXPECT_EQ("0,0,2,2,5,0,9,2,13,0,23,2",
640            AutocompleteMatch::ClassificationsToString(spans_c));
641
642  ClassifyTest classify_test2(ASCIIToUTF16("Yahoo! Sports - Sports News, "
643      "Scores, Rumors, Fantasy Games, and more"), matches);
644
645  ACMatchClassifications spans_d = classify_test2.RunTest(ASCIIToUTF16("ne"));
646  // ACMatch spans should match first two letters of the "news".
647  EXPECT_EQ("0,0,23,2,25,0",
648            AutocompleteMatch::ClassificationsToString(spans_d));
649
650  ACMatchClassifications spans_e =
651      classify_test2.RunTest(ASCIIToUTF16("news r"));
652  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,"
653            "66,2,67,0", AutocompleteMatch::ClassificationsToString(spans_e));
654
655  matches = AutocompleteMatch::ClassificationsFromString("0,1");
656  ClassifyTest classify_test3(ASCIIToUTF16("livescore.goal.com"), matches);
657
658  ACMatchClassifications spans_f = classify_test3.RunTest(ASCIIToUTF16("go"));
659  // ACMatch spans should match first two letters of the "goal".
660  EXPECT_EQ("0,1,10,3,12,1",
661            AutocompleteMatch::ClassificationsToString(spans_f));
662
663  matches = AutocompleteMatch::ClassificationsFromString("0,0,13,1");
664  ClassifyTest classify_test4(ASCIIToUTF16("Email login: mail.somecorp.com"),
665                              matches);
666
667  ACMatchClassifications spans_g = classify_test4.RunTest(ASCIIToUTF16("ail"));
668  EXPECT_EQ("0,0,2,2,5,0,13,1,14,3,17,1",
669            AutocompleteMatch::ClassificationsToString(spans_g));
670
671  ACMatchClassifications spans_h =
672      classify_test4.RunTest(ASCIIToUTF16("lo log"));
673  EXPECT_EQ("0,0,6,2,9,0,13,1",
674            AutocompleteMatch::ClassificationsToString(spans_h));
675
676  ACMatchClassifications spans_i =
677      classify_test4.RunTest(ASCIIToUTF16("ail em"));
678  // 'Email' and 'ail' should be matched.
679  EXPECT_EQ("0,2,5,0,13,1,14,3,17,1",
680            AutocompleteMatch::ClassificationsToString(spans_i));
681
682  // Some web sites do not have a description.  If the string being searched is
683  // empty, the classifications must also be empty: http://crbug.com/148647
684  // Extra parens in the next line hack around C++03's "most vexing parse".
685  class ClassifyTest classify_test5((base::string16()),
686                                    ACMatchClassifications());
687  ACMatchClassifications spans_j = classify_test5.RunTest(ASCIIToUTF16("man"));
688  ASSERT_EQ(0U, spans_j.size());
689
690  // Matches which end at beginning of classification merge properly.
691  matches = AutocompleteMatch::ClassificationsFromString("0,4,9,0");
692  ClassifyTest classify_test6(ASCIIToUTF16("html password example"), matches);
693
694  // Extra space in the next string avoids having the string be a prefix of the
695  // text above, which would allow for two different valid classification sets,
696  // one of which uses two spans (the first of which would mark all of "html
697  // pass" as a match) and one which uses four (which marks the individual words
698  // as matches but not the space between them).  This way only the latter is
699  // valid.
700  ACMatchClassifications spans_k =
701      classify_test6.RunTest(ASCIIToUTF16("html  pass"));
702  EXPECT_EQ("0,6,4,4,5,6,9,0",
703            AutocompleteMatch::ClassificationsToString(spans_k));
704
705  // Multiple matches with both beginning and end at beginning of
706  // classifications merge properly.
707  matches = AutocompleteMatch::ClassificationsFromString("0,1,11,0");
708  ClassifyTest classify_test7(ASCIIToUTF16("http://a.co is great"), matches);
709
710  ACMatchClassifications spans_l =
711      classify_test7.RunTest(ASCIIToUTF16("ht co"));
712  EXPECT_EQ("0,3,2,1,9,3,11,0",
713            AutocompleteMatch::ClassificationsToString(spans_l));
714}
715
716TEST_F(ShortcutsProviderTest, CalculateScore) {
717  history::ShortcutsDatabase::Shortcut shortcut(
718      std::string(), ASCIIToUTF16("test"),
719      history::ShortcutsDatabase::Shortcut::MatchCore(
720          ASCIIToUTF16("www.test.com"), GURL("http://www.test.com"),
721          ASCIIToUTF16("www.test.com"), "0,1,4,3,8,1",
722          ASCIIToUTF16("A test"), "0,0,2,2", content::PAGE_TRANSITION_TYPED,
723          AutocompleteMatchType::HISTORY_URL, base::string16()),
724      base::Time::Now(), 1);
725
726  // Maximal score.
727  const int max_relevance =
728      ShortcutsProvider::kShortcutsProviderDefaultMaxRelevance;
729  const int kMaxScore = CalculateScore("test", shortcut, max_relevance);
730
731  // Score decreases as percent of the match is decreased.
732  int score_three_quarters = CalculateScore("tes", shortcut, max_relevance);
733  EXPECT_LT(score_three_quarters, kMaxScore);
734  int score_one_half = CalculateScore("te", shortcut, max_relevance);
735  EXPECT_LT(score_one_half, score_three_quarters);
736  int score_one_quarter = CalculateScore("t", shortcut, max_relevance);
737  EXPECT_LT(score_one_quarter, score_one_half);
738
739  // Should decay with time - one week.
740  shortcut.last_access_time = base::Time::Now() - base::TimeDelta::FromDays(7);
741  int score_week_old = CalculateScore("test", shortcut, max_relevance);
742  EXPECT_LT(score_week_old, kMaxScore);
743
744  // Should decay more in two weeks.
745  shortcut.last_access_time = base::Time::Now() - base::TimeDelta::FromDays(14);
746  int score_two_weeks_old = CalculateScore("test", shortcut, max_relevance);
747  EXPECT_LT(score_two_weeks_old, score_week_old);
748
749  // But not if it was activly clicked on. 2 hits slow decaying power.
750  shortcut.number_of_hits = 2;
751  shortcut.last_access_time = base::Time::Now() - base::TimeDelta::FromDays(14);
752  int score_popular_two_weeks_old =
753      CalculateScore("test", shortcut, max_relevance);
754  EXPECT_LT(score_two_weeks_old, score_popular_two_weeks_old);
755  // But still decayed.
756  EXPECT_LT(score_popular_two_weeks_old, kMaxScore);
757
758  // 3 hits slow decaying power even more.
759  shortcut.number_of_hits = 3;
760  shortcut.last_access_time = base::Time::Now() - base::TimeDelta::FromDays(14);
761  int score_more_popular_two_weeks_old =
762      CalculateScore("test", shortcut, max_relevance);
763  EXPECT_LT(score_two_weeks_old, score_more_popular_two_weeks_old);
764  EXPECT_LT(score_popular_two_weeks_old, score_more_popular_two_weeks_old);
765  // But still decayed.
766  EXPECT_LT(score_more_popular_two_weeks_old, kMaxScore);
767}
768
769TEST_F(ShortcutsProviderTest, DeleteMatch) {
770  TestShortcutInfo shortcuts_to_test_delete[] = {
771    { "BD85DBA2-8C29-49F9-84AE-48E1E90881F1", "delete", "www.deletetest.com/1",
772      "http://www.deletetest.com/1", "http://www.deletetest.com/1", "0,2",
773      "Erase this shortcut!", "0,0", content::PAGE_TRANSITION_TYPED,
774      AutocompleteMatchType::HISTORY_URL, "", 1, 1},
775    { "BD85DBA2-8C29-49F9-84AE-48E1E90881F2", "erase", "www.deletetest.com/1",
776      "http://www.deletetest.com/1", "http://www.deletetest.com/1", "0,2",
777      "Erase this shortcut!", "0,0", content::PAGE_TRANSITION_TYPED,
778      AutocompleteMatchType::HISTORY_TITLE, "", 1, 1},
779    { "BD85DBA2-8C29-49F9-84AE-48E1E90881F3", "keep", "www.deletetest.com/1/2",
780      "http://www.deletetest.com/1/2", "http://www.deletetest.com/1/2", "0,2",
781      "Keep this shortcut!", "0,0", content::PAGE_TRANSITION_TYPED,
782      AutocompleteMatchType::HISTORY_TITLE, "", 1, 1},
783    { "BD85DBA2-8C29-49F9-84AE-48E1E90881F4", "delete", "www.deletetest.com/2",
784      "http://www.deletetest.com/2", "http://www.deletetest.com/2", "0,2",
785      "Erase this shortcut!", "0,0", content::PAGE_TRANSITION_TYPED,
786      AutocompleteMatchType::HISTORY_URL, "", 1, 1},
787  };
788
789  size_t original_shortcuts_count = backend_->shortcuts_map().size();
790
791  FillData(shortcuts_to_test_delete, arraysize(shortcuts_to_test_delete));
792
793  EXPECT_EQ(original_shortcuts_count + 4, backend_->shortcuts_map().size());
794  EXPECT_FALSE(backend_->shortcuts_map().end() ==
795               backend_->shortcuts_map().find(ASCIIToUTF16("delete")));
796  EXPECT_FALSE(backend_->shortcuts_map().end() ==
797               backend_->shortcuts_map().find(ASCIIToUTF16("erase")));
798
799  AutocompleteMatch match(
800      provider_.get(), 1200, true, AutocompleteMatchType::HISTORY_TITLE);
801
802  match.destination_url = GURL(shortcuts_to_test_delete[0].destination_url);
803  match.contents = ASCIIToUTF16(shortcuts_to_test_delete[0].contents);
804  match.description = ASCIIToUTF16(shortcuts_to_test_delete[0].description);
805
806  provider_->DeleteMatch(match);
807
808  // shortcuts_to_test_delete[0] and shortcuts_to_test_delete[1] should be
809  // deleted, but not shortcuts_to_test_delete[2] or
810  // shortcuts_to_test_delete[3], which have different URLs.
811  EXPECT_EQ(original_shortcuts_count + 2, backend_->shortcuts_map().size());
812  EXPECT_FALSE(backend_->shortcuts_map().end() ==
813               backend_->shortcuts_map().find(ASCIIToUTF16("delete")));
814  EXPECT_TRUE(backend_->shortcuts_map().end() ==
815              backend_->shortcuts_map().find(ASCIIToUTF16("erase")));
816
817  match.destination_url = GURL(shortcuts_to_test_delete[3].destination_url);
818  match.contents = ASCIIToUTF16(shortcuts_to_test_delete[3].contents);
819  match.description = ASCIIToUTF16(shortcuts_to_test_delete[3].description);
820
821  provider_->DeleteMatch(match);
822  EXPECT_EQ(original_shortcuts_count + 1, backend_->shortcuts_map().size());
823  EXPECT_TRUE(backend_->shortcuts_map().end() ==
824              backend_->shortcuts_map().find(ASCIIToUTF16("delete")));
825}
826
827TEST_F(ShortcutsProviderTest, Extension) {
828  // Try an input string that matches an extension URL.
829  base::string16 text(ASCIIToUTF16("echo"));
830  std::string expected_url(
831      "chrome-extension://cedabbhfglmiikkmdgcpjdkocfcmbkee/?q=echo");
832  ExpectedURLs expected_urls;
833  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
834      expected_url, true));
835  RunTest(text, false, expected_urls, expected_url, ASCIIToUTF16(" echo"));
836
837  // Claim the extension has been unloaded.
838  scoped_refptr<const extensions::Extension> extension =
839      extensions::ExtensionBuilder()
840          .SetManifest(extensions::DictionaryBuilder()
841              .Set("name", "Echo")
842              .Set("version", "1.0"))
843          .SetID("cedabbhfglmiikkmdgcpjdkocfcmbkee")
844          .Build();
845  extensions::UnloadedExtensionInfo details(
846      extension.get(), extensions::UnloadedExtensionInfo::REASON_UNINSTALL);
847  content::NotificationService::current()->Notify(
848      chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED,
849      content::Source<Profile>(&profile_),
850      content::Details<extensions::UnloadedExtensionInfo>(&details));
851
852  // Now the URL should have disappeared.
853  RunTest(text, false, ExpectedURLs(), std::string(), base::string16());
854}
855