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