spellcheck_unittest.cc 62 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671
  1. // Copyright (c) 2012 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. #include "components/spellcheck/renderer/spellcheck.h"
  5. #include <stddef.h>
  6. #include <memory>
  7. #include <utility>
  8. #include "base/containers/contains.h"
  9. #include "base/files/file_path.h"
  10. #include "base/files/file_util.h"
  11. #include "base/path_service.h"
  12. #include "base/run_loop.h"
  13. #include "base/strings/string_number_conversions.h"
  14. #include "base/strings/utf_string_conversions.h"
  15. #include "base/test/task_environment.h"
  16. #include "build/build_config.h"
  17. #include "components/spellcheck/common/spellcheck_common.h"
  18. #include "components/spellcheck/common/spellcheck_result.h"
  19. #include "components/spellcheck/renderer/empty_local_interface_provider.h"
  20. #include "components/spellcheck/renderer/hunspell_engine.h"
  21. #include "components/spellcheck/renderer/spellcheck_language.h"
  22. #include "testing/gtest/include/gtest/gtest.h"
  23. #include "third_party/blink/public/platform/web_vector.h"
  24. #include "third_party/blink/public/web/web_text_checking_completion.h"
  25. #include "third_party/blink/public/web/web_text_checking_result.h"
  26. namespace {
  27. const int kNoOffset = 0;
  28. const int kNoTag = 0;
  29. base::FilePath GetHunspellDirectory() {
  30. base::FilePath hunspell_directory;
  31. if (!base::PathService::Get(base::DIR_SOURCE_ROOT, &hunspell_directory))
  32. return base::FilePath();
  33. hunspell_directory = hunspell_directory.AppendASCII("third_party");
  34. hunspell_directory = hunspell_directory.AppendASCII("hunspell_dictionaries");
  35. return hunspell_directory;
  36. }
  37. } // namespace
  38. // TODO(groby): This needs to be a BrowserTest for OSX.
  39. class SpellCheckTest : public testing::Test {
  40. public:
  41. SpellCheckTest() {
  42. ReinitializeSpellCheck("en-US");
  43. }
  44. void ReinitializeSpellCheck(const std::string& language) {
  45. UninitializeSpellCheck();
  46. InitializeSpellCheck(language);
  47. }
  48. void UninitializeSpellCheck() {
  49. spell_check_ = std::make_unique<SpellCheck>(&embedder_provider_);
  50. }
  51. bool InitializeIfNeeded() {
  52. return spell_check()->InitializeIfNeeded();
  53. }
  54. void InitializeSpellCheck(const std::string& language) {
  55. base::FilePath hunspell_directory = GetHunspellDirectory();
  56. EXPECT_FALSE(hunspell_directory.empty());
  57. base::FilePath hunspell_file_path =
  58. spellcheck::GetVersionedFileName(language, hunspell_directory);
  59. base::File file(hunspell_file_path,
  60. base::File::FLAG_OPEN | base::File::FLAG_READ);
  61. EXPECT_TRUE(file.IsValid()) << hunspell_file_path << " is not valid"
  62. << file.ErrorToString(file.GetLastFileError());
  63. #if BUILDFLAG(IS_APPLE)
  64. // TODO(groby): Forcing spellcheck to use hunspell, even on OSX.
  65. // Instead, tests should exercise individual spelling engines.
  66. spell_check_->languages_.push_back(
  67. std::make_unique<SpellcheckLanguage>(&embedder_provider_));
  68. spell_check_->languages_.front()->platform_spelling_engine_ =
  69. std::make_unique<HunspellEngine>(&embedder_provider_);
  70. spell_check_->languages_.front()->Init(std::move(file), language);
  71. #else
  72. spell_check_->AddSpellcheckLanguage(std::move(file), language);
  73. #endif
  74. }
  75. ~SpellCheckTest() override {}
  76. SpellCheck* spell_check() { return spell_check_.get(); }
  77. bool CheckSpelling(const std::string& word, int tag) {
  78. return spell_check_->languages_.front()
  79. ->platform_spelling_engine_->CheckSpelling(base::ASCIIToUTF16(word),
  80. tag);
  81. }
  82. bool IsValidContraction(const std::u16string& word, int tag) {
  83. return spell_check_->languages_.front()->IsValidContraction(word, tag);
  84. }
  85. static void FillSuggestions(
  86. const std::vector<std::vector<std::u16string>>& suggestions_list,
  87. std::vector<std::u16string>* optional_suggestions) {
  88. spellcheck::FillSuggestions(suggestions_list, optional_suggestions);
  89. }
  90. #if !BUILDFLAG(IS_APPLE)
  91. protected:
  92. void TestSpellCheckParagraph(const std::u16string& input,
  93. const std::vector<SpellCheckResult>& expected) {
  94. blink::WebVector<blink::WebTextCheckingResult> results;
  95. spell_check()->SpellCheckParagraph(input, &results);
  96. EXPECT_EQ(results.size(), expected.size());
  97. size_t size = std::min(results.size(), expected.size());
  98. for (size_t j = 0; j < size; ++j) {
  99. EXPECT_EQ(results[j].decoration, blink::kWebTextDecorationTypeSpelling);
  100. EXPECT_EQ(results[j].location, expected[j].location);
  101. EXPECT_EQ(results[j].length, expected[j].length);
  102. }
  103. }
  104. #endif
  105. private:
  106. spellcheck::EmptyLocalInterfaceProvider embedder_provider_;
  107. std::unique_ptr<SpellCheck> spell_check_;
  108. base::test::SingleThreadTaskEnvironment task_environment_;
  109. };
  110. struct MockTextCheckingResult {
  111. size_t completion_count_ = 0;
  112. blink::WebVector<blink::WebTextCheckingResult> last_results_;
  113. };
  114. // A fake completion object for verification.
  115. class MockTextCheckingCompletion : public blink::WebTextCheckingCompletion {
  116. public:
  117. explicit MockTextCheckingCompletion(MockTextCheckingResult* result)
  118. : result_(result) {}
  119. void DidFinishCheckingText(
  120. const blink::WebVector<blink::WebTextCheckingResult>& results) override {
  121. result_->completion_count_++;
  122. result_->last_results_ = results;
  123. }
  124. void DidCancelCheckingText() override { result_->completion_count_++; }
  125. MockTextCheckingResult* result_;
  126. };
  127. // Operates unit tests for the content::SpellCheck::SpellCheckWord() function
  128. // with the US English dictionary.
  129. // The unit tests in this function consist of:
  130. // * Tests for the function with empty strings;
  131. // * Tests for the function with a valid English word;
  132. // * Tests for the function with a valid non-English word;
  133. // * Tests for the function with a valid English word with a preceding
  134. // space character;
  135. // * Tests for the function with a valid English word with a preceding
  136. // non-English word;
  137. // * Tests for the function with a valid English word with a following
  138. // space character;
  139. // * Tests for the function with a valid English word with a following
  140. // non-English word;
  141. // * Tests for the function with two valid English words concatenated
  142. // with space characters or non-English words;
  143. // * Tests for the function with an invalid English word;
  144. // * Tests for the function with an invalid English word with a preceding
  145. // space character;
  146. // * Tests for the function with an invalid English word with a preceding
  147. // non-English word;
  148. // * Tests for the function with an invalid English word with a following
  149. // space character;
  150. // * Tests for the function with an invalid English word with a following
  151. // non-English word, and;
  152. // * Tests for the function with two invalid English words concatenated
  153. // with space characters or non-English words.
  154. // A test with a "[ROBUSTNESS]" mark shows it is a robustness test and it uses
  155. // grammatically incorrect string.
  156. // TODO(groby): Please feel free to add more tests.
  157. TEST_F(SpellCheckTest, SpellCheckStrings_EN_US) {
  158. static const struct {
  159. // A string to be tested.
  160. const wchar_t* input;
  161. // An expected result for this test case.
  162. // * true: the input string does not have any invalid words.
  163. // * false: the input string has one or more invalid words.
  164. bool expected_result;
  165. // The position and the length of the first invalid word.
  166. size_t misspelling_start;
  167. size_t misspelling_length;
  168. } kTestCases[] = {
  169. // Empty strings.
  170. {L"", true},
  171. {L" ", true},
  172. {L"\xA0", true},
  173. {L"\x3000", true},
  174. // A valid English word "hello".
  175. {L"hello", true},
  176. // A valid Chinese word (meaning "hello") consisting of two CJKV
  177. // ideographs
  178. {L"\x4F60\x597D", true},
  179. // A valid Korean word (meaning "hello") consisting of five hangul
  180. // syllables
  181. {L"\xC548\xB155\xD558\xC138\xC694", true},
  182. // A valid Japanese word (meaning "hello") consisting of five Hiragana
  183. // letters
  184. {L"\x3053\x3093\x306B\x3061\x306F", true},
  185. // A valid Hindi word (meaning ?) consisting of six Devanagari letters
  186. // (This word is copied from "http://b/issue?id=857583".)
  187. {L"\x0930\x093E\x091C\x0927\x093E\x0928", true},
  188. // A valid English word "affix" using a Latin ligature 'ffi'
  189. {L"a\xFB03x", true},
  190. // A valid English word "hello" (fullwidth version)
  191. {L"\xFF28\xFF45\xFF4C\xFF4C\xFF4F", true},
  192. // Two valid Greek words (meaning "hello") consisting of seven Greek
  193. // letters
  194. {L"\x03B3\x03B5\x03B9\x03AC"
  195. L" "
  196. L"\x03C3\x03BF\x03C5",
  197. true},
  198. // A valid Russian word (meaning "hello") consisting of twelve Cyrillic
  199. // letters
  200. {L"\x0437\x0434\x0440\x0430\x0432\x0441"
  201. L"\x0442\x0432\x0443\x0439\x0442\x0435",
  202. true},
  203. // A valid English contraction
  204. {L"isn't", true},
  205. // A valid English contraction with a typographical apostrophe.
  206. {L"isn’t", true},
  207. // A valid English word enclosed with underscores.
  208. {L"_hello_", true},
  209. // A valid English word with a preceding whitespace
  210. {L" "
  211. L"hello",
  212. true},
  213. // A valid English word with a preceding no-break space
  214. {L"\xA0"
  215. L"hello",
  216. true},
  217. // A valid English word with a preceding ideographic space
  218. {L"\x3000"
  219. L"hello",
  220. true},
  221. // A valid English word with a preceding Chinese word
  222. {L"\x4F60\x597D"
  223. L"hello",
  224. true},
  225. // [ROBUSTNESS] A valid English word with a preceding Korean word
  226. {L"\xC548\xB155\xD558\xC138\xC694"
  227. L"hello",
  228. true},
  229. // A valid English word with a preceding Japanese word
  230. {L"\x3053\x3093\x306B\x3061\x306F"
  231. L"hello",
  232. true},
  233. // [ROBUSTNESS] A valid English word with a preceding Hindi word
  234. {L"\x0930\x093E\x091C\x0927\x093E\x0928"
  235. L"hello",
  236. true},
  237. // [ROBUSTNESS] A valid English word with two preceding Greek words
  238. {L"\x03B3\x03B5\x03B9\x03AC"
  239. L" "
  240. L"\x03C3\x03BF\x03C5"
  241. L"hello",
  242. true},
  243. // [ROBUSTNESS] A valid English word with a preceding Russian word
  244. {L"\x0437\x0434\x0440\x0430\x0432\x0441"
  245. L"\x0442\x0432\x0443\x0439\x0442\x0435"
  246. L"hello",
  247. true},
  248. // A valid English word with a following whitespace
  249. {L"hello"
  250. L" ",
  251. true},
  252. // A valid English word with a following no-break space
  253. {L"hello"
  254. L"\xA0",
  255. true},
  256. // A valid English word with a following ideographic space
  257. {L"hello"
  258. L"\x3000",
  259. true},
  260. // A valid English word with a following Chinese word
  261. {L"hello"
  262. L"\x4F60\x597D",
  263. true},
  264. // [ROBUSTNESS] A valid English word with a following Korean word
  265. {L"hello"
  266. L"\xC548\xB155\xD558\xC138\xC694",
  267. true},
  268. // A valid English word with a following Japanese word
  269. {L"hello"
  270. L"\x3053\x3093\x306B\x3061\x306F",
  271. true},
  272. // [ROBUSTNESS] A valid English word with a following Hindi word
  273. {L"hello"
  274. L"\x0930\x093E\x091C\x0927\x093E\x0928",
  275. true},
  276. // [ROBUSTNESS] A valid English word with two following Greek words
  277. {L"hello"
  278. L"\x03B3\x03B5\x03B9\x03AC"
  279. L" "
  280. L"\x03C3\x03BF\x03C5",
  281. true},
  282. // [ROBUSTNESS] A valid English word with a following Russian word
  283. {L"hello"
  284. L"\x0437\x0434\x0440\x0430\x0432\x0441"
  285. L"\x0442\x0432\x0443\x0439\x0442\x0435",
  286. true},
  287. // Two valid English words concatenated with a whitespace
  288. {L"hello"
  289. L" "
  290. L"hello",
  291. true},
  292. // Two valid English words concatenated with a no-break space
  293. {L"hello"
  294. L"\xA0"
  295. L"hello",
  296. true},
  297. // Two valid English words concatenated with an ideographic space
  298. {L"hello"
  299. L"\x3000"
  300. L"hello",
  301. true},
  302. // Two valid English words concatenated with a Chinese word
  303. {L"hello"
  304. L"\x4F60\x597D"
  305. L"hello",
  306. true},
  307. // [ROBUSTNESS] Two valid English words concatenated with a Korean word
  308. {L"hello"
  309. L"\xC548\xB155\xD558\xC138\xC694"
  310. L"hello",
  311. true},
  312. // Two valid English words concatenated with a Japanese word
  313. {L"hello"
  314. L"\x3053\x3093\x306B\x3061\x306F"
  315. L"hello",
  316. true},
  317. // [ROBUSTNESS] Two valid English words concatenated with a Hindi word
  318. {L"hello"
  319. L"\x0930\x093E\x091C\x0927\x093E\x0928"
  320. L"hello",
  321. true},
  322. // [ROBUSTNESS] Two valid English words concatenated with two Greek words
  323. {L"hello"
  324. L"\x03B3\x03B5\x03B9\x03AC"
  325. L" "
  326. L"\x03C3\x03BF\x03C5"
  327. L"hello",
  328. true},
  329. // [ROBUSTNESS] Two valid English words concatenated with a Russian word
  330. {L"hello"
  331. L"\x0437\x0434\x0440\x0430\x0432\x0441"
  332. L"\x0442\x0432\x0443\x0439\x0442\x0435"
  333. L"hello",
  334. true},
  335. // [ROBUSTNESS] Two valid English words concatenated with a contraction
  336. // character.
  337. {L"hello:hello", true},
  338. // An invalid English word
  339. {L"ifmmp", false, 0, 5},
  340. // An invalid English word "bffly" containing a Latin ligature 'ffl'
  341. {L"b\xFB04y", false, 0, 3},
  342. // An invalid English word "ifmmp" (fullwidth version)
  343. {L"\xFF29\xFF46\xFF4D\xFF4D\xFF50", false, 0, 5},
  344. // An invalid English contraction
  345. {L"jtm'u", false, 0, 5},
  346. // An invalid English word enclosed with underscores.
  347. {L"_ifmmp_", false, 1, 5},
  348. // An invalid English word with a preceding whitespace
  349. {L" "
  350. L"ifmmp",
  351. false, 1, 5},
  352. // An invalid English word with a preceding no-break space
  353. {L"\xA0"
  354. L"ifmmp",
  355. false, 1, 5},
  356. // An invalid English word with a preceding ideographic space
  357. {L"\x3000"
  358. L"ifmmp",
  359. false, 1, 5},
  360. // An invalid English word with a preceding Chinese word
  361. {L"\x4F60\x597D"
  362. L"ifmmp",
  363. false, 2, 5},
  364. // [ROBUSTNESS] An invalid English word with a preceding Korean word
  365. {L"\xC548\xB155\xD558\xC138\xC694"
  366. L"ifmmp",
  367. false, 5, 5},
  368. // An invalid English word with a preceding Japanese word
  369. {L"\x3053\x3093\x306B\x3061\x306F"
  370. L"ifmmp",
  371. false, 5, 5},
  372. // [ROBUSTNESS] An invalid English word with a preceding Hindi word
  373. {L"\x0930\x093E\x091C\x0927\x093E\x0928"
  374. L"ifmmp",
  375. false, 6, 5},
  376. // [ROBUSTNESS] An invalid English word with two preceding Greek words
  377. {L"\x03B3\x03B5\x03B9\x03AC"
  378. L" "
  379. L"\x03C3\x03BF\x03C5"
  380. L"ifmmp",
  381. false, 8, 5},
  382. // [ROBUSTNESS] An invalid English word with a preceding Russian word
  383. {L"\x0437\x0434\x0440\x0430\x0432\x0441"
  384. L"\x0442\x0432\x0443\x0439\x0442\x0435"
  385. L"ifmmp",
  386. false, 12, 5},
  387. // An invalid English word with a following whitespace
  388. {L"ifmmp"
  389. L" ",
  390. false, 0, 5},
  391. // An invalid English word with a following no-break space
  392. {L"ifmmp"
  393. L"\xA0",
  394. false, 0, 5},
  395. // An invalid English word with a following ideographic space
  396. {L"ifmmp"
  397. L"\x3000",
  398. false, 0, 5},
  399. // An invalid English word with a following Chinese word
  400. {L"ifmmp"
  401. L"\x4F60\x597D",
  402. false, 0, 5},
  403. // [ROBUSTNESS] An invalid English word with a following Korean word
  404. {L"ifmmp"
  405. L"\xC548\xB155\xD558\xC138\xC694",
  406. false, 0, 5},
  407. // An invalid English word with a following Japanese word
  408. {L"ifmmp"
  409. L"\x3053\x3093\x306B\x3061\x306F",
  410. false, 0, 5},
  411. // [ROBUSTNESS] An invalid English word with a following Hindi word
  412. {L"ifmmp"
  413. L"\x0930\x093E\x091C\x0927\x093E\x0928",
  414. false, 0, 5},
  415. // [ROBUSTNESS] An invalid English word with two following Greek words
  416. {L"ifmmp"
  417. L"\x03B3\x03B5\x03B9\x03AC"
  418. L" "
  419. L"\x03C3\x03BF\x03C5",
  420. false, 0, 5},
  421. // [ROBUSTNESS] An invalid English word with a following Russian word
  422. {L"ifmmp"
  423. L"\x0437\x0434\x0440\x0430\x0432\x0441"
  424. L"\x0442\x0432\x0443\x0439\x0442\x0435",
  425. false, 0, 5},
  426. // Two invalid English words concatenated with a whitespace
  427. {L"ifmmp"
  428. L" "
  429. L"ifmmp",
  430. false, 0, 5},
  431. // Two invalid English words concatenated with a no-break space
  432. {L"ifmmp"
  433. L"\xA0"
  434. L"ifmmp",
  435. false, 0, 5},
  436. // Two invalid English words concatenated with an ideographic space
  437. {L"ifmmp"
  438. L"\x3000"
  439. L"ifmmp",
  440. false, 0, 5},
  441. // Two invalid English words concatenated with a Chinese word
  442. {L"ifmmp"
  443. L"\x4F60\x597D"
  444. L"ifmmp",
  445. false, 0, 5},
  446. // [ROBUSTNESS] Two invalid English words concatenated with a Korean word
  447. {L"ifmmp"
  448. L"\xC548\xB155\xD558\xC138\xC694"
  449. L"ifmmp",
  450. false, 0, 5},
  451. // Two invalid English words concatenated with a Japanese word
  452. {L"ifmmp"
  453. L"\x3053\x3093\x306B\x3061\x306F"
  454. L"ifmmp",
  455. false, 0, 5},
  456. // [ROBUSTNESS] Two invalid English words concatenated with a Hindi word
  457. {L"ifmmp"
  458. L"\x0930\x093E\x091C\x0927\x093E\x0928"
  459. L"ifmmp",
  460. false, 0, 5},
  461. // [ROBUSTNESS] Two invalid English words concatenated with two Greek
  462. // words
  463. {L"ifmmp"
  464. L"\x03B3\x03B5\x03B9\x03AC"
  465. L" "
  466. L"\x03C3\x03BF\x03C5"
  467. L"ifmmp",
  468. false, 0, 5},
  469. // [ROBUSTNESS] Two invalid English words concatenated with a Russian word
  470. {L"ifmmp"
  471. L"\x0437\x0434\x0440\x0430\x0432\x0441"
  472. L"\x0442\x0432\x0443\x0439\x0442\x0435"
  473. L"ifmmp",
  474. false, 0, 5},
  475. // [ROBUSTNESS] Two invalid English words concatenated with a contraction
  476. // character.
  477. {L"ifmmp:ifmmp", false, 0, 11},
  478. // [REGRESSION] Issue 13432: "Any word of 13 or 14 characters is not
  479. // spellcheck" <http://crbug.com/13432>.
  480. {L"qwertyuiopasd", false, 0, 13},
  481. {L"qwertyuiopasdf", false, 0, 14},
  482. // [REGRESSION] Issue 128896: "en_US hunspell dictionary includes
  483. // acknowledgement but not acknowledgements" <http://crbug.com/128896>
  484. {L"acknowledgement", true},
  485. {L"acknowledgements", true},
  486. // Issue 123290: "Spellchecker should treat numbers as word characters"
  487. {L"0th", true},
  488. {L"1st", true},
  489. {L"2nd", true},
  490. {L"3rd", true},
  491. {L"4th", true},
  492. {L"5th", true},
  493. {L"6th", true},
  494. {L"7th", true},
  495. {L"8th", true},
  496. {L"9th", true},
  497. {L"10th", true},
  498. {L"100th", true},
  499. {L"1000th", true},
  500. {L"25", true},
  501. {L"2012", true},
  502. {L"100,000,000", true},
  503. {L"3.141592653", true},
  504. };
  505. for (size_t i = 0; i < std::size(kTestCases); ++i) {
  506. size_t input_length = 0;
  507. if (kTestCases[i].input)
  508. input_length = wcslen(kTestCases[i].input);
  509. size_t misspelling_start;
  510. size_t misspelling_length;
  511. bool result = spell_check()->SpellCheckWord(
  512. base::WideToUTF16(kTestCases[i].input).c_str(), kNoOffset, input_length,
  513. kNoTag, &misspelling_start, &misspelling_length, nullptr);
  514. EXPECT_EQ(kTestCases[i].expected_result, result);
  515. EXPECT_EQ(kTestCases[i].misspelling_start, misspelling_start);
  516. EXPECT_EQ(kTestCases[i].misspelling_length, misspelling_length);
  517. }
  518. }
  519. TEST_F(SpellCheckTest, SpellCheckSuggestions_EN_US) {
  520. static const struct {
  521. // A string to be tested.
  522. const wchar_t* input;
  523. // An expected result for this test case.
  524. // * true: the input string does not have any invalid words.
  525. // * false: the input string has one or more invalid words.
  526. bool expected_result;
  527. // The position and the length of the first invalid word.
  528. int misspelling_start;
  529. int misspelling_length;
  530. // A suggested word that should occur.
  531. const wchar_t* suggested_word;
  532. } kTestCases[] = {
  533. {L"ello", false, 0, 0, L"hello"},
  534. {L"ello", false, 0, 0, L"cello"},
  535. {L"wate", false, 0, 0, L"water"},
  536. {L"wate", false, 0, 0, L"waste"},
  537. {L"wate", false, 0, 0, L"sate"},
  538. {L"wate", false, 0, 0, L"ate"},
  539. {L"jum", false, 0, 0, L"jump"},
  540. {L"jum", false, 0, 0, L"hum"},
  541. {L"jum", false, 0, 0, L"sum"},
  542. {L"jum", false, 0, 0, L"um"},
  543. {L"alot", false, 0, 0, L"a lot"},
  544. // A regression test for Issue 36523.
  545. {L"privliged", false, 0, 0, L"privileged"},
  546. // TODO (Sidchat): add many more examples.
  547. };
  548. for (const auto& test_case : kTestCases) {
  549. std::vector<std::u16string> suggestions;
  550. size_t input_length = 0;
  551. if (test_case.input)
  552. input_length = wcslen(test_case.input);
  553. size_t misspelling_start;
  554. size_t misspelling_length;
  555. bool result = spell_check()->SpellCheckWord(
  556. base::WideToUTF16(test_case.input).c_str(), kNoOffset, input_length,
  557. kNoTag, &misspelling_start, &misspelling_length, &suggestions);
  558. // Check for spelling.
  559. EXPECT_EQ(test_case.expected_result, result);
  560. // Check if the suggested words occur.
  561. bool suggested_word_is_present = base::Contains(
  562. suggestions, base::WideToUTF16(test_case.suggested_word));
  563. EXPECT_TRUE(suggested_word_is_present);
  564. }
  565. }
  566. // This test verifies our spellchecker can split a text into words and check
  567. // the spelling of each word in the text.
  568. TEST_F(SpellCheckTest, SpellCheckText) {
  569. static const struct {
  570. const char* language;
  571. const wchar_t* input;
  572. } kTestCases[] = {
  573. {// Afrikaans
  574. "af-ZA",
  575. L"Google se missie is om die w\x00EAreld se inligting te organiseer en "
  576. L"dit bruikbaar en toeganklik te maak."},
  577. {// Bulgarian
  578. "bg-BG",
  579. L"\x041c\x0438\x0441\x0438\x044f\x0442\x0430 "
  580. L"\x043d\x0430 Google \x0435 \x0434\x0430 \x043e"
  581. L"\x0440\x0433\x0430\x043d\x0438\x0437\x0438\x0440"
  582. L"\x0430 \x0441\x0432\x0435\x0442\x043e\x0432"
  583. L"\x043d\x0430\x0442\x0430 \x0438\x043d\x0444"
  584. L"\x043e\x0440\x043c\x0430\x0446\x0438\x044f "
  585. L"\x0438 \x0434\x0430 \x044f \x043d"
  586. L"\x0430\x043f\x0440\x0430\x0432\x0438 \x0443"
  587. L"\x043d\x0438\x0432\x0435\x0440\x0441\x0430\x043b"
  588. L"\x043d\x043e \x0434\x043e\x0441\x0442\x044a"
  589. L"\x043f\x043d\x0430 \x0438 \x043f\x043e"
  590. L"\x043b\x0435\x0437\x043d\x0430."},
  591. {// Catalan
  592. "ca-ES",
  593. L"La missi\x00F3 de Google \x00E9s organitzar la informaci\x00F3 "
  594. L"del m\x00F3n i fer que sigui \x00FAtil i accessible universalment."},
  595. {// Czech
  596. "cs-CZ",
  597. L"Posl\x00E1n\x00EDm spole\x010Dnosti Google je "
  598. L"uspo\x0159\x00E1\x0064\x0061t informace z cel\x00E9ho sv\x011Bta "
  599. L"tak, aby byly v\x0161\x0065obecn\x011B p\x0159\x00EDstupn\x00E9 "
  600. L"a u\x017Eite\x010Dn\x00E9."},
  601. {// Welsh
  602. "cy-GB",
  603. L"Y genhadaeth yw trefnu gwybodaeth y byd a'i gwneud yn hygyrch ac yn "
  604. L"ddefnyddiol i bawb."},
  605. {// Danish
  606. "da-DK",
  607. L"Googles "
  608. L"mission er at organisere verdens information og g\x00F8re den "
  609. L"almindeligt tilg\x00E6ngelig og nyttig."},
  610. {// German
  611. "de-DE",
  612. L"Das Ziel von Google besteht darin, die auf der Welt vorhandenen "
  613. L"Informationen zu organisieren und allgemein zug\x00E4nglich und "
  614. L"nutzbar zu machen."},
  615. {// Greek
  616. "el-GR",
  617. L"\x0391\x03C0\x03BF\x03C3\x03C4\x03BF\x03BB\x03AE "
  618. L"\x03C4\x03B7\x03C2 Google \x03B5\x03AF\x03BD\x03B1\x03B9 "
  619. L"\x03BD\x03B1 \x03BF\x03C1\x03B3\x03B1\x03BD\x03CE\x03BD\x03B5\x03B9 "
  620. L"\x03C4\x03B9\x03C2 "
  621. L"\x03C0\x03BB\x03B7\x03C1\x03BF\x03C6\x03BF\x03C1\x03AF\x03B5\x03C2 "
  622. L"\x03C4\x03BF\x03C5 \x03BA\x03CC\x03C3\x03BC\x03BF\x03C5 "
  623. L"\x03BA\x03B1\x03B9 \x03BD\x03B1 \x03C4\x03B9\x03C2 "
  624. L"\x03BA\x03B1\x03B8\x03B9\x03C3\x03C4\x03AC "
  625. L"\x03C0\x03C1\x03BF\x03C3\x03B2\x03AC\x03C3\x03B9\x03BC\x03B5\x03C2 "
  626. L"\x03BA\x03B1\x03B9 \x03C7\x03C1\x03AE\x03C3\x03B9\x03BC\x03B5\x03C2."},
  627. {// English (Australia)
  628. "en-AU",
  629. L"Google's mission is to organise the world's information and make it "
  630. L"universally accessible and useful."},
  631. {// English (Canada)
  632. "en-CA",
  633. L"Google's mission is to organize the world's information and make it "
  634. L"universally accessible and useful."},
  635. {// English (United Kingdom)
  636. "en-GB",
  637. L"Google's mission is to organise the world's information and make it "
  638. L"universally accessible and useful."},
  639. {// English (United States)
  640. "en-US",
  641. L"Google's mission is to organize the world's information and make it "
  642. L"universally accessible and useful."},
  643. {// Spanish
  644. "es-ES",
  645. L"La misi\x00F3n de "
  646. // L"Google" - to be added.
  647. L" es organizar la informaci\x00F3n mundial "
  648. L"para que resulte universalmente accesible y \x00FAtil."},
  649. {
  650. // Estonian
  651. "et-EE",
  652. // L"Google'ile " - to be added.
  653. L"\x00FClesanne on korraldada maailma teavet ja teeb selle "
  654. L"k\x00F5igile k\x00E4ttesaadavaks ja kasulikuks.",
  655. },
  656. {// Persian
  657. "fa",
  658. L"\x0686\x0647 \x0637\x0648\x0631 \x0622\x06cc\x0627 \x0634\x0645\x0627 "
  659. L"\x0627\x06cc\x0631\x0627\x0646\x06cc \x0647\x0633\x062a\x06cc\x062f"},
  660. {// Faroese
  661. "fo-FO",
  662. L"Google er at samskipa alla vitan \x00ED heiminum og gera hana alment "
  663. L"atkomiliga og n\x00FDtiliga."},
  664. {// French
  665. "fr-FR",
  666. L"Google a pour mission d'organiser les informations \x00E0 "
  667. L"l'\x00E9\x0063helle mondiale dans le but de les rendre accessibles "
  668. L"et utiles \x00E0 tous."},
  669. {// Hebrew
  670. "he-IL",
  671. L"\x05D4\x05DE\x05E9\x05D9\x05DE\x05D4 \x05E9\x05DC Google "
  672. L"\x05D4\x05D9\x05D0 \x05DC\x05D0\x05E8\x05D2\x05DF "
  673. L"\x05D0\x05EA \x05D4\x05DE\x05D9\x05D3\x05E2 "
  674. L"\x05D4\x05E2\x05D5\x05DC\x05DE\x05D9 "
  675. L"\x05D5\x05DC\x05D4\x05E4\x05D5\x05DA \x05D0\x05D5\x05EA\x05D5 "
  676. L"\x05DC\x05D6\x05DE\x05D9\x05DF "
  677. L"\x05D5\x05E9\x05D9\x05DE\x05D5\x05E9\x05D9 \x05D1\x05DB\x05DC "
  678. L"\x05D4\x05E2\x05D5\x05DC\x05DD. "
  679. // Two words with ASCII double/single quoation marks.
  680. L"\x05DE\x05E0\x05DB\x0022\x05DC \x05E6\x0027\x05D9\x05E4\x05E1"},
  681. {// Hindi
  682. "hi-IN",
  683. L"Google \x0915\x093E \x092E\x093F\x0936\x0928 "
  684. L"\x0926\x0941\x0928\x093F\x092F\x093E \x0915\x0940 "
  685. L"\x091C\x093E\x0928\x0915\x093E\x0930\x0940 \x0915\x094B "
  686. L"\x0935\x094D\x092F\x0935\x0938\x094D\x0925\x093F\x0924 "
  687. L"\x0915\x0930\x0928\x093E \x0914\x0930 \x0909\x0938\x0947 "
  688. L"\x0938\x093E\x0930\x094D\x0935\x092D\x094C\x092E\x093F\x0915 "
  689. L"\x0930\x0942\x092A \x0938\x0947 \x092A\x0939\x0941\x0901\x091A "
  690. L"\x092E\x0947\x0902 \x0914\x0930 \x0909\x092A\x092F\x094B\x0917\x0940 "
  691. L"\x092C\x0928\x093E\x0928\x093E \x0939\x0948."},
  692. {
  693. #if !BUILDFLAG(IS_WIN)
  694. // Hungarian
  695. "hu-HU",
  696. L"A Google azt a k\x00FCldet\x00E9st v\x00E1llalta mag\x00E1ra, "
  697. L"hogy a vil\x00E1gon fellelhet\x0151 inform\x00E1\x0063i\x00F3kat "
  698. L"rendszerezze \x00E9s \x00E1ltal\x00E1nosan "
  699. L"el\x00E9rhet\x0151v\x00E9, "
  700. L"illetve haszn\x00E1lhat\x00F3v\x00E1 tegye."},
  701. {
  702. #endif // !BUILDFLAG(IS_WIN)
  703. // Croatian
  704. "hr-HR",
  705. // L"Googleova " - to be added.
  706. L"je misija organizirati svjetske informacije i u\x010Diniti ih "
  707. // L"univerzalno " - to be added.
  708. L"pristupa\x010Dnima i korisnima."},
  709. {// Armenian
  710. "hy",
  711. L"Google- \x056B \x0561\x057C\x0561\x0584\x0565\x056C\x0578\x0582\x0569"
  712. L"\x0575\x0578\x0582\x0576\x0576 \x0567 \x0570\x0561\x0574\x0561\x0577"
  713. L"\x056D\x0561\x0580\x0570\x0561\x0575\x056B\x0576 \x057F\x0565\x0572"
  714. L"\x0565\x056F\x0561\x057F\x057E\x0578\x0582\x0569\x0575\x0578\x0582"
  715. L"\x0576\x0568 \x056F\x0561\x0566\x0574\x0561\x056F\x0565\x0580\x057A"
  716. L"\x0565\x056C \x0565\x0582 \x0564\x0561\x0580\x0571\x0576\x0565\x056C "
  717. L"\x0561\x0575\x0576 \x0570\x0561\x0574\x0568\x0576\x0564\x0570\x0561"
  718. L"\x0576\x0578\x0582\x0580 \x0570\x0561\x057D\x0561\x0576\x0565\x056C"
  719. L"\x056B \x0565\x0582 \x0585\x0563\x057F\x0561\x056F\x0561\x0580:"},
  720. {// Indonesian
  721. "id-ID",
  722. L"Misi Google adalah untuk mengelola informasi dunia dan membuatnya "
  723. L"dapat diakses dan bermanfaat secara universal."},
  724. {// Italian
  725. "it-IT",
  726. L"La missione di Google \x00E8 organizzare le informazioni a livello "
  727. L"mondiale e renderle universalmente accessibili e fruibili."},
  728. {// Lithuanian
  729. "lt-LT",
  730. L"\x201EGoogle\x201C tikslas \x2013 rinkti ir sisteminti pasaulio "
  731. L"informacij\x0105 bei padaryti j\x0105 prieinam\x0105 ir "
  732. L"nauding\x0105 visiems."},
  733. {// Latvian
  734. "lv-LV",
  735. L"Google uzdevums ir k\x0101rtot pasaules inform\x0101"
  736. L"ciju un padar\x012Bt to univers\x0101li pieejamu un noder\x012Bgu."},
  737. {// Norwegian
  738. "nb-NO",
  739. // L"Googles " - to be added.
  740. L"m\x00E5l er \x00E5 organisere informasjonen i verden og "
  741. L"gj\x00F8re den tilgjengelig og nyttig for alle."},
  742. {// Dutch
  743. "nl-NL",
  744. L"Het doel van Google is om alle informatie wereldwijd toegankelijk "
  745. L"en bruikbaar te maken."},
  746. {// Polish
  747. "pl-PL",
  748. L"Misj\x0105 Google jest uporz\x0105"
  749. L"dkowanie \x015Bwiatowych "
  750. L"zasob\x00F3w informacji, aby sta\x0142y si\x0119 one powszechnie "
  751. L"dost\x0119pne i u\x017Cyteczne."},
  752. {
  753. #if !BUILDFLAG(IS_WIN)
  754. // Portuguese (Brazil)
  755. "pt-BR",
  756. L"A miss\x00E3o do "
  757. #if !BUILDFLAG(IS_APPLE)
  758. L"Google "
  759. #endif
  760. L"\x00E9 organizar as informa\x00E7\x00F5"
  761. L"es do mundo todo e "
  762. #if !BUILDFLAG(IS_APPLE)
  763. L"torn\x00E1-las "
  764. #endif
  765. L"acess\x00EDveis e \x00FAteis em car\x00E1ter universal."},
  766. {
  767. #endif // !BUILDFLAG(IS_WIN)
  768. // Portuguese (Portugal)
  769. "pt-PT",
  770. L"O "
  771. #if !BUILDFLAG(IS_APPLE)
  772. L"Google "
  773. #endif
  774. L"tem por miss\x00E3o organizar a informa\x00E7\x00E3o do "
  775. L"mundo e "
  776. #if !BUILDFLAG(IS_APPLE)
  777. L"torn\x00E1-la "
  778. #endif
  779. L"universalmente acess\x00EDvel e \x00FAtil"},
  780. {// Romanian
  781. "ro-RO",
  782. L"Misiunea Google este de a organiza informa\x021B3iile lumii \x0219i "
  783. L"de "
  784. L"a le face accesibile \x0219i utile la nivel universal."},
  785. {// Russian
  786. "ru-RU",
  787. L"\x041C\x0438\x0441\x0441\x0438\x044F Google "
  788. L"\x0441\x043E\x0441\x0442\x043E\x0438\x0442 \x0432 "
  789. L"\x043E\x0440\x0433\x0430\x043D\x0438\x0437\x0430\x0446\x0438\x0438 "
  790. L"\x043C\x0438\x0440\x043E\x0432\x043E\x0439 "
  791. L"\x0438\x043D\x0444\x043E\x0440\x043C\x0430\x0446\x0438\x0438, "
  792. L"\x043E\x0431\x0435\x0441\x043F\x0435\x0447\x0435\x043D\x0438\x0438 "
  793. L"\x0435\x0435 "
  794. L"\x0434\x043E\x0441\x0442\x0443\x043F\x043D\x043E\x0441\x0442\x0438 "
  795. L"\x0438 \x043F\x043E\x043B\x044C\x0437\x044B \x0434\x043B\x044F "
  796. L"\x0432\x0441\x0435\x0445."
  797. // A Russian word including U+0451. (Bug 15558 <http://crbug.com/15558>)
  798. L"\x0451\x043B\x043A\x0430"},
  799. {// Serbo-Croatian (Serbian Latin)
  800. "sh",
  801. L"Guglova misija je organizirati svjetske informacije i u\x010diniti ih "
  802. L"univerzalno dostupnim i korisnim."},
  803. {// Serbian
  804. "sr",
  805. L"\x0413\x0443\x0433\x043B\x043E\x0432\x0430 "
  806. L"\x043C\x0438\x0441\x0438\x0458\x0430 \x0458\x0435 \x0434\x0430 "
  807. L"\x043E\x0440\x0433\x0430\x043D\x0438\x0437\x0443\x0458\x0435 "
  808. L"\x0441\x0432\x0435\x0442\x0441\x043A\x0435 "
  809. L"\x0438\x043D\x0444\x043E\x0440\x043C\x0430\x0446\x0438\x0458\x0435 "
  810. L"\x0438 \x0443\x0447\x0438\x043D\x0438 \x0438\x0445 "
  811. L"\x0443\x043D\x0438\x0432\x0435\x0440\x0437\x0430\x043B\x043D\x0438"
  812. L"\x043C \x0434\x043E\x0441\x0442\x0443\x043F\x043D\x0438\x043C \x0438 "
  813. L"\x043A\x043E\x0440\x0438\x0441\x043D\x0438\x043C."},
  814. {// Slovak
  815. "sk-SK",
  816. L"Spolo\x010Dnos\x0165 Google si dala za \x00FAlohu usporiada\x0165 "
  817. L"inform\x00E1\x0063ie "
  818. L"z cel\x00E9ho sveta a zabezpe\x010Di\x0165, "
  819. L"aby boli v\x0161eobecne dostupn\x00E9 a u\x017Eito\x010Dn\x00E9."},
  820. {// Slovenian
  821. "sl-SI",
  822. // L"Googlovo " - to be added.
  823. L"poslanstvo je organizirati svetovne informacije in "
  824. L"omogo\x010Diti njihovo dostopnost in s tem uporabnost za vse."},
  825. {// Swedish
  826. "sv-SE",
  827. L"Googles m\x00E5ls\x00E4ttning \x00E4r att ordna v\x00E4rldens "
  828. L"samlade information och g\x00F6ra den tillg\x00E4nglig f\x00F6r "
  829. L"alla."},
  830. {
  831. #if !BUILDFLAG(IS_WIN)
  832. // Turkish
  833. "tr-TR",
  834. // L"Google\x2019\x0131n " - to be added.
  835. L"misyonu, d\x00FCnyadaki t\x00FCm bilgileri "
  836. L"organize etmek ve evrensel olarak eri\x015Filebilir ve "
  837. L"kullan\x0131\x015Fl\x0131 k\x0131lmakt\x0131r."},
  838. {
  839. #endif // !BUILDFLAG(IS_WIN)
  840. // Ukrainian
  841. "uk-UA",
  842. L"\x041c\x0456\x0441\x0456\x044f "
  843. L"\x043a\x043e\x043c\x043f\x0430\x043d\x0456\x0457 Google "
  844. L"\x043f\x043e\x043b\x044f\x0433\x0430\x0454 \x0432 "
  845. L"\x0442\x043e\x043c\x0443, \x0449\x043e\x0431 "
  846. L"\x0443\x043f\x043e\x0440\x044f\x0434\x043a\x0443\x0432\x0430\x0442"
  847. L"\x0438 "
  848. L"\x0456\x043d\x0444\x043e\x0440\x043c\x0430\x0446\x0456\x044e "
  849. L"\x0437 \x0443\x0441\x044c\x043e\x0433\x043e "
  850. L"\x0441\x0432\x0456\x0442\x0443 \x0442\x0430 "
  851. L"\x0437\x0440\x043e\x0431\x0438\x0442\x0438 \x0457\x0457 "
  852. L"\x0443\x043d\x0456\x0432\x0435\x0440\x0441\x0430\x043b\x044c\x043d"
  853. L"\x043e \x0434\x043e\x0441\x0442\x0443\x043f\x043d\x043e\x044e "
  854. L"\x0442\x0430 \x043a\x043e\x0440\x0438\x0441\x043d\x043e\x044e."},
  855. {// Vietnamese
  856. "vi-VN",
  857. L"Nhi\x1EC7m v\x1EE5 c\x1EE7\x0061 "
  858. L"Google la \x0111\x1EC3 t\x1ED5 ch\x1EE9\x0063 "
  859. L"c\x00E1\x0063 th\x00F4ng tin c\x1EE7\x0061 "
  860. L"th\x1EBF gi\x1EDBi va l\x00E0m cho n\x00F3 universal c\x00F3 "
  861. L"th\x1EC3 truy c\x1EADp va h\x1EEFu d\x1EE5ng h\x01A1n."},
  862. {
  863. #if !BUILDFLAG(IS_WIN)
  864. // Korean
  865. "ko",
  866. L"Google\xC758 \xBAA9\xD45C\xB294 \xC804\xC138\xACC4\xC758 "
  867. L"\xC815\xBCF4\xB97C \xCCB4\xACC4\xD654\xD558\xC5EC "
  868. L"\xBAA8\xB450\xAC00 "
  869. L"\xD3B8\xB9AC\xD558\xAC8C \xC774\xC6A9\xD560 \xC218 "
  870. L"\xC788\xB3C4\xB85D \xD558\xB294 \xAC83\xC785\xB2C8\xB2E4."},
  871. {
  872. #endif // !BUILDFLAG(IS_WIN)
  873. // Albanian
  874. "sq",
  875. L"Misioni i Google \x00EBsht\x00EB q\x00EB t\x00EB organizoj\x00EB "
  876. L"informacionin e bot\x00EBs dhe t\x00EB b\x00EBjn\x00EB at\x00EB "
  877. L"universalisht t\x00EB arritshme dhe t\x00EB dobishme."},
  878. {// Tamil
  879. "ta",
  880. L"Google \x0B87\x0BA9\x0BCD "
  881. L"\x0BA8\x0BC7\x0BBE\x0B95\x0BCD\x0B95\x0BAE\x0BCD "
  882. L"\x0B89\x0BB2\x0B95\x0BBF\x0BA9\x0BCD \x0BA4\x0B95\x0BB5\x0BB2\x0BCD "
  883. L"\x0B8F\x0BB1\x0BCD\x0BAA\x0BBE\x0B9F\x0BC1 \x0B87\x0BA4\x0BC1 "
  884. L"\x0B89\x0BB2\x0B95\x0BB3\x0BBE\x0BB5\x0BBF\x0BAF "
  885. L"\x0B85\x0BA3\x0BC1\x0B95\x0B95\x0BCD \x0B95\x0BC2\x0B9F\x0BBF\x0BAF "
  886. L"\x0BAE\x0BB1\x0BCD\x0BB1\x0BC1\x0BAE\x0BCD "
  887. L"\x0BAA\x0BAF\x0BA9\x0BC1\x0BB3\x0BCD\x0BB3 "
  888. L"\x0B9A\x0BC6\x0BAF\x0BCD\x0BAF \x0B89\x0BB3\x0BCD\x0BB3\x0BA4\x0BC1."},
  889. {// Tajik
  890. "tg",
  891. L"\x041c\x0438\x0441\x0441\x0438\x044f\x0438 Google \x0438\x043d "
  892. L"\x043c\x0443\x0440\x0430\x0442\x0442\x0430\x0431 "
  893. L"\x0441\x043e\x0445\x0442\x0430\x043d\x0438 "
  894. L"\x043c\x0430\x044a\x043b\x0443\x043c\x043e\x0442\x04b3\x043e\x0438 "
  895. L"\x043c\x0430\x0432\x04b7\x0443\x0434\x0430, \x043e\x0441\x043e\x043d "
  896. L"\x043d\x0430\x043c\x0443\x0434\x0430\x043d\x0438 "
  897. L"\x0438\x0441\x0442\x0438\x0444\x043e\x0434\x0430\x0431\x0430\x0440"
  898. L"\x04e3 \x0432\x0430 \x0434\x0430\x0441\x0442\x0440\x0430\x0441\x0438 "
  899. L"\x0443\x043c\x0443\x043c "
  900. L"\x0433\x0430\x0440\x0434\x043e\x043d\x0438\x0434\x0430\x043d\x0438 "
  901. L"\x043e\x043d\x04b3\x043e \x0430\x0441\x0442."},
  902. };
  903. for (size_t i = 0; i < std::size(kTestCases); ++i) {
  904. ReinitializeSpellCheck(kTestCases[i].language);
  905. size_t input_length = 0;
  906. if (kTestCases[i].input)
  907. input_length = wcslen(kTestCases[i].input);
  908. size_t misspelling_start = 0;
  909. size_t misspelling_length = 0;
  910. bool result = spell_check()->SpellCheckWord(
  911. base::WideToUTF16(kTestCases[i].input).c_str(), kNoOffset, input_length,
  912. kNoTag, &misspelling_start, &misspelling_length, nullptr);
  913. EXPECT_TRUE(result)
  914. << "\""
  915. << std::wstring(kTestCases[i].input).substr(
  916. misspelling_start, misspelling_length)
  917. << "\" is misspelled in "
  918. << kTestCases[i].language
  919. << ".";
  920. EXPECT_EQ(0u, misspelling_start);
  921. EXPECT_EQ(0u, misspelling_length);
  922. }
  923. }
  924. // Verify that our SpellCheck::SpellCheckWord() returns false when it checks
  925. // misspelled words.
  926. TEST_F(SpellCheckTest, MisspelledWords) {
  927. static const struct {
  928. const char* language;
  929. const wchar_t* input;
  930. } kTestCases[] = {
  931. {
  932. // A misspelled word for English
  933. "en-US",
  934. L"aaaaaaaaaa",
  935. }, {
  936. // A misspelled word for Greek.
  937. "el-GR",
  938. L"\x03B1\x03B1\x03B1\x03B1\x03B1\x03B1\x03B1\x03B1\x03B1\x03B1",
  939. }, {
  940. // A misspelled word for Persian.
  941. "fa",
  942. L"\x06cc\x06a9\x06cc\x0634\x0627\x0646",
  943. }, {
  944. // A misspelled word for Hebrew
  945. "he-IL",
  946. L"\x05D0\x05D0\x05D0\x05D0\x05D0\x05D0\x05D0\x05D0\x05D0\x05D0",
  947. }, {
  948. // Hindi
  949. "hi-IN",
  950. L"\x0905\x0905\x0905\x0905\x0905\x0905\x0905\x0905\x0905\x0905",
  951. }, {
  952. // A misspelled word for Russian
  953. "ru-RU",
  954. L"\x0430\x0430\x0430\x0430\x0430\x0430\x0430\x0430\x0430\x0430",
  955. },
  956. };
  957. for (size_t i = 0; i < std::size(kTestCases); ++i) {
  958. ReinitializeSpellCheck(kTestCases[i].language);
  959. std::u16string word(base::WideToUTF16(kTestCases[i].input));
  960. size_t word_length = word.length();
  961. size_t misspelling_start = 0;
  962. size_t misspelling_length = 0;
  963. bool result = spell_check()->SpellCheckWord(
  964. word.c_str(), kNoOffset, word_length, kNoTag, &misspelling_start,
  965. &misspelling_length, nullptr);
  966. EXPECT_FALSE(result);
  967. EXPECT_EQ(0u, misspelling_start);
  968. EXPECT_EQ(word_length, misspelling_length);
  969. }
  970. }
  971. // Since SpellCheck::SpellCheckParagraph is not implemented on Mac,
  972. // we skip these SpellCheckParagraph tests on Mac.
  973. #if !BUILDFLAG(IS_APPLE)
  974. // Make sure SpellCheckParagraph does not crash if the input is empty.
  975. TEST_F(SpellCheckTest, SpellCheckParagraphEmptyParagraph) {
  976. std::vector<SpellCheckResult> expected;
  977. TestSpellCheckParagraph(u"", expected);
  978. }
  979. // A simple test case having no misspellings.
  980. TEST_F(SpellCheckTest, SpellCheckParagraphNoMisspellings) {
  981. const std::u16string text = u"apple";
  982. std::vector<SpellCheckResult> expected;
  983. TestSpellCheckParagraph(text, expected);
  984. }
  985. // A simple test case having one misspelling.
  986. TEST_F(SpellCheckTest, SpellCheckParagraphSingleMisspellings) {
  987. const std::u16string text = u"zz";
  988. std::vector<SpellCheckResult> expected;
  989. expected.push_back(SpellCheckResult(
  990. SpellCheckResult::SPELLING, 0, 2));
  991. TestSpellCheckParagraph(text, expected);
  992. }
  993. // A simple test case having multiple misspellings.
  994. TEST_F(SpellCheckTest, SpellCheckParagraphMultipleMisspellings) {
  995. const std::u16string text = u"zz, zz";
  996. std::vector<SpellCheckResult> expected;
  997. expected.push_back(SpellCheckResult(
  998. SpellCheckResult::SPELLING, 0, 2));
  999. expected.push_back(SpellCheckResult(
  1000. SpellCheckResult::SPELLING, 4, 2));
  1001. TestSpellCheckParagraph(text, expected);
  1002. }
  1003. // Make sure a relatively long (correct) sentence can be spellchecked.
  1004. TEST_F(SpellCheckTest, SpellCheckParagraphLongSentence) {
  1005. std::vector<SpellCheckResult> expected;
  1006. // The text is taken from US constitution preamble.
  1007. const std::u16string text =
  1008. u"We the people of the United States, in order to form a more perfect "
  1009. u"union, establish justice, insure domestic tranquility, provide for "
  1010. u"the common defense, promote the general welfare, and secure the "
  1011. u"blessings of liberty to ourselves and our posterity, do ordain and "
  1012. u"establish this Constitution for the United States of America.";
  1013. TestSpellCheckParagraph(text, expected);
  1014. }
  1015. // Make sure all misspellings can be found in a relatively long sentence.
  1016. TEST_F(SpellCheckTest, SpellCheckParagraphLongSentenceMultipleMisspellings) {
  1017. std::vector<SpellCheckResult> expected;
  1018. // All 'the' are converted to 'hte' in US consitition preamble.
  1019. const std::u16string text =
  1020. u"We hte people of hte United States, in order to form a more perfect "
  1021. u"union, establish justice, insure domestic tranquility, provide for "
  1022. u"hte common defense, promote hte general welfare, and secure hte "
  1023. u"blessings of liberty to ourselves and our posterity, do ordain and "
  1024. u"establish this Constitution for hte United States of America.";
  1025. expected.push_back(SpellCheckResult(
  1026. SpellCheckResult::SPELLING, 3, 3));
  1027. expected.push_back(SpellCheckResult(
  1028. SpellCheckResult::SPELLING, 17, 3));
  1029. expected.push_back(SpellCheckResult(
  1030. SpellCheckResult::SPELLING, 135, 3));
  1031. expected.push_back(SpellCheckResult(
  1032. SpellCheckResult::SPELLING, 163, 3));
  1033. expected.push_back(SpellCheckResult(
  1034. SpellCheckResult::SPELLING, 195, 3));
  1035. expected.push_back(SpellCheckResult(
  1036. SpellCheckResult::SPELLING, 298, 3));
  1037. TestSpellCheckParagraph(text, expected);
  1038. }
  1039. // We also skip RequestSpellCheck tests on Mac, because a system spellchecker
  1040. // is used on Mac instead of SpellCheck::RequestTextChecking.
  1041. // Make sure RequestTextChecking does not crash if input is empty.
  1042. TEST_F(SpellCheckTest, RequestSpellCheckWithEmptyString) {
  1043. MockTextCheckingResult completion;
  1044. spell_check()->RequestTextChecking(
  1045. std::u16string(),
  1046. std::make_unique<MockTextCheckingCompletion>(&completion));
  1047. base::RunLoop().RunUntilIdle();
  1048. EXPECT_EQ(completion.completion_count_, 1U);
  1049. }
  1050. // A simple test case having no misspellings.
  1051. TEST_F(SpellCheckTest, RequestSpellCheckWithoutMisspelling) {
  1052. MockTextCheckingResult completion;
  1053. const std::u16string text = u"hello";
  1054. spell_check()->RequestTextChecking(
  1055. text, std::make_unique<MockTextCheckingCompletion>(&completion));
  1056. base::RunLoop().RunUntilIdle();
  1057. EXPECT_EQ(completion.completion_count_, 1U);
  1058. }
  1059. // A simple test case having one misspelling.
  1060. TEST_F(SpellCheckTest, RequestSpellCheckWithSingleMisspelling) {
  1061. MockTextCheckingResult completion;
  1062. const std::u16string text = u"apple, zz";
  1063. spell_check()->RequestTextChecking(
  1064. text, std::make_unique<MockTextCheckingCompletion>(&completion));
  1065. base::RunLoop().RunUntilIdle();
  1066. EXPECT_EQ(completion.completion_count_, 1U);
  1067. ASSERT_EQ(completion.last_results_.size(), 1U);
  1068. EXPECT_EQ(completion.last_results_[0].location, 7);
  1069. EXPECT_EQ(completion.last_results_[0].length, 2);
  1070. }
  1071. // A simple test case having a few misspellings.
  1072. TEST_F(SpellCheckTest, RequestSpellCheckWithMisspellings) {
  1073. MockTextCheckingResult completion;
  1074. const std::u16string text = u"apple, zz, orange, zz";
  1075. spell_check()->RequestTextChecking(
  1076. text, std::make_unique<MockTextCheckingCompletion>(&completion));
  1077. base::RunLoop().RunUntilIdle();
  1078. EXPECT_EQ(completion.completion_count_, 1U);
  1079. ASSERT_EQ(completion.last_results_.size(), 2U);
  1080. EXPECT_EQ(completion.last_results_[0].location, 7);
  1081. EXPECT_EQ(completion.last_results_[0].length, 2);
  1082. EXPECT_EQ(completion.last_results_[1].location, 19);
  1083. EXPECT_EQ(completion.last_results_[1].length, 2);
  1084. }
  1085. // A test case that multiple requests comes at once. Make sure all
  1086. // requests are processed.
  1087. TEST_F(SpellCheckTest, RequestSpellCheckWithMultipleRequests) {
  1088. MockTextCheckingResult completion[3];
  1089. const std::u16string text[3] = {u"what, zz", u"apple, zz", u"orange, zz"};
  1090. for (int i = 0; i < 3; ++i)
  1091. spell_check()->RequestTextChecking(
  1092. text[i], std::make_unique<MockTextCheckingCompletion>(&completion[i]));
  1093. base::RunLoop().RunUntilIdle();
  1094. for (int i = 0; i < 3; ++i) {
  1095. EXPECT_EQ(completion[i].completion_count_, 1U);
  1096. ASSERT_EQ(completion[i].last_results_.size(), 1U);
  1097. EXPECT_EQ(completion[i].last_results_[0].location, 6 + i);
  1098. EXPECT_EQ(completion[i].last_results_[0].length, 2);
  1099. }
  1100. }
  1101. // A test case that spellchecking is requested before initializing.
  1102. // In this case, we postpone to post a request.
  1103. TEST_F(SpellCheckTest, RequestSpellCheckWithoutInitialization) {
  1104. UninitializeSpellCheck();
  1105. MockTextCheckingResult completion;
  1106. const std::u16string text = u"zz";
  1107. spell_check()->RequestTextChecking(
  1108. text, std::make_unique<MockTextCheckingCompletion>(&completion));
  1109. // The task will not be posted yet.
  1110. base::RunLoop().RunUntilIdle();
  1111. EXPECT_EQ(completion.completion_count_, 0U);
  1112. }
  1113. // Requests several spellchecking before initializing. Except the last one,
  1114. // posting requests is cancelled and text is rendered as correct one.
  1115. TEST_F(SpellCheckTest, RequestSpellCheckMultipleTimesWithoutInitialization) {
  1116. UninitializeSpellCheck();
  1117. MockTextCheckingResult completion[3];
  1118. const std::u16string text[3] = {u"what, zz", u"apple, zz", u"orange, zz"};
  1119. // Calls RequestTextchecking a few times.
  1120. for (int i = 0; i < 3; ++i)
  1121. spell_check()->RequestTextChecking(
  1122. text[i], std::make_unique<MockTextCheckingCompletion>(&completion[i]));
  1123. // The last task will be posted after initialization, however the other
  1124. // requests should be pressed without spellchecking.
  1125. base::RunLoop().RunUntilIdle();
  1126. for (int i = 0; i < 2; ++i)
  1127. EXPECT_EQ(completion[i].completion_count_, 1U);
  1128. EXPECT_EQ(completion[2].completion_count_, 0U);
  1129. // Checks the last request is processed after initialization.
  1130. InitializeSpellCheck("en-US");
  1131. // Calls PostDelayedSpellCheckTask instead of OnInit here for simplicity.
  1132. spell_check()->PostDelayedSpellCheckTask(
  1133. spell_check()->pending_request_param_.release());
  1134. base::RunLoop().RunUntilIdle();
  1135. for (int i = 0; i < 3; ++i)
  1136. EXPECT_EQ(completion[i].completion_count_, 1U);
  1137. }
  1138. #endif
  1139. // Verify that the SpellCheck class keeps the spelling marker added to a
  1140. // misspelled word "zz".
  1141. TEST_F(SpellCheckTest, CreateTextCheckingResultsKeepsMarkers) {
  1142. std::u16string text = u"zz";
  1143. std::vector<SpellCheckResult> spellcheck_results;
  1144. spellcheck_results.push_back(
  1145. SpellCheckResult(SpellCheckResult::SPELLING, 0, 2, std::u16string()));
  1146. blink::WebVector<blink::WebTextCheckingResult> textcheck_results;
  1147. spell_check()->CreateTextCheckingResults(SpellCheck::USE_HUNSPELL_FOR_GRAMMAR,
  1148. 0, text, spellcheck_results,
  1149. &textcheck_results);
  1150. ASSERT_EQ(spellcheck_results.size(), textcheck_results.size());
  1151. EXPECT_EQ(blink::kWebTextDecorationTypeSpelling,
  1152. textcheck_results[0].decoration);
  1153. EXPECT_EQ(spellcheck_results[0].location, textcheck_results[0].location);
  1154. EXPECT_EQ(spellcheck_results[0].length, textcheck_results[0].length);
  1155. }
  1156. // Verify that the SpellCheck class replaces the spelling marker added to a
  1157. // contextually-misspelled word "bean" with a grammar marker.
  1158. TEST_F(SpellCheckTest, CreateTextCheckingResultsAddsGrammarMarkers) {
  1159. std::u16string text = u"I have bean to USA.";
  1160. std::vector<SpellCheckResult> spellcheck_results;
  1161. spellcheck_results.push_back(
  1162. SpellCheckResult(SpellCheckResult::SPELLING, 7, 4, std::u16string()));
  1163. blink::WebVector<blink::WebTextCheckingResult> textcheck_results;
  1164. spell_check()->CreateTextCheckingResults(SpellCheck::USE_HUNSPELL_FOR_GRAMMAR,
  1165. 0, text, spellcheck_results,
  1166. &textcheck_results);
  1167. ASSERT_EQ(spellcheck_results.size(), textcheck_results.size());
  1168. EXPECT_EQ(blink::kWebTextDecorationTypeGrammar,
  1169. textcheck_results[0].decoration);
  1170. EXPECT_EQ(spellcheck_results[0].location, textcheck_results[0].location);
  1171. EXPECT_EQ(spellcheck_results[0].length, textcheck_results[0].length);
  1172. }
  1173. // Verify that the SpellCheck preserves the original apostrophe type in the
  1174. // checked text, regardless of the type of apostrophe the browser returns.
  1175. TEST_F(SpellCheckTest, CreateTextCheckingResultsKeepsTypographicalApostrophe) {
  1176. std::u16string text = u"Ik've havn’t ni'n’out-s I've I’ve";
  1177. std::vector<SpellCheckResult> spellcheck_results;
  1178. // All typewriter apostrophe results.
  1179. spellcheck_results.push_back(
  1180. SpellCheckResult(SpellCheckResult::SPELLING, 0, 5, u"I've"));
  1181. spellcheck_results.push_back(
  1182. SpellCheckResult(SpellCheckResult::SPELLING, 6, 6, u"haven't"));
  1183. spellcheck_results.push_back(
  1184. SpellCheckResult(SpellCheckResult::SPELLING, 13, 10, u"in'n'out's"));
  1185. // Replacements that differ only by apostrophe type should be ignored.
  1186. spellcheck_results.push_back(
  1187. SpellCheckResult(SpellCheckResult::SPELLING, 24, 4, u"I've"));
  1188. spellcheck_results.push_back(
  1189. SpellCheckResult(SpellCheckResult::SPELLING, 29, 4, u"I've"));
  1190. // All typographical apostrophe results.
  1191. spellcheck_results.push_back(
  1192. SpellCheckResult(SpellCheckResult::SPELLING, 0, 5, u"I’ve"));
  1193. spellcheck_results.push_back(
  1194. SpellCheckResult(SpellCheckResult::SPELLING, 6, 6, u"haven’t"));
  1195. spellcheck_results.push_back(
  1196. SpellCheckResult(SpellCheckResult::SPELLING, 13, 10, u"in’n’out’s"));
  1197. // Replacements that differ only by apostrophe type should be ignored.
  1198. spellcheck_results.push_back(
  1199. SpellCheckResult(SpellCheckResult::SPELLING, 24, 4, u"I’ve"));
  1200. spellcheck_results.push_back(
  1201. SpellCheckResult(SpellCheckResult::SPELLING, 29, 4, u"I’ve"));
  1202. // If we have no suggested replacements, we should keep this misspelling.
  1203. spellcheck_results.push_back(SpellCheckResult(
  1204. SpellCheckResult::SPELLING, 0, 5, std::vector<std::u16string>()));
  1205. // If we have multiple replacements that all differ only by apostrophe type,
  1206. // we should ignore this misspelling.
  1207. spellcheck_results.push_back(SpellCheckResult(
  1208. SpellCheckResult::SPELLING, 0, 11,
  1209. std::vector<std::u16string>({u"Ik've havn'", u"Ik’ve havn’"})));
  1210. // If we have multiple replacements where some only differ by apostrophe type
  1211. // and some don't, we should keep this misspelling, but remove the
  1212. // replacements that only differ by apostrophe type.
  1213. spellcheck_results.push_back(SpellCheckResult(
  1214. SpellCheckResult::SPELLING, 0, 5,
  1215. std::vector<std::u16string>({u"I've", u"Ive", u"Ik’ve"})));
  1216. // Similar to the previous case except with the apostrophe changing from
  1217. // typographical to straight instead of the other direction
  1218. spellcheck_results.push_back(SpellCheckResult(
  1219. SpellCheckResult::SPELLING, 6, 6,
  1220. std::vector<std::u16string>({u"havn't", u"havnt", u"haven't"})));
  1221. // If we have multiple replacements, none of which differ only by apostrophe
  1222. // type, we should keep this misspelling.
  1223. spellcheck_results.push_back(
  1224. SpellCheckResult(SpellCheckResult::SPELLING, 6, 6,
  1225. std::vector<std::u16string>({u"have", u"haven't"})));
  1226. blink::WebVector<blink::WebTextCheckingResult> textcheck_results;
  1227. spell_check()->CreateTextCheckingResults(SpellCheck::USE_HUNSPELL_FOR_GRAMMAR,
  1228. 0, text, spellcheck_results,
  1229. &textcheck_results);
  1230. static std::vector<std::vector<const wchar_t*>> kExpectedReplacements = {
  1231. {L"I've"},
  1232. {L"haven’t"},
  1233. {L"in'n’out's"},
  1234. {L"I've"},
  1235. {L"haven’t"},
  1236. {L"in'n’out’s"},
  1237. std::vector<const wchar_t*>(),
  1238. {L"I've", L"Ive"},
  1239. {L"havnt", L"haven’t"},
  1240. {L"have", L"haven’t"},
  1241. };
  1242. ASSERT_EQ(kExpectedReplacements.size(), textcheck_results.size());
  1243. for (size_t i = 0; i < kExpectedReplacements.size(); ++i) {
  1244. ASSERT_EQ(kExpectedReplacements[i].size(),
  1245. textcheck_results[i].replacements.size());
  1246. for (size_t j = 0; j < kExpectedReplacements[i].size(); ++j) {
  1247. EXPECT_EQ(base::WideToUTF16(kExpectedReplacements[i][j]),
  1248. textcheck_results[i].replacements[j].Utf16())
  1249. << "i=" << i << "\nj=" << j << "\nactual: \""
  1250. << textcheck_results[i].replacements[j].Utf16() << "\"";
  1251. }
  1252. }
  1253. }
  1254. // Checks some words that should be present in all English dictionaries.
  1255. TEST_F(SpellCheckTest, EnglishWords) {
  1256. static const struct {
  1257. const char* input;
  1258. bool should_pass;
  1259. } kTestCases[] = {
  1260. // Issue 146093: "Chromebook" and "Chromebox" not included in spell-checking
  1261. // dictionary.
  1262. {"Chromebook", true},
  1263. {"Chromebooks", true},
  1264. {"Chromebox", true},
  1265. {"Chromeboxes", true},
  1266. {"Chromeblade", true},
  1267. {"Chromeblades", true},
  1268. {"Chromebase", true},
  1269. {"Chromebases", true},
  1270. // Issue 94708: Spell-checker incorrectly reports whisky as misspelled.
  1271. {"whisky", true},
  1272. {"whiskey", true},
  1273. {"whiskies", true},
  1274. // Issue 98678: "Recency" should be included in client-side dictionary.
  1275. {"recency", true},
  1276. {"recencies", false},
  1277. // Issue 140486
  1278. {"movie", true},
  1279. {"movies", true},
  1280. };
  1281. static const char* const kLocales[] = { "en-GB", "en-US", "en-CA", "en-AU" };
  1282. for (size_t j = 0; j < std::size(kLocales); ++j) {
  1283. ReinitializeSpellCheck(kLocales[j]);
  1284. for (size_t i = 0; i < std::size(kTestCases); ++i) {
  1285. size_t input_length = 0;
  1286. if (kTestCases[i].input)
  1287. input_length = strlen(kTestCases[i].input);
  1288. size_t misspelling_start = 0;
  1289. size_t misspelling_length = 0;
  1290. bool result = spell_check()->SpellCheckWord(
  1291. base::ASCIIToUTF16(kTestCases[i].input).c_str(), kNoOffset,
  1292. input_length, kNoTag, &misspelling_start, &misspelling_length,
  1293. nullptr);
  1294. EXPECT_EQ(kTestCases[i].should_pass, result) << kTestCases[i].input <<
  1295. " in " << kLocales[j];
  1296. }
  1297. }
  1298. }
  1299. // Checks that NOSUGGEST works in English dictionaries.
  1300. TEST_F(SpellCheckTest, NoSuggest) {
  1301. ReinitializeSpellCheck("xx-XX");
  1302. static const struct {
  1303. const char* input;
  1304. const char* suggestion;
  1305. bool should_pass;
  1306. } kTestCases[] = {{"typograpy", "typographit", true},
  1307. {"typograpy", "typographits", true}};
  1308. for (const auto& test_case : kTestCases) {
  1309. size_t suggestion_length = 0;
  1310. if (test_case.suggestion)
  1311. suggestion_length = strlen(test_case.suggestion);
  1312. // First check that the NOSUGGEST flag didn't mark this word as not being in
  1313. // the dictionary.
  1314. size_t misspelling_start = 0;
  1315. size_t misspelling_length = 0;
  1316. bool result = spell_check()->SpellCheckWord(
  1317. base::ASCIIToUTF16(test_case.suggestion).c_str(), kNoOffset,
  1318. suggestion_length, kNoTag, &misspelling_start, &misspelling_length,
  1319. nullptr);
  1320. EXPECT_EQ(test_case.should_pass, result) << test_case.suggestion;
  1321. // Now verify that this test case does not show up as a suggestion.
  1322. std::vector<std::u16string> suggestions;
  1323. size_t input_length = 0;
  1324. if (test_case.input)
  1325. input_length = strlen(test_case.input);
  1326. result = spell_check()->SpellCheckWord(
  1327. base::ASCIIToUTF16(test_case.input).c_str(), kNoOffset,
  1328. input_length, kNoTag, &misspelling_start,
  1329. &misspelling_length, &suggestions);
  1330. // Input word should be a misspelling.
  1331. EXPECT_FALSE(result) << test_case.input << " is not a misspelling";
  1332. // Check if the suggested words occur.
  1333. for (const std::u16string& suggestion : suggestions) {
  1334. for (const auto& test_case_to_check : kTestCases) {
  1335. int compare_result = suggestion.compare(
  1336. base::ASCIIToUTF16(test_case_to_check.suggestion));
  1337. EXPECT_FALSE(compare_result == 0) << test_case_to_check.suggestion;
  1338. }
  1339. }
  1340. }
  1341. }
  1342. // Check that the correct dictionary files are checked in.
  1343. TEST_F(SpellCheckTest, DictionaryFiles) {
  1344. std::vector<std::string> spellcheck_languages =
  1345. spellcheck::SpellCheckLanguages();
  1346. EXPECT_FALSE(spellcheck_languages.empty());
  1347. base::FilePath hunspell = GetHunspellDirectory();
  1348. for (size_t i = 0; i < spellcheck_languages.size(); ++i) {
  1349. base::FilePath dict =
  1350. spellcheck::GetVersionedFileName(spellcheck_languages[i], hunspell);
  1351. EXPECT_TRUE(base::PathExists(dict)) << dict.value() << " not found";
  1352. }
  1353. }
  1354. // TODO(groby): Add a test for hunspell itself, when MAXWORDLEN is exceeded.
  1355. TEST_F(SpellCheckTest, SpellingEngine_CheckSpelling) {
  1356. static const struct {
  1357. const char* word;
  1358. bool expected_result;
  1359. } kTestCases[] = {
  1360. { "", true },
  1361. { "automatic", true },
  1362. { "hello", true },
  1363. { "forglobantic", false },
  1364. { "xfdssfsdfaasds", false },
  1365. { // 64 chars are the longest word to check - this should fail checking.
  1366. "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl",
  1367. false
  1368. },
  1369. { // Any word longer than 64 chars should be exempt from checking.
  1370. "reallylongwordthatabsolutelyexceedsthespecifiedcharacterlimitabit",
  1371. true
  1372. }
  1373. };
  1374. // Initialization magic - call InitializeIfNeeded twice. The first one simply
  1375. // flags internal state that a dictionary was requested. The second one will
  1376. // take the passed-in file and initialize hunspell with it. (The file was
  1377. // passed to hunspell in the ctor for the test fixture).
  1378. // This needs to be done since we need to ensure the SpellingEngine objects
  1379. // contained in the SpellcheckLanguages in |languages_| from the test fixture
  1380. // does get initialized.
  1381. // TODO(groby): Clean up this mess.
  1382. InitializeIfNeeded();
  1383. ASSERT_FALSE(InitializeIfNeeded());
  1384. for (size_t i = 0; i < std::size(kTestCases); ++i) {
  1385. bool result = CheckSpelling(kTestCases[i].word, 0);
  1386. EXPECT_EQ(kTestCases[i].expected_result, result) <<
  1387. "Failed test for " << kTestCases[i].word;
  1388. }
  1389. }
  1390. // Chrome should not suggest "Othello" for "hellllo" or "identically" for
  1391. // "accidently".
  1392. TEST_F(SpellCheckTest, LogicalSuggestions) {
  1393. static const struct {
  1394. const char* misspelled;
  1395. const char* suggestion;
  1396. } kTestCases[] = {
  1397. { "hellllo", "hello" },
  1398. { "accidently", "accidentally" }
  1399. };
  1400. for (size_t i = 0; i < std::size(kTestCases); ++i) {
  1401. size_t misspelling_start = 0;
  1402. size_t misspelling_length = 0;
  1403. std::vector<std::u16string> suggestions;
  1404. EXPECT_FALSE(spell_check()->SpellCheckWord(
  1405. base::ASCIIToUTF16(kTestCases[i].misspelled).c_str(),
  1406. kNoOffset,
  1407. strlen(kTestCases[i].misspelled),
  1408. kNoTag,
  1409. &misspelling_start,
  1410. &misspelling_length,
  1411. &suggestions));
  1412. ASSERT_GE(suggestions.size(), 1U);
  1413. EXPECT_EQ(suggestions[0], base::ASCIIToUTF16(kTestCases[i].suggestion));
  1414. }
  1415. }
  1416. // Words with apostrophes should be valid contractions.
  1417. TEST_F(SpellCheckTest, IsValidContraction) {
  1418. static constexpr const char* kLanguages[] = {
  1419. "en-AU", "en-CA", "en-GB", "en-US",
  1420. };
  1421. static constexpr const wchar_t* kWords[] = {
  1422. L"in'n'out",
  1423. L"in’n’out",
  1424. };
  1425. for (size_t i = 0; i < std::size(kLanguages); ++i) {
  1426. ReinitializeSpellCheck(kLanguages[i]);
  1427. for (size_t j = 0; j < std::size(kWords); ++j)
  1428. EXPECT_TRUE(IsValidContraction(base::WideToUTF16(kWords[j]), 0));
  1429. }
  1430. }
  1431. TEST_F(SpellCheckTest, FillSuggestions_OneLanguageNoSuggestions) {
  1432. std::vector<std::vector<std::u16string>> suggestions_list;
  1433. std::vector<std::u16string> suggestion_results;
  1434. suggestions_list.resize(1);
  1435. FillSuggestions(suggestions_list, &suggestion_results);
  1436. EXPECT_TRUE(suggestion_results.empty());
  1437. }
  1438. TEST_F(SpellCheckTest, FillSuggestions_OneLanguageFewSuggestions) {
  1439. std::vector<std::vector<std::u16string>> suggestions_list;
  1440. std::vector<std::u16string> suggestion_results;
  1441. suggestions_list.resize(1);
  1442. suggestions_list[0].push_back(u"foo");
  1443. FillSuggestions(suggestions_list, &suggestion_results);
  1444. ASSERT_EQ(1U, suggestion_results.size());
  1445. EXPECT_EQ(u"foo", suggestion_results[0]);
  1446. }
  1447. TEST_F(SpellCheckTest, FillSuggestions_OneLanguageManySuggestions) {
  1448. std::vector<std::vector<std::u16string>> suggestions_list;
  1449. std::vector<std::u16string> suggestion_results;
  1450. suggestions_list.resize(1);
  1451. for (int i = 0; i < spellcheck::kMaxSuggestions + 2; ++i)
  1452. suggestions_list[0].push_back(base::ASCIIToUTF16(base::NumberToString(i)));
  1453. FillSuggestions(suggestions_list, &suggestion_results);
  1454. ASSERT_EQ(static_cast<size_t>(spellcheck::kMaxSuggestions),
  1455. suggestion_results.size());
  1456. for (int i = 0; i < spellcheck::kMaxSuggestions; ++i)
  1457. EXPECT_EQ(base::ASCIIToUTF16(base::NumberToString(i)),
  1458. suggestion_results[i]);
  1459. }
  1460. TEST_F(SpellCheckTest, FillSuggestions_RemoveDuplicates) {
  1461. std::vector<std::vector<std::u16string>> suggestions_list;
  1462. std::vector<std::u16string> suggestion_results;
  1463. suggestions_list.resize(2);
  1464. for (size_t i = 0; i < 2; ++i) {
  1465. suggestions_list[i].push_back(u"foo");
  1466. suggestions_list[i].push_back(u"bar");
  1467. suggestions_list[i].push_back(u"baz");
  1468. }
  1469. FillSuggestions(suggestions_list, &suggestion_results);
  1470. ASSERT_EQ(3U, suggestion_results.size());
  1471. EXPECT_EQ(u"foo", suggestion_results[0]);
  1472. EXPECT_EQ(u"bar", suggestion_results[1]);
  1473. EXPECT_EQ(u"baz", suggestion_results[2]);
  1474. }
  1475. TEST_F(SpellCheckTest, FillSuggestions_TwoLanguages) {
  1476. std::vector<std::vector<std::u16string>> suggestions_list;
  1477. std::vector<std::u16string> suggestion_results;
  1478. suggestions_list.resize(2);
  1479. for (size_t i = 0; i < 2; ++i) {
  1480. std::string prefix = base::NumberToString(i);
  1481. suggestions_list[i].push_back(base::ASCIIToUTF16(prefix + "foo"));
  1482. suggestions_list[i].push_back(base::ASCIIToUTF16(prefix + "bar"));
  1483. suggestions_list[i].push_back(base::ASCIIToUTF16(prefix + "baz"));
  1484. }
  1485. // Yes, this test assumes kMaxSuggestions is 5. If it isn't, the test needs
  1486. // to be updated accordingly.
  1487. ASSERT_EQ(5, spellcheck::kMaxSuggestions);
  1488. FillSuggestions(suggestions_list, &suggestion_results);
  1489. ASSERT_EQ(5U, suggestion_results.size());
  1490. EXPECT_EQ(u"0foo", suggestion_results[0]);
  1491. EXPECT_EQ(u"1foo", suggestion_results[1]);
  1492. EXPECT_EQ(u"0bar", suggestion_results[2]);
  1493. EXPECT_EQ(u"1bar", suggestion_results[3]);
  1494. EXPECT_EQ(u"0baz", suggestion_results[4]);
  1495. }
  1496. TEST_F(SpellCheckTest, FillSuggestions_ThreeLanguages) {
  1497. std::vector<std::vector<std::u16string>> suggestions_list;
  1498. std::vector<std::u16string> suggestion_results;
  1499. suggestions_list.resize(3);
  1500. for (size_t i = 0; i < 3; ++i) {
  1501. std::string prefix = base::NumberToString(i);
  1502. suggestions_list[i].push_back(base::ASCIIToUTF16(prefix + "foo"));
  1503. suggestions_list[i].push_back(base::ASCIIToUTF16(prefix + "bar"));
  1504. suggestions_list[i].push_back(base::ASCIIToUTF16(prefix + "baz"));
  1505. }
  1506. // Yes, this test assumes kMaxSuggestions is 5. If it isn't, the test needs
  1507. // to be updated accordingly.
  1508. ASSERT_EQ(5, spellcheck::kMaxSuggestions);
  1509. FillSuggestions(suggestions_list, &suggestion_results);
  1510. ASSERT_EQ(5U, suggestion_results.size());
  1511. EXPECT_EQ(u"0foo", suggestion_results[0]);
  1512. EXPECT_EQ(u"1foo", suggestion_results[1]);
  1513. EXPECT_EQ(u"2foo", suggestion_results[2]);
  1514. EXPECT_EQ(u"0bar", suggestion_results[3]);
  1515. EXPECT_EQ(u"1bar", suggestion_results[4]);
  1516. }