• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2023 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.base.test.transit;
6 
7 import static androidx.test.espresso.Espresso.onView;
8 import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
9 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
10 
11 import static org.hamcrest.CoreMatchers.allOf;
12 import static org.hamcrest.CoreMatchers.any;
13 import static org.hamcrest.CoreMatchers.is;
14 
15 import android.content.res.Resources;
16 import android.view.View;
17 
18 import androidx.test.espresso.AmbiguousViewMatcherException;
19 import androidx.test.espresso.NoMatchingRootException;
20 import androidx.test.espresso.NoMatchingViewException;
21 import androidx.test.espresso.UiController;
22 import androidx.test.espresso.ViewAction;
23 import androidx.test.espresso.ViewInteraction;
24 import androidx.test.platform.app.InstrumentationRegistry;
25 
26 import org.hamcrest.Matcher;
27 import org.hamcrest.StringDescription;
28 
29 import java.util.ArrayList;
30 import java.util.regex.Pattern;
31 
32 /** {@link Condition}s related to Android {@link View}s. */
33 public class ViewConditions {
34     /** Fulfilled when a single matching View exists and is displayed. */
35     public static class DisplayedCondition extends ExistsCondition {
DisplayedCondition(Matcher<View> matcher)36         public DisplayedCondition(Matcher<View> matcher) {
37             super(allOf(matcher, isDisplayed()));
38         }
39     }
40 
41     /** Fulfilled when a single matching View exists. */
42     public static class ExistsCondition extends InstrumentationThreadCondition {
43         private final Matcher<View> mMatcher;
44         private View mViewMatched;
45 
ExistsCondition(Matcher<View> matcher)46         public ExistsCondition(Matcher<View> matcher) {
47             super();
48             this.mMatcher = matcher;
49         }
50 
51         @Override
buildDescription()52         public String buildDescription() {
53             return "View: " + ViewConditions.createMatcherDescription(mMatcher);
54         }
55 
56         @Override
check()57         public boolean check() {
58             ViewInteraction viewInteraction = onView(mMatcher);
59             try {
60                 viewInteraction.perform(
61                         new ViewAction() {
62                             @Override
63                             public Matcher<View> getConstraints() {
64                                 return any(View.class);
65                             }
66 
67                             @Override
68                             public String getDescription() {
69                                 return "check exists and consistent";
70                             }
71 
72                             @Override
73                             public void perform(UiController uiController, View view) {
74                                 if (mViewMatched != null && mViewMatched != view) {
75                                     throw new IllegalStateException(
76                                             String.format(
77                                                     "Matched a different view, was %s, now %s",
78                                                     mViewMatched, view));
79                                 }
80                                 mViewMatched = view;
81                             }
82                         });
83                 return true;
84             } catch (NoMatchingViewException
85                     | NoMatchingRootException
86                     | AmbiguousViewMatcherException e) {
87                 if (mViewMatched != null) {
88                     throw new IllegalStateException(
89                             String.format(
90                                     "Had matched a view (%s), but now got %s",
91                                     mViewMatched, e.getClass().getSimpleName()),
92                             e);
93                 }
94                 return false;
95             }
96         }
97 
getViewMatched()98         public View getViewMatched() {
99             return mViewMatched;
100         }
101     }
102 
103     /** Fulfilled when no matching Views exist. */
104     public static class DoesNotExistAnymoreCondition extends InstrumentationThreadCondition {
105         private final Matcher<View> mMatcher;
106         private Matcher<View> mStricterMatcher;
107         private final ExistsCondition mExistsCondition;
108 
DoesNotExistAnymoreCondition( Matcher<View> matcher, ExistsCondition existsCondition)109         public DoesNotExistAnymoreCondition(
110                 Matcher<View> matcher, ExistsCondition existsCondition) {
111             super();
112             mMatcher = matcher;
113             mExistsCondition = existsCondition;
114         }
115 
116         @Override
buildDescription()117         public String buildDescription() {
118             if (mStricterMatcher != null) {
119                 return "No more view: "
120                         + ViewConditions.createMatcherDescription(mMatcher)
121                         + " that exactly "
122                         + ViewConditions.createMatcherDescription(mStricterMatcher);
123             } else {
124                 return "No more view: " + ViewConditions.createMatcherDescription(mMatcher);
125             }
126         }
127 
128         @Override
check()129         public boolean check() {
130             Matcher<View> matcherToUse;
131             if (mStricterMatcher != null) {
132                 matcherToUse = mStricterMatcher;
133             } else if (mExistsCondition.getViewMatched() != null) {
134                 mStricterMatcher = is(mExistsCondition.getViewMatched());
135                 rebuildDescription();
136                 matcherToUse = mStricterMatcher;
137             } else {
138                 matcherToUse = mMatcher;
139             }
140 
141             try {
142                 onView(matcherToUse).check(doesNotExist());
143                 return true;
144             } catch (AssertionError e) {
145                 return false;
146             }
147         }
148     }
149 
getResourceName(int resId)150     private static String getResourceName(int resId) {
151         return InstrumentationRegistry.getInstrumentation()
152                 .getContext()
153                 .getResources()
154                 .getResourceName(resId);
155     }
156 
157     /** Generates a description for the matcher that replaces raw ids with resource names. */
createMatcherDescription(Matcher<View> matcher)158     private static String createMatcherDescription(Matcher<View> matcher) {
159         StringDescription d = new StringDescription();
160         matcher.describeTo(d);
161         String description = d.toString();
162         Pattern numberPattern = Pattern.compile("[0-9]+");
163         java.util.regex.Matcher numberMatcher = numberPattern.matcher(description);
164         ArrayList<Integer> starts = new ArrayList<>();
165         ArrayList<Integer> ends = new ArrayList<>();
166         ArrayList<String> resourceNames = new ArrayList<>();
167         while (numberMatcher.find()) {
168             int resourceId = Integer.parseInt(numberMatcher.group());
169             if (resourceId > 0xFFFFFF) {
170                 // Build-time Android resources have ids > 0xFFFFFF
171                 starts.add(numberMatcher.start());
172                 ends.add(numberMatcher.end());
173                 String resourceDescription = createResourceDescription(resourceId);
174                 resourceNames.add(resourceDescription);
175             } else {
176                 resourceNames.add(numberMatcher.group());
177             }
178         }
179 
180         if (starts.size() == 0) return description;
181 
182         String newDescription = description.substring(0, starts.get(0));
183         for (int i = 0; i < starts.size(); i++) {
184             newDescription += resourceNames.get(i);
185             int nextStart = (i == starts.size() - 1) ? description.length() : starts.get(i + 1);
186             newDescription += description.substring(ends.get(i), nextStart);
187         }
188 
189         return newDescription;
190     }
191 
createResourceDescription(int possibleResourceId)192     private static String createResourceDescription(int possibleResourceId) {
193         try {
194             return getResourceName(possibleResourceId);
195         } catch (Resources.NotFoundException e) {
196             return String.valueOf(possibleResourceId);
197         }
198     }
199 }
200