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