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