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 
     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