• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.widget.espresso;
18 
19 import static androidx.test.espresso.Espresso.onView;
20 import static androidx.test.espresso.action.ViewActions.click;
21 import static androidx.test.espresso.assertion.ViewAssertions.matches;
22 import static androidx.test.espresso.matcher.RootMatchers.isPlatformPopup;
23 import static androidx.test.espresso.matcher.RootMatchers.withDecorView;
24 import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
25 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
26 import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
27 import static androidx.test.espresso.matcher.ViewMatchers.withId;
28 import static androidx.test.espresso.matcher.ViewMatchers.withTagValue;
29 import static androidx.test.espresso.matcher.ViewMatchers.withText;
30 
31 import static org.hamcrest.Matchers.allOf;
32 import static org.hamcrest.Matchers.is;
33 
34 import android.view.MenuItem;
35 import android.view.View;
36 import android.view.ViewGroup;
37 
38 import androidx.test.espresso.NoMatchingRootException;
39 import androidx.test.espresso.NoMatchingViewException;
40 import androidx.test.espresso.UiController;
41 import androidx.test.espresso.ViewAction;
42 import androidx.test.espresso.ViewInteraction;
43 
44 import com.android.internal.widget.FloatingToolbar;
45 
46 import org.hamcrest.Description;
47 import org.hamcrest.Matcher;
48 import org.hamcrest.TypeSafeMatcher;
49 
50 import java.util.ArrayList;
51 import java.util.List;
52 import java.util.function.Predicate;
53 
54 /**
55  * Espresso utility methods for the floating toolbar.
56  */
57 public class FloatingToolbarEspressoUtils {
58     private final static Object TAG = FloatingToolbar.FLOATING_TOOLBAR_TAG;
59 
FloatingToolbarEspressoUtils()60     private FloatingToolbarEspressoUtils() {}
61 
onFloatingToolBar()62     private static ViewInteraction onFloatingToolBar() {
63         return onView(withTagValue(is(TAG)))
64                 .inRoot(allOf(
65                         isPlatformPopup(),
66                         withDecorView(hasDescendant(withTagValue(is(TAG))))));
67     }
68 
69     /**
70      * Creates a {@link ViewInteraction} for the floating bar menu item with the given matcher.
71      *
72      * @param matcher The matcher for the menu item.
73      */
onFloatingToolBarItem(Matcher<View> matcher)74     public static ViewInteraction onFloatingToolBarItem(Matcher<View> matcher) {
75         return onView(matcher)
76                 .inRoot(withDecorView(hasDescendant(withTagValue(is(TAG)))));
77     }
78 
79     /**
80      * Asserts that the floating toolbar is displayed on screen.
81      *
82      * @throws AssertionError if the assertion fails
83      */
assertFloatingToolbarIsDisplayed()84     public static void assertFloatingToolbarIsDisplayed() {
85         onFloatingToolBar().check(matches(isDisplayed()));
86     }
87 
88     /**
89      * Asserts that the floating toolbar is not displayed on screen.
90      *
91      * @throws AssertionError if the assertion fails
92      * @deprecated Negative assertions are taking too long to timeout in Espresso.
93      */
94     @Deprecated
assertFloatingToolbarIsNotDisplayed()95     public static void assertFloatingToolbarIsNotDisplayed() {
96         try {
97             onFloatingToolBar().check(matches(isDisplayed()));
98         } catch (NoMatchingRootException | NoMatchingViewException | AssertionError e) {
99             return;
100         }
101         throw new AssertionError("Floating toolbar is displayed");
102     }
103 
toggleOverflow()104     private static void toggleOverflow() {
105         final int id = com.android.internal.R.id.overflow;
106         onView(allOf(withId(id), isDisplayed()))
107                 .inRoot(withDecorView(hasDescendant(withId(id))))
108                 .perform(click());
109         onView(isRoot()).perform(SLEEP);
110     }
111 
sleepForFloatingToolbarPopup()112     public static void sleepForFloatingToolbarPopup() {
113         onView(isRoot()).perform(SLEEP);
114     }
115 
116     /**
117      * Asserts that the floating toolbar contains the specified item.
118      *
119      * @param itemLabel label of the item.
120      * @throws AssertionError if the assertion fails
121      */
assertFloatingToolbarContainsItem(String itemLabel)122     public static void assertFloatingToolbarContainsItem(String itemLabel) {
123         try{
124             onFloatingToolBar().check(matches(hasDescendant(withText(itemLabel))));
125         } catch (AssertionError e) {
126             try{
127                 toggleOverflow();
128             } catch (NoMatchingViewException | NoMatchingRootException e2) {
129                 // No overflow items.
130                 throw e;
131             }
132             try{
133                 onFloatingToolBar().check(matches(hasDescendant(withText(itemLabel))));
134             } finally {
135                 toggleOverflow();
136             }
137         }
138     }
139 
140     /**
141      * Asserts that the floating toolbar contains a specified item at a specified index.
142      *
143      * @param menuItemId id of the menu item
144      * @param index expected index of the menu item in the floating toolbar
145      * @throws AssertionError if the assertion fails
146      */
assertFloatingToolbarItemIndex(final int menuItemId, final int index)147     public static void assertFloatingToolbarItemIndex(final int menuItemId, final int index) {
148         onFloatingToolBar().check(matches(new TypeSafeMatcher<View>() {
149             private List<Integer> menuItemIds = new ArrayList<>();
150 
151             @Override
152             public boolean matchesSafely(View view) {
153                 collectMenuItemIds(view);
154                 return menuItemIds.size() > index && menuItemIds.get(index) == menuItemId;
155             }
156 
157             @Override
158             public void describeTo(Description description) {}
159 
160             private void collectMenuItemIds(View view) {
161                 if (view.getTag() instanceof MenuItem) {
162                     menuItemIds.add(((MenuItem) view.getTag()).getItemId());
163                 } else if (view instanceof ViewGroup) {
164                     ViewGroup viewGroup = (ViewGroup) view;
165                     for (int i = 0; i < viewGroup.getChildCount(); i++) {
166                         collectMenuItemIds(viewGroup.getChildAt(i));
167                     }
168                 }
169             }
170         }));
171     }
172 
173     /**
174      * Asserts that the floating toolbar doesn't contain the specified item.
175      *
176      * @param itemLabel label of the item.
177      * @throws AssertionError if the assertion fails
178      */
assertFloatingToolbarDoesNotContainItem(String itemLabel)179     public static void assertFloatingToolbarDoesNotContainItem(String itemLabel) {
180         final Predicate<View> hasMenuItemLabel = view ->
181                 view.getTag() instanceof MenuItem
182                         && itemLabel.equals(((MenuItem) view.getTag()).getTitle().toString());
183         assertFloatingToolbarMenuItem(hasMenuItemLabel, false);
184     }
185 
186     /**
187      * Asserts that the floating toolbar does not contain a menu item with the specified id.
188      *
189      * @param menuItemId id of the menu item
190      * @throws AssertionError if the assertion fails
191      */
assertFloatingToolbarDoesNotContainItem(final int menuItemId)192     public static void assertFloatingToolbarDoesNotContainItem(final int menuItemId) {
193         final Predicate<View> hasMenuItemId = view ->
194                 view.getTag() instanceof MenuItem
195                         && ((MenuItem) view.getTag()).getItemId() == menuItemId;
196         assertFloatingToolbarMenuItem(hasMenuItemId, false);
197     }
198 
assertFloatingToolbarMenuItem( final Predicate<View> predicate, final boolean positiveAssertion)199     private static void assertFloatingToolbarMenuItem(
200             final Predicate<View> predicate, final boolean positiveAssertion) {
201         onFloatingToolBar().check(matches(new TypeSafeMatcher<View>() {
202             @Override
203             public boolean matchesSafely(View view) {
204                 return positiveAssertion == containsItem(view);
205             }
206 
207             @Override
208             public void describeTo(Description description) {}
209 
210             private boolean containsItem(View view) {
211                 if (predicate.test(view)) {
212                     return true;
213                 } else if (view instanceof ViewGroup) {
214                     ViewGroup viewGroup = (ViewGroup) view;
215                     for (int i = 0; i < viewGroup.getChildCount(); i++) {
216                         if (containsItem(viewGroup.getChildAt(i))) {
217                             return true;
218                         }
219                     }
220                 }
221                 return false;
222             }
223         }));
224     }
225 
226     /**
227      * Click specified item on the floating tool bar.
228      *
229      * @param itemLabel label of the item.
230      */
clickFloatingToolbarItem(String itemLabel)231     public static void clickFloatingToolbarItem(String itemLabel) {
232         try{
233             onFloatingToolBarItem(withText(itemLabel)).check(matches(isDisplayed()));
234         } catch (AssertionError e) {
235             // Try to find the item in the overflow menu.
236             toggleOverflow();
237         }
238         onFloatingToolBarItem(withText(itemLabel)).perform(click());
239     }
240 
241     /**
242      * ViewAction to sleep to wait floating toolbar's animation.
243      */
244     private static final ViewAction SLEEP = new ViewAction() {
245         private static final long SLEEP_DURATION = 400;
246 
247         @Override
248         public Matcher<View> getConstraints() {
249             return isDisplayed();
250         }
251 
252         @Override
253         public String getDescription() {
254             return "Sleep " + SLEEP_DURATION + " ms.";
255         }
256 
257         @Override
258         public void perform(UiController uiController, View view) {
259             uiController.loopMainThreadForAtLeast(SLEEP_DURATION);
260         }
261     };
262 }
263