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