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