root/chrome/browser/spellchecker/feedback_sender_unittest.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. BuildSpellCheckResult
  2. CountOccurences
  3. AppendCommandLineSwitch
  4. EnableFieldTrial
  5. AddPendingFeedback
  6. ExpireSession
  7. UploadDataContains
  8. UploadDataContains
  9. IsUploadingData
  10. ClearUploadData
  11. GetUploadData
  12. TEST_F
  13. TEST_F
  14. TEST_F
  15. TEST_F
  16. TEST_F
  17. TEST_F
  18. TEST_F
  19. TEST_F
  20. TEST_F
  21. TEST_F
  22. TEST_F
  23. TEST_F
  24. TEST_F
  25. TEST_F
  26. TEST_F
  27. TEST_F
  28. TEST_F
  29. TEST_F
  30. TEST_F
  31. TEST_F
  32. TEST_F
  33. TEST_F
  34. TEST_F
  35. TEST_F
  36. TEST_F
  37. TEST_F
  38. TEST_F

// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Unit tests for |FeedbackSender| object.

#include "chrome/browser/spellchecker/feedback_sender.h"

#include "base/bind.h"
#include "base/command_line.h"
#include "base/json/json_reader.h"
#include "base/message_loop/message_loop.h"
#include "base/metrics/field_trial.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/spellcheck_common.h"
#include "chrome/common/spellcheck_marker.h"
#include "chrome/common/spellcheck_result.h"
#include "chrome/test/base/testing_profile.h"
#include "components/variations/entropy_provider.h"
#include "content/public/test/test_browser_thread.h"
#include "net/url_request/test_url_fetcher_factory.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace spellcheck {

namespace {

const char kCountry[] = "USA";
const char kLanguage[] = "en";
const char kText[] = "Helllo world.";
const int kMisspellingLength = 6;
const int kMisspellingStart = 0;
const int kRendererProcessId = 0;
const int kUrlFetcherId = 0;

// Builds a simple spellcheck result.
SpellCheckResult BuildSpellCheckResult() {
  return SpellCheckResult(SpellCheckResult::SPELLING,
                          kMisspellingStart,
                          kMisspellingLength,
                          base::UTF8ToUTF16("Hello"));
}

// Returns the number of times that |needle| appears in |haystack| without
// overlaps. For example, CountOccurences("bananana", "nana") returns 1.
int CountOccurences(const std::string& haystack, const std::string& needle) {
  int number_of_occurrences = 0;
  for (size_t pos = haystack.find(needle);
       pos != std::string::npos;
       pos = haystack.find(needle, pos + needle.length())) {
    ++number_of_occurrences;
  }
  return number_of_occurrences;
}

}  // namespace

// A test fixture to help keep tests simple.
class FeedbackSenderTest : public testing::Test {
 public:
  FeedbackSenderTest() : ui_thread_(content::BrowserThread::UI, &loop_) {
    feedback_.reset(new FeedbackSender(NULL, kLanguage, kCountry));
    feedback_->StartFeedbackCollection();
  }

  virtual ~FeedbackSenderTest() {}

 protected:
  // Appends the "--enable-spelling-service-feedback" switch to the
  // command-line.
  void AppendCommandLineSwitch() {
    // The command-line switch is temporary.
    // TODO(rouslan): Remove the command-line switch. http://crbug.com/247726
    CommandLine::ForCurrentProcess()->AppendSwitch(
        switches::kEnableSpellingFeedbackFieldTrial);
    feedback_.reset(new FeedbackSender(NULL, kLanguage, kCountry));
    feedback_->StartFeedbackCollection();
  }

  // Enables the "SpellingServiceFeedback.Enabled" field trial.
  void EnableFieldTrial() {
    // The field trial is temporary.
    // TODO(rouslan): Remove the field trial. http://crbug.com/247726
    field_trial_list_.reset(
        new base::FieldTrialList(new metrics::SHA1EntropyProvider("foo")));
    field_trial_ = base::FieldTrialList::CreateFieldTrial(
        kFeedbackFieldTrialName, kFeedbackFieldTrialEnabledGroupName);
    field_trial_->group();
    feedback_.reset(new FeedbackSender(NULL, kLanguage, kCountry));
    feedback_->StartFeedbackCollection();
  }

  uint32 AddPendingFeedback() {
    std::vector<SpellCheckResult> results(1, BuildSpellCheckResult());
    feedback_->OnSpellcheckResults(kRendererProcessId,
                                   base::UTF8ToUTF16(kText),
                                   std::vector<SpellCheckMarker>(),
                                   &results);
    return results[0].hash;
  }

  void ExpireSession() {
    feedback_->session_start_ =
        base::Time::Now() -
        base::TimeDelta::FromHours(chrome::spellcheck_common::kSessionHours);
  }

  bool UploadDataContains(const std::string& data) const {
    const net::TestURLFetcher* fetcher =
        fetchers_.GetFetcherByID(kUrlFetcherId);
    return fetcher && CountOccurences(fetcher->upload_data(), data) > 0;
  }

  bool UploadDataContains(const std::string& data,
                          int number_of_occurrences) const {
    const net::TestURLFetcher* fetcher =
        fetchers_.GetFetcherByID(kUrlFetcherId);
    return fetcher && CountOccurences(fetcher->upload_data(), data) ==
                          number_of_occurrences;
  }

  // Returns true if the feedback sender would be uploading data now. The test
  // does not open network connections.
  bool IsUploadingData() const {
    return !!fetchers_.GetFetcherByID(kUrlFetcherId);
  }

  void ClearUploadData() {
    fetchers_.RemoveFetcherFromMap(kUrlFetcherId);
  }

  std::string GetUploadData() const {
    const net::TestURLFetcher* fetcher =
        fetchers_.GetFetcherByID(kUrlFetcherId);
    return fetcher ? fetcher->upload_data() : std::string();
  }

  scoped_ptr<spellcheck::FeedbackSender> feedback_;

 private:
  TestingProfile profile_;
  base::MessageLoop loop_;
  content::TestBrowserThread ui_thread_;
  scoped_ptr<base::FieldTrialList> field_trial_list_;
  scoped_refptr<base::FieldTrial> field_trial_;
  net::TestURLFetcherFactory fetchers_;
};

// Do not send data if there's no feedback.
TEST_F(FeedbackSenderTest, NoFeedback) {
  EXPECT_FALSE(IsUploadingData());
  feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
                                      std::vector<uint32>());
  EXPECT_FALSE(IsUploadingData());
}

// Do not send data if not aware of which markers are still in the document.
TEST_F(FeedbackSenderTest, NoDocumentMarkersReceived) {
  EXPECT_FALSE(IsUploadingData());
  uint32 hash = AddPendingFeedback();
  EXPECT_FALSE(IsUploadingData());
  static const int kSuggestionIndex = 1;
  feedback_->SelectedSuggestion(hash, kSuggestionIndex);
  EXPECT_FALSE(IsUploadingData());
}

// Send PENDING feedback message if the marker is still in the document, and the
// user has not performed any action on it.
TEST_F(FeedbackSenderTest, PendingFeedback) {
  uint32 hash = AddPendingFeedback();
  feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
                                      std::vector<uint32>(1, hash));
  EXPECT_TRUE(UploadDataContains("\"actionType\":\"PENDING\""));
}

// Send NO_ACTION feedback message if the marker has been removed from the
// document.
TEST_F(FeedbackSenderTest, NoActionFeedback) {
  AddPendingFeedback();
  feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
                                      std::vector<uint32>());
  EXPECT_TRUE(UploadDataContains("\"actionType\":\"NO_ACTION\""));
}

// Send SELECT feedback message if the user has selected a spelling suggestion.
TEST_F(FeedbackSenderTest, SelectFeedback) {
  uint32 hash = AddPendingFeedback();
  static const int kSuggestionIndex = 0;
  feedback_->SelectedSuggestion(hash, kSuggestionIndex);
  feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
                                      std::vector<uint32>());
  EXPECT_TRUE(UploadDataContains("\"actionType\":\"SELECT\""));
  EXPECT_TRUE(UploadDataContains("\"actionTargetIndex\":" + kSuggestionIndex));
}

// Send ADD_TO_DICT feedback message if the user has added the misspelled word
// to the custom dictionary.
TEST_F(FeedbackSenderTest, AddToDictFeedback) {
  uint32 hash = AddPendingFeedback();
  feedback_->AddedToDictionary(hash);
  feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
                                      std::vector<uint32>());
  EXPECT_TRUE(UploadDataContains("\"actionType\":\"ADD_TO_DICT\""));
}

// Send IN_DICTIONARY feedback message if the user has the misspelled word in
// the custom dictionary.
TEST_F(FeedbackSenderTest, InDictionaryFeedback) {
  uint32 hash = AddPendingFeedback();
  feedback_->RecordInDictionary(hash);
  feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
                                      std::vector<uint32>());
  EXPECT_TRUE(UploadDataContains("\"actionType\":\"IN_DICTIONARY\""));
}

// Send PENDING feedback message if the user saw the spelling suggestion, but
// decided to not select it, and the marker is still in the document.
TEST_F(FeedbackSenderTest, IgnoreFeedbackMarkerInDocument) {
  uint32 hash = AddPendingFeedback();
  feedback_->IgnoredSuggestions(hash);
  feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
                                      std::vector<uint32>(1, hash));
  EXPECT_TRUE(UploadDataContains("\"actionType\":\"PENDING\""));
}

// Send IGNORE feedback message if the user saw the spelling suggestion, but
// decided to not select it, and the marker is no longer in the document.
TEST_F(FeedbackSenderTest, IgnoreFeedbackMarkerNotInDocument) {
  uint32 hash = AddPendingFeedback();
  feedback_->IgnoredSuggestions(hash);
  feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
                                      std::vector<uint32>());
  EXPECT_TRUE(UploadDataContains("\"actionType\":\"IGNORE\""));
}

// Send MANUALLY_CORRECTED feedback message if the user manually corrected the
// misspelled word.
TEST_F(FeedbackSenderTest, ManuallyCorrectedFeedback) {
  uint32 hash = AddPendingFeedback();
  static const std::string kManualCorrection = "Howdy";
  feedback_->ManuallyCorrected(hash, base::ASCIIToUTF16(kManualCorrection));
  feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
                                      std::vector<uint32>());
  EXPECT_TRUE(UploadDataContains("\"actionType\":\"MANUALLY_CORRECTED\""));
  EXPECT_TRUE(UploadDataContains("\"actionTargetValue\":\"" +
                                 kManualCorrection + "\""));
}

// Send feedback messages in batch.
TEST_F(FeedbackSenderTest, BatchFeedback) {
  std::vector<SpellCheckResult> results;
  results.push_back(SpellCheckResult(SpellCheckResult::SPELLING,
                                     kMisspellingStart,
                                     kMisspellingLength,
                                     base::ASCIIToUTF16("Hello")));
  static const int kSecondMisspellingStart = 7;
  static const int kSecondMisspellingLength = 5;
  results.push_back(SpellCheckResult(SpellCheckResult::SPELLING,
                                     kSecondMisspellingStart,
                                     kSecondMisspellingLength,
                                     base::ASCIIToUTF16("world")));
  feedback_->OnSpellcheckResults(kRendererProcessId,
                                 base::UTF8ToUTF16(kText),
                                 std::vector<SpellCheckMarker>(),
                                 &results);
  feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
                                      std::vector<uint32>());
  EXPECT_TRUE(UploadDataContains("\"actionType\":\"NO_ACTION\"", 2));
}

// Send a series of PENDING feedback messages and one final NO_ACTION feedback
// message with the same hash identifier for a single misspelling.
TEST_F(FeedbackSenderTest, SameHashFeedback) {
  uint32 hash = AddPendingFeedback();
  std::vector<uint32> remaining_markers(1, hash);

  feedback_->OnReceiveDocumentMarkers(kRendererProcessId, remaining_markers);
  EXPECT_TRUE(UploadDataContains("\"actionType\":\"PENDING\""));
  std::string hash_string = base::StringPrintf("\"suggestionId\":\"%u\"", hash);
  EXPECT_TRUE(UploadDataContains(hash_string));
  ClearUploadData();

  feedback_->OnReceiveDocumentMarkers(kRendererProcessId, remaining_markers);
  EXPECT_TRUE(UploadDataContains("\"actionType\":\"PENDING\""));
  EXPECT_TRUE(UploadDataContains(hash_string));
  ClearUploadData();

  feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
                                      std::vector<uint32>());
  EXPECT_TRUE(UploadDataContains("\"actionType\":\"NO_ACTION\""));
  EXPECT_TRUE(UploadDataContains(hash_string));
  ClearUploadData();

  feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
                                      std::vector<uint32>());
  EXPECT_FALSE(IsUploadingData());
}

// When a session expires:
// 1) Pending feedback is finalized and sent to the server in the last message
//    batch in the session.
// 2) No feedback is sent until a spellcheck request happens.
// 3) Existing markers get new hash identifiers.
TEST_F(FeedbackSenderTest, SessionExpirationFeedback) {
  std::vector<SpellCheckResult> results(
      1,
      SpellCheckResult(SpellCheckResult::SPELLING,
                       kMisspellingStart,
                       kMisspellingLength,
                       base::ASCIIToUTF16("Hello")));
  feedback_->OnSpellcheckResults(kRendererProcessId,
                                 base::UTF8ToUTF16(kText),
                                 std::vector<SpellCheckMarker>(),
                                 &results);
  uint32 original_hash = results[0].hash;
  std::vector<uint32> remaining_markers(1, original_hash);

  feedback_->OnReceiveDocumentMarkers(kRendererProcessId, remaining_markers);
  EXPECT_FALSE(UploadDataContains("\"actionType\":\"NO_ACTION\""));
  EXPECT_TRUE(UploadDataContains("\"actionType\":\"PENDING\""));
  std::string original_hash_string =
      base::StringPrintf("\"suggestionId\":\"%u\"", original_hash);
  EXPECT_TRUE(UploadDataContains(original_hash_string));
  ClearUploadData();

  ExpireSession();

  // Last message batch in the current session has only finalized messages.
  feedback_->OnReceiveDocumentMarkers(kRendererProcessId, remaining_markers);
  EXPECT_TRUE(UploadDataContains("\"actionType\":\"NO_ACTION\""));
  EXPECT_FALSE(UploadDataContains("\"actionType\":\"PENDING\""));
  EXPECT_TRUE(UploadDataContains(original_hash_string));
  ClearUploadData();

  // The next session starts on the next spellchecker request. Until then,
  // there's no more feedback sent.
  feedback_->OnReceiveDocumentMarkers(kRendererProcessId, remaining_markers);
  EXPECT_FALSE(IsUploadingData());

  // The first spellcheck request after session expiration creates different
  // document marker hash identifiers.
  std::vector<SpellCheckMarker> original_markers(
      1, SpellCheckMarker(results[0].hash, results[0].location));
  results[0] = SpellCheckResult(SpellCheckResult::SPELLING,
                                kMisspellingStart,
                                kMisspellingLength,
                                base::ASCIIToUTF16("Hello"));
  feedback_->OnSpellcheckResults(
      kRendererProcessId, base::UTF8ToUTF16(kText), original_markers, &results);
  uint32 updated_hash = results[0].hash;
  EXPECT_NE(updated_hash, original_hash);
  remaining_markers[0] = updated_hash;

  // The first feedback message batch in session |i + 1| has the new document
  // marker hash identifiers.
  feedback_->OnReceiveDocumentMarkers(kRendererProcessId, remaining_markers);
  EXPECT_FALSE(UploadDataContains("\"actionType\":\"NO_ACTION\""));
  EXPECT_TRUE(UploadDataContains("\"actionType\":\"PENDING\""));
  EXPECT_FALSE(UploadDataContains(original_hash_string));
  std::string updated_hash_string =
      base::StringPrintf("\"suggestionId\":\"%u\"", updated_hash);
  EXPECT_TRUE(UploadDataContains(updated_hash_string));
}

// First message in session has an indicator.
TEST_F(FeedbackSenderTest, FirstMessageInSessionIndicator) {
  // Session 1, message 1
  AddPendingFeedback();
  feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
                                      std::vector<uint32>());
  EXPECT_TRUE(UploadDataContains("\"isFirstInSession\":true"));

  // Session 1, message 2
  AddPendingFeedback();
  feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
                                      std::vector<uint32>());
  EXPECT_TRUE(UploadDataContains("\"isFirstInSession\":false"));

  ExpireSession();

  // Session 1, message 3 (last)
  AddPendingFeedback();
  feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
                                      std::vector<uint32>());
  EXPECT_TRUE(UploadDataContains("\"isFirstInSession\":false"));

  // Session 2, message 1
  AddPendingFeedback();
  feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
                                      std::vector<uint32>());
  EXPECT_TRUE(UploadDataContains("\"isFirstInSession\":true"));

  // Session 2, message 2
  AddPendingFeedback();
  feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
                                      std::vector<uint32>());
  EXPECT_TRUE(UploadDataContains("\"isFirstInSession\":false"));
}

// Flush all feedback when the spellcheck language and country change.
TEST_F(FeedbackSenderTest, OnLanguageCountryChange) {
  AddPendingFeedback();
  feedback_->OnLanguageCountryChange("pt", "BR");
  EXPECT_TRUE(UploadDataContains("\"language\":\"en\""));
  AddPendingFeedback();
  feedback_->OnLanguageCountryChange("en", "US");
  EXPECT_TRUE(UploadDataContains("\"language\":\"pt\""));
}

// The field names and types should correspond to the API.
TEST_F(FeedbackSenderTest, FeedbackAPI) {
  AddPendingFeedback();
  feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
                                      std::vector<uint32>());
  std::string actual_data = GetUploadData();
  scoped_ptr<base::DictionaryValue> actual(
      static_cast<base::DictionaryValue*>(base::JSONReader::Read(actual_data)));
  actual->SetString("params.key", "TestDummyKey");
  base::ListValue* suggestions = NULL;
  actual->GetList("params.suggestionInfo", &suggestions);
  base::DictionaryValue* suggestion = NULL;
  suggestions->GetDictionary(0, &suggestion);
  suggestion->SetString("suggestionId", "42");
  suggestion->SetString("timestamp", "9001");
  static const std::string expected_data =
      "{\"apiVersion\":\"v2\","
      "\"method\":\"spelling.feedback\","
      "\"params\":"
      "{\"clientName\":\"Chrome\","
      "\"originCountry\":\"USA\","
      "\"key\":\"TestDummyKey\","
      "\"language\":\"en\","
      "\"suggestionInfo\":[{"
      "\"isAutoCorrection\":false,"
      "\"isFirstInSession\":true,"
      "\"misspelledLength\":6,"
      "\"misspelledStart\":0,"
      "\"originalText\":\"Helllo world\","
      "\"suggestionId\":\"42\","
      "\"suggestions\":[\"Hello\"],"
      "\"timestamp\":\"9001\","
      "\"userActions\":[{\"actionType\":\"NO_ACTION\"}]}]}}";
  scoped_ptr<base::Value> expected(base::JSONReader::Read(expected_data));
  EXPECT_TRUE(expected->Equals(actual.get()))
      << "Expected data: " << expected_data
      << "\nActual data:   " << actual_data;
}

// The default API version is "v2".
TEST_F(FeedbackSenderTest, DefaultApiVersion) {
  AddPendingFeedback();
  feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
                                      std::vector<uint32>());
  EXPECT_TRUE(UploadDataContains("\"apiVersion\":\"v2\""));
  EXPECT_FALSE(UploadDataContains("\"apiVersion\":\"v2-internal\""));
}

// The API version should not change for field-trial participants that do not
// append the command-line switch.
TEST_F(FeedbackSenderTest, FieldTrialAloneHasSameApiVersion) {
  EnableFieldTrial();

  AddPendingFeedback();
  feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
                                      std::vector<uint32>());

  EXPECT_TRUE(UploadDataContains("\"apiVersion\":\"v2\""));
  EXPECT_FALSE(UploadDataContains("\"apiVersion\":\"v2-internal\""));
}

// The API version should not change if the command-line switch is appended, but
// the user is not participating in the field-trial.
TEST_F(FeedbackSenderTest, CommandLineSwitchAloneHasSameApiVersion) {
  AppendCommandLineSwitch();

  AddPendingFeedback();
  feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
                                      std::vector<uint32>());

  EXPECT_TRUE(UploadDataContains("\"apiVersion\":\"v2\""));
  EXPECT_FALSE(UploadDataContains("\"apiVersion\":\"v2-internal\""));
}

// The API version should be different for field-trial participants that also
// append the command-line switch.
TEST_F(FeedbackSenderTest, InternalApiVersion) {
  AppendCommandLineSwitch();
  EnableFieldTrial();

  AddPendingFeedback();
  feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
                                      std::vector<uint32>());

  EXPECT_FALSE(UploadDataContains("\"apiVersion\":\"v2\""));
  EXPECT_TRUE(UploadDataContains("\"apiVersion\":\"v2-internal\""));
}

// Duplicate spellcheck results should be matched to the existing markers.
TEST_F(FeedbackSenderTest, MatchDupliateResultsWithExistingMarkers) {
  uint32 hash = AddPendingFeedback();
  std::vector<SpellCheckResult> results(
      1,
      SpellCheckResult(SpellCheckResult::SPELLING,
                       kMisspellingStart,
                       kMisspellingLength,
                       base::ASCIIToUTF16("Hello")));
  std::vector<SpellCheckMarker> markers(
      1, SpellCheckMarker(hash, results[0].location));
  EXPECT_EQ(static_cast<uint32>(0), results[0].hash);
  feedback_->OnSpellcheckResults(
      kRendererProcessId, base::UTF8ToUTF16(kText), markers, &results);
  EXPECT_EQ(hash, results[0].hash);
}

// Adding a word to dictionary should trigger ADD_TO_DICT feedback for every
// occurrence of that word.
TEST_F(FeedbackSenderTest, MultipleAddToDictFeedback) {
  std::vector<SpellCheckResult> results;
  static const int kSentenceLength = 14;
  static const int kNumberOfSentences = 2;
  static const base::string16 kTextWithDuplicates =
      base::ASCIIToUTF16("Helllo world. Helllo world.");
  for (int i = 0; i < kNumberOfSentences; ++i) {
    results.push_back(SpellCheckResult(SpellCheckResult::SPELLING,
                                       kMisspellingStart + i * kSentenceLength,
                                       kMisspellingLength,
                                       base::ASCIIToUTF16("Hello")));
  }
  static const int kNumberOfRenderers = 2;
  int last_renderer_process_id = -1;
  for (int i = 0; i < kNumberOfRenderers; ++i) {
    feedback_->OnSpellcheckResults(kRendererProcessId + i,
                                   kTextWithDuplicates,
                                   std::vector<SpellCheckMarker>(),
                                   &results);
    last_renderer_process_id = kRendererProcessId + i;
  }
  std::vector<uint32> remaining_markers;
  for (size_t i = 0; i < results.size(); ++i)
    remaining_markers.push_back(results[i].hash);
  feedback_->OnReceiveDocumentMarkers(last_renderer_process_id,
                                      remaining_markers);
  EXPECT_TRUE(UploadDataContains("PENDING", 2));
  EXPECT_FALSE(UploadDataContains("ADD_TO_DICT"));

  feedback_->AddedToDictionary(results[0].hash);
  feedback_->OnReceiveDocumentMarkers(last_renderer_process_id,
                                      remaining_markers);
  EXPECT_FALSE(UploadDataContains("PENDING"));
  EXPECT_TRUE(UploadDataContains("ADD_TO_DICT", 2));
}

// ADD_TO_DICT feedback for multiple occurrences of a word should trigger only
// for pending feedback.
TEST_F(FeedbackSenderTest, AddToDictOnlyPending) {
  AddPendingFeedback();
  uint32 add_to_dict_hash = AddPendingFeedback();
  uint32 select_hash = AddPendingFeedback();
  feedback_->SelectedSuggestion(select_hash, 0);
  feedback_->AddedToDictionary(add_to_dict_hash);
  feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
                                      std::vector<uint32>());
  EXPECT_TRUE(UploadDataContains("SELECT", 1));
  EXPECT_TRUE(UploadDataContains("ADD_TO_DICT", 2));
}

// Spellcheck results that are out-of-bounds are not added to feedback.
TEST_F(FeedbackSenderTest, IgnoreOutOfBounds) {
  std::vector<SpellCheckResult> results;
  results.push_back(SpellCheckResult(
      SpellCheckResult::SPELLING, 0, 100, base::UTF8ToUTF16("Hello")));
  results.push_back(SpellCheckResult(
      SpellCheckResult::SPELLING, 100, 3, base::UTF8ToUTF16("world")));
  results.push_back(SpellCheckResult(
      SpellCheckResult::SPELLING, -1, 3, base::UTF8ToUTF16("how")));
  results.push_back(SpellCheckResult(
      SpellCheckResult::SPELLING, 0, 0, base::UTF8ToUTF16("are")));
  results.push_back(SpellCheckResult(
      SpellCheckResult::SPELLING, 2, -1, base::UTF8ToUTF16("you")));
  feedback_->OnSpellcheckResults(kRendererProcessId,
                                 base::UTF8ToUTF16(kText),
                                 std::vector<SpellCheckMarker>(),
                                 &results);
  feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
                                      std::vector<uint32>());
  EXPECT_FALSE(IsUploadingData());
}

// FeedbackSender does not collect and upload feedback when instructed to stop.
TEST_F(FeedbackSenderTest, CanStopFeedbackCollection) {
  feedback_->StopFeedbackCollection();
  AddPendingFeedback();
  feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
                                      std::vector<uint32>());
  EXPECT_FALSE(IsUploadingData());
}

// FeedbackSender resumes collecting and uploading feedback when instructed to
// start after stopping.
TEST_F(FeedbackSenderTest, CanResumeFeedbackCollection) {
  feedback_->StopFeedbackCollection();
  feedback_->StartFeedbackCollection();
  AddPendingFeedback();
  feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
                                      std::vector<uint32>());
  EXPECT_TRUE(IsUploadingData());
}

// FeedbackSender does not collect data while being stopped and upload it later.
TEST_F(FeedbackSenderTest, NoFeedbackCollectionWhenStopped) {
  feedback_->StopFeedbackCollection();
  AddPendingFeedback();
  feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
                                      std::vector<uint32>());
  feedback_->StartFeedbackCollection();
  feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
                                      std::vector<uint32>());
  EXPECT_FALSE(IsUploadingData());
}

}  // namespace spellcheck

/* [<][>][^][v][top][bottom][index][help] */