Home | History | Annotate | Download | only in testutils
      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 androidx.appcompat.testutils;
     18 
     19 import android.database.sqlite.SQLiteCursor;
     20 import android.graphics.Bitmap;
     21 import android.graphics.drawable.Drawable;
     22 import android.support.test.espresso.matcher.BoundedMatcher;
     23 import android.text.TextUtils;
     24 import android.view.View;
     25 import android.view.ViewGroup;
     26 import android.widget.CheckedTextView;
     27 import android.widget.ImageView;
     28 
     29 import androidx.annotation.ColorInt;
     30 import androidx.core.view.TintableBackgroundView;
     31 
     32 import org.hamcrest.Description;
     33 import org.hamcrest.Matcher;
     34 import org.hamcrest.TypeSafeMatcher;
     35 
     36 import java.util.List;
     37 
     38 public class TestUtilsMatchers {
     39     /**
     40      * Returns a matcher that matches <code>ImageView</code>s which have drawable flat-filled
     41      * with the specific color.
     42      */
     43     public static Matcher drawable(@ColorInt final int color) {
     44         return new BoundedMatcher<View, ImageView>(ImageView.class) {
     45             private String failedComparisonDescription;
     46 
     47             @Override
     48             public void describeTo(final Description description) {
     49                 description.appendText("with drawable of color: ");
     50 
     51                 description.appendText(failedComparisonDescription);
     52             }
     53 
     54             @Override
     55             public boolean matchesSafely(final ImageView view) {
     56                 Drawable drawable = view.getDrawable();
     57                 if (drawable == null) {
     58                     return false;
     59                 }
     60 
     61                 // One option is to check if we have a ColorDrawable and then call getColor
     62                 // but that API is v11+. Instead, we call our helper method that checks whether
     63                 // all pixels in a Drawable are of the same specified color.
     64                 try {
     65                     TestUtils.assertAllPixelsOfColor("", drawable, view.getWidth(),
     66                             view.getHeight(), true, color, 0, true);
     67                     // If we are here, the color comparison has passed.
     68                     failedComparisonDescription = null;
     69                     return true;
     70                 } catch (Throwable t) {
     71                     // If we are here, the color comparison has failed.
     72                     failedComparisonDescription = t.getMessage();
     73                     return false;
     74                 }
     75             }
     76         };
     77     }
     78 
     79     /**
     80      * Returns a matcher that matches <code>View</code>s which have background flat-filled
     81      * with the specific color.
     82      */
     83     public static Matcher isBackground(@ColorInt final int color) {
     84         return isBackground(color, false);
     85     }
     86 
     87     /**
     88      * Returns a matcher that matches <code>View</code>s which have background flat-filled
     89      * with the specific color.
     90      */
     91     public static Matcher isBackground(@ColorInt final int color, final boolean onlyTestCenter) {
     92         return new BoundedMatcher<View, View>(View.class) {
     93             private String failedComparisonDescription;
     94 
     95             @Override
     96             public void describeTo(final Description description) {
     97                 description.appendText("with background of color: ");
     98 
     99                 description.appendText(failedComparisonDescription);
    100             }
    101 
    102             @Override
    103             public boolean matchesSafely(final View view) {
    104                 Drawable drawable = view.getBackground();
    105                 if (drawable == null) {
    106                     return false;
    107                 }
    108                 try {
    109                     if (onlyTestCenter) {
    110                         TestUtils.assertCenterPixelOfColor("", drawable, view.getWidth(),
    111                                 view.getHeight(), false, color, 0, true);
    112                     } else {
    113                         TestUtils.assertAllPixelsOfColor("", drawable, view.getWidth(),
    114                                 view.getHeight(), false, color, 0, true);
    115                     }
    116                     // If we are here, the color comparison has passed.
    117                     failedComparisonDescription = null;
    118                     return true;
    119                 } catch (Throwable t) {
    120                     // If we are here, the color comparison has failed.
    121                     failedComparisonDescription = t.getMessage();
    122                     return false;
    123                 }
    124             }
    125         };
    126     }
    127 
    128     /**
    129      * Returns a matcher that matches <code>View</code>s whose combined background starting
    130      * from the view and up its ancestor chain matches the specified color.
    131      */
    132     public static Matcher isCombinedBackground(@ColorInt final int color,
    133             final boolean onlyTestCenterPixel) {
    134         return new BoundedMatcher<View, View>(View.class) {
    135             private String failedComparisonDescription;
    136 
    137             @Override
    138             public void describeTo(final Description description) {
    139                 description.appendText("with ascendant background of color: ");
    140 
    141                 description.appendText(failedComparisonDescription);
    142             }
    143 
    144             @Override
    145             public boolean matchesSafely(View view) {
    146                 // Create a bitmap with combined backgrounds of the view and its ancestors.
    147                 Bitmap combinedBackgroundBitmap = TestUtils.getCombinedBackgroundBitmap(view);
    148                 try {
    149                     if (onlyTestCenterPixel) {
    150                         TestUtils.assertCenterPixelOfColor("", combinedBackgroundBitmap,
    151                                 color, 0, true);
    152                     } else {
    153                         TestUtils.assertAllPixelsOfColor("", combinedBackgroundBitmap,
    154                                 combinedBackgroundBitmap.getWidth(),
    155                                 combinedBackgroundBitmap.getHeight(), color, 0, true);
    156                     }
    157                     // If we are here, the color comparison has passed.
    158                     failedComparisonDescription = null;
    159                     return true;
    160                 } catch (Throwable t) {
    161                     failedComparisonDescription = t.getMessage();
    162                     return false;
    163                 } finally {
    164                     combinedBackgroundBitmap.recycle();
    165                 }
    166             }
    167         };
    168     }
    169 
    170     /**
    171      * Returns a matcher that matches <code>CheckedTextView</code>s which are in checked state.
    172      */
    173     public static Matcher isCheckedTextView() {
    174         return new BoundedMatcher<View, CheckedTextView>(CheckedTextView.class) {
    175             private String failedDescription;
    176 
    177             @Override
    178             public void describeTo(final Description description) {
    179                 description.appendText("checked text view: ");
    180 
    181                 description.appendText(failedDescription);
    182             }
    183 
    184             @Override
    185             public boolean matchesSafely(final CheckedTextView view) {
    186                 if (view.isChecked()) {
    187                     return true;
    188                 }
    189 
    190                 failedDescription = "not checked";
    191                 return false;
    192             }
    193         };
    194     }
    195 
    196     /**
    197      * Returns a matcher that matches <code>CheckedTextView</code>s which are in checked state.
    198      */
    199     public static Matcher isNonCheckedTextView() {
    200         return new BoundedMatcher<View, CheckedTextView>(CheckedTextView.class) {
    201             private String failedDescription;
    202 
    203             @Override
    204             public void describeTo(final Description description) {
    205                 description.appendText("non checked text view: ");
    206 
    207                 description.appendText(failedDescription);
    208             }
    209 
    210             @Override
    211             public boolean matchesSafely(final CheckedTextView view) {
    212                 if (!view.isChecked()) {
    213                     return true;
    214                 }
    215 
    216                 failedDescription = "checked";
    217                 return false;
    218             }
    219         };
    220     }
    221 
    222     /**
    223      * Returns a matcher that matches data entry in <code>SQLiteCursor</code> that has
    224      * the specified text in the specified column.
    225      */
    226     public static Matcher<Object> withCursorItemContent(final String columnName,
    227             final String expectedText) {
    228         return new BoundedMatcher<Object, SQLiteCursor>(SQLiteCursor.class) {
    229             @Override
    230             public void describeTo(Description description) {
    231                 description.appendText("doesn't match " + expectedText);
    232             }
    233 
    234             @Override
    235             protected boolean matchesSafely(SQLiteCursor cursor) {
    236                 return TextUtils.equals(expectedText,
    237                         cursor.getString(cursor.getColumnIndex(columnName)));
    238             }
    239         };
    240     }
    241 
    242     /**
    243      * Returns a matcher that matches Views which implement TintableBackgroundView.
    244      */
    245     public static Matcher<View> isTintableBackgroundView() {
    246         return new TypeSafeMatcher<View>() {
    247             @Override
    248             public void describeTo(Description description) {
    249                 description.appendText("is TintableBackgroundView");
    250             }
    251 
    252             @Override
    253             public boolean matchesSafely(View view) {
    254                 return TintableBackgroundView.class.isAssignableFrom(view.getClass());
    255             }
    256         };
    257     }
    258 
    259     /**
    260      * Returns a matcher that matches lists of float values that fall into the specified range.
    261      */
    262     public static Matcher<List<Float>> inRange(final float from, final float to) {
    263         return new TypeSafeMatcher<List<Float>>() {
    264             private String mFailedDescription;
    265 
    266             @Override
    267             public void describeTo(Description description) {
    268                 description.appendText(mFailedDescription);
    269             }
    270 
    271             @Override
    272             protected boolean matchesSafely(List<Float> item) {
    273                 int itemCount = item.size();
    274 
    275                 for (int i = 0; i < itemCount; i++) {
    276                     float curr = item.get(i);
    277 
    278                     if ((curr < from) || (curr > to)) {
    279                         mFailedDescription = "Value #" + i + ":" + curr + " should be between " +
    280                                 from + " and " + to;
    281                         return false;
    282                     }
    283                 }
    284 
    285                 return true;
    286             }
    287         };
    288     }
    289 
    290     /**
    291      * Returns a matcher that matches lists of float values that are in ascending order.
    292      */
    293     public static Matcher<List<Float>> inAscendingOrder() {
    294         return new TypeSafeMatcher<List<Float>>() {
    295             private String mFailedDescription;
    296 
    297             @Override
    298             public void describeTo(Description description) {
    299                 description.appendText(mFailedDescription);
    300             }
    301 
    302             @Override
    303             protected boolean matchesSafely(List<Float> item) {
    304                 int itemCount = item.size();
    305 
    306                 if (itemCount >= 2) {
    307                     for (int i = 0; i < itemCount - 1; i++) {
    308                         float curr = item.get(i);
    309                         float next = item.get(i + 1);
    310 
    311                         if (curr > next) {
    312                             mFailedDescription = "Values should increase between #" + i +
    313                                     ":" + curr + " and #" + (i + 1) + ":" + next;
    314                             return false;
    315                         }
    316                     }
    317                 }
    318 
    319                 return true;
    320             }
    321         };
    322     }
    323 
    324     /**
    325      * Returns a matcher that matches lists of float values that are in descending order.
    326      */
    327     public static Matcher<List<Float>> inDescendingOrder() {
    328         return new TypeSafeMatcher<List<Float>>() {
    329             private String mFailedDescription;
    330 
    331             @Override
    332             public void describeTo(Description description) {
    333                 description.appendText(mFailedDescription);
    334             }
    335 
    336             @Override
    337             protected boolean matchesSafely(List<Float> item) {
    338                 int itemCount = item.size();
    339 
    340                 if (itemCount >= 2) {
    341                     for (int i = 0; i < itemCount - 1; i++) {
    342                         float curr = item.get(i);
    343                         float next = item.get(i + 1);
    344 
    345                         if (curr < next) {
    346                             mFailedDescription = "Values should decrease between #" + i +
    347                                     ":" + curr + " and #" + (i + 1) + ":" + next;
    348                             return false;
    349                         }
    350                     }
    351                 }
    352 
    353                 return true;
    354             }
    355         };
    356     }
    357 
    358 
    359     /**
    360      * Returns a matcher that matches {@link View}s based on the given child type.
    361      *
    362      * @param childMatcher the type of the child to match on
    363      */
    364     public static Matcher<ViewGroup> hasChild(final Matcher<View> childMatcher) {
    365         return new TypeSafeMatcher<ViewGroup>() {
    366             @Override
    367             public void describeTo(Description description) {
    368                 description.appendText("has child: ");
    369                 childMatcher.describeTo(description);
    370             }
    371 
    372             @Override
    373             public boolean matchesSafely(ViewGroup view) {
    374                 final int childCount = view.getChildCount();
    375                 for (int i = 0; i < childCount; i++) {
    376                     if (childMatcher.matches(view.getChildAt(i))) {
    377                         return true;
    378                     }
    379                 }
    380                 return false;
    381             }
    382         };
    383     }
    384 
    385 }
    386