Home | History | Annotate | Download | only in spellchecker
      1 // Copyright (c) 2013 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 // Unit tests for |FeedbackSender| object.
      6 
      7 #include "chrome/browser/spellchecker/feedback_sender.h"
      8 
      9 #include "base/bind.h"
     10 #include "base/command_line.h"
     11 #include "base/json/json_reader.h"
     12 #include "base/message_loop/message_loop.h"
     13 #include "base/metrics/field_trial.h"
     14 #include "base/strings/stringprintf.h"
     15 #include "base/strings/utf_string_conversions.h"
     16 #include "base/values.h"
     17 #include "chrome/common/chrome_switches.h"
     18 #include "chrome/common/spellcheck_common.h"
     19 #include "chrome/common/spellcheck_marker.h"
     20 #include "chrome/common/spellcheck_result.h"
     21 #include "chrome/test/base/testing_profile.h"
     22 #include "components/variations/entropy_provider.h"
     23 #include "content/public/test/test_browser_thread.h"
     24 #include "net/url_request/test_url_fetcher_factory.h"
     25 #include "testing/gtest/include/gtest/gtest.h"
     26 
     27 namespace spellcheck {
     28 
     29 namespace {
     30 
     31 const char kCountry[] = "USA";
     32 const char kLanguage[] = "en";
     33 const char kText[] = "Helllo world.";
     34 const int kMisspellingLength = 6;
     35 const int kMisspellingStart = 0;
     36 const int kRendererProcessId = 0;
     37 const int kUrlFetcherId = 0;
     38 
     39 // Builds a simple spellcheck result.
     40 SpellCheckResult BuildSpellCheckResult() {
     41   return SpellCheckResult(SpellCheckResult::SPELLING,
     42                           kMisspellingStart,
     43                           kMisspellingLength,
     44                           UTF8ToUTF16("Hello"));
     45 }
     46 
     47 // Returns the number of times that |needle| appears in |haystack| without
     48 // overlaps. For example, CountOccurences("bananana", "nana") returns 1.
     49 int CountOccurences(const std::string& haystack, const std::string& needle) {
     50   int number_of_occurrences = 0;
     51   for (size_t pos = haystack.find(needle);
     52        pos != std::string::npos;
     53        pos = haystack.find(needle, pos + needle.length())) {
     54     ++number_of_occurrences;
     55   }
     56   return number_of_occurrences;
     57 }
     58 
     59 }  // namespace
     60 
     61 // A test fixture to help keep tests simple.
     62 class FeedbackSenderTest : public testing::Test {
     63  public:
     64   FeedbackSenderTest() : ui_thread_(content::BrowserThread::UI, &loop_) {
     65     feedback_.reset(new FeedbackSender(NULL, kLanguage, kCountry));
     66     feedback_->StartFeedbackCollection();
     67   }
     68 
     69   virtual ~FeedbackSenderTest() {}
     70 
     71  protected:
     72   // Appends the "--enable-spelling-service-feedback" switch to the
     73   // command-line.
     74   void AppendCommandLineSwitch() {
     75     // The command-line switch is temporary.
     76     // TODO(rouslan): Remove the command-line switch. http://crbug.com/247726
     77     CommandLine::ForCurrentProcess()->AppendSwitch(
     78         switches::kEnableSpellingFeedbackFieldTrial);
     79     feedback_.reset(new FeedbackSender(NULL, kLanguage, kCountry));
     80     feedback_->StartFeedbackCollection();
     81   }
     82 
     83   // Enables the "SpellingServiceFeedback.Enabled" field trial.
     84   void EnableFieldTrial() {
     85     // The field trial is temporary.
     86     // TODO(rouslan): Remove the field trial. http://crbug.com/247726
     87     field_trial_list_.reset(
     88         new base::FieldTrialList(new metrics::SHA1EntropyProvider("foo")));
     89     field_trial_ = base::FieldTrialList::CreateFieldTrial(
     90         kFeedbackFieldTrialName, kFeedbackFieldTrialEnabledGroupName);
     91     field_trial_->group();
     92     feedback_.reset(new FeedbackSender(NULL, kLanguage, kCountry));
     93     feedback_->StartFeedbackCollection();
     94   }
     95 
     96   uint32 AddPendingFeedback() {
     97     std::vector<SpellCheckResult> results(1, BuildSpellCheckResult());
     98     feedback_->OnSpellcheckResults(kRendererProcessId,
     99                                    UTF8ToUTF16(kText),
    100                                    std::vector<SpellCheckMarker>(),
    101                                    &results);
    102     return results[0].hash;
    103   }
    104 
    105   void ExpireSession() {
    106     feedback_->session_start_ =
    107         base::Time::Now() -
    108         base::TimeDelta::FromHours(chrome::spellcheck_common::kSessionHours);
    109   }
    110 
    111   bool UploadDataContains(const std::string& data) const {
    112     const net::TestURLFetcher* fetcher =
    113         fetchers_.GetFetcherByID(kUrlFetcherId);
    114     return fetcher && CountOccurences(fetcher->upload_data(), data) > 0;
    115   }
    116 
    117   bool UploadDataContains(const std::string& data,
    118                           int number_of_occurrences) const {
    119     const net::TestURLFetcher* fetcher =
    120         fetchers_.GetFetcherByID(kUrlFetcherId);
    121     return fetcher && CountOccurences(fetcher->upload_data(), data) ==
    122                           number_of_occurrences;
    123   }
    124 
    125   // Returns true if the feedback sender would be uploading data now. The test
    126   // does not open network connections.
    127   bool IsUploadingData() const {
    128     return !!fetchers_.GetFetcherByID(kUrlFetcherId);
    129   }
    130 
    131   void ClearUploadData() {
    132     fetchers_.RemoveFetcherFromMap(kUrlFetcherId);
    133   }
    134 
    135   std::string GetUploadData() const {
    136     const net::TestURLFetcher* fetcher =
    137         fetchers_.GetFetcherByID(kUrlFetcherId);
    138     return fetcher ? fetcher->upload_data() : std::string();
    139   }
    140 
    141   scoped_ptr<spellcheck::FeedbackSender> feedback_;
    142 
    143  private:
    144   TestingProfile profile_;
    145   base::MessageLoop loop_;
    146   content::TestBrowserThread ui_thread_;
    147   scoped_ptr<base::FieldTrialList> field_trial_list_;
    148   scoped_refptr<base::FieldTrial> field_trial_;
    149   net::TestURLFetcherFactory fetchers_;
    150 };
    151 
    152 // Do not send data if there's no feedback.
    153 TEST_F(FeedbackSenderTest, NoFeedback) {
    154   EXPECT_FALSE(IsUploadingData());
    155   feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
    156                                       std::vector<uint32>());
    157   EXPECT_FALSE(IsUploadingData());
    158 }
    159 
    160 // Do not send data if not aware of which markers are still in the document.
    161 TEST_F(FeedbackSenderTest, NoDocumentMarkersReceived) {
    162   EXPECT_FALSE(IsUploadingData());
    163   uint32 hash = AddPendingFeedback();
    164   EXPECT_FALSE(IsUploadingData());
    165   static const int kSuggestionIndex = 1;
    166   feedback_->SelectedSuggestion(hash, kSuggestionIndex);
    167   EXPECT_FALSE(IsUploadingData());
    168 }
    169 
    170 // Send PENDING feedback message if the marker is still in the document, and the
    171 // user has not performed any action on it.
    172 TEST_F(FeedbackSenderTest, PendingFeedback) {
    173   uint32 hash = AddPendingFeedback();
    174   feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
    175                                       std::vector<uint32>(1, hash));
    176   EXPECT_TRUE(UploadDataContains("\"actionType\":\"PENDING\""));
    177 }
    178 
    179 // Send NO_ACTION feedback message if the marker has been removed from the
    180 // document.
    181 TEST_F(FeedbackSenderTest, NoActionFeedback) {
    182   AddPendingFeedback();
    183   feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
    184                                       std::vector<uint32>());
    185   EXPECT_TRUE(UploadDataContains("\"actionType\":\"NO_ACTION\""));
    186 }
    187 
    188 // Send SELECT feedback message if the user has selected a spelling suggestion.
    189 TEST_F(FeedbackSenderTest, SelectFeedback) {
    190   uint32 hash = AddPendingFeedback();
    191   static const int kSuggestionIndex = 0;
    192   feedback_->SelectedSuggestion(hash, kSuggestionIndex);
    193   feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
    194                                       std::vector<uint32>());
    195   EXPECT_TRUE(UploadDataContains("\"actionType\":\"SELECT\""));
    196   EXPECT_TRUE(UploadDataContains("\"actionTargetIndex\":" + kSuggestionIndex));
    197 }
    198 
    199 // Send ADD_TO_DICT feedback message if the user has added the misspelled word
    200 // to the custom dictionary.
    201 TEST_F(FeedbackSenderTest, AddToDictFeedback) {
    202   uint32 hash = AddPendingFeedback();
    203   feedback_->AddedToDictionary(hash);
    204   feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
    205                                       std::vector<uint32>());
    206   EXPECT_TRUE(UploadDataContains("\"actionType\":\"ADD_TO_DICT\""));
    207 }
    208 
    209 // Send IN_DICTIONARY feedback message if the user has the misspelled word in
    210 // the custom dictionary.
    211 TEST_F(FeedbackSenderTest, InDictionaryFeedback) {
    212   uint32 hash = AddPendingFeedback();
    213   feedback_->RecordInDictionary(hash);
    214   feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
    215                                       std::vector<uint32>());
    216   EXPECT_TRUE(UploadDataContains("\"actionType\":\"IN_DICTIONARY\""));
    217 }
    218 
    219 // Send PENDING feedback message if the user saw the spelling suggestion, but
    220 // decided to not select it, and the marker is still in the document.
    221 TEST_F(FeedbackSenderTest, IgnoreFeedbackMarkerInDocument) {
    222   uint32 hash = AddPendingFeedback();
    223   feedback_->IgnoredSuggestions(hash);
    224   feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
    225                                       std::vector<uint32>(1, hash));
    226   EXPECT_TRUE(UploadDataContains("\"actionType\":\"PENDING\""));
    227 }
    228 
    229 // Send IGNORE feedback message if the user saw the spelling suggestion, but
    230 // decided to not select it, and the marker is no longer in the document.
    231 TEST_F(FeedbackSenderTest, IgnoreFeedbackMarkerNotInDocument) {
    232   uint32 hash = AddPendingFeedback();
    233   feedback_->IgnoredSuggestions(hash);
    234   feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
    235                                       std::vector<uint32>());
    236   EXPECT_TRUE(UploadDataContains("\"actionType\":\"IGNORE\""));
    237 }
    238 
    239 // Send MANUALLY_CORRECTED feedback message if the user manually corrected the
    240 // misspelled word.
    241 TEST_F(FeedbackSenderTest, ManuallyCorrectedFeedback) {
    242   uint32 hash = AddPendingFeedback();
    243   static const std::string kManualCorrection = "Howdy";
    244   feedback_->ManuallyCorrected(hash, ASCIIToUTF16(kManualCorrection));
    245   feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
    246                                       std::vector<uint32>());
    247   EXPECT_TRUE(UploadDataContains("\"actionType\":\"MANUALLY_CORRECTED\""));
    248   EXPECT_TRUE(UploadDataContains("\"actionTargetValue\":\"" +
    249                                  kManualCorrection + "\""));
    250 }
    251 
    252 // Send feedback messages in batch.
    253 TEST_F(FeedbackSenderTest, BatchFeedback) {
    254   std::vector<SpellCheckResult> results;
    255   results.push_back(SpellCheckResult(SpellCheckResult::SPELLING,
    256                                      kMisspellingStart,
    257                                      kMisspellingLength,
    258                                      ASCIIToUTF16("Hello")));
    259   static const int kSecondMisspellingStart = 7;
    260   static const int kSecondMisspellingLength = 5;
    261   results.push_back(SpellCheckResult(SpellCheckResult::SPELLING,
    262                                      kSecondMisspellingStart,
    263                                      kSecondMisspellingLength,
    264                                      ASCIIToUTF16("world")));
    265   feedback_->OnSpellcheckResults(kRendererProcessId,
    266                                  UTF8ToUTF16(kText),
    267                                  std::vector<SpellCheckMarker>(),
    268                                  &results);
    269   feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
    270                                       std::vector<uint32>());
    271   EXPECT_TRUE(UploadDataContains("\"actionType\":\"NO_ACTION\"", 2));
    272 }
    273 
    274 // Send a series of PENDING feedback messages and one final NO_ACTION feedback
    275 // message with the same hash identifier for a single misspelling.
    276 TEST_F(FeedbackSenderTest, SameHashFeedback) {
    277   uint32 hash = AddPendingFeedback();
    278   std::vector<uint32> remaining_markers(1, hash);
    279 
    280   feedback_->OnReceiveDocumentMarkers(kRendererProcessId, remaining_markers);
    281   EXPECT_TRUE(UploadDataContains("\"actionType\":\"PENDING\""));
    282   std::string hash_string = base::StringPrintf("\"suggestionId\":\"%u\"", hash);
    283   EXPECT_TRUE(UploadDataContains(hash_string));
    284   ClearUploadData();
    285 
    286   feedback_->OnReceiveDocumentMarkers(kRendererProcessId, remaining_markers);
    287   EXPECT_TRUE(UploadDataContains("\"actionType\":\"PENDING\""));
    288   EXPECT_TRUE(UploadDataContains(hash_string));
    289   ClearUploadData();
    290 
    291   feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
    292                                       std::vector<uint32>());
    293   EXPECT_TRUE(UploadDataContains("\"actionType\":\"NO_ACTION\""));
    294   EXPECT_TRUE(UploadDataContains(hash_string));
    295   ClearUploadData();
    296 
    297   feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
    298                                       std::vector<uint32>());
    299   EXPECT_FALSE(IsUploadingData());
    300 }
    301 
    302 // When a session expires:
    303 // 1) Pending feedback is finalized and sent to the server in the last message
    304 //    batch in the session.
    305 // 2) No feedback is sent until a spellcheck request happens.
    306 // 3) Existing markers get new hash identifiers.
    307 TEST_F(FeedbackSenderTest, SessionExpirationFeedback) {
    308   std::vector<SpellCheckResult> results(
    309       1,
    310       SpellCheckResult(SpellCheckResult::SPELLING,
    311                        kMisspellingStart,
    312                        kMisspellingLength,
    313                        ASCIIToUTF16("Hello")));
    314   feedback_->OnSpellcheckResults(kRendererProcessId,
    315                                  UTF8ToUTF16(kText),
    316                                  std::vector<SpellCheckMarker>(),
    317                                  &results);
    318   uint32 original_hash = results[0].hash;
    319   std::vector<uint32> remaining_markers(1, original_hash);
    320 
    321   feedback_->OnReceiveDocumentMarkers(kRendererProcessId, remaining_markers);
    322   EXPECT_FALSE(UploadDataContains("\"actionType\":\"NO_ACTION\""));
    323   EXPECT_TRUE(UploadDataContains("\"actionType\":\"PENDING\""));
    324   std::string original_hash_string =
    325       base::StringPrintf("\"suggestionId\":\"%u\"", original_hash);
    326   EXPECT_TRUE(UploadDataContains(original_hash_string));
    327   ClearUploadData();
    328 
    329   ExpireSession();
    330 
    331   // Last message batch in the current session has only finalized messages.
    332   feedback_->OnReceiveDocumentMarkers(kRendererProcessId, remaining_markers);
    333   EXPECT_TRUE(UploadDataContains("\"actionType\":\"NO_ACTION\""));
    334   EXPECT_FALSE(UploadDataContains("\"actionType\":\"PENDING\""));
    335   EXPECT_TRUE(UploadDataContains(original_hash_string));
    336   ClearUploadData();
    337 
    338   // The next session starts on the next spellchecker request. Until then,
    339   // there's no more feedback sent.
    340   feedback_->OnReceiveDocumentMarkers(kRendererProcessId, remaining_markers);
    341   EXPECT_FALSE(IsUploadingData());
    342 
    343   // The first spellcheck request after session expiration creates different
    344   // document marker hash identifiers.
    345   std::vector<SpellCheckMarker> original_markers(
    346       1, SpellCheckMarker(results[0].hash, results[0].location));
    347   results[0] = SpellCheckResult(SpellCheckResult::SPELLING,
    348                                 kMisspellingStart,
    349                                 kMisspellingLength,
    350                                 ASCIIToUTF16("Hello"));
    351   feedback_->OnSpellcheckResults(
    352       kRendererProcessId, UTF8ToUTF16(kText), original_markers, &results);
    353   uint32 updated_hash = results[0].hash;
    354   EXPECT_NE(updated_hash, original_hash);
    355   remaining_markers[0] = updated_hash;
    356 
    357   // The first feedback message batch in session |i + 1| has the new document
    358   // marker hash identifiers.
    359   feedback_->OnReceiveDocumentMarkers(kRendererProcessId, remaining_markers);
    360   EXPECT_FALSE(UploadDataContains("\"actionType\":\"NO_ACTION\""));
    361   EXPECT_TRUE(UploadDataContains("\"actionType\":\"PENDING\""));
    362   EXPECT_FALSE(UploadDataContains(original_hash_string));
    363   std::string updated_hash_string =
    364       base::StringPrintf("\"suggestionId\":\"%u\"", updated_hash);
    365   EXPECT_TRUE(UploadDataContains(updated_hash_string));
    366 }
    367 
    368 // First message in session has an indicator.
    369 TEST_F(FeedbackSenderTest, FirstMessageInSessionIndicator) {
    370   // Session 1, message 1
    371   AddPendingFeedback();
    372   feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
    373                                       std::vector<uint32>());
    374   EXPECT_TRUE(UploadDataContains("\"isFirstInSession\":true"));
    375 
    376   // Session 1, message 2
    377   AddPendingFeedback();
    378   feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
    379                                       std::vector<uint32>());
    380   EXPECT_TRUE(UploadDataContains("\"isFirstInSession\":false"));
    381 
    382   ExpireSession();
    383 
    384   // Session 1, message 3 (last)
    385   AddPendingFeedback();
    386   feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
    387                                       std::vector<uint32>());
    388   EXPECT_TRUE(UploadDataContains("\"isFirstInSession\":false"));
    389 
    390   // Session 2, message 1
    391   AddPendingFeedback();
    392   feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
    393                                       std::vector<uint32>());
    394   EXPECT_TRUE(UploadDataContains("\"isFirstInSession\":true"));
    395 
    396   // Session 2, message 2
    397   AddPendingFeedback();
    398   feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
    399                                       std::vector<uint32>());
    400   EXPECT_TRUE(UploadDataContains("\"isFirstInSession\":false"));
    401 }
    402 
    403 // Flush all feedback when the spellcheck language and country change.
    404 TEST_F(FeedbackSenderTest, OnLanguageCountryChange) {
    405   AddPendingFeedback();
    406   feedback_->OnLanguageCountryChange("pt", "BR");
    407   EXPECT_TRUE(UploadDataContains("\"language\":\"en\""));
    408   AddPendingFeedback();
    409   feedback_->OnLanguageCountryChange("en", "US");
    410   EXPECT_TRUE(UploadDataContains("\"language\":\"pt\""));
    411 }
    412 
    413 // The field names and types should correspond to the API.
    414 TEST_F(FeedbackSenderTest, FeedbackAPI) {
    415   AddPendingFeedback();
    416   feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
    417                                       std::vector<uint32>());
    418   std::string actual_data = GetUploadData();
    419   scoped_ptr<base::DictionaryValue> actual(
    420       static_cast<base::DictionaryValue*>(base::JSONReader::Read(actual_data)));
    421   actual->SetString("params.key", "TestDummyKey");
    422   base::ListValue* suggestions = NULL;
    423   actual->GetList("params.suggestionInfo", &suggestions);
    424   base::DictionaryValue* suggestion = NULL;
    425   suggestions->GetDictionary(0, &suggestion);
    426   suggestion->SetString("suggestionId", "42");
    427   suggestion->SetString("timestamp", "9001");
    428   static const std::string expected_data =
    429       "{\"apiVersion\":\"v2\","
    430       "\"method\":\"spelling.feedback\","
    431       "\"params\":"
    432       "{\"clientName\":\"Chrome\","
    433       "\"originCountry\":\"USA\","
    434       "\"key\":\"TestDummyKey\","
    435       "\"language\":\"en\","
    436       "\"suggestionInfo\":[{"
    437       "\"isAutoCorrection\":false,"
    438       "\"isFirstInSession\":true,"
    439       "\"misspelledLength\":6,"
    440       "\"misspelledStart\":0,"
    441       "\"originalText\":\"Helllo world\","
    442       "\"suggestionId\":\"42\","
    443       "\"suggestions\":[\"Hello\"],"
    444       "\"timestamp\":\"9001\","
    445       "\"userActions\":[{\"actionType\":\"NO_ACTION\"}]}]}}";
    446   scoped_ptr<base::Value> expected(base::JSONReader::Read(expected_data));
    447   EXPECT_TRUE(expected->Equals(actual.get()))
    448       << "Expected data: " << expected_data
    449       << "\nActual data:   " << actual_data;
    450 }
    451 
    452 // The default API version is "v2".
    453 TEST_F(FeedbackSenderTest, DefaultApiVersion) {
    454   AddPendingFeedback();
    455   feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
    456                                       std::vector<uint32>());
    457   EXPECT_TRUE(UploadDataContains("\"apiVersion\":\"v2\""));
    458   EXPECT_FALSE(UploadDataContains("\"apiVersion\":\"v2-internal\""));
    459 }
    460 
    461 // The API version should not change for field-trial participants that do not
    462 // append the command-line switch.
    463 TEST_F(FeedbackSenderTest, FieldTrialAloneHasSameApiVersion) {
    464   EnableFieldTrial();
    465 
    466   AddPendingFeedback();
    467   feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
    468                                       std::vector<uint32>());
    469 
    470   EXPECT_TRUE(UploadDataContains("\"apiVersion\":\"v2\""));
    471   EXPECT_FALSE(UploadDataContains("\"apiVersion\":\"v2-internal\""));
    472 }
    473 
    474 // The API version should not change if the command-line switch is appended, but
    475 // the user is not participating in the field-trial.
    476 TEST_F(FeedbackSenderTest, CommandLineSwitchAloneHasSameApiVersion) {
    477   AppendCommandLineSwitch();
    478 
    479   AddPendingFeedback();
    480   feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
    481                                       std::vector<uint32>());
    482 
    483   EXPECT_TRUE(UploadDataContains("\"apiVersion\":\"v2\""));
    484   EXPECT_FALSE(UploadDataContains("\"apiVersion\":\"v2-internal\""));
    485 }
    486 
    487 // The API version should be different for field-trial participants that also
    488 // append the command-line switch.
    489 TEST_F(FeedbackSenderTest, InternalApiVersion) {
    490   AppendCommandLineSwitch();
    491   EnableFieldTrial();
    492 
    493   AddPendingFeedback();
    494   feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
    495                                       std::vector<uint32>());
    496 
    497   EXPECT_FALSE(UploadDataContains("\"apiVersion\":\"v2\""));
    498   EXPECT_TRUE(UploadDataContains("\"apiVersion\":\"v2-internal\""));
    499 }
    500 
    501 // Duplicate spellcheck results should be matched to the existing markers.
    502 TEST_F(FeedbackSenderTest, MatchDupliateResultsWithExistingMarkers) {
    503   uint32 hash = AddPendingFeedback();
    504   std::vector<SpellCheckResult> results(
    505       1,
    506       SpellCheckResult(SpellCheckResult::SPELLING,
    507                        kMisspellingStart,
    508                        kMisspellingLength,
    509                        ASCIIToUTF16("Hello")));
    510   std::vector<SpellCheckMarker> markers(
    511       1, SpellCheckMarker(hash, results[0].location));
    512   EXPECT_EQ(static_cast<uint32>(0), results[0].hash);
    513   feedback_->OnSpellcheckResults(
    514       kRendererProcessId, UTF8ToUTF16(kText), markers, &results);
    515   EXPECT_EQ(hash, results[0].hash);
    516 }
    517 
    518 // Adding a word to dictionary should trigger ADD_TO_DICT feedback for every
    519 // occurrence of that word.
    520 TEST_F(FeedbackSenderTest, MultipleAddToDictFeedback) {
    521   std::vector<SpellCheckResult> results;
    522   static const int kSentenceLength = 14;
    523   static const int kNumberOfSentences = 2;
    524   static const base::string16 kTextWithDuplicates =
    525       ASCIIToUTF16("Helllo world. Helllo world.");
    526   for (int i = 0; i < kNumberOfSentences; ++i) {
    527     results.push_back(SpellCheckResult(SpellCheckResult::SPELLING,
    528                                        kMisspellingStart + i * kSentenceLength,
    529                                        kMisspellingLength,
    530                                        ASCIIToUTF16("Hello")));
    531   }
    532   static const int kNumberOfRenderers = 2;
    533   int last_renderer_process_id = -1;
    534   for (int i = 0; i < kNumberOfRenderers; ++i) {
    535     feedback_->OnSpellcheckResults(kRendererProcessId + i,
    536                                    kTextWithDuplicates,
    537                                    std::vector<SpellCheckMarker>(),
    538                                    &results);
    539     last_renderer_process_id = kRendererProcessId + i;
    540   }
    541   std::vector<uint32> remaining_markers;
    542   for (size_t i = 0; i < results.size(); ++i)
    543     remaining_markers.push_back(results[i].hash);
    544   feedback_->OnReceiveDocumentMarkers(last_renderer_process_id,
    545                                       remaining_markers);
    546   EXPECT_TRUE(UploadDataContains("PENDING", 2));
    547   EXPECT_FALSE(UploadDataContains("ADD_TO_DICT"));
    548 
    549   feedback_->AddedToDictionary(results[0].hash);
    550   feedback_->OnReceiveDocumentMarkers(last_renderer_process_id,
    551                                       remaining_markers);
    552   EXPECT_FALSE(UploadDataContains("PENDING"));
    553   EXPECT_TRUE(UploadDataContains("ADD_TO_DICT", 2));
    554 }
    555 
    556 // ADD_TO_DICT feedback for multiple occurrences of a word should trigger only
    557 // for pending feedback.
    558 TEST_F(FeedbackSenderTest, AddToDictOnlyPending) {
    559   AddPendingFeedback();
    560   uint32 add_to_dict_hash = AddPendingFeedback();
    561   uint32 select_hash = AddPendingFeedback();
    562   feedback_->SelectedSuggestion(select_hash, 0);
    563   feedback_->AddedToDictionary(add_to_dict_hash);
    564   feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
    565                                       std::vector<uint32>());
    566   EXPECT_TRUE(UploadDataContains("SELECT", 1));
    567   EXPECT_TRUE(UploadDataContains("ADD_TO_DICT", 2));
    568 }
    569 
    570 // Spellcheck results that are out-of-bounds are not added to feedback.
    571 TEST_F(FeedbackSenderTest, IgnoreOutOfBounds) {
    572   std::vector<SpellCheckResult> results;
    573   results.push_back(SpellCheckResult(
    574       SpellCheckResult::SPELLING, 0, 100, UTF8ToUTF16("Hello")));
    575   results.push_back(SpellCheckResult(
    576       SpellCheckResult::SPELLING, 100, 3, UTF8ToUTF16("world")));
    577   results.push_back(
    578       SpellCheckResult(SpellCheckResult::SPELLING, -1, 3, UTF8ToUTF16("how")));
    579   results.push_back(
    580       SpellCheckResult(SpellCheckResult::SPELLING, 0, 0, UTF8ToUTF16("are")));
    581   results.push_back(
    582       SpellCheckResult(SpellCheckResult::SPELLING, 2, -1, UTF8ToUTF16("you")));
    583   feedback_->OnSpellcheckResults(kRendererProcessId,
    584                                  UTF8ToUTF16(kText),
    585                                  std::vector<SpellCheckMarker>(),
    586                                  &results);
    587   feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
    588                                       std::vector<uint32>());
    589   EXPECT_FALSE(IsUploadingData());
    590 }
    591 
    592 // FeedbackSender does not collect and upload feedback when instructed to stop.
    593 TEST_F(FeedbackSenderTest, CanStopFeedbackCollection) {
    594   feedback_->StopFeedbackCollection();
    595   AddPendingFeedback();
    596   feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
    597                                       std::vector<uint32>());
    598   EXPECT_FALSE(IsUploadingData());
    599 }
    600 
    601 // FeedbackSender resumes collecting and uploading feedback when instructed to
    602 // start after stopping.
    603 TEST_F(FeedbackSenderTest, CanResumeFeedbackCollection) {
    604   feedback_->StopFeedbackCollection();
    605   feedback_->StartFeedbackCollection();
    606   AddPendingFeedback();
    607   feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
    608                                       std::vector<uint32>());
    609   EXPECT_TRUE(IsUploadingData());
    610 }
    611 
    612 // FeedbackSender does not collect data while being stopped and upload it later.
    613 TEST_F(FeedbackSenderTest, NoFeedbackCollectionWhenStopped) {
    614   feedback_->StopFeedbackCollection();
    615   AddPendingFeedback();
    616   feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
    617                                       std::vector<uint32>());
    618   feedback_->StartFeedbackCollection();
    619   feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
    620                                       std::vector<uint32>());
    621   EXPECT_FALSE(IsUploadingData());
    622 }
    623 
    624 }  // namespace spellcheck
    625