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 ~SuggestionsSourceStub() override
{ STLDeleteElements(&items_
); }
147 // Call this method to simulate that the SuggestionsSource has received all
150 combiner_
->OnItemsReady();
154 // SuggestionsSource Override and implementation.
155 void SetDebug(bool enable
) override
{ debug_
= enable
; }
156 int GetWeight() override
{ return weight_
; }
157 int GetItemCount() override
{ return items_
.size(); }
158 base::DictionaryValue
* PopItem() override
{
161 base::DictionaryValue
* item
= items_
.front();
166 void FetchItems(Profile
* profile
) override
{
167 char num_str
[21]; // Enough to hold all numbers up to 64-bits.
168 for (int i
= 0; i
< number_of_suggestions_
; ++i
) {
169 base::snprintf(num_str
, sizeof(num_str
), "%d", i
);
170 AddSuggestion(source_name_
+ ' ' + num_str
);
174 // Adds a fake suggestion. This suggestion is a DictionaryValue with a single
175 // "title" field containing |title|.
176 void AddSuggestion(const std::string
& title
) {
177 base::DictionaryValue
* item
= new base::DictionaryValue();
178 item
->SetString("title", title
);
179 items_
.push_back(item
);
182 void SetCombiner(SuggestionsCombiner
* combiner
) override
{
184 combiner_
= combiner
;
188 SuggestionsCombiner
* combiner_
;
191 std::string source_name_
;
192 int number_of_suggestions_
;
195 // Keep the results of the db query here.
196 std::deque
<base::DictionaryValue
*> items_
;
198 DISALLOW_COPY_AND_ASSIGN(SuggestionsSourceStub
);
201 class SuggestionsCombinerTest
: public testing::Test
{
203 SuggestionsCombinerTest() {
208 SuggestionsHandler
* suggestions_handler_
;
209 SuggestionsCombiner
* combiner_
;
213 combiner_
= new SuggestionsCombiner(suggestions_handler_
, profile_
);
217 virtual void SetUp() {
218 profile_
= new TestingProfile();
219 suggestions_handler_
= new SuggestionsHandler();
220 combiner_
= new SuggestionsCombiner(suggestions_handler_
, profile_
);
223 virtual void TearDown() {
225 delete suggestions_handler_
;
229 DISALLOW_COPY_AND_ASSIGN(SuggestionsCombinerTest
);
232 TEST_F(SuggestionsCombinerTest
, NoSource
) {
233 combiner_
->FetchItems(NULL
);
234 EXPECT_EQ(0UL, combiner_
->GetPageValues()->GetSize());
237 TEST_F(SuggestionsCombinerTest
, SourcesAreNotDoneFetching
) {
238 combiner_
->AddSource(new SuggestionsSourceStub(1, "sourceA", 10));
239 combiner_
->AddSource(new SuggestionsSourceStub(1, "sourceB", 10));
240 combiner_
->FetchItems(NULL
);
241 EXPECT_EQ(0UL, combiner_
->GetPageValues()->GetSize());
244 TEST_F(SuggestionsCombinerTest
, TestSuite
) {
245 size_t test_count
= arraysize(test_suite
);
246 for (size_t i
= 0; i
< test_count
; ++i
) {
247 const TestDescription
& description
= test_suite
[i
];
248 size_t source_count
= arraysize(description
.sources
);
250 scoped_ptr
<SuggestionsSourceStub
*[]> sources(
251 new SuggestionsSourceStub
*[source_count
]);
254 for (size_t j
= 0; j
< source_count
; ++j
) {
255 const SourceInfo
& source_info
= description
.sources
[j
];
256 // A NULL |source_name| means we shouldn't add this source.
257 if (source_info
.source_name
) {
258 sources
[j
] = new SuggestionsSourceStub(source_info
.weight
,
259 source_info
.source_name
, source_info
.number_of_suggestions
);
260 combiner_
->AddSource(sources
[j
]);
267 combiner_
->FetchItems(NULL
);
270 for (size_t j
= 0; j
< source_count
; ++j
) {
275 // Verify expectations.
276 base::ListValue
* results
= combiner_
->GetPageValues();
277 size_t result_count
= results
->GetSize();
278 EXPECT_LE(result_count
, 8UL);
279 for (size_t j
= 0; j
< 8; ++j
) {
280 if (j
< result_count
) {
282 base::DictionaryValue
* dictionary
;
283 results
->GetDictionary(j
, &dictionary
);
284 dictionary
->GetString("title", &value
);
285 EXPECT_STREQ(description
.results
[j
], value
.c_str()) <<
288 EXPECT_EQ(description
.results
[j
], static_cast<const char*>(NULL
)) <<