1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.inputmethod.latin; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertFalse; 21 import static org.junit.Assert.assertTrue; 22 23 import android.content.res.Resources; 24 import android.inputmethodservice.InputMethodService; 25 import android.os.Parcel; 26 import android.text.SpannableString; 27 import android.text.TextUtils; 28 import android.text.style.SuggestionSpan; 29 import android.view.inputmethod.ExtractedText; 30 import android.view.inputmethod.ExtractedTextRequest; 31 import android.view.inputmethod.InputConnection; 32 import android.view.inputmethod.InputConnectionWrapper; 33 34 import androidx.test.InstrumentationRegistry; 35 import androidx.test.filters.SmallTest; 36 import androidx.test.runner.AndroidJUnit4; 37 38 import com.android.inputmethod.latin.common.Constants; 39 import com.android.inputmethod.latin.common.StringUtils; 40 import com.android.inputmethod.latin.settings.SpacingAndPunctuations; 41 import com.android.inputmethod.latin.utils.NgramContextUtils; 42 import com.android.inputmethod.latin.utils.RunInLocale; 43 import com.android.inputmethod.latin.utils.ScriptUtils; 44 import com.android.inputmethod.latin.utils.TextRange; 45 46 import org.junit.Before; 47 import org.junit.Test; 48 import org.junit.runner.RunWith; 49 50 import java.util.Locale; 51 52 @SmallTest 53 @RunWith(AndroidJUnit4.class) 54 public class RichInputConnectionAndTextRangeTests { 55 56 // The following is meant to be a reasonable default for 57 // the "word_separators" resource. 58 private SpacingAndPunctuations mSpacingAndPunctuations; 59 60 @Before setUp()61 public void setUp() throws Exception { 62 final RunInLocale<SpacingAndPunctuations> job = new RunInLocale<SpacingAndPunctuations>() { 63 @Override 64 protected SpacingAndPunctuations job(final Resources res) { 65 return new SpacingAndPunctuations(res); 66 } 67 }; 68 final Resources res = InstrumentationRegistry.getTargetContext().getResources(); 69 mSpacingAndPunctuations = job.runInLocale(res, Locale.ENGLISH); 70 } 71 72 private class MockConnection extends InputConnectionWrapper { 73 final CharSequence mTextBefore; 74 final CharSequence mTextAfter; 75 final ExtractedText mExtractedText; 76 MockConnection(final CharSequence text, final int cursorPosition)77 public MockConnection(final CharSequence text, final int cursorPosition) { 78 super(null, false); 79 // Interaction of spans with Parcels is completely non-trivial, but in the actual case 80 // the CharSequences do go through Parcels because they go through IPC. There 81 // are some significant differences between the behavior of Spanned objects that 82 // have and that have not gone through parceling, so it's much easier to simulate 83 // the environment with Parcels than try to emulate things by hand. 84 final Parcel p = Parcel.obtain(); 85 TextUtils.writeToParcel(text.subSequence(0, cursorPosition), p, 0 /* flags */); 86 TextUtils.writeToParcel(text.subSequence(cursorPosition, text.length()), p, 87 0 /* flags */); 88 final byte[] marshalled = p.marshall(); 89 p.unmarshall(marshalled, 0, marshalled.length); 90 p.setDataPosition(0); 91 mTextBefore = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p); 92 mTextAfter = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p); 93 mExtractedText = null; 94 p.recycle(); 95 } 96 MockConnection(String textBefore, String textAfter, ExtractedText extractedText)97 public MockConnection(String textBefore, String textAfter, ExtractedText extractedText) { 98 super(null, false); 99 mTextBefore = textBefore; 100 mTextAfter = textAfter; 101 mExtractedText = extractedText; 102 } 103 cursorPos()104 public int cursorPos() { 105 return mTextBefore.length(); 106 } 107 108 /* (non-Javadoc) 109 * @see android.view.inputmethod.InputConnectionWrapper#getTextBeforeCursor(int, int) 110 */ 111 @Override getTextBeforeCursor(int n, int flags)112 public CharSequence getTextBeforeCursor(int n, int flags) { 113 return mTextBefore; 114 } 115 116 /* (non-Javadoc) 117 * @see android.view.inputmethod.InputConnectionWrapper#getTextAfterCursor(int, int) 118 */ 119 @Override getTextAfterCursor(int n, int flags)120 public CharSequence getTextAfterCursor(int n, int flags) { 121 return mTextAfter; 122 } 123 124 /* (non-Javadoc) 125 * @see android.view.inputmethod.InputConnectionWrapper#getExtractedText( 126 * ExtractedTextRequest, int) 127 */ 128 @Override getExtractedText(ExtractedTextRequest request, int flags)129 public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { 130 return mExtractedText; 131 } 132 133 @Override beginBatchEdit()134 public boolean beginBatchEdit() { 135 return true; 136 } 137 138 @Override endBatchEdit()139 public boolean endBatchEdit() { 140 return true; 141 } 142 143 @Override finishComposingText()144 public boolean finishComposingText() { 145 return true; 146 } 147 } 148 149 static class MockInputMethodService extends InputMethodService { 150 private MockConnection mMockConnection; setInputConnection(final MockConnection mockConnection)151 public void setInputConnection(final MockConnection mockConnection) { 152 mMockConnection = mockConnection; 153 } cursorPos()154 public int cursorPos() { 155 return mMockConnection.cursorPos(); 156 } 157 @Override getCurrentInputConnection()158 public InputConnection getCurrentInputConnection() { 159 return mMockConnection; 160 } 161 } 162 163 /************************** Tests ************************/ 164 165 /** 166 * Test for getting previous word (for bigram suggestions) 167 */ 168 @Test testGetPreviousWord()169 public void testGetPreviousWord() { 170 // If one of the following cases breaks, the bigram suggestions won't work. 171 assertEquals(NgramContextUtils.getNgramContextFromNthPreviousWord( 172 "abc def", mSpacingAndPunctuations, 2).getNthPrevWord(1), "abc"); 173 assertEquals(NgramContextUtils.getNgramContextFromNthPreviousWord( 174 "abc", mSpacingAndPunctuations, 2), NgramContext.BEGINNING_OF_SENTENCE); 175 assertEquals(NgramContextUtils.getNgramContextFromNthPreviousWord( 176 "abc. def", mSpacingAndPunctuations, 2), NgramContext.BEGINNING_OF_SENTENCE); 177 178 assertFalse(NgramContextUtils.getNgramContextFromNthPreviousWord( 179 "abc def", mSpacingAndPunctuations, 2).isBeginningOfSentenceContext()); 180 assertTrue(NgramContextUtils.getNgramContextFromNthPreviousWord( 181 "abc", mSpacingAndPunctuations, 2).isBeginningOfSentenceContext()); 182 183 // For n-gram 184 assertEquals(NgramContextUtils.getNgramContextFromNthPreviousWord( 185 "abc def", mSpacingAndPunctuations, 1).getNthPrevWord(1), "def"); 186 assertEquals(NgramContextUtils.getNgramContextFromNthPreviousWord( 187 "abc def", mSpacingAndPunctuations, 1).getNthPrevWord(2), "abc"); 188 assertTrue(NgramContextUtils.getNgramContextFromNthPreviousWord( 189 "abc def", mSpacingAndPunctuations, 2).isNthPrevWordBeginningOfSentence(2)); 190 191 // The following tests reflect the current behavior of the function 192 // RichInputConnection#getNthPreviousWord. 193 // TODO: However at this time, the code does never go 194 // into such a path, so it should be safe to change the behavior of 195 // this function if needed - especially since it does not seem very 196 // logical. These tests are just there to catch any unintentional 197 // changes in the behavior of the RichInputConnection#getPreviousWord method. 198 assertEquals(NgramContextUtils.getNgramContextFromNthPreviousWord( 199 "abc def ", mSpacingAndPunctuations, 2).getNthPrevWord(1), "abc"); 200 assertEquals(NgramContextUtils.getNgramContextFromNthPreviousWord( 201 "abc def.", mSpacingAndPunctuations, 2).getNthPrevWord(1), "abc"); 202 assertEquals(NgramContextUtils.getNgramContextFromNthPreviousWord( 203 "abc def .", mSpacingAndPunctuations, 2).getNthPrevWord(1), "def"); 204 assertTrue(NgramContextUtils.getNgramContextFromNthPreviousWord( 205 "abc ", mSpacingAndPunctuations, 2).isBeginningOfSentenceContext()); 206 207 assertEquals(NgramContextUtils.getNgramContextFromNthPreviousWord( 208 "abc def", mSpacingAndPunctuations, 1).getNthPrevWord(1), "def"); 209 assertEquals(NgramContextUtils.getNgramContextFromNthPreviousWord( 210 "abc def ", mSpacingAndPunctuations, 1).getNthPrevWord(1), "def"); 211 assertEquals(NgramContextUtils.getNgramContextFromNthPreviousWord( 212 "abc 'def", mSpacingAndPunctuations, 1).getNthPrevWord(1), "'def"); 213 assertEquals(NgramContextUtils.getNgramContextFromNthPreviousWord( 214 "abc def.", mSpacingAndPunctuations, 1), NgramContext.BEGINNING_OF_SENTENCE); 215 assertEquals(NgramContextUtils.getNgramContextFromNthPreviousWord( 216 "abc def .", mSpacingAndPunctuations, 1), NgramContext.BEGINNING_OF_SENTENCE); 217 assertEquals(NgramContextUtils.getNgramContextFromNthPreviousWord( 218 "abc, def", mSpacingAndPunctuations, 2), NgramContext.EMPTY_PREV_WORDS_INFO); 219 // question mark is treated as the end of the sentence. Hence, beginning of the 220 // sentence is expected. 221 assertEquals(NgramContextUtils.getNgramContextFromNthPreviousWord( 222 "abc? def", mSpacingAndPunctuations, 2), NgramContext.BEGINNING_OF_SENTENCE); 223 // Exclamation mark is treated as the end of the sentence. Hence, beginning of the 224 // sentence is expected. 225 assertEquals(NgramContextUtils.getNgramContextFromNthPreviousWord( 226 "abc! def", mSpacingAndPunctuations, 2), NgramContext.BEGINNING_OF_SENTENCE); 227 assertEquals(NgramContextUtils.getNgramContextFromNthPreviousWord( 228 "abc 'def", mSpacingAndPunctuations, 2), NgramContext.EMPTY_PREV_WORDS_INFO); 229 } 230 231 @Test testGetWordRangeAtCursor()232 public void testGetWordRangeAtCursor() { 233 /** 234 * Test logic in getting the word range at the cursor. 235 */ 236 final SpacingAndPunctuations SPACE = new SpacingAndPunctuations( 237 mSpacingAndPunctuations, new int[] { Constants.CODE_SPACE }); 238 final SpacingAndPunctuations TAB = new SpacingAndPunctuations( 239 mSpacingAndPunctuations, new int[] { Constants.CODE_TAB }); 240 // A character that needs surrogate pair to represent its code point (U+2008A). 241 final String SUPPLEMENTARY_CHAR_STRING = "\uD840\uDC8A"; 242 final SpacingAndPunctuations SUPPLEMENTARY_CHAR = new SpacingAndPunctuations( 243 mSpacingAndPunctuations, StringUtils.toSortedCodePointArray( 244 SUPPLEMENTARY_CHAR_STRING)); 245 final String HIRAGANA_WORD = "\u3042\u3044\u3046\u3048\u304A"; // あいうえお 246 final String GREEK_WORD = "\u03BA\u03B1\u03B9"; // και 247 248 ExtractedText et = new ExtractedText(); 249 final MockInputMethodService mockInputMethodService = new MockInputMethodService(); 250 final RichInputConnection ic = new RichInputConnection(mockInputMethodService); 251 mockInputMethodService.setInputConnection(new MockConnection("word wo", "rd", et)); 252 et.startOffset = 0; 253 et.selectionStart = 7; 254 TextRange r; 255 256 ic.beginBatchEdit(); 257 // basic case 258 r = ic.getWordRangeAtCursor(SPACE, ScriptUtils.SCRIPT_LATIN); 259 assertTrue(TextUtils.equals("word", r.mWord)); 260 261 // tab character instead of space 262 mockInputMethodService.setInputConnection(new MockConnection("one\tword\two", "rd", et)); 263 ic.beginBatchEdit(); 264 r = ic.getWordRangeAtCursor(TAB, ScriptUtils.SCRIPT_LATIN); 265 ic.endBatchEdit(); 266 assertTrue(TextUtils.equals("word", r.mWord)); 267 268 // splitting on supplementary character 269 mockInputMethodService.setInputConnection( 270 new MockConnection("one word" + SUPPLEMENTARY_CHAR_STRING + "wo", "rd", et)); 271 ic.beginBatchEdit(); 272 r = ic.getWordRangeAtCursor(SUPPLEMENTARY_CHAR, ScriptUtils.SCRIPT_LATIN); 273 ic.endBatchEdit(); 274 assertTrue(TextUtils.equals("word", r.mWord)); 275 276 // split on chars outside the specified script 277 mockInputMethodService.setInputConnection( 278 new MockConnection(HIRAGANA_WORD + "wo", "rd" + GREEK_WORD, et)); 279 ic.beginBatchEdit(); 280 r = ic.getWordRangeAtCursor(SUPPLEMENTARY_CHAR, ScriptUtils.SCRIPT_LATIN); 281 ic.endBatchEdit(); 282 assertTrue(TextUtils.equals("word", r.mWord)); 283 284 // likewise for greek 285 mockInputMethodService.setInputConnection( 286 new MockConnection("text" + GREEK_WORD, "text", et)); 287 ic.beginBatchEdit(); 288 r = ic.getWordRangeAtCursor(SUPPLEMENTARY_CHAR, ScriptUtils.SCRIPT_GREEK); 289 ic.endBatchEdit(); 290 assertTrue(TextUtils.equals(GREEK_WORD, r.mWord)); 291 } 292 293 /** 294 * Test logic in getting the word range at the cursor. 295 */ 296 @Test testGetSuggestionSpansAtWord()297 public void testGetSuggestionSpansAtWord() { 298 helpTestGetSuggestionSpansAtWord(10); 299 helpTestGetSuggestionSpansAtWord(12); 300 helpTestGetSuggestionSpansAtWord(15); 301 helpTestGetSuggestionSpansAtWord(16); 302 } 303 helpTestGetSuggestionSpansAtWord(final int cursorPos)304 private void helpTestGetSuggestionSpansAtWord(final int cursorPos) { 305 final SpacingAndPunctuations SPACE = new SpacingAndPunctuations( 306 mSpacingAndPunctuations, new int[] { Constants.CODE_SPACE }); 307 final MockInputMethodService mockInputMethodService = new MockInputMethodService(); 308 final RichInputConnection ic = new RichInputConnection(mockInputMethodService); 309 310 final String[] SUGGESTIONS1 = { "swing", "strong" }; 311 final String[] SUGGESTIONS2 = { "storing", "strung" }; 312 313 // Test the usual case. 314 SpannableString text = new SpannableString("This is a string for test"); 315 text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS1, 0 /* flags */), 316 10 /* start */, 16 /* end */, 0 /* flags */); 317 mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos)); 318 TextRange r; 319 SuggestionSpan[] suggestions; 320 321 r = ic.getWordRangeAtCursor(SPACE, ScriptUtils.SCRIPT_LATIN); 322 suggestions = r.getSuggestionSpansAtWord(); 323 assertEquals(suggestions.length, 1); 324 assertEquals(suggestions[0].getSuggestions(), SUGGESTIONS1); 325 326 // Test the case with 2 suggestion spans in the same place. 327 text = new SpannableString("This is a string for test"); 328 text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS1, 0 /* flags */), 329 10 /* start */, 16 /* end */, 0 /* flags */); 330 text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS2, 0 /* flags */), 331 10 /* start */, 16 /* end */, 0 /* flags */); 332 mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos)); 333 r = ic.getWordRangeAtCursor(SPACE, ScriptUtils.SCRIPT_LATIN); 334 suggestions = r.getSuggestionSpansAtWord(); 335 assertEquals(suggestions.length, 2); 336 assertEquals(suggestions[0].getSuggestions(), SUGGESTIONS1); 337 assertEquals(suggestions[1].getSuggestions(), SUGGESTIONS2); 338 339 // Test a case with overlapping spans, 2nd extending past the start of the word 340 text = new SpannableString("This is a string for test"); 341 text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS1, 0 /* flags */), 342 10 /* start */, 16 /* end */, 0 /* flags */); 343 text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS2, 0 /* flags */), 344 5 /* start */, 16 /* end */, 0 /* flags */); 345 mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos)); 346 r = ic.getWordRangeAtCursor(SPACE, ScriptUtils.SCRIPT_LATIN); 347 suggestions = r.getSuggestionSpansAtWord(); 348 assertEquals(suggestions.length, 1); 349 assertEquals(suggestions[0].getSuggestions(), SUGGESTIONS1); 350 351 // Test a case with overlapping spans, 2nd extending past the end of the word 352 text = new SpannableString("This is a string for test"); 353 text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS1, 0 /* flags */), 354 10 /* start */, 16 /* end */, 0 /* flags */); 355 text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS2, 0 /* flags */), 356 10 /* start */, 20 /* end */, 0 /* flags */); 357 mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos)); 358 r = ic.getWordRangeAtCursor(SPACE, ScriptUtils.SCRIPT_LATIN); 359 suggestions = r.getSuggestionSpansAtWord(); 360 assertEquals(suggestions.length, 1); 361 assertEquals(suggestions[0].getSuggestions(), SUGGESTIONS1); 362 363 // Test a case with overlapping spans, 2nd extending past both ends of the word 364 text = new SpannableString("This is a string for test"); 365 text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS1, 0 /* flags */), 366 10 /* start */, 16 /* end */, 0 /* flags */); 367 text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS2, 0 /* flags */), 368 5 /* start */, 20 /* end */, 0 /* flags */); 369 mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos)); 370 r = ic.getWordRangeAtCursor(SPACE, ScriptUtils.SCRIPT_LATIN); 371 suggestions = r.getSuggestionSpansAtWord(); 372 assertEquals(suggestions.length, 1); 373 assertEquals(suggestions[0].getSuggestions(), SUGGESTIONS1); 374 375 // Test a case with overlapping spans, none right on the word 376 text = new SpannableString("This is a string for test"); 377 text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS1, 0 /* flags */), 378 5 /* start */, 16 /* end */, 0 /* flags */); 379 text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS2, 0 /* flags */), 380 5 /* start */, 20 /* end */, 0 /* flags */); 381 mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos)); 382 r = ic.getWordRangeAtCursor(SPACE, ScriptUtils.SCRIPT_LATIN); 383 suggestions = r.getSuggestionSpansAtWord(); 384 assertEquals(suggestions.length, 0); 385 } 386 387 @Test testCursorTouchingWord()388 public void testCursorTouchingWord() { 389 final MockInputMethodService ims = new MockInputMethodService(); 390 final RichInputConnection ic = new RichInputConnection(ims); 391 final SpacingAndPunctuations sap = mSpacingAndPunctuations; 392 393 ims.setInputConnection(new MockConnection("users", 5)); 394 ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); 395 assertTrue(ic.isCursorTouchingWord(sap, true /* checkTextAfter */)); 396 397 ims.setInputConnection(new MockConnection("users'", 5)); 398 ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); 399 assertTrue(ic.isCursorTouchingWord(sap, true)); 400 401 ims.setInputConnection(new MockConnection("users'", 6)); 402 ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); 403 assertTrue(ic.isCursorTouchingWord(sap, true)); 404 405 ims.setInputConnection(new MockConnection("'users'", 6)); 406 ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); 407 assertTrue(ic.isCursorTouchingWord(sap, true)); 408 409 ims.setInputConnection(new MockConnection("'users'", 7)); 410 ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); 411 assertTrue(ic.isCursorTouchingWord(sap, true)); 412 413 ims.setInputConnection(new MockConnection("users '", 6)); 414 ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); 415 assertFalse(ic.isCursorTouchingWord(sap, true)); 416 417 ims.setInputConnection(new MockConnection("users '", 7)); 418 ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); 419 assertFalse(ic.isCursorTouchingWord(sap, true)); 420 421 ims.setInputConnection(new MockConnection("re-", 3)); 422 ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); 423 assertTrue(ic.isCursorTouchingWord(sap, true)); 424 425 ims.setInputConnection(new MockConnection("re--", 4)); 426 ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); 427 assertFalse(ic.isCursorTouchingWord(sap, true)); 428 429 ims.setInputConnection(new MockConnection("-", 1)); 430 ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); 431 assertFalse(ic.isCursorTouchingWord(sap, true)); 432 433 ims.setInputConnection(new MockConnection("--", 2)); 434 ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); 435 assertFalse(ic.isCursorTouchingWord(sap, true)); 436 437 ims.setInputConnection(new MockConnection(" -", 2)); 438 ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); 439 assertFalse(ic.isCursorTouchingWord(sap, true)); 440 441 ims.setInputConnection(new MockConnection(" --", 3)); 442 ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); 443 assertFalse(ic.isCursorTouchingWord(sap, true)); 444 445 ims.setInputConnection(new MockConnection(" users '", 1)); 446 ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); 447 assertTrue(ic.isCursorTouchingWord(sap, true)); 448 449 ims.setInputConnection(new MockConnection(" users '", 3)); 450 ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); 451 assertTrue(ic.isCursorTouchingWord(sap, true)); 452 453 ims.setInputConnection(new MockConnection(" users '", 7)); 454 ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); 455 assertFalse(ic.isCursorTouchingWord(sap, true)); 456 457 ims.setInputConnection(new MockConnection(" users are", 7)); 458 ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); 459 assertTrue(ic.isCursorTouchingWord(sap, true)); 460 461 ims.setInputConnection(new MockConnection(" users 'are", 7)); 462 ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true); 463 assertFalse(ic.isCursorTouchingWord(sap, true)); 464 } 465 } 466