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