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.
5 #include "chrome/browser/bookmarks/bookmark_index.h"
10 #include "base/message_loop/message_loop.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 "chrome/browser/bookmarks/bookmark_model.h"
16 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
17 #include "chrome/browser/bookmarks/bookmark_test_helpers.h"
18 #include "chrome/browser/bookmarks/bookmark_title_match.h"
19 #include "chrome/browser/history/history_service.h"
20 #include "chrome/browser/history/history_service_factory.h"
21 #include "chrome/browser/history/url_database.h"
22 #include "chrome/test/base/testing_profile.h"
23 #include "content/public/test/test_browser_thread_bundle.h"
24 #include "testing/gtest/include/gtest/gtest.h"
26 using base::ASCIIToUTF16
;
28 class BookmarkIndexTest
: public testing::Test
{
30 BookmarkIndexTest() : model_(new BookmarkModel(NULL
)) {}
32 void AddBookmarksWithTitles(const char** titles
, size_t count
) {
33 std::vector
<std::string
> title_vector
;
34 for (size_t i
= 0; i
< count
; ++i
)
35 title_vector
.push_back(titles
[i
]);
36 AddBookmarksWithTitles(title_vector
);
39 void AddBookmarksWithTitles(const std::vector
<std::string
>& titles
) {
40 GURL
url("about:blank");
41 for (size_t i
= 0; i
< titles
.size(); ++i
)
42 model_
->AddURL(model_
->other_node(), static_cast<int>(i
),
43 ASCIIToUTF16(titles
[i
]), url
);
46 void ExpectMatches(const std::string
& query
,
47 const char** expected_titles
,
48 size_t expected_count
) {
49 std::vector
<std::string
> title_vector
;
50 for (size_t i
= 0; i
< expected_count
; ++i
)
51 title_vector
.push_back(expected_titles
[i
]);
52 ExpectMatches(query
, title_vector
);
55 void ExpectMatches(const std::string
& query
,
56 const std::vector
<std::string
>& expected_titles
) {
57 std::vector
<BookmarkTitleMatch
> matches
;
58 model_
->GetBookmarksWithTitlesMatching(ASCIIToUTF16(query
), 1000, &matches
);
59 ASSERT_EQ(expected_titles
.size(), matches
.size());
60 for (size_t i
= 0; i
< expected_titles
.size(); ++i
) {
62 for (size_t j
= 0; j
< matches
.size(); ++j
) {
63 if (ASCIIToUTF16(expected_titles
[i
]) == matches
[j
].node
->GetTitle()) {
64 matches
.erase(matches
.begin() + j
);
73 void ExtractMatchPositions(const std::string
& string
,
74 BookmarkTitleMatch::MatchPositions
* matches
) {
75 std::vector
<std::string
> match_strings
;
76 base::SplitString(string
, ':', &match_strings
);
77 for (size_t i
= 0; i
< match_strings
.size(); ++i
) {
78 std::vector
<std::string
> chunks
;
79 base::SplitString(match_strings
[i
], ',', &chunks
);
80 ASSERT_EQ(2U, chunks
.size());
81 matches
->push_back(BookmarkTitleMatch::MatchPosition());
83 base::StringToInt(chunks
[0], &chunks0
);
84 base::StringToInt(chunks
[1], &chunks1
);
85 matches
->back().first
= chunks0
;
86 matches
->back().second
= chunks1
;
90 void ExpectMatchPositions(
91 const std::string
& query
,
92 const BookmarkTitleMatch::MatchPositions
& expected_positions
) {
93 std::vector
<BookmarkTitleMatch
> matches
;
94 model_
->GetBookmarksWithTitlesMatching(ASCIIToUTF16(query
), 1000, &matches
);
95 ASSERT_EQ(1U, matches
.size());
96 const BookmarkTitleMatch
& match
= matches
[0];
97 ASSERT_EQ(expected_positions
.size(), match
.match_positions
.size());
98 for (size_t i
= 0; i
< expected_positions
.size(); ++i
) {
99 EXPECT_EQ(expected_positions
[i
].first
, match
.match_positions
[i
].first
);
100 EXPECT_EQ(expected_positions
[i
].second
, match
.match_positions
[i
].second
);
105 scoped_ptr
<BookmarkModel
> model_
;
108 DISALLOW_COPY_AND_ASSIGN(BookmarkIndexTest
);
111 // Various permutations with differing input, queries and output that exercises
113 TEST_F(BookmarkIndexTest
, Tests
) {
115 const std::string input
;
116 const std::string query
;
117 const std::string expected
;
119 // Trivial test case of only one term, exact match.
122 // Prefix match, one term.
123 { "abcd;abc;b", "abc", "abcd;abc" },
125 // Prefix match, multiple terms.
126 { "abcd cdef;abcd;abcd cdefg", "abc cde", "abcd cdef;abcd cdefg"},
128 // Exact and prefix match.
129 { "ab cdef;abcd;abcd cdefg", "ab cdef", "ab cdef"},
131 // Exact and prefix match.
132 { "ab cdef ghij;ab;cde;cdef;ghi;cdef ab;ghij ab",
136 // Title with term multiple times.
137 { "ab ab", "ab", "ab ab"},
139 // Make sure quotes don't do a prefix match.
140 { "think", "\"thi\"", ""},
142 // Prefix matches against multiple candidates.
143 { "abc1 abc2 abc3 abc4", "abc", "abc1 abc2 abc3 abc4"},
145 for (size_t i
= 0; i
< ARRAYSIZE_UNSAFE(data
); ++i
) {
146 std::vector
<std::string
> titles
;
147 base::SplitString(data
[i
].input
, ';', &titles
);
148 AddBookmarksWithTitles(titles
);
150 std::vector
<std::string
> expected
;
151 if (!data
[i
].expected
.empty())
152 base::SplitString(data
[i
].expected
, ';', &expected
);
154 ExpectMatches(data
[i
].query
, expected
);
156 model_
.reset(new BookmarkModel(NULL
));
160 // Makes sure match positions are updated appropriately.
161 TEST_F(BookmarkIndexTest
, MatchPositions
) {
163 const std::string title
;
164 const std::string query
;
165 const std::string expected
;
167 // Trivial test case of only one term, exact match.
169 { "foo bar", "bar", "4,7" },
170 { "fooey bark", "bar foo", "0,3:6,9"},
171 // Non-trivial tests.
172 { "foobar foo", "foobar foo", "0,6:7,10" },
173 { "foobar foo", "foo foobar", "0,6:7,10" },
174 { "foobar foobar", "foobar foo", "0,6:7,13" },
175 { "foobar foobar", "foo foobar", "0,6:7,13" },
177 for (size_t i
= 0; i
< ARRAYSIZE_UNSAFE(data
); ++i
) {
178 std::vector
<std::string
> titles
;
179 titles
.push_back(data
[i
].title
);
180 AddBookmarksWithTitles(titles
);
182 BookmarkTitleMatch::MatchPositions expected_matches
;
183 ExtractMatchPositions(data
[i
].expected
, &expected_matches
);
184 ExpectMatchPositions(data
[i
].query
, expected_matches
);
186 model_
.reset(new BookmarkModel(NULL
));
190 // Makes sure index is updated when a node is removed.
191 TEST_F(BookmarkIndexTest
, Remove
) {
192 const char* input
[] = { "a", "b" };
193 AddBookmarksWithTitles(input
, ARRAYSIZE_UNSAFE(input
));
195 // Remove the node and make sure we don't get back any results.
196 model_
->Remove(model_
->other_node(), 0);
197 ExpectMatches("A", NULL
, 0U);
200 // Makes sure index is updated when a node's title is changed.
201 TEST_F(BookmarkIndexTest
, ChangeTitle
) {
202 const char* input
[] = { "a", "b" };
203 AddBookmarksWithTitles(input
, ARRAYSIZE_UNSAFE(input
));
205 // Remove the node and make sure we don't get back any results.
206 const char* expected
[] = { "blah" };
207 model_
->SetTitle(model_
->other_node()->GetChild(0), ASCIIToUTF16("blah"));
208 ExpectMatches("BlAh", expected
, ARRAYSIZE_UNSAFE(expected
));
211 // Makes sure no more than max queries is returned.
212 TEST_F(BookmarkIndexTest
, HonorMax
) {
213 const char* input
[] = { "abcd", "abcde" };
214 AddBookmarksWithTitles(input
, ARRAYSIZE_UNSAFE(input
));
216 std::vector
<BookmarkTitleMatch
> matches
;
217 model_
->GetBookmarksWithTitlesMatching(ASCIIToUTF16("ABc"), 1, &matches
);
218 EXPECT_EQ(1U, matches
.size());
221 // Makes sure if the lower case string of a bookmark title is more characters
222 // than the upper case string no match positions are returned.
223 TEST_F(BookmarkIndexTest
, EmptyMatchOnMultiwideLowercaseString
) {
224 const BookmarkNode
* n1
= model_
->AddURL(model_
->other_node(), 0,
225 base::WideToUTF16(L
"\u0130 i"),
226 GURL("http://www.google.com"));
228 std::vector
<BookmarkTitleMatch
> matches
;
229 model_
->GetBookmarksWithTitlesMatching(ASCIIToUTF16("i"), 100, &matches
);
230 ASSERT_EQ(1U, matches
.size());
231 EXPECT_TRUE(matches
[0].node
== n1
);
232 EXPECT_TRUE(matches
[0].match_positions
.empty());
235 TEST_F(BookmarkIndexTest
, GetResultsSortedByTypedCount
) {
236 // This ensures MessageLoop::current() will exist, which is needed by
237 // TestingProfile::BlockUntilHistoryProcessesPendingRequests().
238 content::TestBrowserThreadBundle thread_bundle
;
240 TestingProfile profile
;
241 ASSERT_TRUE(profile
.CreateHistoryService(true, false));
242 profile
.BlockUntilHistoryProcessesPendingRequests();
243 profile
.CreateBookmarkModel(true);
245 BookmarkModel
* model
= BookmarkModelFactory::GetForProfile(&profile
);
246 test::WaitForBookmarkModelToLoad(model
);
248 HistoryService
* const history_service
=
249 HistoryServiceFactory::GetForProfile(&profile
, Profile::EXPLICIT_ACCESS
);
251 history::URLDatabase
* url_db
= history_service
->InMemoryDatabase();
256 const int typed_count
;
258 { GURL("http://www.google.com/"), "Google", 100 },
259 { GURL("http://maps.google.com/"), "Google Maps", 40 },
260 { GURL("http://docs.google.com/"), "Google Docs", 50 },
261 { GURL("http://reader.google.com/"), "Google Reader", 80 },
264 for (size_t i
= 0; i
< ARRAYSIZE_UNSAFE(data
); ++i
) {
265 history::URLRow
info(data
[i
].url
);
266 info
.set_title(base::UTF8ToUTF16(data
[i
].title
));
267 info
.set_typed_count(data
[i
].typed_count
);
268 // Populate the InMemoryDatabase....
269 url_db
->AddURL(info
);
270 // Populate the BookmarkIndex.
271 model
->AddURL(model
->other_node(), i
, base::UTF8ToUTF16(data
[i
].title
),
275 // Check that the InMemoryDatabase stored the URLs properly.
276 history::URLRow result1
;
277 url_db
->GetRowForURL(data
[0].url
, &result1
);
278 EXPECT_EQ(data
[0].title
, base::UTF16ToUTF8(result1
.title()));
280 history::URLRow result2
;
281 url_db
->GetRowForURL(data
[1].url
, &result2
);
282 EXPECT_EQ(data
[1].title
, base::UTF16ToUTF8(result2
.title()));
284 history::URLRow result3
;
285 url_db
->GetRowForURL(data
[2].url
, &result3
);
286 EXPECT_EQ(data
[2].title
, base::UTF16ToUTF8(result3
.title()));
288 history::URLRow result4
;
289 url_db
->GetRowForURL(data
[3].url
, &result4
);
290 EXPECT_EQ(data
[3].title
, base::UTF16ToUTF8(result4
.title()));
292 // Populate match nodes.
293 std::vector
<BookmarkTitleMatch
> matches
;
294 model
->GetBookmarksWithTitlesMatching(ASCIIToUTF16("google"), 4, &matches
);
296 // The resulting order should be:
297 // 1. Google (google.com) 100
298 // 2. Google Reader (google.com/reader) 80
299 // 3. Google Docs (docs.google.com) 50
300 // 4. Google Maps (maps.google.com) 40
301 EXPECT_EQ(4, static_cast<int>(matches
.size()));
302 EXPECT_EQ(data
[0].url
, matches
[0].node
->url());
303 EXPECT_EQ(data
[3].url
, matches
[1].node
->url());
304 EXPECT_EQ(data
[2].url
, matches
[2].node
->url());
305 EXPECT_EQ(data
[1].url
, matches
[3].node
->url());
308 // Select top two matches.
309 model
->GetBookmarksWithTitlesMatching(ASCIIToUTF16("google"), 2, &matches
);
311 EXPECT_EQ(2, static_cast<int>(matches
.size()));
312 EXPECT_EQ(data
[0].url
, matches
[0].node
->url());
313 EXPECT_EQ(data
[3].url
, matches
[1].node
->url());