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 16 class 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 41 void 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 57 void KeywordProviderTest::TearDown() { 58 model_.reset(); 59 kw_provider_ = NULL; 60 } 61 62 template<class ResultType> 63 void 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 84 TEST_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 129 TEST_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 154 TEST_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 183 TEST_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 203 TEST_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 214 TEST_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