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.
BuildSpellCheckResult()40 SpellCheckResult 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.
CountOccurences(const std::string & haystack,const std::string & needle)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:
FeedbackSenderTest()64 FeedbackSenderTest() : ui_thread_(content::BrowserThread::UI, &loop_) {
65 feedback_.reset(new FeedbackSender(NULL, kLanguage, kCountry));
66 feedback_->StartFeedbackCollection();
67 }
68
~FeedbackSenderTest()69 virtual ~FeedbackSenderTest() {}
70
71 protected:
72 // Appends the "--enable-spelling-service-feedback" switch to the
73 // command-line.
AppendCommandLineSwitch()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.
EnableFieldTrial()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
AddPendingFeedback()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
ExpireSession()105 void ExpireSession() {
106 feedback_->session_start_ =
107 base::Time::Now() -
108 base::TimeDelta::FromHours(chrome::spellcheck_common::kSessionHours);
109 }
110
UploadDataContains(const std::string & data) const111 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
UploadDataContains(const std::string & data,int number_of_occurrences) const117 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.
IsUploadingData() const127 bool IsUploadingData() const {
128 return !!fetchers_.GetFetcherByID(kUrlFetcherId);
129 }
130
ClearUploadData()131 void ClearUploadData() {
132 fetchers_.RemoveFetcherFromMap(kUrlFetcherId);
133 }
134
GetUploadData() const135 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.
TEST_F(FeedbackSenderTest,NoFeedback)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.
TEST_F(FeedbackSenderTest,NoDocumentMarkersReceived)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.
TEST_F(FeedbackSenderTest,PendingFeedback)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.
TEST_F(FeedbackSenderTest,NoActionFeedback)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.
TEST_F(FeedbackSenderTest,SelectFeedback)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.
TEST_F(FeedbackSenderTest,AddToDictFeedback)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.
TEST_F(FeedbackSenderTest,InDictionaryFeedback)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.
TEST_F(FeedbackSenderTest,IgnoreFeedbackMarkerInDocument)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.
TEST_F(FeedbackSenderTest,IgnoreFeedbackMarkerNotInDocument)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.
TEST_F(FeedbackSenderTest,ManuallyCorrectedFeedback)241 TEST_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.
TEST_F(FeedbackSenderTest,BatchFeedback)253 TEST_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.
TEST_F(FeedbackSenderTest,SameHashFeedback)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.
TEST_F(FeedbackSenderTest,SessionExpirationFeedback)307 TEST_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.
TEST_F(FeedbackSenderTest,FirstMessageInSessionIndicator)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.
TEST_F(FeedbackSenderTest,OnLanguageCountryChange)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.
TEST_F(FeedbackSenderTest,FeedbackAPI)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".
TEST_F(FeedbackSenderTest,DefaultApiVersion)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.
TEST_F(FeedbackSenderTest,FieldTrialAloneHasSameApiVersion)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.
TEST_F(FeedbackSenderTest,CommandLineSwitchAloneHasSameApiVersion)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.
TEST_F(FeedbackSenderTest,InternalApiVersion)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.
TEST_F(FeedbackSenderTest,MatchDupliateResultsWithExistingMarkers)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 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.
TEST_F(FeedbackSenderTest,MultipleAddToDictFeedback)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 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.
TEST_F(FeedbackSenderTest,AddToDictOnlyPending)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.
TEST_F(FeedbackSenderTest,IgnoreOutOfBounds)571 TEST_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.
TEST_F(FeedbackSenderTest,CanStopFeedbackCollection)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.
TEST_F(FeedbackSenderTest,CanResumeFeedbackCollection)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.
TEST_F(FeedbackSenderTest,NoFeedbackCollectionWhenStopped)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