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 // TODO(beaudoin): What is really needed here?
10 #include "base/memory/scoped_ptr.h"
11 #include "base/stl_util.h"
12 #include "base/strings/string_util.h"
13 #include "base/values.h"
14 #include "chrome/browser/ui/webui/ntp/suggestions_combiner.h"
15 #include "chrome/browser/ui/webui/ntp/suggestions_page_handler.h"
16 #include "chrome/browser/ui/webui/ntp/suggestions_source.h"
17 #include "chrome/test/base/testing_profile.h"
18 #include "testing/gtest/include/gtest/gtest.h"
24 const char* source_name
;
25 int number_of_suggestions
;
28 struct TestDescription
{
29 SourceInfo sources
[3];
30 const char* results
[8];
32 // One source, more than 8 items.
35 {"A 0", "A 1", "A 2", "A 3", "A 4", "A 5", "A 6", "A 7"}
37 // One source, exactly 8 items.
40 {"A 0", "A 1", "A 2", "A 3", "A 4", "A 5", "A 6", "A 7"}
42 // One source, not enough items.
47 // One source, no items.
52 // Two sources, equal weight, more than 8 items.
54 {{1, "A", 10}, {1, "B", 10}},
55 {"A 0", "A 1", "A 2", "A 3", "B 0", "B 1", "B 2", "B 3"}
57 // Two sources, equal weight, exactly 8 items.
59 {{1, "A", 4}, {1, "B", 4}},
60 {"A 0", "A 1", "A 2", "A 3", "B 0", "B 1", "B 2", "B 3"}
62 // Two sources, equal weight, exactly 8 items but source A has more.
64 {{1, "A", 5}, {1, "B", 3}},
65 {"A 0", "A 1", "A 2", "A 3", "A 4", "B 0", "B 1", "B 2"}
67 // Two sources, equal weight, exactly 8 items but source B has more.
69 {{1, "A", 2}, {1, "B", 6}},
70 {"A 0", "A 1", "B 0", "B 1", "B 2", "B 3", "B 4", "B 5"}
72 // Two sources, equal weight, exactly 8 items but source A has none.
74 {{1, "A", 0}, {1, "B", 8}},
75 {"B 0", "B 1", "B 2", "B 3", "B 4", "B 5", "B 6", "B 7"}
77 // Two sources, equal weight, exactly 8 items but source B has none.
79 {{1, "A", 8}, {1, "B", 0}},
80 {"A 0", "A 1", "A 2", "A 3", "A 4", "A 5", "A 6", "A 7"}
82 // Two sources, equal weight, less than 8 items.
84 {{1, "A", 3}, {1, "B", 3}},
85 {"A 0", "A 1", "A 2", "B 0", "B 1", "B 2"}
87 // Two sources, equal weight, less than 8 items but source A has more.
89 {{1, "A", 4}, {1, "B", 3}},
90 {"A 0", "A 1", "A 2", "A 3", "B 0", "B 1", "B 2"}
92 // Two sources, equal weight, less than 8 items but source B has more.
94 {{1, "A", 1}, {1, "B", 3}},
95 {"A 0", "B 0", "B 1", "B 2"}
97 // Two sources, weights 3/4 A 1/4 B, more than 8 items.
99 {{3, "A", 10}, {1, "B", 10}},
100 {"A 0", "A 1", "A 2", "A 3", "A 4", "A 5", "B 0", "B 1"}
102 // Two sources, weights 1/8 A 7/8 B, more than 8 items.
104 {{1, "A", 10}, {7, "B", 10}},
105 {"A 0", "B 0", "B 1", "B 2", "B 3", "B 4", "B 5", "B 6"}
107 // Two sources, weights 1/3 A 2/3 B, more than 8 items.
109 {{1, "A", 10}, {2, "B", 10}},
110 {"A 0", "A 1", "B 0", "B 1", "B 2", "B 3", "B 4", "B 5"}
112 // Three sources, weights 1/2 A 1/4 B 1/4 C, more than 8 items.
114 {{2, "A", 10}, {1, "B", 10}, {1, "C", 10}},
115 {"A 0", "A 1", "A 2", "A 3", "B 0", "B 1", "C 0", "C 1"}
117 // Three sources, weights 1/3 A 1/3 B 1/3 C, more than 8 items.
119 {{1, "A", 10}, {1, "B", 10}, {1, "C", 10}},
120 {"A 0", "A 1", "B 0", "B 1", "B 2", "C 0", "C 1", "C 2"}
122 // Extra items should be grouped together.
124 {{1, "A", 3}, {1, "B", 4}, {10, "C", 1}},
125 {"A 0", "A 1", "A 2", "B 0", "B 1", "B 2", "B 3", "C 0"}
131 // Stub for a SuggestionsSource that can provide a number of fake suggestions.
132 // Fake suggestions are DictionaryValue with a single "title" string field
133 // containing the |source_name| followed by the index of the suggestion.
134 // Not in the empty namespace since it's a friend of SuggestionsCombiner.
135 class SuggestionsSourceStub
: public SuggestionsSource
{
137 explicit SuggestionsSourceStub(int weight
,
138 const std::string
& source_name
, int number_of_suggestions
)
141 source_name_(source_name
),
142 number_of_suggestions_(number_of_suggestions
),
145 virtual ~SuggestionsSourceStub() {
146 STLDeleteElements(&items_
);
149 // Call this method to simulate that the SuggestionsSource has received all
152 combiner_
->OnItemsReady();
156 // SuggestionsSource Override and implementation.
157 virtual void SetDebug(bool enable
) OVERRIDE
{
160 virtual int GetWeight() OVERRIDE
{
163 virtual int GetItemCount() OVERRIDE
{
164 return items_
.size();
166 virtual base::DictionaryValue
* PopItem() OVERRIDE
{
169 base::DictionaryValue
* item
= items_
.front();
174 virtual void FetchItems(Profile
* profile
) OVERRIDE
{
175 char num_str
[21]; // Enough to hold all numbers up to 64-bits.
176 for (int i
= 0; i
< number_of_suggestions_
; ++i
) {
177 base::snprintf(num_str
, sizeof(num_str
), "%d", i
);
178 AddSuggestion(source_name_
+ ' ' + num_str
);
182 // Adds a fake suggestion. This suggestion is a DictionaryValue with a single
183 // "title" field containing |title|.
184 void AddSuggestion(const std::string
& title
) {
185 base::DictionaryValue
* item
= new base::DictionaryValue();
186 item
->SetString("title", title
);
187 items_
.push_back(item
);
190 virtual void SetCombiner(SuggestionsCombiner
* combiner
) OVERRIDE
{
192 combiner_
= combiner
;
196 SuggestionsCombiner
* combiner_
;
199 std::string source_name_
;
200 int number_of_suggestions_
;
203 // Keep the results of the db query here.
204 std::deque
<base::DictionaryValue
*> items_
;
206 DISALLOW_COPY_AND_ASSIGN(SuggestionsSourceStub
);
209 class SuggestionsCombinerTest
: public testing::Test
{
211 SuggestionsCombinerTest() {
216 SuggestionsHandler
* suggestions_handler_
;
217 SuggestionsCombiner
* combiner_
;
221 combiner_
= new SuggestionsCombiner(suggestions_handler_
, profile_
);
225 virtual void SetUp() {
226 profile_
= new TestingProfile();
227 suggestions_handler_
= new SuggestionsHandler();
228 combiner_
= new SuggestionsCombiner(suggestions_handler_
, profile_
);
231 virtual void TearDown() {
233 delete suggestions_handler_
;
237 DISALLOW_COPY_AND_ASSIGN(SuggestionsCombinerTest
);
240 TEST_F(SuggestionsCombinerTest
, NoSource
) {
241 combiner_
->FetchItems(NULL
);
242 EXPECT_EQ(0UL, combiner_
->GetPageValues()->GetSize());
245 TEST_F(SuggestionsCombinerTest
, SourcesAreNotDoneFetching
) {
246 combiner_
->AddSource(new SuggestionsSourceStub(1, "sourceA", 10));
247 combiner_
->AddSource(new SuggestionsSourceStub(1, "sourceB", 10));
248 combiner_
->FetchItems(NULL
);
249 EXPECT_EQ(0UL, combiner_
->GetPageValues()->GetSize());
252 TEST_F(SuggestionsCombinerTest
, TestSuite
) {
253 size_t test_count
= arraysize(test_suite
);
254 for (size_t i
= 0; i
< test_count
; ++i
) {
255 const TestDescription
& description
= test_suite
[i
];
256 size_t source_count
= arraysize(description
.sources
);
258 scoped_ptr
<SuggestionsSourceStub
*[]> sources(
259 new SuggestionsSourceStub
*[source_count
]);
262 for (size_t j
= 0; j
< source_count
; ++j
) {
263 const SourceInfo
& source_info
= description
.sources
[j
];
264 // A NULL |source_name| means we shouldn't add this source.
265 if (source_info
.source_name
) {
266 sources
[j
] = new SuggestionsSourceStub(source_info
.weight
,
267 source_info
.source_name
, source_info
.number_of_suggestions
);
268 combiner_
->AddSource(sources
[j
]);
275 combiner_
->FetchItems(NULL
);
278 for (size_t j
= 0; j
< source_count
; ++j
) {
283 // Verify expectations.
284 base::ListValue
* results
= combiner_
->GetPageValues();
285 size_t result_count
= results
->GetSize();
286 EXPECT_LE(result_count
, 8UL);
287 for (size_t j
= 0; j
< 8; ++j
) {
288 if (j
< result_count
) {
290 base::DictionaryValue
* dictionary
;
291 results
->GetDictionary(j
, &dictionary
);
292 dictionary
->GetString("title", &value
);
293 EXPECT_STREQ(description
.results
[j
], value
.c_str()) <<
296 EXPECT_EQ(description
.results
[j
], static_cast<const char*>(NULL
)) <<