closure: fix compile error by adding missing externs
[chromium-blink-merge.git] / components / bookmarks / browser / bookmark_index_unittest.cc
blob22efeaaa754a5a4bc5b4d61333b23f02b6a63b87
1 // Copyright 2014 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.
5 #include "components/bookmarks/browser/bookmark_index.h"
7 #include <string>
8 #include <vector>
10 #include "base/macros.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/string_split.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "components/bookmarks/browser/bookmark_match.h"
16 #include "components/bookmarks/browser/bookmark_model.h"
17 #include "components/bookmarks/test/bookmark_test_helpers.h"
18 #include "components/bookmarks/test/test_bookmark_client.h"
19 #include "testing/gtest/include/gtest/gtest.h"
21 using base::ASCIIToUTF16;
22 using base::UTF8ToUTF16;
24 namespace bookmarks {
25 namespace {
27 const char kAboutBlankURL[] = "about:blank";
29 class BookmarkClientMock : public TestBookmarkClient {
30 public:
31 BookmarkClientMock(const std::map<GURL, int>& typed_count_map)
32 : typed_count_map_(typed_count_map) {}
34 bool SupportsTypedCountForNodes() override { return true; }
36 void GetTypedCountForNodes(
37 const NodeSet& nodes,
38 NodeTypedCountPairs* node_typed_count_pairs) override {
39 for (NodeSet::const_iterator it = nodes.begin(); it != nodes.end(); ++it) {
40 const BookmarkNode* node = *it;
41 std::map<GURL, int>::const_iterator found =
42 typed_count_map_.find(node->url());
43 if (found == typed_count_map_.end())
44 continue;
46 node_typed_count_pairs->push_back(std::make_pair(node, found->second));
50 private:
51 const std::map<GURL, int> typed_count_map_;
53 DISALLOW_COPY_AND_ASSIGN(BookmarkClientMock);
56 class BookmarkIndexTest : public testing::Test {
57 public:
58 BookmarkIndexTest() : model_(client_.CreateModel()) {}
60 typedef std::pair<std::string, std::string> TitleAndURL;
62 void AddBookmarks(const char** titles, const char** urls, size_t count) {
63 // The pair is (title, url).
64 std::vector<TitleAndURL> bookmarks;
65 for (size_t i = 0; i < count; ++i) {
66 TitleAndURL bookmark(titles[i], urls[i]);
67 bookmarks.push_back(bookmark);
69 AddBookmarks(bookmarks);
72 void AddBookmarks(const std::vector<TitleAndURL>& bookmarks) {
73 for (size_t i = 0; i < bookmarks.size(); ++i) {
74 model_->AddURL(model_->other_node(), static_cast<int>(i),
75 ASCIIToUTF16(bookmarks[i].first),
76 GURL(bookmarks[i].second));
80 void ExpectMatches(const std::string& query,
81 const char** expected_titles,
82 size_t expected_count) {
83 std::vector<std::string> title_vector;
84 for (size_t i = 0; i < expected_count; ++i)
85 title_vector.push_back(expected_titles[i]);
86 ExpectMatches(query, query_parser::MatchingAlgorithm::DEFAULT,
87 title_vector);
90 void ExpectMatches(const std::string& query,
91 query_parser::MatchingAlgorithm matching_algorithm,
92 const std::vector<std::string>& expected_titles) {
93 std::vector<BookmarkMatch> matches;
94 model_->GetBookmarksMatching(ASCIIToUTF16(query), 1000, matching_algorithm,
95 &matches);
96 ASSERT_EQ(expected_titles.size(), matches.size());
97 for (size_t i = 0; i < expected_titles.size(); ++i) {
98 bool found = false;
99 for (size_t j = 0; j < matches.size(); ++j) {
100 if (ASCIIToUTF16(expected_titles[i]) == matches[j].node->GetTitle()) {
101 matches.erase(matches.begin() + j);
102 found = true;
103 break;
106 ASSERT_TRUE(found);
110 void ExtractMatchPositions(const std::string& string,
111 BookmarkMatch::MatchPositions* matches) {
112 std::vector<std::string> match_strings;
113 base::SplitString(string, ':', &match_strings);
114 for (size_t i = 0; i < match_strings.size(); ++i) {
115 std::vector<std::string> chunks;
116 base::SplitString(match_strings[i], ',', &chunks);
117 ASSERT_EQ(2U, chunks.size());
118 matches->push_back(BookmarkMatch::MatchPosition());
119 int chunks0, chunks1;
120 EXPECT_TRUE(base::StringToInt(chunks[0], &chunks0));
121 EXPECT_TRUE(base::StringToInt(chunks[1], &chunks1));
122 matches->back().first = chunks0;
123 matches->back().second = chunks1;
127 void ExpectMatchPositions(
128 const BookmarkMatch::MatchPositions& actual_positions,
129 const BookmarkMatch::MatchPositions& expected_positions) {
130 ASSERT_EQ(expected_positions.size(), actual_positions.size());
131 for (size_t i = 0; i < expected_positions.size(); ++i) {
132 EXPECT_EQ(expected_positions[i].first, actual_positions[i].first);
133 EXPECT_EQ(expected_positions[i].second, actual_positions[i].second);
137 protected:
138 TestBookmarkClient client_;
139 scoped_ptr<BookmarkModel> model_;
141 private:
142 DISALLOW_COPY_AND_ASSIGN(BookmarkIndexTest);
145 // Various permutations with differing input, queries and output that exercises
146 // all query paths.
147 TEST_F(BookmarkIndexTest, GetBookmarksMatching) {
148 struct TestData {
149 const std::string titles;
150 const std::string query;
151 const std::string expected;
152 } data[] = {
153 // Trivial test case of only one term, exact match.
154 { "a;b", "A", "a" },
156 // Two terms, exact matches.
157 { "a b;b", "a b", "a b" },
159 // Prefix match, one term.
160 { "abcd;abc;b", "abc", "abcd;abc" },
162 // Prefix match, multiple terms.
163 { "abcd cdef;abcd;abcd cdefg", "abc cde", "abcd cdef;abcd cdefg"},
165 // Exact and prefix match.
166 { "ab cdef;abcd;abcd cdefg", "ab cdef", "ab cdef"},
168 // Exact and prefix match.
169 { "ab cdef ghij;ab;cde;cdef;ghi;cdef ab;ghij ab",
170 "ab cde ghi",
171 "ab cdef ghij"},
173 // Title with term multiple times.
174 { "ab ab", "ab", "ab ab"},
176 // Make sure quotes don't do a prefix match.
177 { "think", "\"thi\"", ""},
179 // Prefix matches against multiple candidates.
180 { "abc1 abc2 abc3 abc4", "abc", "abc1 abc2 abc3 abc4"},
182 // Multiple prefix matches (with a lot of redundancy) against multiple
183 // candidates.
184 { "abc1 abc2 abc3 abc4 def1 def2 def3 def4",
185 "abc def abc def abc def abc def abc def",
186 "abc1 abc2 abc3 abc4 def1 def2 def3 def4"},
188 // Prefix match on the first term.
189 { "abc", "a", "" },
191 // Prefix match on subsequent terms.
192 { "abc def", "abc d", "" },
194 for (size_t i = 0; i < arraysize(data); ++i) {
195 std::vector<std::string> titles;
196 base::SplitString(data[i].titles, ';', &titles);
197 std::vector<TitleAndURL> bookmarks;
198 for (size_t j = 0; j < titles.size(); ++j) {
199 TitleAndURL bookmark(titles[j], kAboutBlankURL);
200 bookmarks.push_back(bookmark);
202 AddBookmarks(bookmarks);
204 std::vector<std::string> expected;
205 if (!data[i].expected.empty())
206 base::SplitString(data[i].expected, ';', &expected);
208 ExpectMatches(data[i].query, query_parser::MatchingAlgorithm::DEFAULT,
209 expected);
211 model_ = client_.CreateModel();
215 TEST_F(BookmarkIndexTest, GetBookmarksMatchingAlwaysPrefixSearch) {
216 struct TestData {
217 const std::string titles;
218 const std::string query;
219 const std::string expected;
220 } data[] = {
221 // Trivial test case of only one term, exact match.
222 { "z;y", "Z", "z" },
224 // Prefix match, one term.
225 { "abcd;abc;b", "abc", "abcd;abc" },
227 // Prefix match, multiple terms.
228 { "abcd cdef;abcd;abcd cdefg", "abc cde", "abcd cdef;abcd cdefg" },
230 // Exact and prefix match.
231 { "ab cdef ghij;ab;cde;cdef;ghi;cdef ab;ghij ab",
232 "ab cde ghi",
233 "ab cdef ghij" },
235 // Title with term multiple times.
236 { "ab ab", "ab", "ab ab" },
238 // Make sure quotes don't do a prefix match.
239 { "think", "\"thi\"", "" },
241 // Prefix matches against multiple candidates.
242 { "abc1 abc2 abc3 abc4", "abc", "abc1 abc2 abc3 abc4" },
244 // Prefix match on the first term.
245 { "abc", "a", "abc" },
247 // Prefix match on subsequent terms.
248 { "abc def", "abc d", "abc def" },
250 // Exact and prefix match.
251 { "ab cdef;abcd;abcd cdefg", "ab cdef", "ab cdef;abcd cdefg" },
253 for (size_t i = 0; i < arraysize(data); ++i) {
254 std::vector<std::string> titles;
255 base::SplitString(data[i].titles, ';', &titles);
256 std::vector<TitleAndURL> bookmarks;
257 for (size_t j = 0; j < titles.size(); ++j) {
258 TitleAndURL bookmark(titles[j], kAboutBlankURL);
259 bookmarks.push_back(bookmark);
261 AddBookmarks(bookmarks);
263 std::vector<std::string> expected;
264 if (!data[i].expected.empty())
265 base::SplitString(data[i].expected, ';', &expected);
267 ExpectMatches(data[i].query,
268 query_parser::MatchingAlgorithm::ALWAYS_PREFIX_SEARCH,
269 expected);
271 model_ = client_.CreateModel();
275 // Analogous to GetBookmarksMatching, this test tests various permutations
276 // of title, URL, and input to see if the title/URL matches the input as
277 // expected.
278 TEST_F(BookmarkIndexTest, GetBookmarksMatchingWithURLs) {
279 struct TestData {
280 const std::string query;
281 const std::string title;
282 const std::string url;
283 const bool should_be_retrieved;
284 } data[] = {
285 // Test single-word inputs. Include both exact matches and prefix matches.
286 { "foo", "Foo", "http://www.bar.com/", true },
287 { "foo", "Foodie", "http://www.bar.com/", true },
288 { "foo", "Bar", "http://www.foo.com/", true },
289 { "foo", "Bar", "http://www.foodie.com/", true },
290 { "foo", "Foo", "http://www.foo.com/", true },
291 { "foo", "Bar", "http://www.bar.com/", false },
292 { "foo", "Bar", "http://www.bar.com/blah/foo/blah-again/ ", true },
293 { "foo", "Bar", "http://www.bar.com/blah/foodie/blah-again/ ", true },
294 { "foo", "Bar", "http://www.bar.com/blah-foo/blah-again/ ", true },
295 { "foo", "Bar", "http://www.bar.com/blah-foodie/blah-again/ ", true },
296 { "foo", "Bar", "http://www.bar.com/blahafoo/blah-again/ ", false },
298 // Test multi-word inputs.
299 { "foo bar", "Foo Bar", "http://baz.com/", true },
300 { "foo bar", "Foodie Bar", "http://baz.com/", true },
301 { "bar foo", "Foo Bar", "http://baz.com/", true },
302 { "bar foo", "Foodie Barly", "http://baz.com/", true },
303 { "foo bar", "Foo Baz", "http://baz.com/", false },
304 { "foo bar", "Foo Baz", "http://bar.com/", true },
305 { "foo bar", "Foo Baz", "http://barly.com/", true },
306 { "foo bar", "Foodie Baz", "http://barly.com/", true },
307 { "bar foo", "Foo Baz", "http://bar.com/", true },
308 { "bar foo", "Foo Baz", "http://barly.com/", true },
309 { "foo bar", "Baz Bar", "http://blah.com/foo", true },
310 { "foo bar", "Baz Barly", "http://blah.com/foodie", true },
311 { "foo bar", "Baz Bur", "http://blah.com/foo/bar", true },
312 { "foo bar", "Baz Bur", "http://blah.com/food/barly", true },
313 { "foo bar", "Baz Bur", "http://bar.com/blah/foo", true },
314 { "foo bar", "Baz Bur", "http://barly.com/blah/food", true },
315 { "foo bar", "Baz Bur", "http://bar.com/blah/flub", false },
316 { "foo bar", "Baz Bur", "http://foo.com/blah/flub", false }
319 for (size_t i = 0; i < arraysize(data); ++i) {
320 model_ = client_.CreateModel();
321 std::vector<TitleAndURL> bookmarks;
322 bookmarks.push_back(TitleAndURL(data[i].title, data[i].url));
323 AddBookmarks(bookmarks);
325 std::vector<std::string> expected;
326 if (data[i].should_be_retrieved)
327 expected.push_back(data[i].title);
329 ExpectMatches(data[i].query, query_parser::MatchingAlgorithm::DEFAULT,
330 expected);
334 TEST_F(BookmarkIndexTest, Normalization) {
335 struct TestData {
336 const char* const title;
337 const char* const query;
338 } data[] = {
339 { "fooa\xcc\x88-test", "foo\xc3\xa4-test" },
340 { "fooa\xcc\x88-test", "fooa\xcc\x88-test" },
341 { "fooa\xcc\x88-test", "foo\xc3\xa4" },
342 { "fooa\xcc\x88-test", "fooa\xcc\x88" },
343 { "fooa\xcc\x88-test", "foo" },
344 { "foo\xc3\xa4-test", "foo\xc3\xa4-test" },
345 { "foo\xc3\xa4-test", "fooa\xcc\x88-test" },
346 { "foo\xc3\xa4-test", "foo\xc3\xa4" },
347 { "foo\xc3\xa4-test", "fooa\xcc\x88" },
348 { "foo\xc3\xa4-test", "foo" },
349 { "foo", "foo" }
352 GURL url(kAboutBlankURL);
353 for (size_t i = 0; i < arraysize(data); ++i) {
354 model_->AddURL(model_->other_node(), 0, UTF8ToUTF16(data[i].title), url);
355 std::vector<BookmarkMatch> matches;
356 model_->GetBookmarksMatching(UTF8ToUTF16(data[i].query), 10, &matches);
357 EXPECT_EQ(1u, matches.size());
358 model_ = client_.CreateModel();
362 // Makes sure match positions are updated appropriately for title matches.
363 TEST_F(BookmarkIndexTest, MatchPositionsTitles) {
364 struct TestData {
365 const std::string title;
366 const std::string query;
367 const std::string expected_title_match_positions;
368 } data[] = {
369 // Trivial test case of only one term, exact match.
370 { "a", "A", "0,1" },
371 { "foo bar", "bar", "4,7" },
372 { "fooey bark", "bar foo", "0,3:6,9" },
373 // Non-trivial tests.
374 { "foobar foo", "foobar foo", "0,6:7,10" },
375 { "foobar foo", "foo foobar", "0,6:7,10" },
376 { "foobar foobar", "foobar foo", "0,6:7,13" },
377 { "foobar foobar", "foo foobar", "0,6:7,13" },
379 for (size_t i = 0; i < arraysize(data); ++i) {
380 std::vector<TitleAndURL> bookmarks;
381 TitleAndURL bookmark(data[i].title, kAboutBlankURL);
382 bookmarks.push_back(bookmark);
383 AddBookmarks(bookmarks);
385 std::vector<BookmarkMatch> matches;
386 model_->GetBookmarksMatching(ASCIIToUTF16(data[i].query), 1000, &matches);
387 ASSERT_EQ(1U, matches.size());
389 BookmarkMatch::MatchPositions expected_title_matches;
390 ExtractMatchPositions(data[i].expected_title_match_positions,
391 &expected_title_matches);
392 ExpectMatchPositions(matches[0].title_match_positions,
393 expected_title_matches);
395 model_ = client_.CreateModel();
399 // Makes sure match positions are updated appropriately for URL matches.
400 TEST_F(BookmarkIndexTest, MatchPositionsURLs) {
401 // The encoded stuff between /wiki/ and the # is 第二次世界大戦
402 const std::string ja_wiki_url = "http://ja.wikipedia.org/wiki/%E7%AC%AC%E4"
403 "%BA%8C%E6%AC%A1%E4%B8%96%E7%95%8C%E5%A4%A7%E6%88%A6#.E3.83.B4.E3.82.A7"
404 ".E3.83.AB.E3.82.B5.E3.82.A4.E3.83.A6.E4.BD.93.E5.88.B6";
405 struct TestData {
406 const std::string query;
407 const std::string url;
408 const std::string expected_url_match_positions;
409 } data[] = {
410 { "foo", "http://www.foo.com/", "11,14" },
411 { "foo", "http://www.foodie.com/", "11,14" },
412 { "foo", "http://www.foofoo.com/", "11,14" },
413 { "www", "http://www.foo.com/", "7,10" },
414 { "foo", "http://www.foodie.com/blah/foo/fi", "11,14:27,30" },
415 { "foo", "http://www.blah.com/blah/foo/fi", "25,28" },
416 { "foo www", "http://www.foodie.com/blah/foo/fi", "7,10:11,14:27,30" },
417 { "www foo", "http://www.foodie.com/blah/foo/fi", "7,10:11,14:27,30" },
418 { "www bla", "http://www.foodie.com/blah/foo/fi", "7,10:22,25" },
419 { "http", "http://www.foo.com/", "0,4" },
420 { "http www", "http://www.foo.com/", "0,4:7,10" },
421 { "http foo", "http://www.foo.com/", "0,4:11,14" },
422 { "http foo", "http://www.bar.com/baz/foodie/hi", "0,4:23,26" },
423 { "第二次", ja_wiki_url, "29,56" },
424 { "ja 第二次", ja_wiki_url, "7,9:29,56" },
425 { "第二次 E3.8", ja_wiki_url, "29,56:94,98:103,107:"
426 "112,116:121,125:"
427 "130,134:139,143" }
430 for (size_t i = 0; i < arraysize(data); ++i) {
431 model_ = client_.CreateModel();
432 std::vector<TitleAndURL> bookmarks;
433 TitleAndURL bookmark("123456", data[i].url);
434 bookmarks.push_back(bookmark);
435 AddBookmarks(bookmarks);
437 std::vector<BookmarkMatch> matches;
438 model_->GetBookmarksMatching(UTF8ToUTF16(data[i].query), 1000, &matches);
439 ASSERT_EQ(1U, matches.size()) << data[i].url << data[i].query;
441 BookmarkMatch::MatchPositions expected_url_matches;
442 ExtractMatchPositions(data[i].expected_url_match_positions,
443 &expected_url_matches);
444 ExpectMatchPositions(matches[0].url_match_positions, expected_url_matches);
448 // Makes sure index is updated when a node is removed.
449 TEST_F(BookmarkIndexTest, Remove) {
450 const char* titles[] = { "a", "b" };
451 const char* urls[] = {kAboutBlankURL, kAboutBlankURL};
452 AddBookmarks(titles, urls, arraysize(titles));
454 // Remove the node and make sure we don't get back any results.
455 model_->Remove(model_->other_node(), 0);
456 ExpectMatches("A", NULL, 0U);
459 // Makes sure index is updated when a node's title is changed.
460 TEST_F(BookmarkIndexTest, ChangeTitle) {
461 const char* titles[] = { "a", "b" };
462 const char* urls[] = {kAboutBlankURL, kAboutBlankURL};
463 AddBookmarks(titles, urls, arraysize(titles));
465 // Remove the node and make sure we don't get back any results.
466 const char* expected[] = { "blah" };
467 model_->SetTitle(model_->other_node()->GetChild(0), ASCIIToUTF16("blah"));
468 ExpectMatches("BlAh", expected, arraysize(expected));
471 // Makes sure index is updated when a node's URL is changed.
472 TEST_F(BookmarkIndexTest, ChangeURL) {
473 const char* titles[] = { "a", "b" };
474 const char* urls[] = {"http://fizz",
475 "http://fuzz"};
476 AddBookmarks(titles, urls, arraysize(titles));
478 const char* expected[] = { "a" };
479 model_->SetURL(model_->other_node()->GetChild(0), GURL("http://blah"));
480 ExpectMatches("blah", expected, arraysize(expected));
483 // Makes sure no more than max queries is returned.
484 TEST_F(BookmarkIndexTest, HonorMax) {
485 const char* titles[] = { "abcd", "abcde" };
486 const char* urls[] = {kAboutBlankURL, kAboutBlankURL};
487 AddBookmarks(titles, urls, arraysize(titles));
489 std::vector<BookmarkMatch> matches;
490 model_->GetBookmarksMatching(ASCIIToUTF16("ABc"), 1, &matches);
491 EXPECT_EQ(1U, matches.size());
494 // Makes sure if the lower case string of a bookmark title is more characters
495 // than the upper case string no match positions are returned.
496 TEST_F(BookmarkIndexTest, EmptyMatchOnMultiwideLowercaseString) {
497 const BookmarkNode* n1 = model_->AddURL(model_->other_node(), 0,
498 base::WideToUTF16(L"\u0130 i"),
499 GURL("http://www.google.com"));
501 std::vector<BookmarkMatch> matches;
502 model_->GetBookmarksMatching(ASCIIToUTF16("i"), 100, &matches);
503 ASSERT_EQ(1U, matches.size());
504 EXPECT_EQ(n1, matches[0].node);
505 EXPECT_TRUE(matches[0].title_match_positions.empty());
508 TEST_F(BookmarkIndexTest, GetResultsSortedByTypedCount) {
509 struct TestData {
510 const GURL url;
511 const char* title;
512 const int typed_count;
513 } data[] = {
514 { GURL("http://www.google.com/"), "Google", 100 },
515 { GURL("http://maps.google.com/"), "Google Maps", 40 },
516 { GURL("http://docs.google.com/"), "Google Docs", 50 },
517 { GURL("http://reader.google.com/"), "Google Reader", 80 },
520 std::map<GURL, int> typed_count_map;
521 for (size_t i = 0; i < arraysize(data); ++i)
522 typed_count_map.insert(std::make_pair(data[i].url, data[i].typed_count));
524 BookmarkClientMock client(typed_count_map);
525 scoped_ptr<BookmarkModel> model = client.CreateModel();
527 for (size_t i = 0; i < arraysize(data); ++i)
528 // Populate the BookmarkIndex.
529 model->AddURL(
530 model->other_node(), i, UTF8ToUTF16(data[i].title), data[i].url);
532 // Populate match nodes.
533 std::vector<BookmarkMatch> matches;
534 model->GetBookmarksMatching(ASCIIToUTF16("google"), 4, &matches);
536 // The resulting order should be:
537 // 1. Google (google.com) 100
538 // 2. Google Reader (google.com/reader) 80
539 // 3. Google Docs (docs.google.com) 50
540 // 4. Google Maps (maps.google.com) 40
541 ASSERT_EQ(4U, matches.size());
542 EXPECT_EQ(data[0].url, matches[0].node->url());
543 EXPECT_EQ(data[3].url, matches[1].node->url());
544 EXPECT_EQ(data[2].url, matches[2].node->url());
545 EXPECT_EQ(data[1].url, matches[3].node->url());
547 matches.clear();
548 // Select top two matches.
549 model->GetBookmarksMatching(ASCIIToUTF16("google"), 2, &matches);
551 ASSERT_EQ(2U, matches.size());
552 EXPECT_EQ(data[0].url, matches[0].node->url());
553 EXPECT_EQ(data[3].url, matches[1].node->url());
556 } // namespace
557 } // namespace bookmarks