keyword_provider_unittest.cc revision ddb351dbec246cf1fab5ec20d2d5520909041de1
1// Copyright (c) 2011 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 "base/message_loop.h"
6#include "base/utf_string_conversions.h"
7#include "chrome/browser/autocomplete/autocomplete_match.h"
8#include "chrome/browser/autocomplete/keyword_provider.h"
9#include "chrome/browser/search_engines/template_url.h"
10#include "chrome/browser/search_engines/template_url_model.h"
11#include "chrome/test/testing_browser_process.h"
12#include "chrome/test/testing_browser_process_test.h"
13#include "googleurl/src/gurl.h"
14#include "testing/gtest/include/gtest/gtest.h"
15
16class KeywordProviderTest : public TestingBrowserProcessTest {
17 protected:
18  template<class ResultType>
19  struct test_data {
20    const string16 input;
21    const size_t num_results;
22    const ResultType output[3];
23  };
24
25  KeywordProviderTest() : kw_provider_(NULL) { }
26  virtual ~KeywordProviderTest() { }
27
28  virtual void SetUp();
29  virtual void TearDown();
30
31  template<class ResultType>
32  void RunTest(test_data<ResultType>* keyword_cases,
33               int num_cases,
34               ResultType AutocompleteMatch::* member);
35
36 protected:
37  scoped_refptr<KeywordProvider> kw_provider_;
38  scoped_ptr<TemplateURLModel> model_;
39};
40
41void KeywordProviderTest::SetUp() {
42  static const TemplateURLModel::Initializer kTestKeywordData[] = {
43    { "aa", "aa.com?foo=%s", "aa" },
44    { "aaaa", "http://aaaa/?aaaa=1&b=%s&c", "aaaa" },
45    { "aaaaa", "%s", "aaaaa" },
46    { "ab", "bogus URL %s", "ab" },
47    { "weasel", "weasel%sweasel", "weasel" },
48    { "www", " +%2B?=%sfoo ", "www" },
49    { "z", "%s=z", "z" },
50  };
51
52  model_.reset(new TemplateURLModel(kTestKeywordData,
53                                    arraysize(kTestKeywordData)));
54  kw_provider_ = new KeywordProvider(NULL, model_.get());
55}
56
57void KeywordProviderTest::TearDown() {
58  model_.reset();
59  kw_provider_ = NULL;
60}
61
62template<class ResultType>
63void KeywordProviderTest::RunTest(
64    test_data<ResultType>* keyword_cases,
65    int num_cases,
66    ResultType AutocompleteMatch::* member) {
67  ACMatches matches;
68  for (int i = 0; i < num_cases; ++i) {
69    AutocompleteInput input(keyword_cases[i].input, string16(), true,
70                            false, true, AutocompleteInput::ALL_MATCHES);
71    kw_provider_->Start(input, false);
72    EXPECT_TRUE(kw_provider_->done());
73    matches = kw_provider_->matches();
74    EXPECT_EQ(keyword_cases[i].num_results, matches.size()) <<
75                ASCIIToUTF16("Input was: ") + keyword_cases[i].input;
76    if (matches.size() == keyword_cases[i].num_results) {
77      for (size_t j = 0; j < keyword_cases[i].num_results; ++j) {
78        EXPECT_EQ(keyword_cases[i].output[j], matches[j].*member);
79      }
80    }
81  }
82}
83
84TEST_F(KeywordProviderTest, Edit) {
85  test_data<string16> edit_cases[] = {
86    // Searching for a nonexistent prefix should give nothing.
87    {ASCIIToUTF16("Not Found"),       0, {}},
88    {ASCIIToUTF16("aaaaaNot Found"),  0, {}},
89
90    // Check that tokenization only collapses whitespace between first tokens,
91    // no-query-input cases have a space appended, and action is not escaped.
92    {ASCIIToUTF16("z foo"),           1, {ASCIIToUTF16("z foo")}},
93    {ASCIIToUTF16("z"),               1, {ASCIIToUTF16("z ")}},
94    {ASCIIToUTF16("z    \t"),         1, {ASCIIToUTF16("z ")}},
95    {ASCIIToUTF16("z   a   b   c++"), 1, {ASCIIToUTF16("z a   b   c++")}},
96
97    // Matches should be limited to three, and sorted in quality order, not
98    // alphabetical.
99    {ASCIIToUTF16("aaa"),             2, {ASCIIToUTF16("aaaa "),
100                                          ASCIIToUTF16("aaaaa ")}},
101    {ASCIIToUTF16("a 1 2 3"),         3, {ASCIIToUTF16("aa 1 2 3"),
102                                          ASCIIToUTF16("ab 1 2 3"),
103                                          ASCIIToUTF16("aaaa 1 2 3")}},
104    {ASCIIToUTF16("www.a"),           3, {ASCIIToUTF16("aa "),
105                                          ASCIIToUTF16("ab "),
106                                          ASCIIToUTF16("aaaa ")}},
107    // Exact matches should prevent returning inexact matches.
108    {ASCIIToUTF16("aaaa foo"),        1, {ASCIIToUTF16("aaaa foo")}},
109    {ASCIIToUTF16("www.aaaa foo"),    1, {ASCIIToUTF16("aaaa foo")}},
110
111    // Clean up keyword input properly.  "http" and "https" are the only
112    // allowed schemes.
113    {ASCIIToUTF16("www"),             1, {ASCIIToUTF16("www ")}},
114    {ASCIIToUTF16("www."),            0, {}},
115    {ASCIIToUTF16("www.w w"),         2, {ASCIIToUTF16("www w"),
116                                          ASCIIToUTF16("weasel w")}},
117    {ASCIIToUTF16("http://www"),      1, {ASCIIToUTF16("www ")}},
118    {ASCIIToUTF16("http://www."),     0, {}},
119    {ASCIIToUTF16("ftp: blah"),       0, {}},
120    {ASCIIToUTF16("mailto:z"),        0, {}},
121    {ASCIIToUTF16("ftp://z"),         0, {}},
122    {ASCIIToUTF16("https://z"),       1, {ASCIIToUTF16("z ")}},
123  };
124
125  RunTest<string16>(edit_cases, arraysize(edit_cases),
126                    &AutocompleteMatch::fill_into_edit);
127}
128
129TEST_F(KeywordProviderTest, URL) {
130  test_data<GURL> url_cases[] = {
131    // No query input -> empty destination URL.
132    {ASCIIToUTF16("z"),               1, {GURL()}},
133    {ASCIIToUTF16("z    \t"),         1, {GURL()}},
134
135    // Check that tokenization only collapses whitespace between first tokens
136    // and query input, but not rest of URL, is escaped.
137    {ASCIIToUTF16("z   a   b   c++"), 1, {GURL("a+++b+++c%2B%2B=z")}},
138    {ASCIIToUTF16("www.www www"),     1, {GURL(" +%2B?=wwwfoo ")}},
139
140    // Substitution should work with various locations of the "%s".
141    {ASCIIToUTF16("aaa 1a2b"),        2, {GURL("http://aaaa/?aaaa=1&b=1a2b&c"),
142                                          GURL("1a2b")}},
143    {ASCIIToUTF16("a 1 2 3"),         3, {GURL("aa.com?foo=1+2+3"),
144                                          GURL("bogus URL 1+2+3"),
145                                        GURL("http://aaaa/?aaaa=1&b=1+2+3&c")}},
146    {ASCIIToUTF16("www.w w"),         2, {GURL(" +%2B?=wfoo "),
147                                          GURL("weaselwweasel")}},
148  };
149
150  RunTest<GURL>(url_cases, arraysize(url_cases),
151                &AutocompleteMatch::destination_url);
152}
153
154TEST_F(KeywordProviderTest, Contents) {
155  test_data<string16> contents_cases[] = {
156    // No query input -> substitute "<enter query>" into contents.
157    {ASCIIToUTF16("z"),               1,
158        {ASCIIToUTF16("Search z for <enter query>")}},
159    {ASCIIToUTF16("z    \t"),         1,
160        {ASCIIToUTF16("Search z for <enter query>")}},
161
162    // Check that tokenization only collapses whitespace between first tokens
163    // and contents are not escaped or unescaped.
164    {ASCIIToUTF16("z   a   b   c++"), 1,
165        {ASCIIToUTF16("Search z for a   b   c++")}},
166    {ASCIIToUTF16("www.www www"),     1, {ASCIIToUTF16("Search www for www")}},
167
168    // Substitution should work with various locations of the "%s".
169    {ASCIIToUTF16("aaa"),             2,
170        {ASCIIToUTF16("Search aaaa for <enter query>"),
171         ASCIIToUTF16("Search aaaaa for <enter query>")}},
172    {ASCIIToUTF16("a 1 2 3"),         3, {ASCIIToUTF16("Search aa for 1 2 3"),
173                                          ASCIIToUTF16("Search ab for 1 2 3"),
174                                        ASCIIToUTF16("Search aaaa for 1 2 3")}},
175    {ASCIIToUTF16("www.w w"),         2, {ASCIIToUTF16("Search www for w"),
176        ASCIIToUTF16("Search weasel for w")}},
177  };
178
179  RunTest<string16>(contents_cases, arraysize(contents_cases),
180                        &AutocompleteMatch::contents);
181}
182
183TEST_F(KeywordProviderTest, Description) {
184  test_data<string16> description_cases[] = {
185    // Whole keyword should be returned for both exact and inexact matches.
186    {ASCIIToUTF16("z foo"),           1, {ASCIIToUTF16("(Keyword: z)")}},
187    {ASCIIToUTF16("a foo"),           3, {ASCIIToUTF16("(Keyword: aa)"),
188                                          ASCIIToUTF16("(Keyword: ab)"),
189                                          ASCIIToUTF16("(Keyword: aaaa)")}},
190    {ASCIIToUTF16("ftp://www.www w"), 0, {}},
191    {ASCIIToUTF16("http://www.ab w"), 1, {ASCIIToUTF16("(Keyword: ab)")}},
192
193    // Keyword should be returned regardless of query input.
194    {ASCIIToUTF16("z"),               1, {ASCIIToUTF16("(Keyword: z)")}},
195    {ASCIIToUTF16("z    \t"),         1, {ASCIIToUTF16("(Keyword: z)")}},
196    {ASCIIToUTF16("z   a   b   c++"), 1, {ASCIIToUTF16("(Keyword: z)")}},
197  };
198
199  RunTest<string16>(description_cases, arraysize(description_cases),
200                        &AutocompleteMatch::description);
201}
202
203TEST_F(KeywordProviderTest, AddKeyword) {
204  TemplateURL* template_url = new TemplateURL();
205  string16 keyword(ASCIIToUTF16("foo"));
206  std::string url("http://www.google.com/foo?q={searchTerms}");
207  template_url->SetURL(url, 0, 0);
208  template_url->set_keyword(keyword);
209  template_url->set_short_name(ASCIIToUTF16("Test"));
210  model_->Add(template_url);
211  ASSERT_TRUE(template_url == model_->GetTemplateURLForKeyword(keyword));
212}
213
214TEST_F(KeywordProviderTest, RemoveKeyword) {
215  string16 url(ASCIIToUTF16("http://aaaa/?aaaa=1&b={searchTerms}&c"));
216  model_->Remove(model_->GetTemplateURLForKeyword(ASCIIToUTF16("aaaa")));
217  ASSERT_TRUE(model_->GetTemplateURLForKeyword(ASCIIToUTF16("aaaa")) == NULL);
218}
219