1/* 2 * Copyright (C) 2014 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 com.google.android.apps.common.testing.ui.espresso.assertion; 18 19import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.assertThat; 20import static com.google.android.apps.common.testing.ui.espresso.util.TreeIterables.breadthFirstViewTraversal; 21import static com.google.common.base.Preconditions.checkNotNull; 22import static org.hamcrest.Matchers.is; 23 24import com.google.android.apps.common.testing.ui.espresso.NoMatchingViewException; 25import com.google.android.apps.common.testing.ui.espresso.ViewAssertion; 26import com.google.android.apps.common.testing.ui.espresso.util.HumanReadables; 27import com.google.common.base.Preconditions; 28import com.google.common.base.Predicate; 29import com.google.common.collect.Iterables; 30 31import android.util.Log; 32import android.view.View; 33 34import junit.framework.AssertionFailedError; 35 36import org.hamcrest.Matcher; 37import org.hamcrest.StringDescription; 38 39import java.util.ArrayList; 40import java.util.Iterator; 41import java.util.List; 42 43/** 44 * A collection of common {@link ViewAssertion}s. 45 */ 46public final class ViewAssertions { 47 48 private static final String TAG = ViewAssertions.class.getSimpleName(); 49 50 51 private ViewAssertions() {} 52 53 /** 54 * Returns an assert that ensures the view matcher does not find any matching view in the 55 * hierarchy. 56 */ 57 public static ViewAssertion doesNotExist() { 58 return new ViewAssertion() { 59 @Override 60 public void check(View view, NoMatchingViewException noView) { 61 if (view != null) { 62 assertThat("View is present in the hierarchy: " + HumanReadables.describe(view), true, 63 is(false)); 64 } 65 } 66 }; 67 } 68 69 /** 70 * Returns a generic {@link ViewAssertion} that asserts that a view exists in the view hierarchy 71 * and is matched by the given view matcher. 72 */ 73 public static ViewAssertion matches(final Matcher<? super View> viewMatcher) { 74 checkNotNull(viewMatcher); 75 return new ViewAssertion() { 76 @Override 77 public void check(View view, NoMatchingViewException noViewException) { 78 StringDescription description = new StringDescription(); 79 description.appendText("'"); 80 viewMatcher.describeTo(description); 81 if (noViewException != null) { 82 description.appendText(String.format( 83 "' check could not be performed because view '%s' was not found.\n", viewMatcher)); 84 Log.e(TAG, description.toString()); 85 throw noViewException; 86 } else { 87 // TODO(valeraz): ideally, we should append the matcher used to find the view 88 // This can be done in DefaultFailureHandler (just like we currently to with 89 // PerformException) 90 description.appendText("' doesn't match the selected view."); 91 assertThat(description.toString(), view, viewMatcher); 92 } 93 } 94 }; 95 } 96 97 98 /** 99 * Returns a generic {@link ViewAssertion} that asserts that the descendant views selected by the 100 * selector match the specified matcher. 101 * 102 * Example: onView(rootView).check(selectedDescendantsMatch( 103 * not(isAssignableFrom(TextView.class)), hasContentDescription())); 104 */ 105 public static ViewAssertion selectedDescendantsMatch( 106 final Matcher<View> selector, final Matcher<View> matcher) { 107 return new ViewAssertion() { 108 @SuppressWarnings("unchecked") 109 @Override 110 public void check(View view, NoMatchingViewException noViewException) { 111 Preconditions.checkNotNull(view); 112 113 final Predicate<View> viewPredicate = new Predicate<View>() { 114 @Override 115 public boolean apply(View input) { 116 return selector.matches(input); 117 } 118 }; 119 120 Iterator<View> selectedViewIterator = 121 Iterables.filter(breadthFirstViewTraversal(view), viewPredicate).iterator(); 122 123 List<View> nonMatchingViews = new ArrayList<View>(); 124 while (selectedViewIterator.hasNext()) { 125 View selectedView = selectedViewIterator.next(); 126 127 if (!matcher.matches(selectedView)) { 128 nonMatchingViews.add(selectedView); 129 } 130 } 131 132 if (nonMatchingViews.size() > 0) { 133 String errorMessage = HumanReadables.getViewHierarchyErrorMessage(view, 134 nonMatchingViews, 135 String.format("At least one view did not match the required matcher: %s", matcher), 136 "****DOES NOT MATCH****"); 137 throw new AssertionFailedError(errorMessage); 138 } 139 } 140 }; 141 } 142} 143