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