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