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.hasFocus; 25 import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom; 26 import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; 27 import static android.support.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast; 28 import static android.support.test.espresso.matcher.ViewMatchers.isEnabled; 29 import static android.support.test.espresso.matcher.ViewMatchers.withText; 30 31 import static org.hamcrest.Matchers.allOf; 32 import static org.hamcrest.Matchers.not; 33 34 import android.support.test.espresso.NoMatchingRootException; 35 import android.support.test.espresso.NoMatchingViewException; 36 import android.support.test.espresso.ViewInteraction; 37 import android.support.test.espresso.matcher.ViewMatchers; 38 import android.view.View; 39 import android.widget.MenuPopupWindow.MenuDropDownListView; 40 41 import com.android.internal.view.menu.ListMenuItemView; 42 43 import org.hamcrest.Description; 44 import org.hamcrest.Matcher; 45 import org.hamcrest.TypeSafeMatcher; 46 47 /** 48 * Espresso utility methods for the context menu. 49 */ 50 public final class ContextMenuUtils { 51 private ContextMenuUtils() {} 52 53 private static ViewInteraction onContextMenu() { 54 // TODO: Have more reliable way to get context menu. 55 return onView(ViewMatchers.isAssignableFrom(MenuDropDownListView.class)) 56 .inRoot(withDecorView(hasFocus())); 57 } 58 59 /** 60 * Asserts that the context menu is displayed 61 * 62 * @throws AssertionError if the assertion fails 63 */ 64 private static void assertContextMenuIsDisplayed() { 65 onContextMenu().check(matches(isDisplayed())); 66 } 67 68 /** 69 * Asserts that the context menu is not displayed 70 * 71 * @throws AssertionError if the assertion fails 72 */ 73 public static void assertContextMenuIsNotDisplayed() { 74 try { 75 assertContextMenuIsDisplayed(); 76 } catch (NoMatchingRootException | NoMatchingViewException | AssertionError e) { 77 return; 78 } 79 throw new AssertionError("Context menu is displayed"); 80 } 81 82 /** 83 * Asserts that the context menu contains the specified item and the item has specified enabled 84 * state. 85 * 86 * @param itemLabel label of the item. 87 * @param enabled enabled state of the item. 88 * @throws AssertionError if the assertion fails 89 */ 90 private static void asssertContextMenuContainsItemWithEnabledState(String itemLabel, 91 boolean enabled) { 92 onContextMenu().check(matches( 93 hasDescendant(getVisibleMenuItemMatcher(itemLabel, enabled)))); 94 } 95 96 private static Matcher<View> getVisibleMenuItemMatcher(String itemLabel, boolean enabled) { 97 return allOf( 98 isAssignableFrom(ListMenuItemView.class), 99 hasDescendant(withText(itemLabel)), 100 enabled ? isEnabled() : not(isEnabled()), 101 isDisplayingAtLeast(90)); 102 } 103 104 /** 105 * Asserts that the context menu contains the specified item and the item is enabled. 106 * 107 * @param itemLabel label of the item. 108 * @throws AssertionError if the assertion fails 109 */ 110 public static void assertContextMenuContainsItemEnabled(String itemLabel) { 111 asssertContextMenuContainsItemWithEnabledState(itemLabel, true); 112 } 113 114 /** 115 * Asserts that the context menu contains the specified item and the item is disabled. 116 * 117 * @param itemLabel label of the item. 118 * @throws AssertionError if the assertion fails 119 */ 120 public static void assertContextMenuContainsItemDisabled(String itemLabel) { 121 asssertContextMenuContainsItemWithEnabledState(itemLabel, false); 122 } 123 124 /** 125 * Asserts that the context menu window is aligned to a given view with a given offset. 126 * 127 * @param anchor Anchor view. 128 * @param offsetX x offset 129 * @param offsetY y offset. 130 * @throws AssertionError if the assertion fails 131 */ 132 public static void assertContextMenuAlignment(View anchor, int offsetX, int offsetY) { 133 int [] expectedLocation = new int[2]; 134 anchor.getLocationOnScreen(expectedLocation); 135 expectedLocation[0] += offsetX; 136 expectedLocation[1] += offsetY; 137 138 final boolean rtl = anchor.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; 139 140 onContextMenu().check(matches(new TypeSafeMatcher<View>() { 141 @Override 142 public void describeTo(Description description) { 143 description.appendText("root view "); 144 description.appendText(rtl ? "right" : "left"); 145 description.appendText("="); 146 description.appendText(Integer.toString(offsetX)); 147 description.appendText(", top="); 148 description.appendText(Integer.toString(offsetY)); 149 } 150 151 @Override 152 public boolean matchesSafely(View view) { 153 View rootView = view.getRootView(); 154 int [] actualLocation = new int[2]; 155 rootView.getLocationOnScreen(actualLocation); 156 if (rtl) { 157 actualLocation[0] += rootView.getWidth(); 158 } 159 return expectedLocation[0] == actualLocation[0] 160 && expectedLocation[1] == actualLocation[1]; 161 } 162 })); 163 } 164 165 /** 166 * Check is the menu item is clickable (i.e. visible and enabled). 167 * 168 * @param itemLabel Label of the item. 169 * @return True if the menu item is clickable. 170 */ 171 public static boolean isMenuItemClickable(String itemLabel) { 172 try { 173 onContextMenu().check(matches( 174 hasDescendant(getVisibleMenuItemMatcher(itemLabel, true)))); 175 return true; 176 } catch (NoMatchingRootException | NoMatchingViewException | AssertionError e) { 177 return false; 178 } 179 } 180 181 /** 182 * Click on a menu item with the specified label 183 * @param itemLabel Label of the item. 184 */ 185 public static void clickMenuItem(String itemLabel) { 186 onView(getVisibleMenuItemMatcher(itemLabel, true)) 187 .inRoot(withDecorView(hasFocus())).perform(click()); 188 } 189 } 190