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