1 /* 2 * Copyright (C) 2015 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 android.widget.espresso; 18 19 import static android.support.test.espresso.matcher.ViewMatchers.assertThat; 20 import static com.android.internal.util.Preconditions.checkNotNull; 21 import static org.hamcrest.Matchers.is; 22 import static org.hamcrest.number.IsCloseTo.closeTo; 23 24 import android.annotation.IntDef; 25 import android.graphics.Rect; 26 import android.graphics.drawable.Drawable; 27 import android.support.test.espresso.NoMatchingViewException; 28 import android.support.test.espresso.ViewAssertion; 29 import android.text.Spanned; 30 import android.text.TextUtils; 31 import android.view.View; 32 import android.widget.EditText; 33 import android.widget.TextView; 34 35 import junit.framework.AssertionFailedError; 36 import org.hamcrest.Matcher; 37 38 import java.lang.annotation.Retention; 39 import java.lang.annotation.RetentionPolicy; 40 41 /** 42 * A collection of assertions on a {@link android.widget.TextView}. 43 */ 44 public final class TextViewAssertions { 45 46 private TextViewAssertions() {} 47 48 /** 49 * Returns a {@link ViewAssertion} that asserts that the text view has a specified 50 * selection.<br> 51 * <br> 52 * View constraints: 53 * <ul> 54 * <li>must be a text view displayed on screen 55 * <ul> 56 * 57 * @param selection The expected selection. 58 */ 59 public static ViewAssertion hasSelection(String selection) { 60 return hasSelection(is(selection)); 61 } 62 63 /** 64 * Returns a {@link ViewAssertion} that asserts that the text view has a specified 65 * selection.<br> 66 * <br> 67 * View constraints: 68 * <ul> 69 * <li>must be a text view displayed on screen 70 * <ul> 71 * 72 * @param selection A matcher representing the expected selection. 73 */ 74 public static ViewAssertion hasSelection(Matcher<String> selection) { 75 return new TextSelectionAssertion(selection); 76 } 77 78 /** 79 * Returns a {@link ViewAssertion} that asserts that the text view insertion pointer is at 80 * a specified index.<br> 81 * <br> 82 * View constraints: 83 * <ul> 84 * <li>must be a text view displayed on screen 85 * <ul> 86 * 87 * @param index The expected index. 88 */ 89 public static ViewAssertion hasInsertionPointerAtIndex(int index) { 90 return hasInsertionPointerAtIndex(is(index)); 91 } 92 93 /** 94 * Returns a {@link ViewAssertion} that asserts that the text view insertion pointer is at 95 * a specified index.<br> 96 * <br> 97 * View constraints: 98 * <ul> 99 * <li>must be a text view displayed on screen 100 * <ul> 101 * 102 * @param index A matcher representing the expected index. 103 */ 104 public static ViewAssertion hasInsertionPointerAtIndex(final Matcher<Integer> index) { 105 return (view, exception) -> { 106 if (view instanceof TextView) { 107 TextView textView = (TextView) view; 108 int selectionStart = textView.getSelectionStart(); 109 int selectionEnd = textView.getSelectionEnd(); 110 try { 111 assertThat(selectionStart, index); 112 assertThat(selectionEnd, index); 113 } catch (IndexOutOfBoundsException e) { 114 throw new AssertionFailedError(e.getMessage()); 115 } 116 } else { 117 throw new AssertionFailedError("TextView not found"); 118 } 119 }; 120 } 121 122 /** 123 * Returns a {@link ViewAssertion} that asserts that the EditText insertion pointer is on 124 * the left edge. 125 */ 126 public static ViewAssertion hasInsertionPointerOnLeft() { 127 return new CursorPositionAssertion(CursorPositionAssertion.LEFT); 128 } 129 130 /** 131 * Returns a {@link ViewAssertion} that asserts that the EditText insertion pointer is on 132 * the right edge. 133 */ 134 public static ViewAssertion hasInsertionPointerOnRight() { 135 return new CursorPositionAssertion(CursorPositionAssertion.RIGHT); 136 } 137 138 /** 139 * Returns a {@link ViewAssertion} that asserts that the TextView does not contain styled text. 140 */ 141 public static ViewAssertion doesNotHaveStyledText() { 142 return (view, exception) -> { 143 final CharSequence text = ((TextView) view).getText(); 144 if (text instanceof Spanned && !TextUtils.hasStyleSpan((Spanned) text)) { 145 return; 146 } 147 throw new AssertionFailedError("TextView has styled text"); 148 }; 149 } 150 151 /** 152 * A {@link ViewAssertion} to check the selected text in a {@link TextView}. 153 */ 154 private static final class TextSelectionAssertion implements ViewAssertion { 155 156 private final Matcher<String> mSelection; 157 158 public TextSelectionAssertion(Matcher<String> selection) { 159 mSelection = checkNotNull(selection); 160 } 161 162 @Override 163 public void check(View view, NoMatchingViewException exception) { 164 if (view instanceof TextView) { 165 TextView textView = (TextView) view; 166 int selectionStart = textView.getSelectionStart(); 167 int selectionEnd = textView.getSelectionEnd(); 168 try { 169 String selectedText = textView.getText() 170 .subSequence(selectionStart, selectionEnd) 171 .toString(); 172 assertThat(selectedText, mSelection); 173 } catch (IndexOutOfBoundsException e) { 174 throw new AssertionFailedError(e.getMessage()); 175 } 176 } else { 177 throw new AssertionFailedError("TextView not found"); 178 } 179 } 180 } 181 182 /** 183 * {@link ViewAssertion} to check that EditText cursor is on a given position. 184 */ 185 static class CursorPositionAssertion implements ViewAssertion { 186 187 @Retention(RetentionPolicy.SOURCE) 188 @IntDef({LEFT, RIGHT}) 189 public @interface CursorEdgePositionType {} 190 public static final int LEFT = 0; 191 public static final int RIGHT = 1; 192 193 private final int mPosition; 194 195 private CursorPositionAssertion(@CursorEdgePositionType int position) { 196 this.mPosition = position; 197 } 198 199 @Override 200 public void check(View view, NoMatchingViewException exception) { 201 if (!(view instanceof EditText)) { 202 throw new AssertionFailedError("View should be an instance of EditText"); 203 } 204 EditText editText = (EditText) view; 205 Drawable drawable = editText.getEditorForTesting().getCursorDrawable()[0]; 206 Rect drawableBounds = drawable.getBounds(); 207 Rect drawablePadding = new Rect(); 208 drawable.getPadding(drawablePadding); 209 210 final int diff; 211 final String positionStr; 212 switch (mPosition) { 213 case LEFT: 214 positionStr = "left"; 215 diff = drawableBounds.left - editText.getScrollX() + drawablePadding.left; 216 break; 217 case RIGHT: 218 positionStr = "right"; 219 int maxRight = editText.getWidth() - editText.getCompoundPaddingRight() 220 - editText.getCompoundPaddingLeft() + editText.getScrollX(); 221 diff = drawableBounds.right - drawablePadding.right - maxRight; 222 break; 223 default: 224 throw new AssertionFailedError("Unknown position for cursor assertion"); 225 } 226 227 assertThat("Cursor should be on the " + positionStr, Double.valueOf(diff), 228 closeTo(0f, 1f)); 229 } 230 } 231 } 232