1 /* 2 * Copyright (C) 2011 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 package com.android.browser.autocomplete; 17 18 import com.android.browser.autocomplete.SuggestedTextController.TextOwner; 19 20 import android.graphics.Color; 21 import android.os.Parcelable; 22 import android.test.AndroidTestCase; 23 import android.test.suitebuilder.annotation.SmallTest; 24 import android.text.Editable; 25 import android.text.Selection; 26 import android.text.Spannable; 27 import android.text.SpannableStringBuilder; 28 import android.text.TextWatcher; 29 import android.view.AbsSavedState; 30 31 /** 32 * Test cases for {@link SuggestedTextController}. 33 */ 34 @SmallTest 35 public class SuggestedTextControllerTest extends AndroidTestCase { 36 37 // these two must have a common prefix (but not be identical): 38 private static final String RUBY_MURRAY = "ruby murray"; 39 private static final String RUBY_TUESDAY = "ruby tuesday"; 40 private static final String EXTRA_USER_TEXT = " curry"; 41 // no common prefix with the top two above: 42 private static final String TOD_SLOAN = "tod sloan"; 43 44 private SuggestedTextController mController; 45 private SpannableStringBuilder mString; 46 47 private SuggestedTextController m2ndController; 48 private SpannableStringBuilder m2ndString; 49 50 @Override 51 public void setUp() throws Exception { 52 super.setUp(); 53 mString = new SpannableStringBuilder(); 54 Selection.setSelection(mString, 0); // position cursor 55 mController = new SuggestedTextController(new BufferTextOwner(mString), Color.GRAY); 56 checkInvariant(); 57 } 58 59 private void create2ndController() { 60 m2ndString = new SpannableStringBuilder(); 61 Selection.setSelection(m2ndString, 0); // position cursor 62 m2ndController = new SuggestedTextController(new BufferTextOwner(m2ndString), Color.GRAY); 63 check2ndInvariant(); 64 } 65 66 private int cursorPos(Spannable string) { 67 int selStart = Selection.getSelectionStart(string); 68 int selEnd = Selection.getSelectionEnd(string); 69 assertEquals("Selection has non-zero length", selStart, selEnd); 70 return selEnd; 71 } 72 73 private int cursorPos() { 74 return cursorPos(mString); 75 } 76 77 private void insertAtCursor(String text) { 78 mString.insert(cursorPos(), text); 79 checkInvariant(); 80 } 81 82 private void insertAtCursor(char ch) { 83 insertAtCursor(Character.toString(ch)); 84 } 85 86 private void insertAt2ndCursor(String text) { 87 m2ndString.insert(cursorPos(m2ndString), text); 88 check2ndInvariant(); 89 } 90 91 private void insertAt2ndCursor(char ch) { 92 insertAt2ndCursor(Character.toString(ch)); 93 } 94 95 private void deleteBeforeCursor(int count) { 96 int pos = cursorPos(); 97 count = Math.min(pos, count); 98 mString.delete(pos - count, pos); 99 checkInvariant(); 100 } 101 102 private void replaceSelection(String withThis) { 103 mString.replace(Selection.getSelectionStart(mString), 104 Selection.getSelectionEnd(mString), withThis); 105 checkInvariant(); 106 } 107 108 private void setSuggested(String suggested) { 109 mController.setSuggestedText(suggested); 110 checkInvariant(); 111 } 112 113 private void set2ndSuggested(String suggested) { 114 m2ndController.setSuggestedText(suggested); 115 check2ndInvariant(); 116 } 117 118 private void checkInvariant() { 119 mController.checkInvariant(mString); 120 } 121 122 private void check2ndInvariant() { 123 m2ndController.checkInvariant(m2ndString); 124 } 125 126 private void assertUserEntered(String expected, SuggestedTextController controller) { 127 assertEquals("User entered text not as expected", expected, controller.getUserText()); 128 } 129 130 private void assertUserEntered(String expected) { 131 assertUserEntered(expected, mController); 132 } 133 134 private void assertBuffer(String expected, Editable string) { 135 assertEquals("Buffer contents not as expected", expected, string.toString()); 136 } 137 138 private void assertBuffer(String expected) { 139 assertBuffer(expected, mString); 140 } 141 142 private void assertCursorPos(int where, Spannable string) { 143 assertEquals("Cursor not at expected position", where, cursorPos(string)); 144 } 145 146 private void assertCursorPos(int where) { 147 assertCursorPos(where, mString); 148 } 149 150 private static final String commonPrefix(String a, String b) { 151 int pos = 0; 152 while (a.charAt(pos) == b.charAt(pos)) { 153 pos++; 154 } 155 assertTrue("No common prefix between '" + a + "' and '" + b + "'", pos > 0); 156 return a.substring(0, pos); 157 } 158 159 public void testTypeNoSuggested() { 160 for (int i = 0; i < RUBY_MURRAY.length(); ++i) { 161 assertCursorPos(i); 162 assertUserEntered(RUBY_MURRAY.substring(0, i)); 163 assertBuffer(RUBY_MURRAY.substring(0, i)); 164 insertAtCursor(RUBY_MURRAY.substring(i, i + 1)); 165 } 166 } 167 168 public void testTypeSuggested() { 169 setSuggested(RUBY_MURRAY); 170 assertCursorPos(0); 171 assertBuffer(RUBY_MURRAY); 172 for (int i = 0; i < RUBY_MURRAY.length(); ++i) { 173 assertCursorPos(i); 174 assertUserEntered(RUBY_MURRAY.substring(0, i)); 175 assertBuffer(RUBY_MURRAY); 176 insertAtCursor(RUBY_MURRAY.charAt(i)); 177 } 178 } 179 180 public void testSetSuggestedAfterTextEntry() { 181 final int count = RUBY_MURRAY.length() / 2; 182 for (int i = 0; i < count; ++i) { 183 assertCursorPos(i); 184 assertUserEntered(RUBY_MURRAY.substring(0, i)); 185 insertAtCursor(RUBY_MURRAY.substring(i, i + 1)); 186 } 187 setSuggested(RUBY_MURRAY); 188 assertUserEntered(RUBY_MURRAY.substring(0, count)); 189 assertBuffer(RUBY_MURRAY); 190 } 191 192 public void testTypeSuggestedUpperCase() { 193 setSuggested(RUBY_MURRAY); 194 assertCursorPos(0); 195 for (int i = 0; i < RUBY_MURRAY.length(); ++i) { 196 assertCursorPos(i); 197 assertUserEntered(RUBY_MURRAY.substring(0, i).toUpperCase()); 198 assertTrue("Buffer doesn't contain suggested text", 199 RUBY_MURRAY.equalsIgnoreCase(mString.toString())); 200 insertAtCursor(Character.toUpperCase(RUBY_MURRAY.charAt(i))); 201 } 202 } 203 204 public void testChangeSuggestedText() { 205 String pref = commonPrefix(RUBY_MURRAY, RUBY_TUESDAY); 206 setSuggested(RUBY_MURRAY); 207 insertAtCursor(pref); 208 assertBuffer(RUBY_MURRAY); 209 assertUserEntered(pref); 210 setSuggested(RUBY_TUESDAY); 211 assertBuffer(RUBY_TUESDAY); 212 assertUserEntered(pref); 213 } 214 215 public void testTypeNonSuggested() { 216 setSuggested(RUBY_MURRAY); 217 insertAtCursor(RUBY_MURRAY.charAt(0)); 218 assertBuffer(RUBY_MURRAY); 219 insertAtCursor('x'); 220 assertBuffer("rx"); 221 } 222 223 public void testTypeNonSuggestedThenNewSuggestion() { 224 final String pref = commonPrefix(RUBY_MURRAY, RUBY_TUESDAY); 225 setSuggested(RUBY_MURRAY); 226 assertCursorPos(0); 227 insertAtCursor(pref); 228 assertCursorPos(pref.length()); 229 assertUserEntered(pref); 230 insertAtCursor(RUBY_TUESDAY.charAt(pref.length())); 231 assertBuffer(RUBY_TUESDAY.substring(0, pref.length() + 1)); 232 setSuggested(RUBY_TUESDAY); 233 assertBuffer(RUBY_TUESDAY); 234 } 235 236 public void testChangeSuggestedToNonUserEntered() { 237 final String half = RUBY_MURRAY.substring(0, RUBY_MURRAY.length() / 2); 238 setSuggested(RUBY_MURRAY); 239 insertAtCursor(half); 240 setSuggested(TOD_SLOAN); 241 assertUserEntered(half); 242 assertBuffer(half); 243 } 244 245 public void testChangeSuggestedToUserEntered() { 246 setSuggested(RUBY_MURRAY); 247 insertAtCursor(TOD_SLOAN); 248 setSuggested(TOD_SLOAN); 249 assertUserEntered(TOD_SLOAN); 250 assertBuffer(TOD_SLOAN); 251 } 252 253 public void testChangeSuggestedToEmpty() { 254 final String half = RUBY_MURRAY.substring(0, RUBY_MURRAY.length() / 2); 255 setSuggested(RUBY_MURRAY); 256 insertAtCursor(half); 257 setSuggested(null); 258 assertUserEntered(half); 259 assertBuffer(half); 260 } 261 262 public void testChangeSuggestedToEmptyFromUserEntered() { 263 setSuggested(RUBY_MURRAY); 264 insertAtCursor(RUBY_MURRAY); 265 setSuggested(null); 266 assertUserEntered(RUBY_MURRAY); 267 assertBuffer(RUBY_MURRAY); 268 } 269 270 public void typeNonSuggestedThenDelete() { 271 final String half = RUBY_MURRAY.substring(0, RUBY_MURRAY.length() / 2); 272 assertCursorPos(0); 273 insertAtCursor(half); 274 assertCursorPos(half.length()); 275 setSuggested(RUBY_MURRAY); 276 insertAtCursor('x'); 277 assertBuffer(half + "x"); 278 deleteBeforeCursor(1); 279 assertUserEntered(half); 280 assertBuffer(RUBY_MURRAY); 281 } 282 283 public void testDeleteMultipleFromSuggested() { 284 final String twoThirds = RUBY_MURRAY.substring(0, (RUBY_MURRAY.length() * 2) / 3); 285 setSuggested(RUBY_MURRAY); 286 insertAtCursor(twoThirds); 287 assertCursorPos(twoThirds.length()); 288 // select some of the text just entered: 289 Selection.setSelection(mString, RUBY_MURRAY.length() / 3, twoThirds.length()); 290 // and delete it: 291 replaceSelection(""); 292 assertCursorPos(RUBY_MURRAY.length() / 3); 293 assertUserEntered(RUBY_MURRAY.substring(0, RUBY_MURRAY.length() / 3)); 294 assertBuffer(RUBY_MURRAY); 295 } 296 297 public void testDeleteMultipleToFormSuggested() { 298 final String pref = commonPrefix(RUBY_TUESDAY, RUBY_MURRAY); 299 final int extra = (RUBY_TUESDAY.length() - pref.length()) / 2; 300 setSuggested(RUBY_MURRAY); 301 insertAtCursor(RUBY_TUESDAY.substring(0, pref.length() + extra)); 302 assertCursorPos(pref.length() + extra); 303 // select and delete extra characters, leaving just prefix 304 Selection.setSelection(mString, pref.length(), pref.length() + extra); 305 replaceSelection(""); 306 assertCursorPos(pref.length()); 307 assertBuffer(RUBY_MURRAY); 308 assertUserEntered(pref); 309 } 310 311 public void testBackspaceWithinUserTextFromSuggested() { 312 StringBuffer half = new StringBuffer(RUBY_MURRAY.substring(0, RUBY_MURRAY.length() / 2)); 313 insertAtCursor(half.toString()); 314 int backSpaceFrom = half.length() / 2; 315 Selection.setSelection(mString, backSpaceFrom); 316 deleteBeforeCursor(1); 317 assertCursorPos(backSpaceFrom - 1); 318 half.delete(backSpaceFrom - 1, backSpaceFrom); 319 assertUserEntered(half.toString()); 320 assertBuffer(half.toString()); 321 } 322 323 public void testInsertWithinUserTextToFormSuggested() { 324 final String half = RUBY_MURRAY.substring(0, RUBY_MURRAY.length() / 2); 325 StringBuffer initial = new StringBuffer(half); 326 int pos = initial.length() / 2; 327 char toInsert = initial.charAt(pos); 328 initial.delete(pos, pos + 1); 329 insertAtCursor(initial.toString()); 330 setSuggested(RUBY_MURRAY); 331 assertUserEntered(initial.toString()); 332 assertBuffer(initial.toString()); 333 Selection.setSelection(mString, pos); 334 insertAtCursor(toInsert); 335 assertCursorPos(pos + 1); 336 assertUserEntered(half); 337 assertBuffer(RUBY_MURRAY); 338 } 339 340 public void testEnterTextBeyondSuggested() { 341 setSuggested(RUBY_MURRAY); 342 int i = RUBY_MURRAY.length() / 2; 343 insertAtCursor(RUBY_MURRAY.substring(0, i)); 344 String query = RUBY_MURRAY + EXTRA_USER_TEXT; 345 for (; i < query.length(); ++i) { 346 assertUserEntered(query.substring(0, i)); 347 if (i <= RUBY_MURRAY.length()) { 348 assertBuffer(RUBY_MURRAY); 349 } 350 insertAtCursor(query.charAt(i)); 351 } 352 assertUserEntered(query); 353 } 354 355 public void testDeleteFromLongerThanSuggested() { 356 setSuggested(RUBY_MURRAY); 357 final String entered = RUBY_MURRAY + EXTRA_USER_TEXT; 358 insertAtCursor(entered); 359 for (int i = entered.length(); i > (RUBY_MURRAY.length() / 2); --i) { 360 assertCursorPos(i); 361 assertUserEntered(entered.substring(0, i)); 362 if (i <= RUBY_MURRAY.length()) { 363 assertBuffer(RUBY_MURRAY); 364 } 365 deleteBeforeCursor(1); 366 } 367 } 368 369 public void testReplaceWithShorterToFormSuggested() { 370 final String pref = commonPrefix(RUBY_TUESDAY, RUBY_MURRAY); 371 final int extra = (RUBY_TUESDAY.length() - pref.length()) / 2; 372 setSuggested(RUBY_MURRAY); 373 insertAtCursor(RUBY_TUESDAY.substring(0, pref.length() + extra)); 374 assertCursorPos(pref.length() + extra); 375 // select and replace extra characters, to match suggested 376 Selection.setSelection(mString, pref.length(), pref.length() + extra); 377 replaceSelection(RUBY_MURRAY.substring(pref.length(), pref.length() + extra - 1)); 378 assertBuffer(RUBY_MURRAY); 379 assertUserEntered(RUBY_MURRAY.substring(0, pref.length() + extra - 1)); 380 } 381 382 public void testReplaceWithSameLengthToFormSuggested() { 383 final String pref = commonPrefix(RUBY_TUESDAY, RUBY_MURRAY); 384 final int extra = (RUBY_TUESDAY.length() - pref.length()) / 2; 385 setSuggested(RUBY_MURRAY); 386 insertAtCursor(RUBY_TUESDAY.substring(0, pref.length() + extra)); 387 assertCursorPos(pref.length() + extra); 388 // select and replace extra characters, to match suggested 389 Selection.setSelection(mString, pref.length(), pref.length() + extra); 390 replaceSelection(RUBY_MURRAY.substring(pref.length(), pref.length() + extra)); 391 assertBuffer(RUBY_MURRAY); 392 assertUserEntered(RUBY_MURRAY.substring(0, pref.length() + extra)); 393 } 394 395 public void testReplaceWithLongerToFormSuggested() { 396 final String pref = commonPrefix(RUBY_TUESDAY, RUBY_MURRAY); 397 final int extra = (RUBY_TUESDAY.length() - pref.length()) / 2; 398 setSuggested(RUBY_MURRAY); 399 insertAtCursor(RUBY_TUESDAY.substring(0, pref.length() + extra)); 400 assertCursorPos(pref.length() + extra); 401 // select and replace extra characters, to match suggested 402 Selection.setSelection(mString, pref.length(), pref.length() + extra); 403 replaceSelection(RUBY_MURRAY.substring(pref.length(), pref.length() + extra + 1)); 404 assertBuffer(RUBY_MURRAY); 405 assertUserEntered(RUBY_MURRAY.substring(0, pref.length() + extra + 1)); 406 } 407 408 public void testMoveCursorIntoSuggested() { 409 final String half = RUBY_MURRAY.substring(0, RUBY_MURRAY.length() / 2); 410 insertAtCursor(half); 411 setSuggested(RUBY_MURRAY); 412 assertCursorPos(half.length()); 413 Selection.setSelection(mString, half.length() + 1); 414 checkInvariant(); 415 assertUserEntered(RUBY_MURRAY); 416 } 417 418 public void testMoveCursorWithinUserEntered() { 419 final String half = RUBY_MURRAY.substring(0, RUBY_MURRAY.length() / 2); 420 insertAtCursor(half); 421 setSuggested(RUBY_MURRAY); 422 assertCursorPos(half.length()); 423 Selection.setSelection(mString, half.length() - 1); 424 checkInvariant(); 425 assertUserEntered(half); 426 } 427 428 public void testSelectWithinSuggested() { 429 final String half = RUBY_MURRAY.substring(0, RUBY_MURRAY.length() / 2); 430 insertAtCursor(half); 431 setSuggested(RUBY_MURRAY); 432 assertCursorPos(half.length()); 433 Selection.setSelection(mString, half.length() + 1, half.length() + 2); 434 checkInvariant(); 435 assertUserEntered(RUBY_MURRAY); 436 } 437 438 public void testSelectStraddlingSuggested() { 439 final String half = RUBY_MURRAY.substring(0, RUBY_MURRAY.length() / 2); 440 insertAtCursor(half); 441 setSuggested(RUBY_MURRAY); 442 assertCursorPos(half.length()); 443 Selection.setSelection(mString, half.length() - 1, half.length() + 1); 444 checkInvariant(); 445 assertUserEntered(RUBY_MURRAY); 446 } 447 448 public void testSaveAndRestoreNoText() { 449 create2ndController(); 450 Parcelable state = mController.saveInstanceState(AbsSavedState.EMPTY_STATE); 451 m2ndController.restoreInstanceState(state); 452 check2ndInvariant(); 453 assertBuffer("", m2ndString); 454 } 455 456 public void testSaveAndRestoreWithSuggestedText() { 457 create2ndController(); 458 setSuggested(TOD_SLOAN); 459 Parcelable state = mController.saveInstanceState(AbsSavedState.EMPTY_STATE); 460 m2ndController.restoreInstanceState(state); 461 check2ndInvariant(); 462 assertBuffer(TOD_SLOAN, m2ndString); 463 assertUserEntered("", m2ndController); 464 } 465 466 public void testSaveAndRestoreWithUserEnteredAndSuggestedText() { 467 final String half = TOD_SLOAN.substring(0, TOD_SLOAN.length() / 2); 468 create2ndController(); 469 setSuggested(TOD_SLOAN); 470 insertAtCursor(half); 471 Parcelable state = mController.saveInstanceState(AbsSavedState.EMPTY_STATE); 472 m2ndController.restoreInstanceState(state); 473 check2ndInvariant(); 474 assertBuffer(TOD_SLOAN, m2ndString); 475 assertUserEntered(half, m2ndController); 476 assertCursorPos(half.length(), m2ndString); 477 } 478 479 public void testSaveAndRestoreWithNonSuggested() { 480 final String half = TOD_SLOAN.substring(0, TOD_SLOAN.length() / 2); 481 create2ndController(); 482 setSuggested(RUBY_MURRAY); 483 insertAtCursor(half); 484 Parcelable state = mController.saveInstanceState(AbsSavedState.EMPTY_STATE); 485 m2ndController.restoreInstanceState(state); 486 check2ndInvariant(); 487 assertBuffer(half, m2ndString); 488 assertUserEntered(half, m2ndController); 489 assertCursorPos(half.length(), m2ndString); 490 } 491 492 public void testSaveAndRestoreThenTypeSuggested() { 493 final String half = TOD_SLOAN.substring(0, TOD_SLOAN.length() / 2); 494 create2ndController(); 495 set2ndSuggested(TOD_SLOAN); 496 insertAt2ndCursor(half); 497 insertAt2ndCursor('x'); 498 Parcelable state = m2ndController.saveInstanceState(AbsSavedState.EMPTY_STATE); 499 mController.restoreInstanceState(state); 500 assertCursorPos(half.length() + 1); 501 // delete the x 502 deleteBeforeCursor(1); 503 assertCursorPos(half.length()); 504 assertBuffer(TOD_SLOAN); 505 assertUserEntered(half); 506 } 507 508 public void testSuspendAndResumeCursorProcessing() { 509 final String half = TOD_SLOAN.substring(0, TOD_SLOAN.length() / 2); 510 setSuggested(TOD_SLOAN); 511 insertAtCursor(half); 512 mController.suspendCursorMovementHandling(); 513 Selection.setSelection(mString, TOD_SLOAN.length()); 514 Selection.setSelection(mString, half.length()); 515 mController.resumeCursorMovementHandlingAndApplyChanges(); 516 assertCursorPos(half.length()); 517 assertUserEntered(half); 518 assertBuffer(TOD_SLOAN); 519 } 520 521 private static class BufferTextOwner implements TextOwner { 522 523 private final Editable mBuffer; 524 525 public BufferTextOwner(Editable buffer) { 526 mBuffer = buffer; 527 } 528 529 public void addTextChangedListener(TextWatcher watcher) { 530 mBuffer.setSpan(watcher , 0, mBuffer.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE); 531 } 532 533 public void removeTextChangedListener(TextWatcher watcher) { 534 mBuffer.removeSpan(watcher); 535 } 536 537 public Editable getText() { 538 return mBuffer; 539 } 540 541 public void setText(String text) { 542 mBuffer.replace(0, mBuffer.length(), text); 543 } 544 545 } 546 547 } 548