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