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 17package android.support.v4.testutils; 18 19import java.lang.String; 20import java.util.List; 21 22import android.graphics.Color; 23import android.graphics.drawable.Drawable; 24import android.support.annotation.ColorInt; 25import android.support.test.espresso.matcher.BoundedMatcher; 26import android.support.v4.testutils.TestUtils; 27import android.support.v4.view.ViewCompat; 28import android.view.View; 29import android.view.ViewGroup; 30import android.view.ViewParent; 31 32import junit.framework.Assert; 33 34import org.hamcrest.Description; 35import org.hamcrest.Matcher; 36import org.hamcrest.TypeSafeMatcher; 37 38public class TestUtilsMatchers { 39 /** 40 * Returns a matcher that matches views which have specific background color. 41 */ 42 public static Matcher backgroundColor(@ColorInt final int backgroundColor) { 43 return new BoundedMatcher<View, View>(View.class) { 44 private String failedComparisonDescription; 45 46 @Override 47 public void describeTo(final Description description) { 48 description.appendText("with background color: "); 49 50 description.appendText(failedComparisonDescription); 51 } 52 53 @Override 54 public boolean matchesSafely(final View view) { 55 Drawable actualBackgroundDrawable = view.getBackground(); 56 if (actualBackgroundDrawable == null) { 57 return false; 58 } 59 60 // One option is to check if we have a ColorDrawable and then call getColor 61 // but that API is v11+. Instead, we call our helper method that checks whether 62 // all pixels in a Drawable are of the same specified color. Here we pass 63 // hard-coded dimensions of 40x40 since we can't rely on the intrinsic dimensions 64 // being set on our drawable. 65 try { 66 TestUtils.assertAllPixelsOfColor("", actualBackgroundDrawable, 67 40, 40, backgroundColor, true); 68 // If we are here, the color comparison has passed. 69 failedComparisonDescription = null; 70 return true; 71 } catch (Throwable t) { 72 // If we are here, the color comparison has failed. 73 failedComparisonDescription = t.getMessage(); 74 return false; 75 } 76 } 77 }; 78 } 79 80 /** 81 * Returns a matcher that matches Views which are an instance of the provided class. 82 */ 83 public static Matcher<View> isOfClass(final Class<? extends View> clazz) { 84 if (clazz == null) { 85 Assert.fail("Passed null Class instance"); 86 } 87 return new TypeSafeMatcher<View>() { 88 @Override 89 public void describeTo(Description description) { 90 description.appendText("is identical to class: " + clazz); 91 } 92 93 @Override 94 public boolean matchesSafely(View view) { 95 return clazz.equals(view.getClass()); 96 } 97 }; 98 } 99 100 /** 101 * Returns a matcher that matches Views that are aligned to the left / start edge of 102 * their parent. 103 */ 104 public static Matcher<View> startAlignedToParent() { 105 return new BoundedMatcher<View, View>(View.class) { 106 private String failedCheckDescription; 107 108 @Override 109 public void describeTo(final Description description) { 110 description.appendText(failedCheckDescription); 111 } 112 113 @Override 114 public boolean matchesSafely(final View view) { 115 final ViewParent parent = view.getParent(); 116 if (!(parent instanceof ViewGroup)) { 117 return false; 118 } 119 final ViewGroup parentGroup = (ViewGroup) parent; 120 121 final int parentLayoutDirection = ViewCompat.getLayoutDirection(parentGroup); 122 if (parentLayoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) { 123 if (view.getLeft() == 0) { 124 return true; 125 } else { 126 failedCheckDescription = 127 "not aligned to start (left) edge of parent : left=" + 128 view.getLeft(); 129 return false; 130 } 131 } else { 132 if (view.getRight() == parentGroup.getWidth()) { 133 return true; 134 } else { 135 failedCheckDescription = 136 "not aligned to start (right) edge of parent : right=" + 137 view.getRight() + ", parent width=" + 138 parentGroup.getWidth(); 139 return false; 140 } 141 } 142 } 143 }; 144 } 145 146 /** 147 * Returns a matcher that matches Views that are aligned to the right / end edge of 148 * their parent. 149 */ 150 public static Matcher<View> endAlignedToParent() { 151 return new BoundedMatcher<View, View>(View.class) { 152 private String failedCheckDescription; 153 154 @Override 155 public void describeTo(final Description description) { 156 description.appendText(failedCheckDescription); 157 } 158 159 @Override 160 public boolean matchesSafely(final View view) { 161 final ViewParent parent = view.getParent(); 162 if (!(parent instanceof ViewGroup)) { 163 return false; 164 } 165 final ViewGroup parentGroup = (ViewGroup) parent; 166 167 final int parentLayoutDirection = ViewCompat.getLayoutDirection(parentGroup); 168 if (parentLayoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) { 169 if (view.getRight() == parentGroup.getWidth()) { 170 return true; 171 } else { 172 failedCheckDescription = 173 "not aligned to end (right) edge of parent : right=" + 174 view.getRight() + ", parent width=" + 175 parentGroup.getWidth(); 176 return false; 177 } 178 } else { 179 if (view.getLeft() == 0) { 180 return true; 181 } else { 182 failedCheckDescription = 183 "not aligned to end (left) edge of parent : left=" + 184 view.getLeft(); 185 return false; 186 } 187 } 188 } 189 }; 190 } 191 192 /** 193 * Returns a matcher that matches Views that are centered horizontally in their parent. 194 */ 195 public static Matcher<View> centerAlignedInParent() { 196 return new BoundedMatcher<View, View>(View.class) { 197 private String failedCheckDescription; 198 199 @Override 200 public void describeTo(final Description description) { 201 description.appendText(failedCheckDescription); 202 } 203 204 @Override 205 public boolean matchesSafely(final View view) { 206 final ViewParent parent = view.getParent(); 207 if (!(parent instanceof ViewGroup)) { 208 return false; 209 } 210 final ViewGroup parentGroup = (ViewGroup) parent; 211 212 final int viewLeft = view.getLeft(); 213 final int viewRight = view.getRight(); 214 final int parentWidth = parentGroup.getWidth(); 215 216 final int viewMiddle = (viewLeft + viewRight) / 2; 217 final int parentMiddle = parentWidth / 2; 218 219 // Check that the view is centered in its parent, accounting for off-by-one 220 // pixel difference in case one is even and the other is odd. 221 if (Math.abs(viewMiddle - parentMiddle) > 1) { 222 failedCheckDescription = 223 "not aligned to center of parent : own span=[" + 224 viewLeft + "-" + viewRight + "], parent width=" + parentWidth; 225 return false; 226 } 227 228 return true; 229 } 230 }; 231 } 232 233 /** 234 * Returns a matcher that matches lists of integer values that match the specified sequence 235 * of values. 236 */ 237 public static Matcher<List<Integer>> matches(final int ... expectedValues) { 238 return new TypeSafeMatcher<List<Integer>>() { 239 private String mFailedDescription; 240 241 @Override 242 public void describeTo(Description description) { 243 description.appendText(mFailedDescription); 244 } 245 246 @Override 247 protected boolean matchesSafely(List<Integer> item) { 248 int actualCount = item.size(); 249 int expectedCount = expectedValues.length; 250 251 if (actualCount != expectedCount) { 252 mFailedDescription = "Expected " + expectedCount + " values, but got " + 253 actualCount; 254 return false; 255 } 256 257 for (int i = 0; i < expectedCount; i++) { 258 int curr = item.get(i); 259 260 if (curr != expectedValues[i]) { 261 mFailedDescription = "At #" + i + " got " + curr + " but should be " + 262 expectedValues[i]; 263 return false; 264 } 265 } 266 267 return true; 268 } 269 }; 270 } 271 272} 273