1// Copyright (c) 2011 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#include "base/bind.h"
6#include "base/message_loop/message_loop.h"
7#include "base/strings/string_util.h"
8#include "base/strings/utf_string_conversions.h"
9#include "chrome/browser/spellchecker/spellcheck_platform_mac.h"
10#include "chrome/common/spellcheck_result.h"
11#include "content/public/test/test_utils.h"
12#include "testing/gtest/include/gtest/gtest.h"
13
14namespace {
15
16class SpellcheckMacTest: public testing::Test {
17 public:
18  SpellcheckMacTest()
19      : callback_(base::Bind(&SpellcheckMacTest::CompletionCallback,
20                             base::Unretained(this))),
21        callback_finished_(false) {}
22
23  void WaitForCallback() {
24    content::RunMessageLoop();
25  }
26
27  std::vector<SpellCheckResult> results_;
28  spellcheck_mac::TextCheckCompleteCallback callback_;
29  bool callback_finished_;
30
31 private:
32  void QuitMessageLoop() {
33    CHECK(base::MessageLoop::current() == &message_loop_);
34    base::MessageLoop::current()->Quit();
35  }
36
37  void CompletionCallback(const std::vector<SpellCheckResult>& results) {
38    results_ = results;
39    callback_finished_ = true;
40    message_loop_.PostTask(FROM_HERE,
41                           base::Bind(&SpellcheckMacTest::QuitMessageLoop,
42                                      base::Unretained(this)));
43  }
44
45  base::MessageLoopForUI message_loop_;
46  spellcheck_mac::ScopedEnglishLanguageForTest scoped_language_;
47};
48
49// Tests that words are properly ignored. Currently only enabled on OS X as it
50// is the only platform to support ignoring words. Note that in this test, we
51// supply a non-zero doc_tag, in order to test that ignored words are matched to
52// the correct document.
53TEST_F(SpellcheckMacTest, IgnoreWords_EN_US) {
54  const char* kTestCases[] = {
55    "teh",
56    "morblier",
57    "watre",
58    "noooen",
59  };
60
61  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) {
62    const base::string16 word(base::ASCIIToUTF16(kTestCases[i]));
63    const int doc_tag = spellcheck_mac::GetDocumentTag();
64
65    // The word should show up as misspelled.
66    EXPECT_FALSE(spellcheck_mac::CheckSpelling(word, doc_tag)) << word;
67
68    // Ignore the word.
69    spellcheck_mac::IgnoreWord(word);
70
71    // The word should now show up as correctly spelled.
72    EXPECT_TRUE(spellcheck_mac::CheckSpelling(word, doc_tag)) << word;
73
74    // Close the docuemnt. Any words that we had previously ignored should no
75    // longer be ignored and thus should show up as misspelled.
76    spellcheck_mac::CloseDocumentWithTag(doc_tag);
77
78    // The word should now show be spelled wrong again
79    EXPECT_FALSE(spellcheck_mac::CheckSpelling(word, doc_tag)) << word;
80  }
81}  // Test IgnoreWords_EN_US
82
83TEST_F(SpellcheckMacTest, SpellCheckSuggestions_EN_US) {
84  static const struct {
85    const char* input;           // A string to be tested.
86    const char* suggested_word;  // A suggested word that should occur.
87  } kTestCases[] = {
88    // We need to have separate test cases here, since hunspell and the OS X
89    // spellchecking service occasionally differ on what they consider a valid
90    // suggestion for a given word, although these lists could likely be
91    // integrated somewhat. The test cases for non-Mac are in
92    // chrome/renderer/spellcheck_unittest.cc
93    // These words come from the wikipedia page of the most commonly
94    // misspelled words in english.
95    // (http://en.wikipedia.org/wiki/Commonly_misspelled_words).
96    // However, 10.6 loads multiple dictionaries and enables many non-English
97    // dictionaries by default. As a result, we have removed from the list any
98    // word that is marked as correct because it is correct in another
99    // language.
100    {"absense", "absence"},
101    {"acceptible", "acceptable"},
102    {"accidentaly", "accidentally"},
103    {"acheive", "achieve"},
104    {"acknowlege", "acknowledge"},
105    {"acquaintence", "acquaintance"},
106    {"aquire", "acquire"},
107    {"aquit", "acquit"},
108    {"acrage", "acreage"},
109    {"adultary", "adultery"},
110    {"advertize", "advertise"},
111    {"adviseable", "advisable"},
112    {"alchohol", "alcohol"},
113    {"alege", "allege"},
114    {"allegaince", "allegiance"},
115    {"allmost", "almost"},
116    // Ideally, this test should pass. It works in firefox, but not in hunspell
117    // or OS X.
118    // {"alot", "a lot"},
119    {"amatuer", "amateur"},
120    {"ammend", "amend"},
121    {"amung", "among"},
122    {"anually", "annually"},
123    {"apparant", "apparent"},
124    {"artic", "arctic"},
125    {"arguement", "argument"},
126    {"athiest", "atheist"},
127    {"athelete", "athlete"},
128    {"avrage", "average"},
129    {"awfull", "awful"},
130    {"ballance", "balance"},
131    {"basicly", "basically"},
132    {"becuase", "because"},
133    {"becomeing", "becoming"},
134    {"befor", "before"},
135    {"begining", "beginning"},
136    {"beleive", "believe"},
137    {"bellweather", "bellwether"},
138    {"benifit", "benefit"},
139    // This particular spelling correction was removed in OSX 10.10. Replacing
140    // it with another spelling correction that also works on older OSes.
141    // {"bouy", "buoy"},
142    {"bouy", "body"},
143    {"briliant", "brilliant"},
144    {"burgler", "burglar"},
145    {"camoflage", "camouflage"},
146    {"carefull", "careful"},
147    {"Carribean", "Caribbean"},
148    {"catagory", "category"},
149    {"cauhgt", "caught"},
150    {"cieling", "ceiling"},
151    {"cemetary", "cemetery"},
152    {"certin", "certain"},
153    {"changable", "changeable"},
154    {"cheif", "chief"},
155    {"citezen", "citizen"},
156    {"collaegue", "colleague"},
157    {"colum", "column"},
158    {"comming", "coming"},
159    {"commited", "committed"},
160    {"compitition", "competition"},
161    {"conceed", "concede"},
162    {"congradulate", "congratulate"},
163    {"consciencious", "conscientious"},
164    {"concious", "conscious"},
165    {"concensus", "consensus"},
166    {"contraversy", "controversy"},
167    {"conveniance", "convenience"},
168    {"critecize", "criticize"},
169    {"dacquiri", "daiquiri"},
170    {"decieve", "deceive"},
171    {"dicide", "decide"},
172    {"definate", "definite"},
173    {"definitly", "definitely"},
174    {"desparate", "desperate"},
175    {"develope", "develop"},
176    {"diffrence", "difference"},
177    {"disapear", "disappear"},
178    {"disapoint", "disappoint"},
179    {"disasterous", "disastrous"},
180    {"disipline", "discipline"},
181    {"drunkeness", "drunkenness"},
182    {"dumbell", "dumbbell"},
183    {"easely", "easily"},
184    {"eigth", "eight"},
185    {"embarass", "embarrass"},
186    {"enviroment", "environment"},
187    {"equiped", "equipped"},
188    {"equiptment", "equipment"},
189    {"exagerate", "exaggerate"},
190    {"exellent", "excellent"},
191    {"exsept", "except"},
192    {"exercize", "exercise"},
193    {"exilerate", "exhilarate"},
194    {"existance", "existence"},
195    {"experiance", "experience"},
196    {"experament", "experiment"},
197    {"explaination", "explanation"},
198    {"facinating", "fascinating"},
199    {"firey", "fiery"},
200    {"finaly", "finally"},
201    {"flourescent", "fluorescent"},
202    {"foriegn", "foreign"},
203    {"fourty", "forty"},
204    {"foreward", "forward"},
205    {"freind", "friend"},
206    {"fundemental", "fundamental"},
207    {"guage", "gauge"},
208    {"generaly", "generally"},
209    {"goverment", "government"},
210    {"gratefull", "grateful"},
211    {"garantee", "guarantee"},
212    {"guidence", "guidance"},
213    {"happyness", "happiness"},
214    {"harrass", "harass"},
215    {"heighth", "height"},
216    {"heirarchy", "hierarchy"},
217    {"humerous", "humorous"},
218    {"hygene", "hygiene"},
219    {"hipocrit", "hypocrite"},
220    {"idenity", "identity"},
221    {"ignorence", "ignorance"},
222    {"imaginery", "imaginary"},
223    {"immitate", "imitate"},
224    {"immitation", "imitation"},
225    {"imediately", "immediately"},
226    {"incidently", "incidentally"},
227    {"independant", "independent"},
228    {"indispensible", "indispensable"},
229    {"innoculate", "inoculate"},
230    {"inteligence", "intelligence"},
231    {"intresting", "interesting"},
232    {"interuption", "interruption"},
233    {"irrelevent", "irrelevant"},
234    {"irritible", "irritable"},
235    {"jellous", "jealous"},
236    {"knowlege", "knowledge"},
237    {"labratory", "laboratory"},
238    {"lenght", "length"},
239    {"liason", "liaison"},
240    {"libary", "library"},
241    {"lisence", "license"},
242    {"lonelyness", "loneliness"},
243    {"lieing", "lying"},
244    {"maintenence", "maintenance"},
245    {"manuever", "maneuver"},
246    {"marrige", "marriage"},
247    {"mathmatics", "mathematics"},
248    {"medcine", "medicine"},
249    {"miniture", "miniature"},
250    {"minite", "minute"},
251    {"mischevous", "mischievous"},
252    {"mispell", "misspell"},
253    // Maybe this one should pass, as it works in hunspell, but not in firefox.
254    // {"misterius", "mysterious"},
255    {"naturaly", "naturally"},
256    {"neccessary", "necessary"},
257    {"neice", "niece"},
258    {"nieghbor", "neighbor"},
259    {"nieghbour", "neighbor"},
260    {"niether", "neither"},
261    {"noticable", "noticeable"},
262    {"occassion", "occasion"},
263    {"occasionaly", "occasionally"},
264    {"occurrance", "occurrence"},
265    {"occured", "occurred"},
266    {"ommision", "omission"},
267    {"oppurtunity", "opportunity"},
268    {"outragous", "outrageous"},
269    {"parrallel", "parallel"},
270    {"parliment", "parliament"},
271    {"particurly", "particularly"},
272    {"passtime", "pastime"},
273    {"peculier", "peculiar"},
274    {"percieve", "perceive"},
275    {"pernament", "permanent"},
276    {"perseverence", "perseverance"},
277    {"personaly", "personally"},
278    {"persaude", "persuade"},
279    {"pichure", "picture"},
280    {"peice", "piece"},
281    {"plagerize", "plagiarize"},
282    {"playright", "playwright"},
283    {"plesant", "pleasant"},
284    {"pollitical", "political"},
285    {"posession", "possession"},
286    {"potatos", "potatoes"},
287    {"practicle", "practical"},
288    {"preceed", "precede"},
289    {"predjudice", "prejudice"},
290    {"presance", "presence"},
291    {"privelege", "privilege"},
292    // This one should probably work. It does in FF and Hunspell.
293    // {"probly", "probably"},
294    {"proffesional", "professional"},
295    {"promiss", "promise"},
296    {"pronounciation", "pronunciation"},
297    {"prufe", "proof"},
298    {"psycology", "psychology"},
299    {"publically", "publicly"},
300    {"quanity", "quantity"},
301    {"quarentine", "quarantine"},
302    {"questionaire", "questionnaire"},
303    {"readible", "readable"},
304    {"realy", "really"},
305    {"recieve", "receive"},
306    {"reciept", "receipt"},
307    {"reconize", "recognize"},
308    {"recomend", "recommend"},
309    {"refered", "referred"},
310    {"referance", "reference"},
311    {"relevent", "relevant"},
312    {"religous", "religious"},
313    {"repitition", "repetition"},
314    {"restarant", "restaurant"},
315    {"rythm", "rhythm"},
316    {"rediculous", "ridiculous"},
317    {"sacrefice", "sacrifice"},
318    {"saftey", "safety"},
319    {"sissors", "scissors"},
320    {"secratary", "secretary"},
321    {"seperate", "separate"},
322    {"sargent", "sergeant"},
323    {"shineing", "shining"},
324    {"similer", "similar"},
325    {"sinceerly", "sincerely"},
326    {"speach", "speech"},
327    {"strenght", "strength"},
328    {"succesful", "successful"},
329    {"supercede", "supersede"},
330    {"surelly", "surely"},
331    {"suprise", "surprise"},
332    {"temperture", "temperature"},
333    {"temprary", "temporary"},
334    {"tommorrow", "tomorrow"},
335    {"tounge", "tongue"},
336    {"truely", "truly"},
337    {"twelth", "twelfth"},
338    {"tyrany", "tyranny"},
339    {"underate", "underrate"},
340    {"untill", "until"},
341    {"unuseual", "unusual"},
342    {"upholstry", "upholstery"},
343    {"usible", "usable"},
344    {"useing", "using"},
345    {"usualy", "usually"},
346    {"vaccuum", "vacuum"},
347    {"vegatarian", "vegetarian"},
348    {"vehical", "vehicle"},
349    {"visious", "vicious"},
350    {"villege", "village"},
351    {"wierd", "weird"},
352    {"wellcome", "welcome"},
353    {"wellfare", "welfare"},
354    {"wilfull", "willful"},
355    {"withold", "withhold"},
356    {"writting", "writing"},
357  };
358
359  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) {
360    const base::string16 word(base::ASCIIToUTF16(kTestCases[i].input));
361    EXPECT_FALSE(spellcheck_mac::CheckSpelling(word, 0)) << word;
362
363    // Check if the suggested words occur.
364    std::vector<base::string16> suggestions;
365    spellcheck_mac::FillSuggestionList(word, &suggestions);
366    bool suggested_word_is_present = false;
367    const base::string16 suggested_word(
368        base::ASCIIToUTF16(kTestCases[i].suggested_word));
369    for (size_t j = 0; j < suggestions.size(); j++) {
370      if (suggestions[j].compare(suggested_word) == 0) {
371        suggested_word_is_present = true;
372        break;
373      }
374    }
375    EXPECT_TRUE(suggested_word_is_present) << suggested_word;
376  }
377}
378
379// The OSX spellchecker returns non-spellcheck results when invoked on a
380// sentence, specifically an NSTextCheckingTypeOrthography result indicating
381// the language used in that sentence. Test that it is filtered out from
382// RequestTextCheck results.
383TEST_F(SpellcheckMacTest, SpellCheckIgnoresOrthography)  {
384  base::string16 test_string(base::ASCIIToUTF16("Icland is awesome."));
385  spellcheck_mac::RequestTextCheck(0, test_string, callback_);
386  WaitForCallback();
387  EXPECT_TRUE(callback_finished_);
388  EXPECT_EQ(1U, results_.size());
389}
390
391}  // namespace
392