InputLogicTests.java revision 6ec1209a33fe2dc151b86d3f662e22e564e2f4f8
1/* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17package com.android.inputmethod.latin; 18 19import android.content.Context; 20import android.content.Intent; 21import android.content.SharedPreferences; 22import android.os.Looper; 23import android.os.MessageQueue; 24import android.preference.PreferenceManager; 25import android.test.ServiceTestCase; 26import android.text.InputType; 27import android.text.SpannableStringBuilder; 28import android.text.style.SuggestionSpan; 29import android.util.Log; 30import android.view.LayoutInflater; 31import android.view.ViewGroup; 32import android.view.View; 33import android.view.inputmethod.BaseInputConnection; 34import android.view.inputmethod.EditorInfo; 35import android.view.inputmethod.InputConnection; 36import android.widget.FrameLayout; 37import android.widget.TextView; 38 39import com.android.inputmethod.keyboard.Keyboard; 40import com.android.inputmethod.keyboard.KeyboardActionListener; 41import com.android.inputmethod.latin.spellcheck.AndroidSpellCheckerService; // for proximity info 42import com.android.inputmethod.latin.spellcheck.SpellCheckerProximityInfo; 43 44import java.util.Arrays; 45import java.util.HashMap; 46 47public class InputLogicTests extends ServiceTestCase<LatinIME> { 48 49 private static final String PREF_DEBUG_MODE = "debug_mode"; 50 51 private LatinIME mLatinIME; 52 private TextView mTextView; 53 private InputConnection mInputConnection; 54 55 public InputLogicTests() { 56 super(LatinIME.class); 57 } 58 59 // returns the previous setting value 60 private boolean setDebugMode(final boolean mode) { 61 final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mLatinIME); 62 final boolean previousDebugSetting = prefs.getBoolean(PREF_DEBUG_MODE, false); 63 final SharedPreferences.Editor editor = prefs.edit(); 64 editor.putBoolean(PREF_DEBUG_MODE, true); 65 editor.commit(); 66 return previousDebugSetting; 67 } 68 69 @Override 70 protected void setUp() { 71 try { 72 super.setUp(); 73 } catch (Exception e) { 74 e.printStackTrace(); 75 } 76 mTextView = new TextView(getContext()); 77 mTextView.setInputType(InputType.TYPE_CLASS_TEXT); 78 mTextView.setEnabled(true); 79 setupService(); 80 mLatinIME = getService(); 81 final boolean previousDebugSetting = setDebugMode(true); 82 mLatinIME.onCreate(); 83 setDebugMode(previousDebugSetting); 84 final EditorInfo ei = new EditorInfo(); 85 ei.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT; 86 final InputConnection ic = mTextView.onCreateInputConnection(ei); 87 ei.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT; 88 final LayoutInflater inflater = 89 (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); 90 final ViewGroup vg = new FrameLayout(getContext()); 91 final View inputView = inflater.inflate(R.layout.input_view, vg); 92 mLatinIME.setInputView(inputView); 93 mLatinIME.onBindInput(); 94 mLatinIME.onCreateInputView(); 95 mLatinIME.onStartInputView(ei, false); 96 mLatinIME.onCreateInputMethodInterface().startInput(ic, ei); 97 mInputConnection = ic; 98 changeLanguage("en_US"); 99 } 100 101 // We need to run the messages added to the handler from LatinIME. The only way to do 102 // that is to call Looper#loop() on the right looper, so we're going to get the looper 103 // object and call #loop() here. The messages in the handler actually run on the UI 104 // thread of the keyboard by design of the handler, so we want to call it synchronously 105 // on the same thread that the tests are running on to mimic the actual environment as 106 // closely as possible. 107 // Now, Looper#loop() never exits in normal operation unless the Looper#quit() method 108 // is called, so we need to do that at the right time so that #loop() returns at some 109 // point and we don't end up in an infinite loop. 110 // After we quit, the looper is still technically ready to process more messages but 111 // the handler will refuse to enqueue any because #quit() has been called and it 112 // explicitly tests for it on message enqueuing, so we'll have to reset it so that 113 // it lets us continue normal operation. 114 private void runMessages() { 115 // Here begins deep magic. 116 final Looper looper = mLatinIME.mHandler.getLooper(); 117 mLatinIME.mHandler.post(new Runnable() { 118 @Override 119 public void run() { 120 looper.quit(); 121 } 122 }); 123 // The only way to get out of Looper#loop() is to call #quit() on it (or on its queue). 124 // Once #quit() is called remaining messages are not processed, which is why we post 125 // a message that calls it instead of calling it directly. 126 looper.loop(); 127 128 // Once #quit() has been called, the message queue has an "mQuiting" field that prevents 129 // any subsequent post in this queue. However the queue itself is still fully functional! 130 // If we have a way of resetting "queue.mQuiting" then we can continue using it as normal, 131 // coming back to this method to run the messages. 132 MessageQueue queue = looper.getQueue(); 133 try { 134 // However there is no way of doing it externally, and mQuiting is private. 135 // So... get out the big guns. 136 java.lang.reflect.Field f = MessageQueue.class.getDeclaredField("mQuiting"); 137 f.setAccessible(true); // What do you mean "private"? 138 f.setBoolean(queue, false); 139 } catch (NoSuchFieldException e) { 140 throw new RuntimeException(e); 141 } catch (IllegalAccessException e) { 142 throw new RuntimeException(e); 143 } 144 } 145 146 // type(int) and type(String): helper methods to send a code point resp. a string to LatinIME. 147 private void type(final int codePoint) { 148 // onPressKey and onReleaseKey are explicitly deactivated here, but they do happen in the 149 // code (although multitouch/slide input and other factors make the sequencing complicated). 150 // They are supposed to be entirely deconnected from the input logic from LatinIME point of 151 // view and only delegates to the parts of the code that care. So we don't include them here 152 // to keep these tests as pinpoint as possible and avoid bringing it too many dependencies, 153 // but keep them in mind if something breaks. Commenting them out as is should work. 154 //mLatinIME.onPressKey(codePoint); 155 mLatinIME.onCodeInput(codePoint, 156 KeyboardActionListener.SPELL_CHECKER_COORDINATE, 157 KeyboardActionListener.SPELL_CHECKER_COORDINATE); 158 //mLatinIME.onReleaseKey(codePoint, false); 159 } 160 161 private void type(final String stringToType) { 162 for (int i = 0; i < stringToType.length(); i = stringToType.offsetByCodePoints(i, 1)) { 163 type(stringToType.codePointAt(i)); 164 } 165 } 166 167 private void waitForDictionaryToBeLoaded() { 168 int remainingAttempts = 10; 169 while (remainingAttempts > 0 && !mLatinIME.mSuggest.hasMainDictionary()) { 170 try { 171 Thread.sleep(200); 172 } catch (InterruptedException e) { 173 // Don't do much 174 } finally { 175 --remainingAttempts; 176 } 177 } 178 if (!mLatinIME.mSuggest.hasMainDictionary()) { 179 throw new RuntimeException("Can't initialize the main dictionary"); 180 } 181 } 182 183 private void changeLanguage(final String locale) { 184 SubtypeSwitcher.getInstance().updateSubtype( 185 new ArbitrarySubtype(locale, LatinIME.SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE)); 186 waitForDictionaryToBeLoaded(); 187 } 188 189 190 // Helper to avoid writing the try{}catch block each time 191 private static void sleep(final int milliseconds) { 192 try { 193 Thread.sleep(milliseconds); 194 } catch (InterruptedException e) {} 195 } 196 197 public void testTypeWord() { 198 final String WORD_TO_TYPE = "abcd"; 199 type(WORD_TO_TYPE); 200 assertEquals("type word", WORD_TO_TYPE, mTextView.getText().toString()); 201 } 202 203 public void testPickSuggestionThenBackspace() { 204 final String WORD_TO_TYPE = "this"; 205 final String EXPECTED_RESULT = "this"; 206 type(WORD_TO_TYPE); 207 mLatinIME.pickSuggestionManually(0, WORD_TO_TYPE); 208 mLatinIME.onUpdateSelection(0, 0, WORD_TO_TYPE.length(), WORD_TO_TYPE.length(), -1, -1); 209 type(Keyboard.CODE_DELETE); 210 assertEquals("press suggestion then backspace", EXPECTED_RESULT, 211 mTextView.getText().toString()); 212 } 213 214 public void testPickAutoCorrectionThenBackspace() { 215 final String WORD_TO_TYPE = "tgis"; 216 final String WORD_TO_PICK = "this"; 217 final String EXPECTED_RESULT = "tgis"; 218 type(WORD_TO_TYPE); 219 // Choose the auto-correction, which is always in position 0. For "tgis", the 220 // auto-correction should be "this". 221 mLatinIME.pickSuggestionManually(0, WORD_TO_PICK); 222 mLatinIME.onUpdateSelection(0, 0, WORD_TO_TYPE.length(), WORD_TO_TYPE.length(), -1, -1); 223 assertEquals("pick typed word over auto-correction then backspace", WORD_TO_PICK, 224 mTextView.getText().toString()); 225 type(Keyboard.CODE_DELETE); 226 assertEquals("pick typed word over auto-correction then backspace", EXPECTED_RESULT, 227 mTextView.getText().toString()); 228 } 229 230 public void testPickTypedWordOverAutoCorrectionThenBackspace() { 231 final String WORD_TO_TYPE = "tgis"; 232 final String EXPECTED_RESULT = "tgis"; 233 type(WORD_TO_TYPE); 234 // Choose the typed word, which should be in position 1 (because position 0 should 235 // be occupied by the "this" auto-correction, as checked by testAutoCorrect()) 236 mLatinIME.pickSuggestionManually(1, WORD_TO_TYPE); 237 mLatinIME.onUpdateSelection(0, 0, WORD_TO_TYPE.length(), WORD_TO_TYPE.length(), -1, -1); 238 assertEquals("pick typed word over auto-correction then backspace", WORD_TO_TYPE, 239 mTextView.getText().toString()); 240 type(Keyboard.CODE_DELETE); 241 assertEquals("pick typed word over auto-correction then backspace", EXPECTED_RESULT, 242 mTextView.getText().toString()); 243 } 244 245 public void testPickDifferentSuggestionThenBackspace() { 246 final String WORD_TO_TYPE = "tgis"; 247 final String WORD_TO_PICK = "thus"; 248 final String EXPECTED_RESULT = "tgis"; 249 type(WORD_TO_TYPE); 250 // Choose the second suggestion, which should be in position 2 and should be "thus" 251 // when "tgis is typed. 252 mLatinIME.pickSuggestionManually(2, WORD_TO_PICK); 253 mLatinIME.onUpdateSelection(0, 0, WORD_TO_TYPE.length(), WORD_TO_TYPE.length(), -1, -1); 254 assertEquals("pick different suggestion then backspace", WORD_TO_PICK, 255 mTextView.getText().toString()); 256 type(Keyboard.CODE_DELETE); 257 assertEquals("pick different suggestion then backspace", EXPECTED_RESULT, 258 mTextView.getText().toString()); 259 } 260 261 public void testDeleteSelection() { 262 final String STRING_TO_TYPE = "some text delete me some text"; 263 final int SELECTION_START = 10; 264 final int SELECTION_END = 19; 265 final String EXPECTED_RESULT = "some text some text"; 266 type(STRING_TO_TYPE); 267 // There is no IMF to call onUpdateSelection for us so we must do it by hand. 268 // Send once to simulate the cursor actually responding to the move caused by typing. 269 // This is necessary because LatinIME is bookkeeping to avoid confusing a real cursor 270 // move with a move triggered by LatinIME inputting stuff. 271 mLatinIME.onUpdateSelection(0, 0, STRING_TO_TYPE.length(), STRING_TO_TYPE.length(), -1, -1); 272 mInputConnection.setSelection(SELECTION_START, SELECTION_END); 273 // And now we simulate the user actually selecting some text. 274 mLatinIME.onUpdateSelection(0, 0, SELECTION_START, SELECTION_END, -1, -1); 275 type(Keyboard.CODE_DELETE); 276 assertEquals("delete selection", EXPECTED_RESULT, mTextView.getText().toString()); 277 } 278 279 public void testAutoCorrect() { 280 final String STRING_TO_TYPE = "tgis "; 281 final String EXPECTED_RESULT = "this "; 282 type(STRING_TO_TYPE); 283 assertEquals("simple auto-correct", EXPECTED_RESULT, mTextView.getText().toString()); 284 } 285 286 public void testAutoCorrectForFrench() { 287 final String STRING_TO_TYPE = "irq "; 288 final String EXPECTED_RESULT = "ira "; 289 changeLanguage("fr"); 290 type(STRING_TO_TYPE); 291 assertEquals("simple auto-correct for French", EXPECTED_RESULT, 292 mTextView.getText().toString()); 293 } 294 295 public void testAutoCorrectWithPeriod() { 296 final String STRING_TO_TYPE = "tgis."; 297 final String EXPECTED_RESULT = "this."; 298 type(STRING_TO_TYPE); 299 assertEquals("auto-correct with period", EXPECTED_RESULT, mTextView.getText().toString()); 300 } 301 302 public void testAutoCorrectWithPeriodThenRevert() { 303 final String STRING_TO_TYPE = "tgis."; 304 final String EXPECTED_RESULT = "tgis."; 305 type(STRING_TO_TYPE); 306 mLatinIME.onUpdateSelection(0, 0, STRING_TO_TYPE.length(), STRING_TO_TYPE.length(), -1, -1); 307 type(Keyboard.CODE_DELETE); 308 assertEquals("auto-correct with period then revert", EXPECTED_RESULT, 309 mTextView.getText().toString()); 310 } 311 312 public void testDoubleSpace() { 313 final String STRING_TO_TYPE = "this "; 314 final String EXPECTED_RESULT = "this. "; 315 type(STRING_TO_TYPE); 316 assertEquals("double space make a period", EXPECTED_RESULT, mTextView.getText().toString()); 317 } 318 319 public void testCancelDoubleSpace() { 320 final String STRING_TO_TYPE = "this "; 321 final String EXPECTED_RESULT = "this "; 322 type(STRING_TO_TYPE); 323 type(Keyboard.CODE_DELETE); 324 assertEquals("double space make a period", EXPECTED_RESULT, mTextView.getText().toString()); 325 } 326 327 public void testBackspaceAtStartAfterAutocorrect() { 328 final String STRING_TO_TYPE = "tgis "; 329 final String EXPECTED_RESULT = "this "; 330 final int NEW_CURSOR_POSITION = 0; 331 type(STRING_TO_TYPE); 332 mLatinIME.onUpdateSelection(0, 0, STRING_TO_TYPE.length(), STRING_TO_TYPE.length(), -1, -1); 333 mInputConnection.setSelection(NEW_CURSOR_POSITION, NEW_CURSOR_POSITION); 334 mLatinIME.onUpdateSelection(0, 0, NEW_CURSOR_POSITION, NEW_CURSOR_POSITION, -1, -1); 335 type(Keyboard.CODE_DELETE); 336 assertEquals("auto correct then move cursor to start of line then backspace", 337 EXPECTED_RESULT, mTextView.getText().toString()); 338 } 339 340 public void testAutoCorrectThenMoveCursorThenBackspace() { 341 final String STRING_TO_TYPE = "and tgis "; 342 final String EXPECTED_RESULT = "andthis "; 343 final int NEW_CURSOR_POSITION = STRING_TO_TYPE.indexOf('t'); 344 type(STRING_TO_TYPE); 345 mLatinIME.onUpdateSelection(0, 0, STRING_TO_TYPE.length(), STRING_TO_TYPE.length(), -1, -1); 346 mInputConnection.setSelection(NEW_CURSOR_POSITION, NEW_CURSOR_POSITION); 347 mLatinIME.onUpdateSelection(0, 0, NEW_CURSOR_POSITION, NEW_CURSOR_POSITION, -1, -1); 348 type(Keyboard.CODE_DELETE); 349 assertEquals("auto correct then move cursor then backspace", 350 EXPECTED_RESULT, mTextView.getText().toString()); 351 } 352 353 public void testNoSpaceAfterManualPick() { 354 final String WORD_TO_TYPE = "this"; 355 final String EXPECTED_RESULT = WORD_TO_TYPE; 356 type(WORD_TO_TYPE); 357 mLatinIME.pickSuggestionManually(0, WORD_TO_TYPE); 358 assertEquals("no space after manual pick", EXPECTED_RESULT, 359 mTextView.getText().toString()); 360 } 361 362 public void testManualPickThenType() { 363 final String WORD1_TO_TYPE = "this"; 364 final String WORD2_TO_TYPE = "is"; 365 final String EXPECTED_RESULT = "this is"; 366 type(WORD1_TO_TYPE); 367 mLatinIME.pickSuggestionManually(0, WORD1_TO_TYPE); 368 type(WORD2_TO_TYPE); 369 assertEquals("manual pick then type", EXPECTED_RESULT, mTextView.getText().toString()); 370 } 371 372 public void testManualPickThenSeparator() { 373 final String WORD1_TO_TYPE = "this"; 374 final String WORD2_TO_TYPE = "!"; 375 final String EXPECTED_RESULT = "this!"; 376 type(WORD1_TO_TYPE); 377 mLatinIME.pickSuggestionManually(0, WORD1_TO_TYPE); 378 type(WORD2_TO_TYPE); 379 assertEquals("manual pick then separator", EXPECTED_RESULT, mTextView.getText().toString()); 380 } 381 382 public void testWordThenSpaceThenPunctuationFromStripTwice() { 383 final String WORD_TO_TYPE = "this "; 384 final String PUNCTUATION_FROM_STRIP = "!"; 385 final String EXPECTED_RESULT = "this!! "; 386 type(WORD_TO_TYPE); 387 sleep(DELAY_TO_WAIT_FOR_UNDERLINE); 388 runMessages(); 389 assertTrue("type word then type space should display punctuation strip", 390 mLatinIME.isShowingPunctuationList()); 391 mLatinIME.pickSuggestionManually(0, PUNCTUATION_FROM_STRIP); 392 mLatinIME.pickSuggestionManually(0, PUNCTUATION_FROM_STRIP); 393 assertEquals("type word then type space then punctuation from strip twice", EXPECTED_RESULT, 394 mTextView.getText().toString()); 395 } 396 397 public void testManualPickThenSeparatorForFrench() { 398 final String WORD1_TO_TYPE = "test"; 399 final String WORD2_TO_TYPE = "!"; 400 final String EXPECTED_RESULT = "test !"; 401 changeLanguage("fr"); 402 type(WORD1_TO_TYPE); 403 mLatinIME.pickSuggestionManually(0, WORD1_TO_TYPE); 404 type(WORD2_TO_TYPE); 405 assertEquals("manual pick then separator for French", EXPECTED_RESULT, 406 mTextView.getText().toString()); 407 } 408 409 public void testWordThenSpaceThenPunctuationFromStripTwiceForFrench() { 410 final String WORD_TO_TYPE = "test "; 411 final String PUNCTUATION_FROM_STRIP = "!"; 412 final String EXPECTED_RESULT = "test !!"; 413 changeLanguage("fr"); 414 type(WORD_TO_TYPE); 415 sleep(DELAY_TO_WAIT_FOR_UNDERLINE); 416 runMessages(); 417 assertTrue("type word then type space should display punctuation strip", 418 mLatinIME.isShowingPunctuationList()); 419 mLatinIME.pickSuggestionManually(0, PUNCTUATION_FROM_STRIP); 420 mLatinIME.pickSuggestionManually(0, PUNCTUATION_FROM_STRIP); 421 assertEquals("type word then type space then punctuation from strip twice for French", 422 EXPECTED_RESULT, mTextView.getText().toString()); 423 } 424 425 public void testWordThenSpaceThenPunctuationFromKeyboardTwice() { 426 final String WORD_TO_TYPE = "this !!"; 427 final String EXPECTED_RESULT = "this !!"; 428 type(WORD_TO_TYPE); 429 assertEquals("manual pick then space then punctuation from keyboard twice", EXPECTED_RESULT, 430 mTextView.getText().toString()); 431 } 432 433 public void testManualPickThenPunctuationFromStripTwiceThenType() { 434 final String WORD1_TO_TYPE = "this"; 435 final String WORD2_TO_TYPE = "is"; 436 final String PUNCTUATION_FROM_STRIP = "!"; 437 final String EXPECTED_RESULT = "this!! is"; 438 type(WORD1_TO_TYPE); 439 mLatinIME.pickSuggestionManually(0, WORD1_TO_TYPE); 440 mLatinIME.pickSuggestionManually(0, PUNCTUATION_FROM_STRIP); 441 mLatinIME.pickSuggestionManually(0, PUNCTUATION_FROM_STRIP); 442 type(WORD2_TO_TYPE); 443 assertEquals("pick word then pick punctuation twice then type", EXPECTED_RESULT, 444 mTextView.getText().toString()); 445 } 446 447 public void testManualPickThenSpaceThenType() { 448 final String WORD1_TO_TYPE = "this"; 449 final String WORD2_TO_TYPE = " is"; 450 final String EXPECTED_RESULT = "this is"; 451 type(WORD1_TO_TYPE); 452 mLatinIME.pickSuggestionManually(0, WORD1_TO_TYPE); 453 type(WORD2_TO_TYPE); 454 assertEquals("manual pick then space then type", EXPECTED_RESULT, 455 mTextView.getText().toString()); 456 } 457 458 public void testManualPickThenManualPick() { 459 final String WORD1_TO_TYPE = "this"; 460 final String WORD2_TO_PICK = "is"; 461 final String EXPECTED_RESULT = "this is"; 462 type(WORD1_TO_TYPE); 463 mLatinIME.pickSuggestionManually(0, WORD1_TO_TYPE); 464 // Here we fake picking a word through bigram prediction. This test is taking 465 // advantage of the fact that Latin IME blindly trusts the caller of #pickSuggestionManually 466 // to actually pass the right string. 467 mLatinIME.pickSuggestionManually(1, WORD2_TO_PICK); 468 assertEquals("manual pick then manual pick", EXPECTED_RESULT, 469 mTextView.getText().toString()); 470 } 471 472 public void testManualPickThenManualPickWithPunctAtStart() { 473 final String WORD1_TO_TYPE = "this"; 474 final String WORD2_TO_PICK = "!is"; 475 final String EXPECTED_RESULT = "this!is"; 476 type(WORD1_TO_TYPE); 477 mLatinIME.pickSuggestionManually(0, WORD1_TO_TYPE); 478 mLatinIME.pickSuggestionManually(1, WORD2_TO_PICK); 479 assertEquals("manual pick then manual pick a word with punct at start", EXPECTED_RESULT, 480 mTextView.getText().toString()); 481 } 482 483 public void testDeleteWholeComposingWord() { 484 final String WORD_TO_TYPE = "this"; 485 type(WORD_TO_TYPE); 486 for (int i = 0; i < WORD_TO_TYPE.length(); ++i) { 487 type(Keyboard.CODE_DELETE); 488 } 489 assertEquals("delete whole composing word", "", mTextView.getText().toString()); 490 } 491 492 public void testManuallyPickedWordThenColon() { 493 final String WORD_TO_TYPE = "this"; 494 final String PUNCTUATION = ":"; 495 final String EXPECTED_RESULT = "this:"; 496 type(WORD_TO_TYPE); 497 mLatinIME.pickSuggestionManually(0, WORD_TO_TYPE); 498 type(PUNCTUATION); 499 assertEquals("manually pick word then colon", 500 EXPECTED_RESULT, mTextView.getText().toString()); 501 } 502 503 public void testManuallyPickedWordThenOpenParen() { 504 final String WORD_TO_TYPE = "this"; 505 final String PUNCTUATION = "("; 506 final String EXPECTED_RESULT = "this ("; 507 type(WORD_TO_TYPE); 508 mLatinIME.pickSuggestionManually(0, WORD_TO_TYPE); 509 type(PUNCTUATION); 510 assertEquals("manually pick word then open paren", 511 EXPECTED_RESULT, mTextView.getText().toString()); 512 } 513 514 public void testManuallyPickedWordThenCloseParen() { 515 final String WORD_TO_TYPE = "this"; 516 final String PUNCTUATION = ")"; 517 final String EXPECTED_RESULT = "this)"; 518 type(WORD_TO_TYPE); 519 mLatinIME.pickSuggestionManually(0, WORD_TO_TYPE); 520 type(PUNCTUATION); 521 assertEquals("manually pick word then close paren", 522 EXPECTED_RESULT, mTextView.getText().toString()); 523 } 524 525 public void testManuallyPickedWordThenSmiley() { 526 final String WORD_TO_TYPE = "this"; 527 final String SPECIAL_KEY = ":-)"; 528 final String EXPECTED_RESULT = "this :-)"; 529 type(WORD_TO_TYPE); 530 mLatinIME.pickSuggestionManually(0, WORD_TO_TYPE); 531 mLatinIME.onTextInput(SPECIAL_KEY); 532 assertEquals("manually pick word then press the smiley key", 533 EXPECTED_RESULT, mTextView.getText().toString()); 534 } 535 536 public void testManuallyPickedWordThenDotCom() { 537 final String WORD_TO_TYPE = "this"; 538 final String SPECIAL_KEY = ".com"; 539 final String EXPECTED_RESULT = "this.com"; 540 type(WORD_TO_TYPE); 541 mLatinIME.pickSuggestionManually(0, WORD_TO_TYPE); 542 mLatinIME.onTextInput(SPECIAL_KEY); 543 assertEquals("manually pick word then press the .com key", 544 EXPECTED_RESULT, mTextView.getText().toString()); 545 } 546 547 public void testTypeWordTypeDotThenPressDotCom() { 548 final String WORD_TO_TYPE = "this."; 549 final String SPECIAL_KEY = ".com"; 550 final String EXPECTED_RESULT = "this.com"; 551 type(WORD_TO_TYPE); 552 mLatinIME.onTextInput(SPECIAL_KEY); 553 assertEquals("type word type dot then press the .com key", 554 EXPECTED_RESULT, mTextView.getText().toString()); 555 } 556 557 public void testAutoCorrectionWithSingleQuoteInside() { 558 final String WORD_TO_TYPE = "you'f "; 559 final String EXPECTED_RESULT = "you'd "; 560 type(WORD_TO_TYPE); 561 assertEquals("auto-correction with single quote inside", 562 EXPECTED_RESULT, mTextView.getText().toString()); 563 } 564 565 public void testAutoCorrectionWithSingleQuotesAround() { 566 final String WORD_TO_TYPE = "'tgis' "; 567 final String EXPECTED_RESULT = "'this' "; 568 type(WORD_TO_TYPE); 569 assertEquals("auto-correction with single quotes around", 570 EXPECTED_RESULT, mTextView.getText().toString()); 571 } 572 573 // A helper class to ease span tests 574 private static class Span { 575 final SpannableStringBuilder mInputText; 576 final SuggestionSpan mSpan; 577 final int mStart; 578 final int mEnd; 579 // The supplied CharSequence should be an instance of SpannableStringBuilder, 580 // and it should contain exactly zero or one SuggestionSpan. Otherwise, an exception 581 // is thrown. 582 public Span(final CharSequence inputText) { 583 mInputText = (SpannableStringBuilder)inputText; 584 final SuggestionSpan[] spans = 585 mInputText.getSpans(0, mInputText.length(), SuggestionSpan.class); 586 if (0 == spans.length) { 587 mSpan = null; 588 mStart = -1; 589 mEnd = -1; 590 } else if (1 == spans.length) { 591 mSpan = spans[0]; 592 mStart = mInputText.getSpanStart(mSpan); 593 mEnd = mInputText.getSpanEnd(mSpan); 594 } else { 595 throw new RuntimeException("Expected one SuggestionSpan, found " + spans.length); 596 } 597 } 598 public boolean isAutoCorrectionIndicator() { 599 return 0 != (SuggestionSpan.FLAG_AUTO_CORRECTION & mSpan.getFlags()); 600 } 601 } 602 603 static final int DELAY_TO_WAIT_FOR_UNDERLINE = 200; // The message is posted with a 100 ms delay 604 public void testBlueUnderline() { 605 final String STRING_TO_TYPE = "tgis"; 606 final int EXPECTED_SPAN_START = 0; 607 final int EXPECTED_SPAN_END = 4; 608 type(STRING_TO_TYPE); 609 sleep(DELAY_TO_WAIT_FOR_UNDERLINE); 610 runMessages(); 611 final Span span = new Span(mTextView.getText()); 612 assertEquals("show blue underline, span start", EXPECTED_SPAN_START, span.mStart); 613 assertEquals("show blue underline, span end", EXPECTED_SPAN_END, span.mEnd); 614 assertEquals("show blue underline, span color", true, span.isAutoCorrectionIndicator()); 615 } 616 617 public void testBlueUnderlineDisappears() { 618 final String STRING_1_TO_TYPE = "tgis"; 619 final String STRING_2_TO_TYPE = "q"; 620 final int EXPECTED_SPAN_START = 0; 621 final int EXPECTED_SPAN_END = 5; 622 type(STRING_1_TO_TYPE); 623 sleep(DELAY_TO_WAIT_FOR_UNDERLINE); 624 runMessages(); 625 type(STRING_2_TO_TYPE); 626 // We haven't have time to look into the dictionary yet, so the line should still be 627 // blue to avoid any flicker. 628 final Span spanBefore = new Span(mTextView.getText()); 629 assertEquals("extend blue underline, span start", EXPECTED_SPAN_START, spanBefore.mStart); 630 assertEquals("extend blue underline, span end", EXPECTED_SPAN_END, spanBefore.mEnd); 631 assertEquals("extend blue underline, span color", true, 632 spanBefore.isAutoCorrectionIndicator()); 633 sleep(DELAY_TO_WAIT_FOR_UNDERLINE); 634 runMessages(); 635 // Now we have been able to re-evaluate the word, there shouldn't be an auto-correction span 636 final Span spanAfter = new Span(mTextView.getText()); 637 assertNull("hide blue underline", spanAfter.mSpan); 638 } 639 640 public void testBlueUnderlineOnBackspace() { 641 final String STRING_TO_TYPE = "tgis"; 642 final int EXPECTED_SPAN_START = 0; 643 final int EXPECTED_SPAN_END = 4; 644 type(STRING_TO_TYPE); 645 sleep(DELAY_TO_WAIT_FOR_UNDERLINE); 646 runMessages(); 647 type(Keyboard.CODE_SPACE); 648 sleep(DELAY_TO_WAIT_FOR_UNDERLINE); 649 runMessages(); 650 type(Keyboard.CODE_DELETE); 651 sleep(DELAY_TO_WAIT_FOR_UNDERLINE); 652 runMessages(); 653 type(Keyboard.CODE_DELETE); 654 sleep(DELAY_TO_WAIT_FOR_UNDERLINE); 655 runMessages(); 656 final Span span = new Span(mTextView.getText()); 657 assertEquals("show blue underline after backspace, span start", 658 EXPECTED_SPAN_START, span.mStart); 659 assertEquals("show blue underline after backspace, span end", 660 EXPECTED_SPAN_END, span.mEnd); 661 assertEquals("show blue underline after backspace, span color", true, 662 span.isAutoCorrectionIndicator()); 663 } 664 665 public void testBlueUnderlineDisappearsWhenCursorMoved() { 666 final String STRING_TO_TYPE = "tgis"; 667 final int NEW_CURSOR_POSITION = 0; 668 type(STRING_TO_TYPE); 669 sleep(DELAY_TO_WAIT_FOR_UNDERLINE); 670 // Simulate the onUpdateSelection() event 671 mLatinIME.onUpdateSelection(0, 0, STRING_TO_TYPE.length(), STRING_TO_TYPE.length(), -1, -1); 672 runMessages(); 673 // Here the blue underline has been set. testBlueUnderline() is testing for this already, 674 // so let's not test it here again. 675 // Now simulate the user moving the cursor. 676 mInputConnection.setSelection(NEW_CURSOR_POSITION, NEW_CURSOR_POSITION); 677 mLatinIME.onUpdateSelection(0, 0, NEW_CURSOR_POSITION, NEW_CURSOR_POSITION, -1, -1); 678 sleep(DELAY_TO_WAIT_FOR_UNDERLINE); 679 runMessages(); 680 final Span span = new Span(mTextView.getText()); 681 assertNull("blue underline removed when cursor is moved", span.mSpan); 682 } 683 // TODO: Add some tests for non-BMP characters 684} 685