Home | History | Annotate | Download | only in ntp
      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.
      4 
      5 // TODO(beaudoin): What is really needed here?
      6 
      7 #include <deque>
      8 #include <string>
      9 
     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"
     19 
     20 namespace {
     21 
     22 struct SourceInfo {
     23   int weight;
     24   const char* source_name;
     25   int number_of_suggestions;
     26 };
     27 
     28 struct TestDescription {
     29   SourceInfo sources[3];
     30   const char* results[8];
     31 } test_suite[] = {
     32   // One source, more than 8 items.
     33   {
     34     {{1, "A", 10}},
     35     {"A 0", "A 1", "A 2", "A 3", "A 4", "A 5", "A 6", "A 7"}
     36   },
     37   // One source, exactly 8 items.
     38   {
     39     {{1, "A", 8}},
     40     {"A 0", "A 1", "A 2", "A 3", "A 4", "A 5", "A 6", "A 7"}
     41   },
     42   // One source, not enough items.
     43   {
     44     {{1, "A", 3}},
     45     {"A 0", "A 1", "A 2"}
     46   },
     47   // One source, no items.
     48   {
     49     {{1, "A", 0}},
     50     {}
     51   },
     52   // Two sources, equal weight, more than 8 items.
     53   {
     54     {{1, "A", 10}, {1, "B", 10}},
     55     {"A 0", "A 1", "A 2", "A 3", "B 0", "B 1", "B 2", "B 3"}
     56   },
     57   // Two sources, equal weight, exactly 8 items.
     58   {
     59     {{1, "A", 4}, {1, "B", 4}},
     60     {"A 0", "A 1", "A 2", "A 3", "B 0", "B 1", "B 2", "B 3"}
     61   },
     62   // Two sources, equal weight, exactly 8 items but source A has more.
     63   {
     64     {{1, "A", 5}, {1, "B", 3}},
     65     {"A 0", "A 1", "A 2", "A 3", "A 4", "B 0", "B 1", "B 2"}
     66   },
     67   // Two sources, equal weight, exactly 8 items but source B has more.
     68   {
     69     {{1, "A", 2}, {1, "B", 6}},
     70     {"A 0", "A 1", "B 0", "B 1", "B 2", "B 3", "B 4", "B 5"}
     71   },
     72   // Two sources, equal weight, exactly 8 items but source A has none.
     73   {
     74     {{1, "A", 0}, {1, "B", 8}},
     75     {"B 0", "B 1", "B 2", "B 3", "B 4", "B 5", "B 6", "B 7"}
     76   },
     77   // Two sources, equal weight, exactly 8 items but source B has none.
     78   {
     79     {{1, "A", 8}, {1, "B", 0}},
     80     {"A 0", "A 1", "A 2", "A 3", "A 4", "A 5", "A 6", "A 7"}
     81   },
     82   // Two sources, equal weight, less than 8 items.
     83   {
     84     {{1, "A", 3}, {1, "B", 3}},
     85     {"A 0", "A 1", "A 2", "B 0", "B 1", "B 2"}
     86   },
     87   // Two sources, equal weight, less than 8 items but source A has more.
     88   {
     89     {{1, "A", 4}, {1, "B", 3}},
     90     {"A 0", "A 1", "A 2", "A 3", "B 0", "B 1", "B 2"}
     91   },
     92   // Two sources, equal weight, less than 8 items but source B has more.
     93   {
     94     {{1, "A", 1}, {1, "B", 3}},
     95     {"A 0", "B 0", "B 1", "B 2"}
     96   },
     97   // Two sources, weights 3/4 A  1/4 B, more than 8 items.
     98   {
     99     {{3, "A", 10}, {1, "B", 10}},
    100     {"A 0", "A 1", "A 2", "A 3", "A 4", "A 5", "B 0", "B 1"}
    101   },
    102   // Two sources, weights 1/8 A  7/8 B, more than 8 items.
    103   {
    104     {{1, "A", 10}, {7, "B", 10}},
    105     {"A 0", "B 0", "B 1", "B 2", "B 3", "B 4", "B 5", "B 6"}
    106   },
    107   // Two sources, weights 1/3 A  2/3 B, more than 8 items.
    108   {
    109     {{1, "A", 10}, {2, "B", 10}},
    110     {"A 0", "A 1", "B 0", "B 1", "B 2", "B 3", "B 4", "B 5"}
    111   },
    112   // Three sources, weights 1/2 A  1/4 B  1/4 C, more than 8 items.
    113   {
    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"}
    116   },
    117   // Three sources, weights 1/3 A  1/3 B  1/3 C, more than 8 items.
    118   {
    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"}
    121   },
    122   // Extra items should be grouped together.
    123   {
    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"}
    126   }
    127 };
    128 
    129 }  // namespace
    130 
    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 {
    136  public:
    137   explicit SuggestionsSourceStub(int weight,
    138       const std::string& source_name, int number_of_suggestions)
    139       : combiner_(NULL),
    140         weight_(weight),
    141         source_name_(source_name),
    142         number_of_suggestions_(number_of_suggestions),
    143         debug_(false) {
    144   }
    145   virtual ~SuggestionsSourceStub() {
    146     STLDeleteElements(&items_);
    147   }
    148 
    149   // Call this method to simulate that the SuggestionsSource has received all
    150   // its suggestions.
    151   void Done() {
    152     combiner_->OnItemsReady();
    153   }
    154 
    155  private:
    156   // SuggestionsSource Override and implementation.
    157   virtual void SetDebug(bool enable) OVERRIDE {
    158     debug_ = enable;
    159   }
    160   virtual int GetWeight() OVERRIDE {
    161     return weight_;
    162   }
    163   virtual int GetItemCount() OVERRIDE {
    164     return items_.size();
    165   }
    166   virtual base::DictionaryValue* PopItem() OVERRIDE {
    167     if (items_.empty())
    168       return NULL;
    169     DictionaryValue* item = items_.front();
    170     items_.pop_front();
    171     return item;
    172   }
    173 
    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);
    179     }
    180   }
    181 
    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     DictionaryValue* item = new DictionaryValue();
    186     item->SetString("title", title);
    187     items_.push_back(item);
    188   }
    189 
    190   virtual void SetCombiner(SuggestionsCombiner* combiner) OVERRIDE {
    191     DCHECK(!combiner_);
    192     combiner_ = combiner;
    193   }
    194 
    195   // Our combiner.
    196   SuggestionsCombiner* combiner_;
    197 
    198   int weight_;
    199   std::string source_name_;
    200   int number_of_suggestions_;
    201   bool debug_;
    202 
    203   // Keep the results of the db query here.
    204   std::deque<base::DictionaryValue*> items_;
    205 
    206   DISALLOW_COPY_AND_ASSIGN(SuggestionsSourceStub);
    207 };
    208 
    209 class SuggestionsCombinerTest : public testing::Test {
    210  public:
    211   SuggestionsCombinerTest() {
    212   }
    213 
    214  protected:
    215   Profile* profile_;
    216   SuggestionsHandler* suggestions_handler_;
    217   SuggestionsCombiner* combiner_;
    218 
    219   void Reset() {
    220     delete combiner_;
    221     combiner_ = new SuggestionsCombiner(suggestions_handler_, profile_);
    222   }
    223 
    224  private:
    225   virtual void SetUp() {
    226     profile_ = new TestingProfile();
    227     suggestions_handler_ = new SuggestionsHandler();
    228     combiner_ = new SuggestionsCombiner(suggestions_handler_, profile_);
    229   }
    230 
    231   virtual void TearDown() {
    232     delete combiner_;
    233     delete suggestions_handler_;
    234     delete profile_;
    235   }
    236 
    237   DISALLOW_COPY_AND_ASSIGN(SuggestionsCombinerTest);
    238 };
    239 
    240 TEST_F(SuggestionsCombinerTest, NoSource) {
    241   combiner_->FetchItems(NULL);
    242   EXPECT_EQ(0UL, combiner_->GetPageValues()->GetSize());
    243 }
    244 
    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());
    250 }
    251 
    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);
    257 
    258     scoped_ptr<SuggestionsSourceStub*[]> sources(
    259         new SuggestionsSourceStub*[source_count]);
    260 
    261     // Setup sources.
    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]);
    269       } else {
    270         sources[j] = NULL;
    271       }
    272     }
    273 
    274     // Start fetching.
    275     combiner_->FetchItems(NULL);
    276 
    277     // Sources complete.
    278     for (size_t j = 0; j < source_count; ++j) {
    279       if (sources[j])
    280         sources[j]->Done();
    281     }
    282 
    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) {
    289         std::string value;
    290         base::DictionaryValue* dictionary;
    291         results->GetDictionary(j, &dictionary);
    292         dictionary->GetString("title", &value);
    293         EXPECT_STREQ(description.results[j], value.c_str()) <<
    294             " test index:" << i;
    295       } else {
    296         EXPECT_EQ(description.results[j], static_cast<const char*>(NULL)) <<
    297             " test index:" << i;
    298       }
    299     }
    300 
    301     Reset();
    302   }
    303 }
    304 
    305