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 5#include "chrome/browser/chromeos/input_method/input_method_util.h" 6 7#include <string> 8 9#include "base/bind.h" 10#include "base/memory/scoped_ptr.h" 11#include "base/strings/utf_string_conversions.h" 12#include "chromeos/ime/extension_ime_util.h" 13#include "chromeos/ime/fake_input_method_delegate.h" 14#include "chromeos/ime/input_method_manager.h" 15#include "chromeos/ime/input_method_whitelist.h" 16#include "testing/gtest/include/gtest/gtest.h" 17#include "ui/base/l10n/l10n_util.h" 18 19using base::ASCIIToUTF16; 20 21namespace chromeos { 22 23extern const char* kExtensionImePrefix; 24 25namespace input_method { 26 27namespace { 28 29const char pinyin_ime_id[] = "zh-t-i0-pinyin"; 30const char zhuyin_ime_id[] = "zh-hant-t-i0-und"; 31 32class TestableInputMethodUtil : public InputMethodUtil { 33 public: 34 explicit TestableInputMethodUtil(InputMethodDelegate* delegate, 35 scoped_ptr<InputMethodDescriptors> methods) 36 : InputMethodUtil(delegate) { 37 ResetInputMethods(*methods); 38 } 39 // Change access rights. 40 using InputMethodUtil::GetInputMethodIdsFromLanguageCodeInternal; 41 using InputMethodUtil::GetKeyboardLayoutName; 42}; 43 44} // namespace 45 46class InputMethodUtilTest : public testing::Test { 47 public: 48 InputMethodUtilTest() 49 : util_(&delegate_, whitelist_.GetSupportedInputMethods()) { 50 delegate_.set_get_localized_string_callback( 51 base::Bind(&l10n_util::GetStringUTF16)); 52 delegate_.set_get_display_language_name_callback( 53 base::Bind(&InputMethodUtilTest::GetDisplayLanguageName)); 54 } 55 56 virtual void SetUp() OVERRIDE { 57 InputMethodDescriptors input_methods; 58 59 std::vector<std::string> layouts; 60 std::vector<std::string> languages; 61 layouts.push_back("us"); 62 languages.push_back("zh-CN"); 63 64 InputMethodDescriptor pinyin_ime(Id(pinyin_ime_id), 65 "Pinyin input for testing", 66 "CN", 67 layouts, 68 languages, 69 false, 70 GURL(""), 71 GURL("")); 72 input_methods.push_back(pinyin_ime); 73 74 languages.clear(); 75 languages.push_back("zh-TW"); 76 InputMethodDescriptor zhuyin_ime(zhuyin_ime_id, 77 "Zhuyin input for testing", 78 "TW", 79 layouts, 80 languages, 81 false, 82 GURL(""), 83 GURL("")); 84 input_methods.push_back(zhuyin_ime); 85 86 util_.InitXkbInputMethodsForTesting(); 87 util_.AppendInputMethods(input_methods); 88 } 89 90 std::string Id(const std::string& id) { 91 return extension_ime_util::GetInputMethodIDByEngineID(id); 92 } 93 94 InputMethodDescriptor GetDesc(const std::string& id, 95 const std::string& raw_layout, 96 const std::string& language_code, 97 const std::string& indicator) { 98 std::vector<std::string> layouts; 99 layouts.push_back(raw_layout); 100 std::vector<std::string> languages; 101 languages.push_back(language_code); 102 return InputMethodDescriptor(Id(id), 103 "", // Description. 104 indicator, // Short name used for indicator. 105 layouts, 106 languages, 107 true, 108 GURL(), // options page url 109 GURL()); // input view page url 110 } 111 112 static base::string16 GetDisplayLanguageName(const std::string& language_code) { 113 return l10n_util::GetDisplayNameForLocale(language_code, "en", true); 114 } 115 116 FakeInputMethodDelegate delegate_; 117 InputMethodWhitelist whitelist_; 118 TestableInputMethodUtil util_; 119}; 120 121TEST_F(InputMethodUtilTest, GetInputMethodShortNameTest) { 122 // Test invalid cases. Two-letter language code should be returned. 123 { 124 InputMethodDescriptor desc = GetDesc("invalid-id", "us", "xx", ""); 125 // Upper-case string of the unknown language code, "xx", should be returned. 126 EXPECT_EQ(ASCIIToUTF16("XX"), util_.GetInputMethodShortName(desc)); 127 } 128 129 // Test special cases. 130 { 131 InputMethodDescriptor desc = 132 GetDesc("xkb:us:dvorak:eng", "us", "en-US", "DV"); 133 EXPECT_EQ(ASCIIToUTF16("DV"), util_.GetInputMethodShortName(desc)); 134 } 135 { 136 InputMethodDescriptor desc = 137 GetDesc("xkb:us:colemak:eng", "us", "en-US", "CO"); 138 EXPECT_EQ(ASCIIToUTF16("CO"), util_.GetInputMethodShortName(desc)); 139 } 140 { 141 InputMethodDescriptor desc = 142 GetDesc("xkb:us:altgr-intl:eng", "us", "en-US", "EXTD"); 143 EXPECT_EQ(ASCIIToUTF16("EXTD"), util_.GetInputMethodShortName(desc)); 144 } 145 { 146 InputMethodDescriptor desc = 147 GetDesc("xkb:us:intl:eng", "us", "en-US", "INTL"); 148 EXPECT_EQ(ASCIIToUTF16("INTL"), util_.GetInputMethodShortName(desc)); 149 } 150 { 151 InputMethodDescriptor desc = 152 GetDesc("xkb:de:neo:ger", "de(neo)", "de", "NEO"); 153 EXPECT_EQ(ASCIIToUTF16("NEO"), util_.GetInputMethodShortName(desc)); 154 } 155 { 156 InputMethodDescriptor desc = 157 GetDesc("xkb:es:cat:cat", "es(cat)", "ca", "CAS"); 158 EXPECT_EQ(ASCIIToUTF16("CAS"), util_.GetInputMethodShortName(desc)); 159 } 160 { 161 InputMethodDescriptor desc = 162 GetDesc(pinyin_ime_id, "us", "zh-CN", ""); 163 EXPECT_EQ(base::UTF8ToUTF16("\xe6\x8b\xbc"), 164 util_.GetInputMethodShortName(desc)); 165 } 166 { 167 InputMethodDescriptor desc = GetDesc(zhuyin_ime_id, "us", "zh-TW", ""); 168 EXPECT_EQ(base::UTF8ToUTF16("\xE6\xB3\xA8"), 169 util_.GetInputMethodShortName(desc)); 170 } 171} 172 173TEST_F(InputMethodUtilTest, GetInputMethodMediumNameTest) { 174 { 175 // input methods with medium name equal to short name 176 const char * input_method_id[] = { 177 "xkb:us:altgr-intl:eng", 178 "xkb:us:dvorak:eng", 179 "xkb:us:intl:eng", 180 "xkb:us:colemak:eng", 181 "xkb:de:neo:ger", 182 "xkb:es:cat:cat", 183 "xkb:gb:dvorak:eng", 184 }; 185 const int len = ARRAYSIZE_UNSAFE(input_method_id); 186 for (int i=0; i<len; ++i) { 187 InputMethodDescriptor desc = GetDesc(input_method_id[i], "", "", ""); 188 base::string16 medium_name = util_.GetInputMethodMediumName(desc); 189 base::string16 short_name = util_.GetInputMethodShortName(desc); 190 EXPECT_EQ(medium_name,short_name); 191 } 192 } 193 { 194 // input methods with medium name not equal to short name 195 const char * input_method_id[] = { 196 pinyin_ime_id, 197 zhuyin_ime_id, 198 }; 199 const int len = ARRAYSIZE_UNSAFE(input_method_id); 200 for (int i=0; i<len; ++i) { 201 InputMethodDescriptor desc = GetDesc(input_method_id[i], "", "", ""); 202 base::string16 medium_name = util_.GetInputMethodMediumName(desc); 203 base::string16 short_name = util_.GetInputMethodShortName(desc); 204 EXPECT_NE(medium_name,short_name); 205 } 206 } 207} 208 209TEST_F(InputMethodUtilTest, GetInputMethodLongNameTest) { 210 // For most languages input method or keyboard layout name is returned. 211 // See below for exceptions. 212 { 213 InputMethodDescriptor desc = GetDesc("xkb:jp::jpn", "jp", "ja", ""); 214 EXPECT_EQ(ASCIIToUTF16("Japanese"), 215 util_.GetInputMethodLongName(desc)); 216 } 217 { 218 InputMethodDescriptor desc = 219 GetDesc("xkb:us:dvorak:eng", "us(dvorak)", "en-US", ""); 220 EXPECT_EQ(ASCIIToUTF16("US Dvorak"), 221 util_.GetInputMethodLongName(desc)); 222 } 223 { 224 InputMethodDescriptor desc = 225 GetDesc("xkb:gb:dvorak:eng", "gb(dvorak)", "en-US", ""); 226 EXPECT_EQ(ASCIIToUTF16("UK Dvorak"), 227 util_.GetInputMethodLongName(desc)); 228 } 229 230 // For Dutch, French, German and Hindi, 231 // "language - keyboard layout" pair is returned. 232 { 233 InputMethodDescriptor desc = GetDesc("xkb:be::nld", "be", "nl", ""); 234 EXPECT_EQ(ASCIIToUTF16("Dutch - Belgian"), 235 util_.GetInputMethodLongName(desc)); 236 } 237 { 238 InputMethodDescriptor desc = GetDesc("xkb:fr::fra", "fr", "fr", ""); 239 EXPECT_EQ(ASCIIToUTF16("French - French"), 240 util_.GetInputMethodLongName(desc)); 241 } 242 { 243 InputMethodDescriptor desc = GetDesc("xkb:be::fra", "be", "fr", ""); 244 EXPECT_EQ(ASCIIToUTF16("French - Belgian"), 245 util_.GetInputMethodLongName(desc)); 246 } 247 { 248 InputMethodDescriptor desc = GetDesc("xkb:de::ger", "de", "de", ""); 249 EXPECT_EQ(ASCIIToUTF16("German - German"), 250 util_.GetInputMethodLongName(desc)); 251 } 252 { 253 InputMethodDescriptor desc = GetDesc("xkb:be::ger", "be", "de", ""); 254 EXPECT_EQ(ASCIIToUTF16("German - Belgian"), 255 util_.GetInputMethodLongName(desc)); 256 } 257 258 { 259 InputMethodDescriptor desc = GetDesc("invalid-id", "us", "xx", ""); 260 // You can safely ignore the "Resouce ID is not found for: invalid-id" 261 // error. 262 EXPECT_EQ(ASCIIToUTF16("invalid-id"), 263 util_.GetInputMethodLongName(desc)); 264 } 265} 266 267TEST_F(InputMethodUtilTest, TestIsValidInputMethodId) { 268 EXPECT_TRUE(util_.IsValidInputMethodId(Id("xkb:us:colemak:eng"))); 269 EXPECT_TRUE(util_.IsValidInputMethodId(Id(pinyin_ime_id))); 270 EXPECT_FALSE(util_.IsValidInputMethodId("unsupported-input-method")); 271} 272 273TEST_F(InputMethodUtilTest, TestIsKeyboardLayout) { 274 EXPECT_TRUE(InputMethodUtil::IsKeyboardLayout("xkb:us::eng")); 275 EXPECT_FALSE(InputMethodUtil::IsKeyboardLayout(Id(pinyin_ime_id))); 276} 277 278TEST_F(InputMethodUtilTest, TestGetKeyboardLayoutName) { 279 // Unsupported case. 280 EXPECT_EQ("", util_.GetKeyboardLayoutName("UNSUPPORTED_ID")); 281 282 // Supported cases (samples). 283 EXPECT_EQ("us", util_.GetKeyboardLayoutName(Id(pinyin_ime_id))); 284 EXPECT_EQ("es", util_.GetKeyboardLayoutName(Id("xkb:es::spa"))); 285 EXPECT_EQ("es(cat)", util_.GetKeyboardLayoutName(Id("xkb:es:cat:cat"))); 286 EXPECT_EQ("gb(extd)", util_.GetKeyboardLayoutName(Id("xkb:gb:extd:eng"))); 287 EXPECT_EQ("us", util_.GetKeyboardLayoutName(Id("xkb:us::eng"))); 288 EXPECT_EQ("us(dvorak)", util_.GetKeyboardLayoutName(Id("xkb:us:dvorak:eng"))); 289 EXPECT_EQ("us(colemak)", 290 util_.GetKeyboardLayoutName(Id("xkb:us:colemak:eng"))); 291 EXPECT_EQ("de(neo)", util_.GetKeyboardLayoutName(Id("xkb:de:neo:ger"))); 292} 293 294TEST_F(InputMethodUtilTest, TestGetInputMethodDisplayNameFromId) { 295 EXPECT_EQ("US", 296 util_.GetInputMethodDisplayNameFromId("xkb:us::eng")); 297 EXPECT_EQ("", util_.GetInputMethodDisplayNameFromId("nonexistent")); 298} 299 300TEST_F(InputMethodUtilTest, TestGetInputMethodDescriptorFromId) { 301 EXPECT_EQ(NULL, util_.GetInputMethodDescriptorFromId("non_existent")); 302 303 const InputMethodDescriptor* descriptor = 304 util_.GetInputMethodDescriptorFromId(Id(pinyin_ime_id)); 305 ASSERT_TRUE(NULL != descriptor); // ASSERT_NE doesn't compile. 306 EXPECT_EQ(Id(pinyin_ime_id), descriptor->id()); 307 EXPECT_EQ("us", descriptor->GetPreferredKeyboardLayout()); 308 // This used to be "zh" but now we have "zh-CN" in input_methods.h, 309 // hence this should be zh-CN now. 310 ASSERT_TRUE(!descriptor->language_codes().empty()); 311 EXPECT_EQ("zh-CN", descriptor->language_codes().at(0)); 312} 313 314TEST_F(InputMethodUtilTest, TestGetInputMethodIdsForLanguageCode) { 315 std::multimap<std::string, std::string> language_code_to_ids_map; 316 language_code_to_ids_map.insert(std::make_pair("ja", pinyin_ime_id)); 317 language_code_to_ids_map.insert(std::make_pair("ja", pinyin_ime_id)); 318 language_code_to_ids_map.insert(std::make_pair("ja", "xkb:jp:jpn")); 319 language_code_to_ids_map.insert(std::make_pair("fr", "xkb:fr:fra")); 320 321 std::vector<std::string> result; 322 EXPECT_TRUE(util_.GetInputMethodIdsFromLanguageCodeInternal( 323 language_code_to_ids_map, "ja", kAllInputMethods, &result)); 324 EXPECT_EQ(3U, result.size()); 325 EXPECT_TRUE(util_.GetInputMethodIdsFromLanguageCodeInternal( 326 language_code_to_ids_map, "ja", kKeyboardLayoutsOnly, &result)); 327 ASSERT_EQ(1U, result.size()); 328 EXPECT_EQ("xkb:jp:jpn", result[0]); 329 330 EXPECT_TRUE(util_.GetInputMethodIdsFromLanguageCodeInternal( 331 language_code_to_ids_map, "fr", kAllInputMethods, &result)); 332 ASSERT_EQ(1U, result.size()); 333 EXPECT_EQ("xkb:fr:fra", result[0]); 334 EXPECT_TRUE(util_.GetInputMethodIdsFromLanguageCodeInternal( 335 language_code_to_ids_map, "fr", kKeyboardLayoutsOnly, &result)); 336 ASSERT_EQ(1U, result.size()); 337 EXPECT_EQ("xkb:fr:fra", result[0]); 338 339 EXPECT_FALSE(util_.GetInputMethodIdsFromLanguageCodeInternal( 340 language_code_to_ids_map, "invalid_lang", kAllInputMethods, &result)); 341 EXPECT_FALSE(util_.GetInputMethodIdsFromLanguageCodeInternal( 342 language_code_to_ids_map, "invalid_lang", kKeyboardLayoutsOnly, &result)); 343} 344 345// US keyboard + English US UI = US keyboard only. 346TEST_F(InputMethodUtilTest, TestGetFirstLoginInputMethodIds_Us_And_EnUs) { 347 const InputMethodDescriptor* descriptor = 348 util_.GetInputMethodDescriptorFromId(Id("xkb:us::eng")); // US keyboard. 349 ASSERT_TRUE(NULL != descriptor); // ASSERT_NE doesn't compile. 350 std::vector<std::string> input_method_ids; 351 util_.GetFirstLoginInputMethodIds("en-US", *descriptor, &input_method_ids); 352 ASSERT_EQ(1U, input_method_ids.size()); 353 EXPECT_EQ(Id("xkb:us::eng"), input_method_ids[0]); 354} 355 356// US keyboard + Chinese UI = US keyboard + Pinyin IME. 357TEST_F(InputMethodUtilTest, TestGetFirstLoginInputMethodIds_Us_And_Zh) { 358 const InputMethodDescriptor* descriptor = 359 util_.GetInputMethodDescriptorFromId(Id("xkb:us::eng")); // US keyboard. 360 ASSERT_TRUE(NULL != descriptor); // ASSERT_NE doesn't compile. 361 std::vector<std::string> input_method_ids; 362 util_.GetFirstLoginInputMethodIds("zh-CN", *descriptor, &input_method_ids); 363 ASSERT_EQ(2U, input_method_ids.size()); 364 EXPECT_EQ(Id("xkb:us::eng"), input_method_ids[0]); 365 EXPECT_EQ(Id(pinyin_ime_id), input_method_ids[1]); // Pinyin for US keybaord. 366} 367 368// US keyboard + Russian UI = US keyboard + Russsian keyboard 369TEST_F(InputMethodUtilTest, TestGetFirstLoginInputMethodIds_Us_And_Ru) { 370 const InputMethodDescriptor* descriptor = 371 util_.GetInputMethodDescriptorFromId(Id("xkb:us::eng")); // US keyboard. 372 ASSERT_TRUE(NULL != descriptor); // ASSERT_NE doesn't compile. 373 std::vector<std::string> input_method_ids; 374 util_.GetFirstLoginInputMethodIds("ru", *descriptor, &input_method_ids); 375 ASSERT_EQ(2U, input_method_ids.size()); 376 EXPECT_EQ(Id("xkb:us::eng"), input_method_ids[0]); 377 EXPECT_EQ(Id("xkb:ru::rus"), input_method_ids[1]); // Russian keyboard. 378} 379 380// US keyboard + Traditional Chinese = US keyboard + chewing. 381TEST_F(InputMethodUtilTest, TestGetFirstLoginInputMethodIds_Us_And_ZhTw) { 382 const InputMethodDescriptor* descriptor = 383 util_.GetInputMethodDescriptorFromId(Id("xkb:us::eng")); // US keyboard. 384 ASSERT_TRUE(NULL != descriptor); // ASSERT_NE doesn't compile. 385 std::vector<std::string> input_method_ids; 386 util_.GetFirstLoginInputMethodIds("zh-TW", *descriptor, &input_method_ids); 387 ASSERT_EQ(2U, input_method_ids.size()); 388 EXPECT_EQ(Id("xkb:us::eng"), input_method_ids[0]); 389 EXPECT_EQ(Id(zhuyin_ime_id), input_method_ids[1]); // Chewing. 390} 391 392// US keyboard + Thai = US keyboard + kesmanee. 393TEST_F(InputMethodUtilTest, TestGetFirstLoginInputMethodIds_Us_And_Th) { 394 const InputMethodDescriptor* descriptor = 395 util_.GetInputMethodDescriptorFromId(Id("xkb:us::eng")); // US keyboard. 396 ASSERT_TRUE(NULL != descriptor); // ASSERT_NE doesn't compile. 397 std::vector<std::string> input_method_ids; 398 util_.GetFirstLoginInputMethodIds("th", *descriptor, &input_method_ids); 399 ASSERT_EQ(2U, input_method_ids.size()); 400 EXPECT_EQ(Id("xkb:us::eng"), input_method_ids[0]); 401 EXPECT_EQ(Id("vkd_th"), input_method_ids[1]); // Kesmanee. 402} 403 404// US keyboard + Vietnamese = US keyboard + TCVN6064. 405TEST_F(InputMethodUtilTest, TestGetFirstLoginInputMethodIds_Us_And_Vi) { 406 const InputMethodDescriptor* descriptor = 407 util_.GetInputMethodDescriptorFromId(Id("xkb:us::eng")); // US keyboard. 408 ASSERT_TRUE(NULL != descriptor); // ASSERT_NE doesn't compile. 409 std::vector<std::string> input_method_ids; 410 util_.GetFirstLoginInputMethodIds("vi", *descriptor, &input_method_ids); 411 ASSERT_EQ(2U, input_method_ids.size()); 412 EXPECT_EQ(Id("xkb:us::eng"), input_method_ids[0]); 413 EXPECT_EQ(Id("vkd_vi_tcvn"), input_method_ids[1]); // TCVN6064. 414} 415 416TEST_F(InputMethodUtilTest, TestGetLanguageCodesFromInputMethodIds) { 417 std::vector<std::string> input_method_ids; 418 input_method_ids.push_back(Id("xkb:us::eng")); // English US. 419 input_method_ids.push_back(Id("xkb:us:dvorak:eng")); // English US Dvorak. 420 input_method_ids.push_back(Id(pinyin_ime_id)); // Pinyin 421 input_method_ids.push_back(Id("xkb:fr::fra")); // French France. 422 std::vector<std::string> language_codes; 423 util_.GetLanguageCodesFromInputMethodIds(input_method_ids, &language_codes); 424 ASSERT_EQ(3U, language_codes.size()); 425 EXPECT_EQ("en", language_codes[0]); 426 EXPECT_EQ("zh-CN", language_codes[1]); 427 EXPECT_EQ("fr", language_codes[2]); 428} 429 430// Test all supported descriptors to detect a typo in input_methods.txt. 431TEST_F(InputMethodUtilTest, TestIBusInputMethodText) { 432 const std::map<std::string, InputMethodDescriptor>& id_to_descriptor = 433 util_.GetIdToDesciptorMapForTesting(); 434 for (std::map<std::string, InputMethodDescriptor>::const_iterator it = 435 id_to_descriptor.begin(); it != id_to_descriptor.end(); ++it) { 436 const std::string language_code = it->second.language_codes().at(0); 437 const base::string16 display_name = 438 l10n_util::GetDisplayNameForLocale(language_code, "en", false); 439 // Only two formats, like "fr" (lower case) and "en-US" (lower-upper), are 440 // allowed. See the text file for details. 441 EXPECT_TRUE(language_code == "fil" || language_code.length() == 2 || 442 (language_code.length() == 5 && language_code[2] == '-')) 443 << "Invalid language code " << language_code; 444 EXPECT_TRUE(l10n_util::IsValidLocaleSyntax(language_code)) 445 << "Invalid language code " << language_code; 446 EXPECT_FALSE(display_name.empty()) 447 << "Invalid language code " << language_code; 448 // On error, GetDisplayNameForLocale() returns the |language_code| as-is. 449 EXPECT_NE(language_code, base::UTF16ToUTF8(display_name)) 450 << "Invalid language code " << language_code; 451 } 452} 453 454// Test the input method ID migration. 455TEST_F(InputMethodUtilTest, TestInputMethodIDMigration) { 456 const char* const migration_cases[][2] = { 457 {"ime:zh:pinyin", "zh-t-i0-pinyin"}, 458 {"ime:zh-t:zhuyin", "zh-hant-t-i0-und"}, 459 {"ime:zh-t:quick", "zh-hant-t-i0-cangjie-1987-x-m0-simplified"}, 460 {"ime:jp:mozc_us", "nacl_mozc_us"}, 461 {"ime:ko:hangul", "hangul_2set"}, 462 {"m17n:deva_phone", "vkd_deva_phone"}, 463 {"m17n:ar", "vkd_ar"}, 464 {"t13n:hi", "hi-t-i0-und"}, 465 {"unknown", "unknown"}, 466 }; 467 std::vector<std::string> input_method_ids; 468 for (size_t i = 0; i < arraysize(migration_cases); ++i) 469 input_method_ids.push_back(migration_cases[i][0]); 470 // Duplicated hangul_2set. 471 input_method_ids.push_back("ime:ko:hangul_2set"); 472 473 util_.MigrateInputMethods(&input_method_ids); 474 475 EXPECT_EQ(arraysize(migration_cases), input_method_ids.size()); 476 for (size_t i = 0; i < arraysize(migration_cases); ++i) { 477 EXPECT_EQ( 478 extension_ime_util::GetInputMethodIDByEngineID(migration_cases[i][1]), 479 input_method_ids[i]); 480 } 481} 482 483} // namespace input_method 484} // namespace chromeos 485