| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/renderer/spellchecker/spellcheck.h" | 5 #include "chrome/renderer/spellchecker/spellcheck.h" |
| 6 | 6 |
| 7 #include "base/files/file_path.h" | 7 #include "base/files/file_path.h" |
| 8 #include "base/files/file_util.h" | 8 #include "base/files/file_util.h" |
| 9 #include "base/message_loop/message_loop.h" | 9 #include "base/message_loop/message_loop.h" |
| 10 #include "base/path_service.h" | 10 #include "base/path_service.h" |
| 11 #include "base/strings/utf_string_conversions.h" | 11 #include "base/strings/utf_string_conversions.h" |
| 12 #include "chrome/common/spellcheck_common.h" | 12 #include "chrome/common/spellcheck_common.h" |
| 13 #include "chrome/common/spellcheck_result.h" | 13 #include "chrome/common/spellcheck_result.h" |
| 14 #include "chrome/renderer/spellchecker/hunspell_engine.h" | 14 #include "chrome/renderer/spellchecker/hunspell_engine.h" |
| 15 #include "testing/gtest/include/gtest/gtest.h" | 15 #include "testing/gtest/include/gtest/gtest.h" |
| 16 #include "third_party/WebKit/public/platform/WebVector.h" | 16 #include "third_party/WebKit/public/platform/WebVector.h" |
| 17 #include "third_party/WebKit/public/web/WebTextCheckingCompletion.h" | 17 #include "third_party/WebKit/public/web/WebTextCheckingCompletion.h" |
| 18 #include "third_party/WebKit/public/web/WebTextCheckingResult.h" | 18 #include "third_party/WebKit/public/web/WebTextCheckingResult.h" |
| 19 | 19 |
| 20 #define TYPOGRAPHICAL_APOSTROPHE L"\x2019" |
| 21 |
| 20 namespace { | 22 namespace { |
| 21 | 23 |
| 22 base::FilePath GetHunspellDirectory() { | 24 base::FilePath GetHunspellDirectory() { |
| 23 base::FilePath hunspell_directory; | 25 base::FilePath hunspell_directory; |
| 24 if (!PathService::Get(base::DIR_SOURCE_ROOT, &hunspell_directory)) | 26 if (!PathService::Get(base::DIR_SOURCE_ROOT, &hunspell_directory)) |
| 25 return base::FilePath(); | 27 return base::FilePath(); |
| 26 | 28 |
| 27 hunspell_directory = hunspell_directory.AppendASCII("third_party"); | 29 hunspell_directory = hunspell_directory.AppendASCII("third_party"); |
| 28 hunspell_directory = hunspell_directory.AppendASCII("hunspell_dictionaries"); | 30 hunspell_directory = hunspell_directory.AppendASCII("hunspell_dictionaries"); |
| 29 return hunspell_directory; | 31 return hunspell_directory; |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 73 | 75 |
| 74 ~SpellCheckTest() override {} | 76 ~SpellCheckTest() override {} |
| 75 | 77 |
| 76 SpellCheck* spell_check() { return spell_check_.get(); } | 78 SpellCheck* spell_check() { return spell_check_.get(); } |
| 77 | 79 |
| 78 bool CheckSpelling(const std::string& word, int tag) { | 80 bool CheckSpelling(const std::string& word, int tag) { |
| 79 return spell_check_->spellcheck_.platform_spelling_engine_->CheckSpelling( | 81 return spell_check_->spellcheck_.platform_spelling_engine_->CheckSpelling( |
| 80 base::ASCIIToUTF16(word), tag); | 82 base::ASCIIToUTF16(word), tag); |
| 81 } | 83 } |
| 82 | 84 |
| 85 bool IsValidContraction(const base::string16& word, int tag) { |
| 86 return spell_check_->spellcheck_.IsValidContraction(word, tag); |
| 87 } |
| 88 |
| 83 #if !defined(OS_MACOSX) | 89 #if !defined(OS_MACOSX) |
| 84 protected: | 90 protected: |
| 85 void TestSpellCheckParagraph( | 91 void TestSpellCheckParagraph( |
| 86 const base::string16& input, | 92 const base::string16& input, |
| 87 const std::vector<SpellCheckResult>& expected) { | 93 const std::vector<SpellCheckResult>& expected) { |
| 88 blink::WebVector<blink::WebTextCheckingResult> results; | 94 blink::WebVector<blink::WebTextCheckingResult> results; |
| 89 spell_check()->SpellCheckParagraph(input, | 95 spell_check()->SpellCheckParagraph(input, |
| 90 &results); | 96 &results); |
| 91 | 97 |
| 92 EXPECT_EQ(results.size(), expected.size()); | 98 EXPECT_EQ(results.size(), expected.size()); |
| (...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 194 // Two valid Greek words (meaning "hello") consisting of seven Greek | 200 // Two valid Greek words (meaning "hello") consisting of seven Greek |
| 195 // letters | 201 // letters |
| 196 {L"\x03B3\x03B5\x03B9\x03AC" L" " L"\x03C3\x03BF\x03C5", true}, | 202 {L"\x03B3\x03B5\x03B9\x03AC" L" " L"\x03C3\x03BF\x03C5", true}, |
| 197 // A valid Russian word (meaning "hello") consisting of twelve Cyrillic | 203 // A valid Russian word (meaning "hello") consisting of twelve Cyrillic |
| 198 // letters | 204 // letters |
| 199 {L"\x0437\x0434\x0440\x0430\x0432\x0441" | 205 {L"\x0437\x0434\x0440\x0430\x0432\x0441" |
| 200 L"\x0442\x0432\x0443\x0439\x0442\x0435", true}, | 206 L"\x0442\x0432\x0443\x0439\x0442\x0435", true}, |
| 201 // A valid English contraction | 207 // A valid English contraction |
| 202 {L"isn't", true}, | 208 {L"isn't", true}, |
| 203 // A valid English contraction with a typographical apostrophe. | 209 // A valid English contraction with a typographical apostrophe. |
| 204 {L"isn\x2019t", true}, | 210 {L"isn" TYPOGRAPHICAL_APOSTROPHE L"t", true}, |
| 205 // A valid English word enclosed with underscores. | 211 // A valid English word enclosed with underscores. |
| 206 {L"_hello_", true}, | 212 {L"_hello_", true}, |
| 207 | 213 |
| 208 // A valid English word with a preceding whitespace | 214 // A valid English word with a preceding whitespace |
| 209 {L" " L"hello", true}, | 215 {L" " L"hello", true}, |
| 210 // A valid English word with a preceding no-break space | 216 // A valid English word with a preceding no-break space |
| 211 {L"\xA0" L"hello", true}, | 217 {L"\xA0" L"hello", true}, |
| 212 // A valid English word with a preceding ideographic space | 218 // A valid English word with a preceding ideographic space |
| 213 {L"\x3000" L"hello", true}, | 219 {L"\x3000" L"hello", true}, |
| 214 // A valid English word with a preceding Chinese word | 220 // A valid English word with a preceding Chinese word |
| (...skipping 918 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1133 base::string16 text = base::ASCIIToUTF16("zz"); | 1139 base::string16 text = base::ASCIIToUTF16("zz"); |
| 1134 std::vector<SpellCheckResult> spellcheck_results; | 1140 std::vector<SpellCheckResult> spellcheck_results; |
| 1135 spellcheck_results.push_back(SpellCheckResult( | 1141 spellcheck_results.push_back(SpellCheckResult( |
| 1136 SpellCheckResult::SPELLING, 0, 2, base::string16())); | 1142 SpellCheckResult::SPELLING, 0, 2, base::string16())); |
| 1137 blink::WebVector<blink::WebTextCheckingResult> textcheck_results; | 1143 blink::WebVector<blink::WebTextCheckingResult> textcheck_results; |
| 1138 spell_check()->CreateTextCheckingResults(SpellCheck::USE_NATIVE_CHECKER, | 1144 spell_check()->CreateTextCheckingResults(SpellCheck::USE_NATIVE_CHECKER, |
| 1139 0, | 1145 0, |
| 1140 text, | 1146 text, |
| 1141 spellcheck_results, | 1147 spellcheck_results, |
| 1142 &textcheck_results); | 1148 &textcheck_results); |
| 1143 EXPECT_EQ(spellcheck_results.size(), textcheck_results.size()); | 1149 ASSERT_EQ(spellcheck_results.size(), textcheck_results.size()); |
| 1144 EXPECT_EQ(blink::WebTextDecorationTypeSpelling, | 1150 EXPECT_EQ(blink::WebTextDecorationTypeSpelling, |
| 1145 textcheck_results[0].decoration); | 1151 textcheck_results[0].decoration); |
| 1146 EXPECT_EQ(spellcheck_results[0].location, textcheck_results[0].location); | 1152 EXPECT_EQ(spellcheck_results[0].location, textcheck_results[0].location); |
| 1147 EXPECT_EQ(spellcheck_results[0].length, textcheck_results[0].length); | 1153 EXPECT_EQ(spellcheck_results[0].length, textcheck_results[0].length); |
| 1148 } | 1154 } |
| 1149 | 1155 |
| 1150 // Verify that the SpellCheck class replaces the spelling marker added to a | 1156 // Verify that the SpellCheck class replaces the spelling marker added to a |
| 1151 // contextually-misspelled word "bean" with a grammar marker. | 1157 // contextually-misspelled word "bean" with a grammar marker. |
| 1152 { | 1158 { |
| 1153 base::string16 text = base::ASCIIToUTF16("I have bean to USA."); | 1159 base::string16 text = base::ASCIIToUTF16("I have bean to USA."); |
| 1154 std::vector<SpellCheckResult> spellcheck_results; | 1160 std::vector<SpellCheckResult> spellcheck_results; |
| 1155 spellcheck_results.push_back(SpellCheckResult( | 1161 spellcheck_results.push_back(SpellCheckResult( |
| 1156 SpellCheckResult::SPELLING, 7, 4, base::string16())); | 1162 SpellCheckResult::SPELLING, 7, 4, base::string16())); |
| 1157 blink::WebVector<blink::WebTextCheckingResult> textcheck_results; | 1163 blink::WebVector<blink::WebTextCheckingResult> textcheck_results; |
| 1158 spell_check()->CreateTextCheckingResults(SpellCheck::USE_NATIVE_CHECKER, | 1164 spell_check()->CreateTextCheckingResults(SpellCheck::USE_NATIVE_CHECKER, |
| 1159 0, | 1165 0, |
| 1160 text, | 1166 text, |
| 1161 spellcheck_results, | 1167 spellcheck_results, |
| 1162 &textcheck_results); | 1168 &textcheck_results); |
| 1163 EXPECT_EQ(spellcheck_results.size(), textcheck_results.size()); | 1169 ASSERT_EQ(spellcheck_results.size(), textcheck_results.size()); |
| 1164 EXPECT_EQ(blink::WebTextDecorationTypeGrammar, | 1170 EXPECT_EQ(blink::WebTextDecorationTypeGrammar, |
| 1165 textcheck_results[0].decoration); | 1171 textcheck_results[0].decoration); |
| 1166 EXPECT_EQ(spellcheck_results[0].location, textcheck_results[0].location); | 1172 EXPECT_EQ(spellcheck_results[0].location, textcheck_results[0].location); |
| 1167 EXPECT_EQ(spellcheck_results[0].length, textcheck_results[0].length); | 1173 EXPECT_EQ(spellcheck_results[0].length, textcheck_results[0].length); |
| 1168 } | 1174 } |
| 1175 |
| 1176 // Verify that the SpellCheck preserves the original apostrophe type in the |
| 1177 // checked text, regardless of the type of apostrophe the browser returns. |
| 1178 { |
| 1179 base::string16 text = base::WideToUTF16( |
| 1180 L"Ik've havn" TYPOGRAPHICAL_APOSTROPHE L"t ni'n" |
| 1181 TYPOGRAPHICAL_APOSTROPHE L"out-s I've I" TYPOGRAPHICAL_APOSTROPHE |
| 1182 L"ve"); |
| 1183 std::vector<SpellCheckResult> spellcheck_results; |
| 1184 |
| 1185 // All typewriter apostrophe results. |
| 1186 spellcheck_results.push_back(SpellCheckResult( |
| 1187 SpellCheckResult::SPELLING, 0, 5, base::UTF8ToUTF16("I've"))); |
| 1188 spellcheck_results.push_back(SpellCheckResult( |
| 1189 SpellCheckResult::SPELLING, 6, 6, base::UTF8ToUTF16("haven't"))); |
| 1190 spellcheck_results.push_back(SpellCheckResult( |
| 1191 SpellCheckResult::SPELLING, 13, 10, base::UTF8ToUTF16("in'n'out's"))); |
| 1192 |
| 1193 // Replacements that differ only by apostrophe type should be ignored. |
| 1194 spellcheck_results.push_back(SpellCheckResult( |
| 1195 SpellCheckResult::SPELLING, 24, 4, base::UTF8ToUTF16("I've"))); |
| 1196 spellcheck_results.push_back(SpellCheckResult( |
| 1197 SpellCheckResult::SPELLING, 29, 4, base::UTF8ToUTF16("I've"))); |
| 1198 |
| 1199 // All typographical apostrophe results. |
| 1200 spellcheck_results.push_back(SpellCheckResult( |
| 1201 SpellCheckResult::SPELLING, 0, 5, |
| 1202 base::WideToUTF16(L"I" TYPOGRAPHICAL_APOSTROPHE L"ve"))); |
| 1203 spellcheck_results.push_back(SpellCheckResult( |
| 1204 SpellCheckResult::SPELLING, 6, 6, |
| 1205 base::WideToUTF16(L"haven" TYPOGRAPHICAL_APOSTROPHE L"t"))); |
| 1206 spellcheck_results.push_back(SpellCheckResult( |
| 1207 SpellCheckResult::SPELLING, 13, 10, base::WideToUTF16( |
| 1208 L"in" TYPOGRAPHICAL_APOSTROPHE L"n" TYPOGRAPHICAL_APOSTROPHE L"out" |
| 1209 TYPOGRAPHICAL_APOSTROPHE L"s"))); |
| 1210 |
| 1211 // Replacements that differ only by apostrophe type should be ignored. |
| 1212 spellcheck_results.push_back(SpellCheckResult( |
| 1213 SpellCheckResult::SPELLING, 24, 4, |
| 1214 base::WideToUTF16(L"I" TYPOGRAPHICAL_APOSTROPHE L"ve"))); |
| 1215 spellcheck_results.push_back(SpellCheckResult( |
| 1216 SpellCheckResult::SPELLING, 29, 4, |
| 1217 base::WideToUTF16(L"I" TYPOGRAPHICAL_APOSTROPHE L"ve"))); |
| 1218 |
| 1219 blink::WebVector<blink::WebTextCheckingResult> textcheck_results; |
| 1220 spell_check()->CreateTextCheckingResults(SpellCheck::USE_NATIVE_CHECKER, 0, |
| 1221 text, spellcheck_results, |
| 1222 &textcheck_results); |
| 1223 |
| 1224 static const wchar_t* kExpectedReplacements[] = { |
| 1225 L"I've", |
| 1226 L"haven" TYPOGRAPHICAL_APOSTROPHE L"t", |
| 1227 L"in'n" TYPOGRAPHICAL_APOSTROPHE L"out's", |
| 1228 L"I've", |
| 1229 L"haven" TYPOGRAPHICAL_APOSTROPHE L"t", |
| 1230 L"in'n" TYPOGRAPHICAL_APOSTROPHE L"out" TYPOGRAPHICAL_APOSTROPHE L"s", |
| 1231 }; |
| 1232 |
| 1233 ASSERT_EQ(arraysize(kExpectedReplacements), textcheck_results.size()); |
| 1234 for (size_t i = 0; i < arraysize(kExpectedReplacements); ++i) { |
| 1235 EXPECT_EQ(base::WideToUTF16(kExpectedReplacements[i]), |
| 1236 textcheck_results[i].replacement) |
| 1237 << "i=" << i << "\nactual: \"" |
| 1238 << base::string16(textcheck_results[i].replacement) << "\""; |
| 1239 } |
| 1240 } |
| 1169 } | 1241 } |
| 1170 | 1242 |
| 1171 #endif | 1243 #endif |
| 1172 | 1244 |
| 1173 // Checks some words that should be present in all English dictionaries. | 1245 // Checks some words that should be present in all English dictionaries. |
| 1174 TEST_F(SpellCheckTest, EnglishWords) { | 1246 TEST_F(SpellCheckTest, EnglishWords) { |
| 1175 static const struct { | 1247 static const struct { |
| 1176 const char* input; | 1248 const char* input; |
| 1177 bool should_pass; | 1249 bool should_pass; |
| 1178 } kTestCases[] = { | 1250 } kTestCases[] = { |
| (...skipping 187 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1366 strlen(kTestCases[i].misspelled), | 1438 strlen(kTestCases[i].misspelled), |
| 1367 0, | 1439 0, |
| 1368 &misspelling_start, | 1440 &misspelling_start, |
| 1369 &misspelling_length, | 1441 &misspelling_length, |
| 1370 &suggestions)); | 1442 &suggestions)); |
| 1371 EXPECT_GE(suggestions.size(), static_cast<size_t>(1)); | 1443 EXPECT_GE(suggestions.size(), static_cast<size_t>(1)); |
| 1372 if (suggestions.size() > 0) | 1444 if (suggestions.size() > 0) |
| 1373 EXPECT_EQ(suggestions[0], base::ASCIIToUTF16(kTestCases[i].suggestion)); | 1445 EXPECT_EQ(suggestions[0], base::ASCIIToUTF16(kTestCases[i].suggestion)); |
| 1374 } | 1446 } |
| 1375 } | 1447 } |
| 1448 |
| 1449 // Words with apostrophes should be valid contractions. |
| 1450 TEST_F(SpellCheckTest, IsValidContraction) { |
| 1451 static const char* kLanguages[] = { |
| 1452 "en-AU", |
| 1453 "en-CA", |
| 1454 "en-GB", |
| 1455 "en-US", |
| 1456 }; |
| 1457 |
| 1458 static const wchar_t* kWords[] = { |
| 1459 L"in'n'out", |
| 1460 L"in" TYPOGRAPHICAL_APOSTROPHE L"n" TYPOGRAPHICAL_APOSTROPHE L"out", |
| 1461 }; |
| 1462 |
| 1463 for (size_t i = 0; i < arraysize(kLanguages); ++i) { |
| 1464 ReinitializeSpellCheck(kLanguages[i]); |
| 1465 for (size_t j = 0; j < arraysize(kWords); ++j) |
| 1466 EXPECT_TRUE(IsValidContraction(base::WideToUTF16(kWords[j]), 0)); |
| 1467 } |
| 1468 } |
| OLD | NEW |