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