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
27namespace spellcheck {
28
29namespace {
30
31const char kCountry[] = "USA";
32const char kLanguage[] = "en";
33const char kText[] = "Helllo world.";
34const int kMisspellingLength = 6;
35const int kMisspellingStart = 0;
36const int kRendererProcessId = 0;
37const int kUrlFetcherId = 0;
38
39// Builds a simple spellcheck result.
40SpellCheckResult BuildSpellCheckResult() {
41  return SpellCheckResult(SpellCheckResult::SPELLING,
42                          kMisspellingStart,
43                          kMisspellingLength,
44                          base::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.
49int 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.
62class 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                                   base::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.
153TEST_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.
161TEST_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.
172TEST_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.
181TEST_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.
189TEST_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.
201TEST_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.
211TEST_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.
221TEST_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.
231TEST_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.
241TEST_F(FeedbackSenderTest, ManuallyCorrectedFeedback) {
242  uint32 hash = AddPendingFeedback();
243  static const std::string kManualCorrection = "Howdy";
244  feedback_->ManuallyCorrected(hash, base::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.
253TEST_F(FeedbackSenderTest, BatchFeedback) {
254  std::vector<SpellCheckResult> results;
255  results.push_back(SpellCheckResult(SpellCheckResult::SPELLING,
256                                     kMisspellingStart,
257                                     kMisspellingLength,
258                                     base::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                                     base::ASCIIToUTF16("world")));
265  feedback_->OnSpellcheckResults(kRendererProcessId,
266                                 base::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.
276TEST_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.
307TEST_F(FeedbackSenderTest, SessionExpirationFeedback) {
308  std::vector<SpellCheckResult> results(
309      1,
310      SpellCheckResult(SpellCheckResult::SPELLING,
311                       kMisspellingStart,
312                       kMisspellingLength,
313                       base::ASCIIToUTF16("Hello")));
314  feedback_->OnSpellcheckResults(kRendererProcessId,
315                                 base::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                                base::ASCIIToUTF16("Hello"));
351  feedback_->OnSpellcheckResults(
352      kRendererProcessId, base::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.
369TEST_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.
404TEST_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.
414TEST_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".
453TEST_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.
463TEST_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.
476TEST_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.
489TEST_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.
502TEST_F(FeedbackSenderTest, MatchDupliateResultsWithExistingMarkers) {
503  uint32 hash = AddPendingFeedback();
504  std::vector<SpellCheckResult> results(
505      1,
506      SpellCheckResult(SpellCheckResult::SPELLING,
507                       kMisspellingStart,
508                       kMisspellingLength,
509                       base::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, base::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.
520TEST_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      base::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                                       base::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.
558TEST_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.
571TEST_F(FeedbackSenderTest, IgnoreOutOfBounds) {
572  std::vector<SpellCheckResult> results;
573  results.push_back(SpellCheckResult(
574      SpellCheckResult::SPELLING, 0, 100, base::UTF8ToUTF16("Hello")));
575  results.push_back(SpellCheckResult(
576      SpellCheckResult::SPELLING, 100, 3, base::UTF8ToUTF16("world")));
577  results.push_back(SpellCheckResult(
578      SpellCheckResult::SPELLING, -1, 3, base::UTF8ToUTF16("how")));
579  results.push_back(SpellCheckResult(
580      SpellCheckResult::SPELLING, 0, 0, base::UTF8ToUTF16("are")));
581  results.push_back(SpellCheckResult(
582      SpellCheckResult::SPELLING, 2, -1, base::UTF8ToUTF16("you")));
583  feedback_->OnSpellcheckResults(kRendererProcessId,
584                                 base::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.
593TEST_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.
603TEST_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.
613TEST_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