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