Keisuke Kuroyanagi | a0b3c06 | 2015-12-07 11:09:40 -0800 | [diff] [blame] | 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; |
Vladislav Kaznacheev | d959c9d | 2018-01-23 14:03:36 -0800 | [diff] [blame] | 20 | import static android.support.test.espresso.action.ViewActions.click; |
Keisuke Kuroyanagi | a0b3c06 | 2015-12-07 11:09:40 -0800 | [diff] [blame] | 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; |
Vladislav Kaznacheev | d959c9d | 2018-01-23 14:03:36 -0800 | [diff] [blame] | 27 | import static android.support.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast; |
Keisuke Kuroyanagi | a0b3c06 | 2015-12-07 11:09:40 -0800 | [diff] [blame] | 28 | import static android.support.test.espresso.matcher.ViewMatchers.isEnabled; |
| 29 | import static android.support.test.espresso.matcher.ViewMatchers.withText; |
Vladislav Kaznacheev | d959c9d | 2018-01-23 14:03:36 -0800 | [diff] [blame] | 30 | |
Keisuke Kuroyanagi | a0b3c06 | 2015-12-07 11:09:40 -0800 | [diff] [blame] | 31 | import static org.hamcrest.Matchers.allOf; |
| 32 | import static org.hamcrest.Matchers.not; |
| 33 | |
Keisuke Kuroyanagi | a0b3c06 | 2015-12-07 11:09:40 -0800 | [diff] [blame] | 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; |
Vladislav Kaznacheev | d959c9d | 2018-01-23 14:03:36 -0800 | [diff] [blame] | 38 | import android.view.View; |
Keisuke Kuroyanagi | a0b3c06 | 2015-12-07 11:09:40 -0800 | [diff] [blame] | 39 | import android.widget.MenuPopupWindow.MenuDropDownListView; |
| 40 | |
Vladislav Kaznacheev | d959c9d | 2018-01-23 14:03:36 -0800 | [diff] [blame] | 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 | |
Keisuke Kuroyanagi | a0b3c06 | 2015-12-07 11:09:40 -0800 | [diff] [blame] | 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( |
Vladislav Kaznacheev | d959c9d | 2018-01-23 14:03:36 -0800 | [diff] [blame] | 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)); |
Keisuke Kuroyanagi | a0b3c06 | 2015-12-07 11:09:40 -0800 | [diff] [blame] | 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 | } |
Vladislav Kaznacheev | d959c9d | 2018-01-23 14:03:36 -0800 | [diff] [blame] | 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 | } |
Keisuke Kuroyanagi | a0b3c06 | 2015-12-07 11:09:40 -0800 | [diff] [blame] | 189 | } |