| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.widget; |
| |
| import android.test.AndroidTestCase; |
| import android.test.suitebuilder.annotation.LargeTest; |
| import android.test.suitebuilder.annotation.Suppress; |
| import android.text.InputType; |
| import android.text.Selection; |
| import android.text.Spannable; |
| import android.text.SpannableString; |
| |
| import java.lang.reflect.Field; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| |
| /** |
| * TextViewPatchTest tests {@link TextView}'s definition of word. Finds and |
| * verifies word limits to be in strings containing different kinds of |
| * characters. |
| */ |
| @Suppress // Failing. |
| public class TextViewWordLimitsTest extends AndroidTestCase { |
| |
| TextView mTv = null; |
| Method mGetWordLimits, mSelectCurrentWord; |
| Field mContextMenuTriggeredByKey, mSelectionControllerEnabled; |
| |
| |
| /** |
| * Sets up common fields used in all test cases. |
| * @throws NoSuchFieldException |
| * @throws SecurityException |
| */ |
| @Override |
| protected void setUp() throws NoSuchMethodException, SecurityException, NoSuchFieldException { |
| mTv = new TextView(getContext()); |
| mTv.setInputType(InputType.TYPE_CLASS_TEXT); |
| |
| mGetWordLimits = mTv.getClass().getDeclaredMethod("getWordLimitsAt", |
| new Class[] {int.class}); |
| mGetWordLimits.setAccessible(true); |
| |
| mSelectCurrentWord = mTv.getClass().getDeclaredMethod("selectCurrentWord", new Class[] {}); |
| mSelectCurrentWord.setAccessible(true); |
| |
| mContextMenuTriggeredByKey = mTv.getClass().getDeclaredField("mContextMenuTriggeredByKey"); |
| mContextMenuTriggeredByKey.setAccessible(true); |
| mSelectionControllerEnabled = mTv.getClass().getDeclaredField("mSelectionControllerEnabled"); |
| mSelectionControllerEnabled.setAccessible(true); |
| } |
| |
| /** |
| * Calculate and verify word limits. Depends on the TextView implementation. |
| * Uses a private method and internal data representation. |
| * |
| * @param text Text to select a word from |
| * @param pos Position to expand word around |
| * @param correctStart Correct start position for the word |
| * @param correctEnd Correct end position for the word |
| * @throws InvocationTargetException |
| * @throws IllegalAccessException |
| * @throws IllegalArgumentException |
| * @throws InvocationTargetException |
| * @throws IllegalAccessException |
| */ |
| private void verifyWordLimits(String text, int pos, int correctStart, int correctEnd) |
| throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { |
| mTv.setText(text, TextView.BufferType.SPANNABLE); |
| |
| long limits = (Long)mGetWordLimits.invoke(mTv, new Object[] {new Integer(pos)}); |
| int actualStart = (int)(limits >>> 32); |
| int actualEnd = (int)(limits & 0x00000000FFFFFFFFL); |
| assertEquals(correctStart, actualStart); |
| assertEquals(correctEnd, actualEnd); |
| } |
| |
| |
| private void verifySelectCurrentWord(Spannable text, int selectionStart, int selectionEnd, int correctStart, |
| int correctEnd) throws InvocationTargetException, IllegalAccessException { |
| mTv.setText(text, TextView.BufferType.SPANNABLE); |
| |
| Selection.setSelection((Spannable)mTv.getText(), selectionStart, selectionEnd); |
| mContextMenuTriggeredByKey.setBoolean(mTv, true); |
| mSelectionControllerEnabled.setBoolean(mTv, true); |
| mSelectCurrentWord.invoke(mTv); |
| |
| assertEquals(correctStart, mTv.getSelectionStart()); |
| assertEquals(correctEnd, mTv.getSelectionEnd()); |
| } |
| |
| |
| /** |
| * Corner cases for string length. |
| */ |
| @LargeTest |
| public void testLengths() throws Exception { |
| final String ONE_TWO = "one two"; |
| final String EMPTY = ""; |
| final String TOOLONG = "ThisWordIsTooLongToBeDefinedAsAWordInTheSenseUsedInTextView"; |
| |
| // Select first word |
| verifyWordLimits(ONE_TWO, 0, 0, 3); |
| verifyWordLimits(ONE_TWO, 3, 0, 3); |
| |
| // Select last word |
| verifyWordLimits(ONE_TWO, 4, 4, 7); |
| verifyWordLimits(ONE_TWO, 7, 4, 7); |
| |
| // Empty string |
| verifyWordLimits(EMPTY, 0, -1, -1); |
| |
| // Too long word |
| verifyWordLimits(TOOLONG, 0, -1, -1); |
| } |
| |
| /** |
| * Unicode classes included. |
| */ |
| @LargeTest |
| public void testIncludedClasses() throws Exception { |
| final String LOWER = "njlj"; |
| final String UPPER = "NJLJ"; |
| final String TITLECASE = "\u01C8\u01CB\u01F2"; // Lj Nj Dz |
| final String OTHER = "\u3042\u3044\u3046"; // Hiragana AIU |
| final String MODIFIER = "\u02C6\u02CA\u02CB"; // Circumflex Acute Grave |
| |
| // Each string contains a single valid word |
| verifyWordLimits(LOWER, 1, 0, 4); |
| verifyWordLimits(UPPER, 1, 0, 4); |
| verifyWordLimits(TITLECASE, 1, 0, 3); |
| verifyWordLimits(OTHER, 1, 0, 3); |
| verifyWordLimits(MODIFIER, 1, 0, 3); |
| } |
| |
| /** |
| * Unicode classes included if combined with a letter. |
| */ |
| @LargeTest |
| public void testPartlyIncluded() throws Exception { |
| final String NUMBER = "123"; |
| final String NUMBER_LOWER = "1st"; |
| final String APOSTROPHE = "''"; |
| final String APOSTROPHE_LOWER = "'Android's'"; |
| |
| // Pure decimal number is ignored |
| verifyWordLimits(NUMBER, 1, -1, -1); |
| |
| // Number with letter is valid |
| verifyWordLimits(NUMBER_LOWER, 1, 0, 3); |
| |
| // Stand apostrophes are ignore |
| verifyWordLimits(APOSTROPHE, 1, -1, -1); |
| |
| // Apostrophes are accepted if they are a part of a word |
| verifyWordLimits(APOSTROPHE_LOWER, 1, 0, 11); |
| } |
| |
| /** |
| * Unicode classes included if combined with a letter. |
| */ |
| @LargeTest |
| public void testFinalSeparator() throws Exception { |
| final String PUNCTUATION = "abc, def."; |
| |
| // Starting from the comma |
| verifyWordLimits(PUNCTUATION, 3, 0, 3); |
| verifyWordLimits(PUNCTUATION, 4, 0, 4); |
| |
| // Starting from the final period |
| verifyWordLimits(PUNCTUATION, 8, 5, 8); |
| verifyWordLimits(PUNCTUATION, 9, 5, 9); |
| } |
| |
| /** |
| * Unicode classes other than listed in testIncludedClasses and |
| * testPartlyIncluded act as word separators. |
| */ |
| @LargeTest |
| public void testNotIncluded() throws Exception { |
| // Selection of character classes excluded |
| final String MARK_NONSPACING = "a\u030A"; // a Combining ring above |
| final String PUNCTUATION_OPEN_CLOSE = "(c)"; // Parenthesis |
| final String PUNCTUATION_DASH = "non-fiction"; // Hyphen |
| final String PUNCTUATION_OTHER = "b&b"; // Ampersand |
| final String SYMBOL_OTHER = "Android\u00AE"; // Registered |
| final String SEPARATOR_SPACE = "one two"; // Space |
| |
| // "a" |
| verifyWordLimits(MARK_NONSPACING, 1, 0, 1); |
| |
| // "c" |
| verifyWordLimits(PUNCTUATION_OPEN_CLOSE, 1, 1, 2); |
| |
| // "non-" |
| verifyWordLimits(PUNCTUATION_DASH, 3, 0, 3); |
| verifyWordLimits(PUNCTUATION_DASH, 4, 4, 11); |
| |
| // "b" |
| verifyWordLimits(PUNCTUATION_OTHER, 0, 0, 1); |
| verifyWordLimits(PUNCTUATION_OTHER, 1, 0, 1); |
| verifyWordLimits(PUNCTUATION_OTHER, 2, 0, 3); // & is considered a punctuation sign. |
| verifyWordLimits(PUNCTUATION_OTHER, 3, 2, 3); |
| |
| // "Android" |
| verifyWordLimits(SYMBOL_OTHER, 7, 0, 7); |
| verifyWordLimits(SYMBOL_OTHER, 8, -1, -1); |
| |
| // "one" |
| verifyWordLimits(SEPARATOR_SPACE, 1, 0, 3); |
| } |
| |
| /** |
| * Surrogate characters are treated as their code points. |
| */ |
| @LargeTest |
| public void testSurrogate() throws Exception { |
| final String SURROGATE_LETTER = "\uD800\uDC00\uD800\uDC01\uD800\uDC02"; // Linear B AEI |
| final String SURROGATE_SYMBOL = "\uD83D\uDE01\uD83D\uDE02\uD83D\uDE03"; // Three smileys |
| |
| // Letter Other is included even when coded as surrogate pairs |
| verifyWordLimits(SURROGATE_LETTER, 0, 0, 6); |
| verifyWordLimits(SURROGATE_LETTER, 1, 0, 6); |
| verifyWordLimits(SURROGATE_LETTER, 2, 0, 6); |
| verifyWordLimits(SURROGATE_LETTER, 3, 0, 6); |
| verifyWordLimits(SURROGATE_LETTER, 4, 0, 6); |
| verifyWordLimits(SURROGATE_LETTER, 5, 0, 6); |
| verifyWordLimits(SURROGATE_LETTER, 6, 0, 6); |
| |
| // Not included classes are ignored even when coded as surrogate pairs |
| verifyWordLimits(SURROGATE_SYMBOL, 0, -1, -1); |
| verifyWordLimits(SURROGATE_SYMBOL, 1, -1, -1); |
| verifyWordLimits(SURROGATE_SYMBOL, 2, -1, -1); |
| verifyWordLimits(SURROGATE_SYMBOL, 3, -1, -1); |
| verifyWordLimits(SURROGATE_SYMBOL, 4, -1, -1); |
| verifyWordLimits(SURROGATE_SYMBOL, 5, -1, -1); |
| verifyWordLimits(SURROGATE_SYMBOL, 6, -1, -1); |
| } |
| |
| /** |
| * Selection is used if present and valid word. |
| */ |
| @LargeTest |
| public void testSelectCurrentWord() throws Exception { |
| SpannableString textLower = new SpannableString("first second"); |
| SpannableString textOther = new SpannableString("\u3042\3044\3046"); // Hiragana AIU |
| SpannableString textDash = new SpannableString("non-fiction"); // Hyphen |
| SpannableString textPunctOther = new SpannableString("b&b"); // Ampersand |
| SpannableString textSymbolOther = new SpannableString("Android\u00AE"); // Registered |
| |
| // Valid selection - Letter, Lower |
| verifySelectCurrentWord(textLower, 2, 5, 0, 5); |
| |
| // Adding the space spreads to the second word |
| verifySelectCurrentWord(textLower, 2, 6, 0, 12); |
| |
| // Valid selection -- Letter, Other |
| verifySelectCurrentWord(textOther, 1, 2, 0, 5); |
| |
| // Zero-width selection is interpreted as a cursor and the selection is ignored |
| verifySelectCurrentWord(textLower, 2, 2, 0, 5); |
| |
| // Hyphen is part of selection |
| verifySelectCurrentWord(textDash, 2, 5, 0, 11); |
| |
| // Ampersand part of selection or not |
| verifySelectCurrentWord(textPunctOther, 1, 2, 0, 3); |
| verifySelectCurrentWord(textPunctOther, 1, 3, 0, 3); |
| |
| // (R) part of the selection |
| verifySelectCurrentWord(textSymbolOther, 2, 7, 0, 7); |
| verifySelectCurrentWord(textSymbolOther, 2, 8, 0, 8); |
| } |
| } |