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