blob: 5c0b1dbd40727241242cb461fb9c4def3a2e5772 [file] [log] [blame]
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +01001// 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.
Ben Murdochbb1529c2013-08-08 10:24:53 +01004//
5// Unit tests for |FeedbackSender| object.
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +01006
Ben Murdochbb1529c2013-08-08 10:24:53 +01007#include "chrome/browser/spellchecker/feedback_sender.h"
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +01008
9#include "base/bind.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010010#include "base/command_line.h"
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010011#include "base/json/json_reader.h"
Ben Murdoch9ab55632013-07-18 11:57:30 +010012#include "base/message_loop/message_loop.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010013#include "base/metrics/field_trial.h"
14#include "base/strings/stringprintf.h"
15#include "base/strings/utf_string_conversions.h"
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010016#include "base/values.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010017#include "chrome/common/chrome_switches.h"
18#include "chrome/common/metrics/entropy_provider.h"
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010019#include "chrome/common/spellcheck_common.h"
20#include "chrome/common/spellcheck_marker.h"
21#include "chrome/common/spellcheck_result.h"
22#include "chrome/test/base/testing_profile.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
Ben Murdochbb1529c2013-08-08 10:24:53 +010031const 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;
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010038
Ben Murdochbb1529c2013-08-08 10:24:53 +010039// Builds a simple spellcheck result.
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010040SpellCheckResult BuildSpellCheckResult() {
41 return SpellCheckResult(SpellCheckResult::SPELLING,
42 kMisspellingStart,
43 kMisspellingLength,
Ben Murdochbb1529c2013-08-08 10:24:53 +010044 UTF8ToUTF16("Hello"));
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010045}
46
Ben Murdocheb525c52013-07-10 11:40:50 +010047// 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
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010059} // namespace
60
Ben Murdochbb1529c2013-08-08 10:24:53 +010061// A test fixture to help keep tests simple.
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010062class FeedbackSenderTest : public testing::Test {
63 public:
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010064 FeedbackSenderTest() : ui_thread_(content::BrowserThread::UI, &loop_) {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010065 // The command-line switch and the field trial are temporary.
66 // TODO(rouslan): Remove the command-line switch and the field trial.
67 // http://crbug.com/247726
68 CommandLine::ForCurrentProcess()->AppendSwitch(
69 switches::kEnableSpellingServiceFeedback);
70 field_trial_list_.reset(
71 new base::FieldTrialList(new metrics::SHA1EntropyProvider("foo")));
72 field_trial_ = base::FieldTrialList::CreateFieldTrial(
73 kFeedbackFieldTrialName, kFeedbackFieldTrialEnabledGroupName);
74 field_trial_->group();
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010075 feedback_.reset(new FeedbackSender(NULL, kLanguage, kCountry));
76 }
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010077 virtual ~FeedbackSenderTest() {}
78
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010079 protected:
80 uint32 AddPendingFeedback() {
81 std::vector<SpellCheckResult> results(1, BuildSpellCheckResult());
Ben Murdochbb1529c2013-08-08 10:24:53 +010082 feedback_->OnSpellcheckResults(kRendererProcessId,
83 UTF8ToUTF16(kText),
84 std::vector<SpellCheckMarker>(),
85 &results);
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010086 return results[0].hash;
87 }
88
89 void ExpireSession() {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010090 feedback_->session_start_ =
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010091 base::Time::Now() -
92 base::TimeDelta::FromHours(chrome::spellcheck_common::kSessionHours);
93 }
94
Ben Murdocheb525c52013-07-10 11:40:50 +010095 bool UploadDataContains(const std::string& data) const {
96 const net::TestURLFetcher* fetcher =
97 fetchers_.GetFetcherByID(kUrlFetcherId);
98 return fetcher && CountOccurences(fetcher->upload_data(), data) > 0;
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010099 }
100
Ben Murdocheb525c52013-07-10 11:40:50 +0100101 bool UploadDataContains(const std::string& data,
102 int number_of_occurrences) const {
103 const net::TestURLFetcher* fetcher =
104 fetchers_.GetFetcherByID(kUrlFetcherId);
105 return fetcher && CountOccurences(fetcher->upload_data(), data) ==
106 number_of_occurrences;
107 }
108
Ben Murdochbb1529c2013-08-08 10:24:53 +0100109 // Returns true if the feedback sender would be uploading data now. The test
110 // does not open network connections.
Ben Murdocheb525c52013-07-10 11:40:50 +0100111 bool IsUploadingData() const {
112 return !!fetchers_.GetFetcherByID(kUrlFetcherId);
113 }
114
115 void ClearUploadData() {
116 fetchers_.RemoveFetcherFromMap(kUrlFetcherId);
117 }
118
119 std::string GetUploadData() const {
120 const net::TestURLFetcher* fetcher =
121 fetchers_.GetFetcherByID(kUrlFetcherId);
122 return fetcher ? fetcher->upload_data() : std::string();
123 }
124
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100125 scoped_ptr<spellcheck::FeedbackSender> feedback_;
Ben Murdochbb1529c2013-08-08 10:24:53 +0100126
127 private:
128 TestingProfile profile_;
129 base::MessageLoop loop_;
130 content::TestBrowserThread ui_thread_;
131 scoped_ptr<base::FieldTrialList> field_trial_list_;
132 scoped_refptr<base::FieldTrial> field_trial_;
133 net::TestURLFetcherFactory fetchers_;
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100134};
135
136// Do not send data if there's no feedback.
137TEST_F(FeedbackSenderTest, NoFeedback) {
Ben Murdocheb525c52013-07-10 11:40:50 +0100138 EXPECT_FALSE(IsUploadingData());
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100139 feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
140 std::vector<uint32>());
Ben Murdocheb525c52013-07-10 11:40:50 +0100141 EXPECT_FALSE(IsUploadingData());
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100142}
143
144// Do not send data if not aware of which markers are still in the document.
145TEST_F(FeedbackSenderTest, NoDocumentMarkersReceived) {
Ben Murdocheb525c52013-07-10 11:40:50 +0100146 EXPECT_FALSE(IsUploadingData());
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100147 uint32 hash = AddPendingFeedback();
Ben Murdocheb525c52013-07-10 11:40:50 +0100148 EXPECT_FALSE(IsUploadingData());
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100149 static const int kSuggestionIndex = 1;
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100150 feedback_->SelectedSuggestion(hash, kSuggestionIndex);
Ben Murdocheb525c52013-07-10 11:40:50 +0100151 EXPECT_FALSE(IsUploadingData());
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100152}
153
154// Send PENDING feedback message if the marker is still in the document, and the
155// user has not performed any action on it.
156TEST_F(FeedbackSenderTest, PendingFeedback) {
157 uint32 hash = AddPendingFeedback();
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100158 feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
159 std::vector<uint32>(1, hash));
Ben Murdocheb525c52013-07-10 11:40:50 +0100160 EXPECT_TRUE(UploadDataContains("\"actionType\":\"PENDING\""));
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100161}
162
163// Send NO_ACTION feedback message if the marker has been removed from the
164// document.
165TEST_F(FeedbackSenderTest, NoActionFeedback) {
166 AddPendingFeedback();
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100167 feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
168 std::vector<uint32>());
Ben Murdocheb525c52013-07-10 11:40:50 +0100169 EXPECT_TRUE(UploadDataContains("\"actionType\":\"NO_ACTION\""));
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100170}
171
172// Send SELECT feedback message if the user has selected a spelling suggestion.
173TEST_F(FeedbackSenderTest, SelectFeedback) {
174 uint32 hash = AddPendingFeedback();
Ben Murdocheb525c52013-07-10 11:40:50 +0100175 static const int kSuggestionIndex = 0;
176 feedback_->SelectedSuggestion(hash, kSuggestionIndex);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100177 feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
178 std::vector<uint32>());
Ben Murdocheb525c52013-07-10 11:40:50 +0100179 EXPECT_TRUE(UploadDataContains("\"actionType\":\"SELECT\""));
180 EXPECT_TRUE(UploadDataContains("\"actionTargetIndex\":" + kSuggestionIndex));
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100181}
182
183// Send ADD_TO_DICT feedback message if the user has added the misspelled word
184// to the custom dictionary.
185TEST_F(FeedbackSenderTest, AddToDictFeedback) {
186 uint32 hash = AddPendingFeedback();
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100187 feedback_->AddedToDictionary(hash);
188 feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
189 std::vector<uint32>());
Ben Murdocheb525c52013-07-10 11:40:50 +0100190 EXPECT_TRUE(UploadDataContains("\"actionType\":\"ADD_TO_DICT\""));
191}
192
193// Send IN_DICTIONARY feedback message if the user has the misspelled word in
194// the custom dictionary.
195TEST_F(FeedbackSenderTest, InDictionaryFeedback) {
196 uint32 hash = AddPendingFeedback();
197 feedback_->RecordInDictionary(hash);
198 feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
199 std::vector<uint32>());
200 EXPECT_TRUE(UploadDataContains("\"actionType\":\"IN_DICTIONARY\""));
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100201}
202
203// Send PENDING feedback message if the user saw the spelling suggestion, but
204// decided to not select it, and the marker is still in the document.
205TEST_F(FeedbackSenderTest, IgnoreFeedbackMarkerInDocument) {
206 uint32 hash = AddPendingFeedback();
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100207 feedback_->IgnoredSuggestions(hash);
208 feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
209 std::vector<uint32>(1, hash));
Ben Murdocheb525c52013-07-10 11:40:50 +0100210 EXPECT_TRUE(UploadDataContains("\"actionType\":\"PENDING\""));
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100211}
212
213// Send IGNORE feedback message if the user saw the spelling suggestion, but
214// decided to not select it, and the marker is no longer in the document.
215TEST_F(FeedbackSenderTest, IgnoreFeedbackMarkerNotInDocument) {
216 uint32 hash = AddPendingFeedback();
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100217 feedback_->IgnoredSuggestions(hash);
218 feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
219 std::vector<uint32>());
Ben Murdocheb525c52013-07-10 11:40:50 +0100220 EXPECT_TRUE(UploadDataContains("\"actionType\":\"IGNORE\""));
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100221}
222
223// Send MANUALLY_CORRECTED feedback message if the user manually corrected the
224// misspelled word.
225TEST_F(FeedbackSenderTest, ManuallyCorrectedFeedback) {
226 uint32 hash = AddPendingFeedback();
227 static const std::string kManualCorrection = "Howdy";
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100228 feedback_->ManuallyCorrected(hash, ASCIIToUTF16(kManualCorrection));
229 feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
230 std::vector<uint32>());
Ben Murdocheb525c52013-07-10 11:40:50 +0100231 EXPECT_TRUE(UploadDataContains("\"actionType\":\"MANUALLY_CORRECTED\""));
232 EXPECT_TRUE(UploadDataContains("\"actionTargetValue\":\"" +
233 kManualCorrection + "\""));
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100234}
235
236// Send feedback messages in batch.
237TEST_F(FeedbackSenderTest, BatchFeedback) {
238 std::vector<SpellCheckResult> results;
239 results.push_back(SpellCheckResult(SpellCheckResult::SPELLING,
240 kMisspellingStart,
241 kMisspellingLength,
242 ASCIIToUTF16("Hello")));
243 static const int kSecondMisspellingStart = 7;
244 static const int kSecondMisspellingLength = 5;
245 results.push_back(SpellCheckResult(SpellCheckResult::SPELLING,
246 kSecondMisspellingStart,
247 kSecondMisspellingLength,
248 ASCIIToUTF16("world")));
Ben Murdochbb1529c2013-08-08 10:24:53 +0100249 feedback_->OnSpellcheckResults(kRendererProcessId,
250 UTF8ToUTF16(kText),
251 std::vector<SpellCheckMarker>(),
252 &results);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100253 feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
254 std::vector<uint32>());
Ben Murdocheb525c52013-07-10 11:40:50 +0100255 EXPECT_TRUE(UploadDataContains("\"actionType\":\"NO_ACTION\"", 2));
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100256}
257
258// Send a series of PENDING feedback messages and one final NO_ACTION feedback
259// message with the same hash identifier for a single misspelling.
260TEST_F(FeedbackSenderTest, SameHashFeedback) {
261 uint32 hash = AddPendingFeedback();
262 std::vector<uint32> remaining_markers(1, hash);
263
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100264 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, remaining_markers);
Ben Murdocheb525c52013-07-10 11:40:50 +0100265 EXPECT_TRUE(UploadDataContains("\"actionType\":\"PENDING\""));
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100266 std::string hash_string = base::StringPrintf("\"suggestionId\":\"%u\"", hash);
Ben Murdocheb525c52013-07-10 11:40:50 +0100267 EXPECT_TRUE(UploadDataContains(hash_string));
268 ClearUploadData();
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100269
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100270 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, remaining_markers);
Ben Murdocheb525c52013-07-10 11:40:50 +0100271 EXPECT_TRUE(UploadDataContains("\"actionType\":\"PENDING\""));
272 EXPECT_TRUE(UploadDataContains(hash_string));
273 ClearUploadData();
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100274
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100275 feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
276 std::vector<uint32>());
Ben Murdocheb525c52013-07-10 11:40:50 +0100277 EXPECT_TRUE(UploadDataContains("\"actionType\":\"NO_ACTION\""));
278 EXPECT_TRUE(UploadDataContains(hash_string));
279 ClearUploadData();
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100280
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100281 feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
282 std::vector<uint32>());
Ben Murdocheb525c52013-07-10 11:40:50 +0100283 EXPECT_FALSE(IsUploadingData());
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100284}
285
286// When a session expires:
287// 1) Pending feedback is finalized and sent to the server in the last message
288// batch in the session.
289// 2) No feedback is sent until a spellcheck request happens.
290// 3) Existing markers get new hash identifiers.
291TEST_F(FeedbackSenderTest, SessionExpirationFeedback) {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100292 std::vector<SpellCheckResult> results(
293 1,
294 SpellCheckResult(SpellCheckResult::SPELLING,
295 kMisspellingStart,
296 kMisspellingLength,
297 ASCIIToUTF16("Hello")));
Ben Murdochbb1529c2013-08-08 10:24:53 +0100298 feedback_->OnSpellcheckResults(kRendererProcessId,
299 UTF8ToUTF16(kText),
300 std::vector<SpellCheckMarker>(),
301 &results);
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100302 uint32 original_hash = results[0].hash;
303 std::vector<uint32> remaining_markers(1, original_hash);
304
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100305 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, remaining_markers);
Ben Murdocheb525c52013-07-10 11:40:50 +0100306 EXPECT_FALSE(UploadDataContains("\"actionType\":\"NO_ACTION\""));
307 EXPECT_TRUE(UploadDataContains("\"actionType\":\"PENDING\""));
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100308 std::string original_hash_string =
309 base::StringPrintf("\"suggestionId\":\"%u\"", original_hash);
Ben Murdocheb525c52013-07-10 11:40:50 +0100310 EXPECT_TRUE(UploadDataContains(original_hash_string));
311 ClearUploadData();
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100312
313 ExpireSession();
314
315 // Last message batch in the current session has only finalized messages.
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100316 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, remaining_markers);
Ben Murdocheb525c52013-07-10 11:40:50 +0100317 EXPECT_TRUE(UploadDataContains("\"actionType\":\"NO_ACTION\""));
318 EXPECT_FALSE(UploadDataContains("\"actionType\":\"PENDING\""));
319 EXPECT_TRUE(UploadDataContains(original_hash_string));
320 ClearUploadData();
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100321
322 // The next session starts on the next spellchecker request. Until then,
323 // there's no more feedback sent.
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100324 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, remaining_markers);
Ben Murdocheb525c52013-07-10 11:40:50 +0100325 EXPECT_FALSE(IsUploadingData());
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100326
327 // The first spellcheck request after session expiration creates different
328 // document marker hash identifiers.
329 std::vector<SpellCheckMarker> original_markers(
330 1, SpellCheckMarker(results[0].hash, results[0].location));
331 results[0] = SpellCheckResult(SpellCheckResult::SPELLING,
332 kMisspellingStart,
333 kMisspellingLength,
334 ASCIIToUTF16("Hello"));
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100335 feedback_->OnSpellcheckResults(
Ben Murdochbb1529c2013-08-08 10:24:53 +0100336 kRendererProcessId, UTF8ToUTF16(kText), original_markers, &results);
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100337 uint32 updated_hash = results[0].hash;
338 EXPECT_NE(updated_hash, original_hash);
339 remaining_markers[0] = updated_hash;
340
341 // The first feedback message batch in session |i + 1| has the new document
342 // marker hash identifiers.
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100343 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, remaining_markers);
Ben Murdocheb525c52013-07-10 11:40:50 +0100344 EXPECT_FALSE(UploadDataContains("\"actionType\":\"NO_ACTION\""));
345 EXPECT_TRUE(UploadDataContains("\"actionType\":\"PENDING\""));
346 EXPECT_FALSE(UploadDataContains(original_hash_string));
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100347 std::string updated_hash_string =
348 base::StringPrintf("\"suggestionId\":\"%u\"", updated_hash);
Ben Murdocheb525c52013-07-10 11:40:50 +0100349 EXPECT_TRUE(UploadDataContains(updated_hash_string));
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100350}
351
352// First message in session has an indicator.
353TEST_F(FeedbackSenderTest, FirstMessageInSessionIndicator) {
354 // Session 1, message 1
355 AddPendingFeedback();
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100356 feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
357 std::vector<uint32>());
Ben Murdocheb525c52013-07-10 11:40:50 +0100358 EXPECT_TRUE(UploadDataContains("\"isFirstInSession\":true"));
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100359
360 // Session 1, message 2
361 AddPendingFeedback();
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100362 feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
363 std::vector<uint32>());
Ben Murdocheb525c52013-07-10 11:40:50 +0100364 EXPECT_TRUE(UploadDataContains("\"isFirstInSession\":false"));
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100365
366 ExpireSession();
367
368 // Session 1, message 3 (last)
369 AddPendingFeedback();
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100370 feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
371 std::vector<uint32>());
Ben Murdocheb525c52013-07-10 11:40:50 +0100372 EXPECT_TRUE(UploadDataContains("\"isFirstInSession\":false"));
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100373
374 // Session 2, message 1
375 AddPendingFeedback();
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100376 feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
377 std::vector<uint32>());
Ben Murdocheb525c52013-07-10 11:40:50 +0100378 EXPECT_TRUE(UploadDataContains("\"isFirstInSession\":true"));
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100379
380 // Session 2, message 2
381 AddPendingFeedback();
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100382 feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
383 std::vector<uint32>());
Ben Murdocheb525c52013-07-10 11:40:50 +0100384 EXPECT_TRUE(UploadDataContains("\"isFirstInSession\":false"));
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100385}
386
387// Flush all feedback when the spellcheck language and country change.
388TEST_F(FeedbackSenderTest, OnLanguageCountryChange) {
389 AddPendingFeedback();
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100390 feedback_->OnLanguageCountryChange("pt", "BR");
Ben Murdocheb525c52013-07-10 11:40:50 +0100391 EXPECT_TRUE(UploadDataContains("\"language\":\"en\""));
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100392 AddPendingFeedback();
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100393 feedback_->OnLanguageCountryChange("en", "US");
Ben Murdocheb525c52013-07-10 11:40:50 +0100394 EXPECT_TRUE(UploadDataContains("\"language\":\"pt\""));
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100395}
396
397// The field names and types should correspond to the API.
398TEST_F(FeedbackSenderTest, FeedbackAPI) {
399 AddPendingFeedback();
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100400 feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
401 std::vector<uint32>());
Ben Murdocheb525c52013-07-10 11:40:50 +0100402 std::string actual_data = GetUploadData();
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100403 scoped_ptr<base::DictionaryValue> actual(
404 static_cast<base::DictionaryValue*>(base::JSONReader::Read(actual_data)));
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100405 actual->SetString("params.key", "TestDummyKey");
406 base::ListValue* suggestions = NULL;
407 actual->GetList("params.suggestionInfo", &suggestions);
408 base::DictionaryValue* suggestion = NULL;
409 suggestions->GetDictionary(0, &suggestion);
410 suggestion->SetString("suggestionId", "42");
411 suggestion->SetString("timestamp", "9001");
412 static const std::string expected_data =
413 "{\"apiVersion\":\"v2\","
414 "\"method\":\"spelling.feedback\","
415 "\"params\":"
416 "{\"clientName\":\"Chrome\","
417 "\"originCountry\":\"USA\","
418 "\"key\":\"TestDummyKey\","
419 "\"language\":\"en\","
420 "\"suggestionInfo\":[{"
421 "\"isAutoCorrection\":false,"
422 "\"isFirstInSession\":true,"
423 "\"misspelledLength\":6,"
424 "\"misspelledStart\":0,"
425 "\"originalText\":\"Helllo world\","
426 "\"suggestionId\":\"42\","
427 "\"suggestions\":[\"Hello\"],"
428 "\"timestamp\":\"9001\","
429 "\"userActions\":[{\"actionType\":\"NO_ACTION\"}]}]}}";
430 scoped_ptr<base::Value> expected(base::JSONReader::Read(expected_data));
431 EXPECT_TRUE(expected->Equals(actual.get()))
432 << "Expected data: " << expected_data
433 << "\nActual data: " << actual_data;
434}
435
436// Duplicate spellcheck results should be matched to the existing markers.
437TEST_F(FeedbackSenderTest, MatchDupliateResultsWithExistingMarkers) {
438 uint32 hash = AddPendingFeedback();
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100439 std::vector<SpellCheckResult> results(
440 1,
441 SpellCheckResult(SpellCheckResult::SPELLING,
Ben Murdochbb1529c2013-08-08 10:24:53 +0100442 kMisspellingStart,
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100443 kMisspellingLength,
444 ASCIIToUTF16("Hello")));
445 std::vector<SpellCheckMarker> markers(
446 1, SpellCheckMarker(hash, results[0].location));
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100447 EXPECT_EQ(static_cast<uint32>(0), results[0].hash);
Ben Murdochbb1529c2013-08-08 10:24:53 +0100448 feedback_->OnSpellcheckResults(
449 kRendererProcessId, UTF8ToUTF16(kText), markers, &results);
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100450 EXPECT_EQ(hash, results[0].hash);
451}
452
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100453// Adding a word to dictionary should trigger ADD_TO_DICT feedback for every
454// occurrence of that word.
455TEST_F(FeedbackSenderTest, MultipleAddToDictFeedback) {
456 std::vector<SpellCheckResult> results;
457 static const int kSentenceLength = 14;
458 static const int kNumberOfSentences = 2;
459 static const string16 kTextWithDuplicates =
460 ASCIIToUTF16("Helllo world. Helllo world.");
461 for (int i = 0; i < kNumberOfSentences; ++i) {
462 results.push_back(SpellCheckResult(SpellCheckResult::SPELLING,
463 kMisspellingStart + i * kSentenceLength,
464 kMisspellingLength,
465 ASCIIToUTF16("Hello")));
466 }
467 static const int kNumberOfRenderers = 2;
468 int last_renderer_process_id = -1;
469 for (int i = 0; i < kNumberOfRenderers; ++i) {
Ben Murdochbb1529c2013-08-08 10:24:53 +0100470 feedback_->OnSpellcheckResults(kRendererProcessId + i,
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100471 kTextWithDuplicates,
Ben Murdochbb1529c2013-08-08 10:24:53 +0100472 std::vector<SpellCheckMarker>(),
473 &results);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100474 last_renderer_process_id = kRendererProcessId + i;
475 }
476 std::vector<uint32> remaining_markers;
477 for (size_t i = 0; i < results.size(); ++i)
478 remaining_markers.push_back(results[i].hash);
479 feedback_->OnReceiveDocumentMarkers(last_renderer_process_id,
480 remaining_markers);
Ben Murdocheb525c52013-07-10 11:40:50 +0100481 EXPECT_TRUE(UploadDataContains("PENDING", 2));
482 EXPECT_FALSE(UploadDataContains("ADD_TO_DICT"));
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100483
484 feedback_->AddedToDictionary(results[0].hash);
485 feedback_->OnReceiveDocumentMarkers(last_renderer_process_id,
486 remaining_markers);
Ben Murdocheb525c52013-07-10 11:40:50 +0100487 EXPECT_FALSE(UploadDataContains("PENDING"));
488 EXPECT_TRUE(UploadDataContains("ADD_TO_DICT", 2));
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100489}
490
491// ADD_TO_DICT feedback for multiple occurrences of a word should trigger only
492// for pending feedback.
493TEST_F(FeedbackSenderTest, AddToDictOnlyPending) {
494 AddPendingFeedback();
495 uint32 add_to_dict_hash = AddPendingFeedback();
496 uint32 select_hash = AddPendingFeedback();
497 feedback_->SelectedSuggestion(select_hash, 0);
498 feedback_->AddedToDictionary(add_to_dict_hash);
499 feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
500 std::vector<uint32>());
Ben Murdocheb525c52013-07-10 11:40:50 +0100501 EXPECT_TRUE(UploadDataContains("SELECT", 1));
502 EXPECT_TRUE(UploadDataContains("ADD_TO_DICT", 2));
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100503}
504
Ben Murdochbb1529c2013-08-08 10:24:53 +0100505// Spellcheck results that are out-of-bounds are not added to feedback.
506TEST_F(FeedbackSenderTest, IgnoreOutOfBounds) {
507 std::vector<SpellCheckResult> results;
508 results.push_back(SpellCheckResult(
509 SpellCheckResult::SPELLING, 0, 100, UTF8ToUTF16("Hello")));
510 results.push_back(SpellCheckResult(
511 SpellCheckResult::SPELLING, 100, 3, UTF8ToUTF16("world")));
512 results.push_back(
513 SpellCheckResult(SpellCheckResult::SPELLING, -1, 3, UTF8ToUTF16("how")));
514 results.push_back(
515 SpellCheckResult(SpellCheckResult::SPELLING, 0, 0, UTF8ToUTF16("are")));
516 results.push_back(
517 SpellCheckResult(SpellCheckResult::SPELLING, 2, -1, UTF8ToUTF16("you")));
518 feedback_->OnSpellcheckResults(kRendererProcessId,
519 UTF8ToUTF16(kText),
520 std::vector<SpellCheckMarker>(),
521 &results);
522 feedback_->OnReceiveDocumentMarkers(kRendererProcessId,
523 std::vector<uint32>());
524 EXPECT_FALSE(IsUploadingData());
525}
526
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100527} // namespace spellcheck