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