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