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