Home | History | Annotate | Download | only in autocomplete
      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