Home | History | Annotate | Download | only in app
      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 androidx.appcompat.app;
     18 
     19 import static android.support.test.espresso.Espresso.onView;
     20 import static android.support.test.espresso.matcher.ViewMatchers.withId;
     21 
     22 import static androidx.appcompat.testutils.DrawerLayoutActions.closeDrawer;
     23 import static androidx.appcompat.testutils.DrawerLayoutActions.openDrawer;
     24 import static androidx.appcompat.testutils.DrawerLayoutActions.setDrawerLockMode;
     25 import static androidx.appcompat.testutils.DrawerLayoutActions.wrap;
     26 import static androidx.appcompat.testutils.TestUtilsMatchers.inAscendingOrder;
     27 import static androidx.appcompat.testutils.TestUtilsMatchers.inDescendingOrder;
     28 import static androidx.appcompat.testutils.TestUtilsMatchers.inRange;
     29 
     30 import static org.hamcrest.MatcherAssert.assertThat;
     31 import static org.junit.Assert.assertEquals;
     32 import static org.junit.Assert.assertFalse;
     33 import static org.junit.Assert.assertTrue;
     34 import static org.junit.Assert.fail;
     35 import static org.mockito.Mockito.any;
     36 import static org.mockito.Mockito.atLeastOnce;
     37 import static org.mockito.Mockito.eq;
     38 import static org.mockito.Mockito.inOrder;
     39 import static org.mockito.Mockito.mock;
     40 import static org.mockito.Mockito.never;
     41 import static org.mockito.Mockito.times;
     42 import static org.mockito.Mockito.verify;
     43 
     44 import android.os.Build;
     45 import android.support.test.espresso.action.GeneralLocation;
     46 import android.support.test.espresso.action.GeneralSwipeAction;
     47 import android.support.test.espresso.action.Press;
     48 import android.support.test.espresso.action.Swipe;
     49 import android.support.test.filters.FlakyTest;
     50 import android.support.test.filters.LargeTest;
     51 import android.support.test.filters.MediumTest;
     52 import android.support.test.filters.Suppress;
     53 import android.support.test.rule.ActivityTestRule;
     54 import android.support.test.runner.AndroidJUnit4;
     55 import android.view.View;
     56 
     57 import androidx.appcompat.custom.CustomDrawerLayout;
     58 import androidx.appcompat.test.R;
     59 import androidx.core.view.GravityCompat;
     60 import androidx.drawerlayout.widget.DrawerLayout;
     61 
     62 import org.junit.Before;
     63 import org.junit.Rule;
     64 import org.junit.Test;
     65 import org.junit.runner.RunWith;
     66 import org.mockito.ArgumentCaptor;
     67 import org.mockito.InOrder;
     68 
     69 @RunWith(AndroidJUnit4.class)
     70 public class DrawerLayoutTest {
     71     @Rule
     72     public final ActivityTestRule<DrawerLayoutActivity> mActivityTestRule =
     73             new ActivityTestRule<DrawerLayoutActivity>(DrawerLayoutActivity.class);
     74 
     75     private CustomDrawerLayout mDrawerLayout;
     76 
     77     private View mStartDrawer;
     78 
     79     private View mContentView;
     80 
     81     @Before
     82     public void setUp() {
     83         final DrawerLayoutActivity activity = mActivityTestRule.getActivity();
     84         mDrawerLayout = (CustomDrawerLayout) activity.findViewById(R.id.drawer_layout);
     85         mStartDrawer = mDrawerLayout.findViewById(R.id.start_drawer);
     86         mContentView = mDrawerLayout.findViewById(R.id.content);
     87 
     88         // Close the drawer to reset the state for the next test
     89         onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START));
     90     }
     91 
     92     // Tests for opening and closing the drawer and checking the open state
     93 
     94     @Test
     95     @LargeTest
     96     public void testDrawerOpenCloseViaAPI() {
     97         assertFalse("Initial state", mDrawerLayout.isDrawerOpen(GravityCompat.START));
     98 
     99         for (int i = 0; i < 5; i++) {
    100             onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
    101             assertTrue("Opened drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
    102 
    103             onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START));
    104             assertFalse("Closed drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
    105         }
    106     }
    107 
    108     @Test
    109     @MediumTest
    110     public void testDrawerOpenCloseNoAnimationViaAPI() {
    111         assertFalse("Initial state", mDrawerLayout.isDrawerOpen(GravityCompat.START));
    112 
    113         for (int i = 0; i < 5; i++) {
    114             onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START, false));
    115             assertTrue("Opened drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
    116 
    117             onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START, false));
    118             assertFalse("Closed drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
    119         }
    120     }
    121 
    122     @Test
    123     @LargeTest
    124     public void testDrawerOpenCloseFocus() throws Throwable {
    125         assertFalse("Initial state", mDrawerLayout.isDrawerOpen(GravityCompat.START));
    126 
    127         mActivityTestRule.runOnUiThread(new Runnable() {
    128             @Override
    129             public void run() {
    130                 mContentView.setFocusableInTouchMode(true);
    131                 mContentView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
    132                     @Override
    133                     public void onFocusChange(View v, boolean hasFocus) {
    134                         fail("Unnecessary focus change");
    135                     }
    136                 });
    137             }
    138         });
    139 
    140         onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
    141         assertTrue("Opened drawer", mDrawerLayout.isDrawerOpen(GravityCompat.START));
    142 
    143         onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START));
    144         assertFalse("Closed drawer", mDrawerLayout.isDrawerOpen(GravityCompat.START));
    145     }
    146 
    147     @Test
    148     @LargeTest
    149     public void testDrawerOpenCloseWithRedundancyViaAPI() {
    150         assertFalse("Initial state", mDrawerLayout.isDrawerOpen(GravityCompat.START));
    151 
    152         for (int i = 0; i < 5; i++) {
    153             onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
    154             assertTrue("Opened drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
    155 
    156             // Try opening the drawer when it's already opened
    157             onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
    158             assertTrue("Opened drawer is still opened #" + i,
    159                     mDrawerLayout.isDrawerOpen(GravityCompat.START));
    160 
    161             onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START));
    162             assertFalse("Closed drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
    163 
    164             // Try closing the drawer when it's already closed
    165             onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START));
    166             assertFalse("Closed drawer is still closed #" + i,
    167                     mDrawerLayout.isDrawerOpen(GravityCompat.START));
    168         }
    169     }
    170 
    171     @Test
    172     @MediumTest
    173     public void testDrawerOpenCloseNoAnimationWithRedundancyViaAPI() {
    174         assertFalse("Initial state", mDrawerLayout.isDrawerOpen(GravityCompat.START));
    175 
    176         for (int i = 0; i < 5; i++) {
    177             onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START, false));
    178             assertTrue("Opened drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
    179 
    180             // Try opening the drawer when it's already opened
    181             onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START, false));
    182             assertTrue("Opened drawer is still opened #" + i,
    183                     mDrawerLayout.isDrawerOpen(GravityCompat.START));
    184 
    185             onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START, false));
    186             assertFalse("Closed drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
    187 
    188             // Try closing the drawer when it's already closed
    189             onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START, false));
    190             assertFalse("Closed drawer is still closed #" + i,
    191                     mDrawerLayout.isDrawerOpen(GravityCompat.START));
    192         }
    193     }
    194 
    195     @Test
    196     @LargeTest
    197     public void testDrawerOpenCloseViaSwipes() {
    198         assertFalse("Initial state", mDrawerLayout.isDrawerOpen(GravityCompat.START));
    199 
    200         // Note that we're using GeneralSwipeAction instead of swipeLeft() / swipeRight().
    201         // Those Espresso actions use edge fuzzying which doesn't work well with edge-based
    202         // detection of swiping the drawers open in DrawerLayout.
    203         // It's critically important to wrap the GeneralSwipeAction to "wait" until the
    204         // DrawerLayout has settled to STATE_IDLE state before continuing to query the drawer
    205         // open / close state. This is done in DrawerLayoutActions.wrap method.
    206         for (int i = 0; i < 5; i++) {
    207             onView(withId(R.id.drawer_layout)).perform(
    208                     wrap(new GeneralSwipeAction(Swipe.FAST, GeneralLocation.CENTER_LEFT,
    209                             GeneralLocation.CENTER_RIGHT, Press.FINGER)));
    210             assertTrue("Opened drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
    211 
    212             onView(withId(R.id.drawer_layout)).perform(
    213                     wrap(new GeneralSwipeAction(Swipe.FAST, GeneralLocation.CENTER_RIGHT,
    214                             GeneralLocation.CENTER_LEFT, Press.FINGER)));
    215             assertFalse("Closed drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
    216         }
    217     }
    218 
    219     @Test
    220     @LargeTest
    221     public void testDrawerOpenCloseWithRedundancyViaSwipes() {
    222         assertFalse("Initial state", mDrawerLayout.isDrawerOpen(GravityCompat.START));
    223 
    224         // Note that we're using GeneralSwipeAction instead of swipeLeft() / swipeRight().
    225         // Those Espresso actions use edge fuzzying which doesn't work well with edge-based
    226         // detection of swiping the drawers open in DrawerLayout.
    227         // It's critically important to wrap the GeneralSwipeAction to "wait" until the
    228         // DrawerLayout has settled to STATE_IDLE state before continuing to query the drawer
    229         // open / close state. This is done in DrawerLayoutActions.wrap method.
    230         for (int i = 0; i < 5; i++) {
    231             onView(withId(R.id.drawer_layout)).perform(
    232                     wrap(new GeneralSwipeAction(Swipe.FAST, GeneralLocation.CENTER_LEFT,
    233                             GeneralLocation.CENTER_RIGHT, Press.FINGER)));
    234             assertTrue("Opened drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
    235 
    236             // Try opening the drawer when it's already opened
    237             onView(withId(R.id.drawer_layout)).perform(
    238                     wrap(new GeneralSwipeAction(Swipe.FAST, GeneralLocation.CENTER_LEFT,
    239                             GeneralLocation.CENTER_RIGHT, Press.FINGER)));
    240             assertTrue("Opened drawer is still opened #" + i,
    241                     mDrawerLayout.isDrawerOpen(GravityCompat.START));
    242 
    243             onView(withId(R.id.drawer_layout)).perform(
    244                     wrap(new GeneralSwipeAction(Swipe.FAST, GeneralLocation.CENTER_RIGHT,
    245                             GeneralLocation.CENTER_LEFT, Press.FINGER)));
    246             assertFalse("Closed drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
    247 
    248             // Try closing the drawer when it's already closed
    249             onView(withId(R.id.drawer_layout)).perform(
    250                     wrap(new GeneralSwipeAction(Swipe.FAST, GeneralLocation.CENTER_RIGHT,
    251                             GeneralLocation.CENTER_LEFT, Press.FINGER)));
    252             assertFalse("Closed drawer is still closed #" + i,
    253                     mDrawerLayout.isDrawerOpen(GravityCompat.START));
    254         }
    255     }
    256 
    257     @Test
    258     @MediumTest
    259     public void testDrawerHeight() {
    260         // Open the drawer so it becomes visible
    261         onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
    262 
    263         final int drawerLayoutHeight = mDrawerLayout.getHeight();
    264         final int startDrawerHeight = mStartDrawer.getHeight();
    265         final int contentHeight = mContentView.getHeight();
    266 
    267         // On all devices the height of the drawer layout and the drawer should be identical.
    268         assertEquals("Drawer layout and drawer heights", drawerLayoutHeight, startDrawerHeight);
    269 
    270         if (Build.VERSION.SDK_INT < 21) {
    271             // On pre-L devices the content height should be the same as the drawer layout height.
    272             assertEquals("Drawer layout and content heights on pre-L",
    273                     drawerLayoutHeight, contentHeight);
    274         } else {
    275             // Our drawer layout is configured with android:fitsSystemWindows="true" which should be
    276             // respected on L+ devices to extend the drawer layout into the system status bar.
    277             // The start drawer is also configured with the same attribute so it should have the
    278             // same height as the drawer layout. The main content does not have that attribute
    279             // specified, so it should have its height reduced by the height of the system status
    280             // bar.
    281 
    282             final int[] contentViewLocationOnScreen = new int[2];
    283             mContentView.getLocationOnScreen(contentViewLocationOnScreen);
    284             final int statusBarHeight = contentViewLocationOnScreen[1];
    285             // Get the system window top inset that was propagated to the top-level DrawerLayout
    286             // during its layout.
    287             int drawerTopInset = mDrawerLayout.getSystemWindowInsetTop();
    288             if (statusBarHeight > 0) {
    289                 assertEquals("Drawer top inset is positive on L+", statusBarHeight, drawerTopInset);
    290             } else {
    291                 assertEquals("Drawer top inset 0 due to no status bar", 0, drawerTopInset);
    292             }
    293             assertEquals("Drawer layout and drawer heights on L+",
    294                     drawerLayoutHeight - drawerTopInset, contentHeight);
    295         }
    296     }
    297 
    298     // Tests for listener(s) being notified of various events
    299 
    300     @Test
    301     @MediumTest
    302     public void testDrawerListenerCallbacksOnOpeningViaAPI() {
    303         // Register a mock listener
    304         DrawerLayout.DrawerListener mockedListener = mock(DrawerLayout.DrawerListener.class);
    305         mDrawerLayout.addDrawerListener(mockedListener);
    306 
    307         // Open the drawer so it becomes visible
    308         onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
    309 
    310         // We expect that our listener has been notified that the drawer has been opened
    311         // with the reference to our drawer
    312         verify(mockedListener, times(1)).onDrawerOpened(mStartDrawer);
    313         // We expect that our listener has not been notified that the drawer has been closed
    314         verify(mockedListener, never()).onDrawerClosed(any(View.class));
    315 
    316         // We expect that our listener has been notified at least once on the drawer slide
    317         // event. We expect that all such callbacks pass the reference to our drawer as the first
    318         // parameter, and we capture the float slide values for further analysis
    319         ArgumentCaptor<Float> floatSlideCaptor = ArgumentCaptor.forClass(float.class);
    320         verify(mockedListener, atLeastOnce()).onDrawerSlide(eq(mStartDrawer),
    321                 floatSlideCaptor.capture());
    322         // Now we verify that calls to onDrawerSlide "gave" us an increasing sequence of values
    323         // in [0..1] range. Note that we don't have any expectation on how many times onDrawerSlide
    324         // is called since that depends on the hardware capabilities of the device and the current
    325         // load on the CPU / GPU.
    326         assertThat(floatSlideCaptor.getAllValues(), inRange(0.0f, 1.0f));
    327         assertThat(floatSlideCaptor.getAllValues(), inAscendingOrder());
    328 
    329         // We expect that our listener will be called with specific state changes
    330         InOrder inOrder = inOrder(mockedListener);
    331         inOrder.verify(mockedListener).onDrawerStateChanged(DrawerLayout.STATE_SETTLING);
    332         inOrder.verify(mockedListener).onDrawerStateChanged(DrawerLayout.STATE_IDLE);
    333 
    334         mDrawerLayout.removeDrawerListener(mockedListener);
    335     }
    336 
    337     @Test
    338     @MediumTest
    339     public void testDrawerListenerCallbacksOnOpeningNoAnimationViaAPI() {
    340         // Register a mock listener
    341         DrawerLayout.DrawerListener mockedListener = mock(DrawerLayout.DrawerListener.class);
    342         mDrawerLayout.addDrawerListener(mockedListener);
    343 
    344         // Open the drawer so it becomes visible
    345         onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START, false));
    346 
    347         // We expect that our listener has been notified that the drawer has been opened
    348         // with the reference to our drawer
    349         verify(mockedListener, times(1)).onDrawerOpened(mStartDrawer);
    350         // We expect that our listener has not been notified that the drawer has been closed
    351         verify(mockedListener, never()).onDrawerClosed(any(View.class));
    352 
    353         verify(mockedListener, times(1)).onDrawerSlide(any(View.class), eq(1f));
    354 
    355         // Request to open the drawer again
    356         onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START, false));
    357 
    358         // We expect that our listener has not been notified again that the drawer has been opened
    359         verify(mockedListener, times(1)).onDrawerOpened(mStartDrawer);
    360         // We expect that our listener has not been notified that the drawer has been closed
    361         verify(mockedListener, never()).onDrawerClosed(any(View.class));
    362 
    363         mDrawerLayout.removeDrawerListener(mockedListener);
    364     }
    365 
    366     @Test
    367     @LargeTest
    368     public void testDrawerListenerCallbacksOnClosingViaAPI() {
    369         // Open the drawer so it becomes visible
    370         onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
    371 
    372         // Register a mock listener
    373         DrawerLayout.DrawerListener mockedListener = mock(DrawerLayout.DrawerListener.class);
    374         mDrawerLayout.addDrawerListener(mockedListener);
    375 
    376         // Close the drawer
    377         onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START));
    378 
    379         // We expect that our listener has not been notified that the drawer has been opened
    380         verify(mockedListener, never()).onDrawerOpened(any(View.class));
    381         // We expect that our listener has been notified that the drawer has been closed
    382         // with the reference to our drawer
    383         verify(mockedListener, times(1)).onDrawerClosed(mStartDrawer);
    384 
    385         // We expect that our listener has been notified at least once on the drawer slide
    386         // event. We expect that all such callbacks pass the reference to our drawer as the first
    387         // parameter, and we capture the float slide values for further analysis
    388         ArgumentCaptor<Float> floatSlideCaptor = ArgumentCaptor.forClass(float.class);
    389         verify(mockedListener, atLeastOnce()).onDrawerSlide(eq(mStartDrawer),
    390                 floatSlideCaptor.capture());
    391         // Now we verify that calls to onDrawerSlide "gave" us a decreasing sequence of values
    392         // in [0..1] range. Note that we don't have any expectation on how many times onDrawerSlide
    393         // is called since that depends on the hardware capabilities of the device and the current
    394         // load on the CPU / GPU.
    395         assertThat(floatSlideCaptor.getAllValues(), inRange(0.0f, 1.0f));
    396         assertThat(floatSlideCaptor.getAllValues(), inDescendingOrder());
    397 
    398         // We expect that our listener will be called with specific state changes
    399         InOrder inOrder = inOrder(mockedListener);
    400         inOrder.verify(mockedListener).onDrawerStateChanged(DrawerLayout.STATE_SETTLING);
    401         inOrder.verify(mockedListener).onDrawerStateChanged(DrawerLayout.STATE_IDLE);
    402 
    403         mDrawerLayout.removeDrawerListener(mockedListener);
    404     }
    405 
    406     @Test
    407     @MediumTest
    408     public void testDrawerListenerCallbacksOnClosingNoAnimationViaAPI() {
    409         // Open the drawer so it becomes visible
    410         onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START, false));
    411 
    412         // Register a mock listener
    413         DrawerLayout.DrawerListener mockedListener = mock(DrawerLayout.DrawerListener.class);
    414         mDrawerLayout.addDrawerListener(mockedListener);
    415 
    416         // Close the drawer
    417         onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START, false));
    418 
    419         // We expect that our listener has not been notified that the drawer has been opened
    420         verify(mockedListener, never()).onDrawerOpened(any(View.class));
    421         // We expect that our listener has been notified that the drawer has been closed
    422         // with the reference to our drawer
    423         verify(mockedListener, times(1)).onDrawerClosed(mStartDrawer);
    424 
    425         verify(mockedListener, times(1)).onDrawerSlide(any(View.class), eq(0f));
    426 
    427         // Attempt to close the drawer again.
    428         onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START, false));
    429 
    430         // We expect that our listener has not been notified that the drawer has been opened
    431         verify(mockedListener, never()).onDrawerOpened(any(View.class));
    432         // We expect that our listener has not been notified again that the drawer has been closed
    433         verify(mockedListener, times(1)).onDrawerClosed(mStartDrawer);
    434 
    435         mDrawerLayout.removeDrawerListener(mockedListener);
    436     }
    437 
    438     @Suppress
    439     @FlakyTest(bugId = 33659300)
    440     @Test
    441     @MediumTest
    442     public void testDrawerListenerCallbacksOnOpeningViaSwipes() {
    443         // Register a mock listener
    444         DrawerLayout.DrawerListener mockedListener = mock(DrawerLayout.DrawerListener.class);
    445         mDrawerLayout.addDrawerListener(mockedListener);
    446 
    447         // Open the drawer so it becomes visible
    448         // Note that we're using GeneralSwipeAction instead of swipeLeft() / swipeRight().
    449         // Those Espresso actions use edge fuzzying which doesn't work well with edge-based
    450         // detection of swiping the drawers open in DrawerLayout.
    451         // It's critically important to wrap the GeneralSwipeAction to "wait" until the
    452         // DrawerLayout has settled to STATE_IDLE state before continuing to query the drawer
    453         // open / close state. This is done in DrawerLayoutActions.wrap method.
    454         onView(withId(R.id.drawer_layout)).perform(
    455                 wrap(new GeneralSwipeAction(Swipe.FAST, GeneralLocation.CENTER_LEFT,
    456                         GeneralLocation.CENTER_RIGHT, Press.FINGER)));
    457 
    458         // We expect that our listener has been notified that the drawer has been opened
    459         // with the reference to our drawer
    460         verify(mockedListener, times(1)).onDrawerOpened(mStartDrawer);
    461         // We expect that our listener has not been notified that the drawer has been closed
    462         verify(mockedListener, never()).onDrawerClosed(any(View.class));
    463 
    464         // We expect that our listener has been notified at least once on the drawer slide
    465         // event. We expect that all such callbacks pass the reference to our drawer as the first
    466         // parameter, and we capture the float slide values for further analysis
    467         ArgumentCaptor<Float> floatSlideCaptor = ArgumentCaptor.forClass(float.class);
    468         verify(mockedListener, atLeastOnce()).onDrawerSlide(eq(mStartDrawer),
    469                 floatSlideCaptor.capture());
    470         // Now we verify that calls to onDrawerSlide "gave" us an increasing sequence of values
    471         // in [0..1] range. Note that we don't have any expectation on how many times onDrawerSlide
    472         // is called since that depends on the hardware capabilities of the device and the current
    473         // load on the CPU / GPU.
    474         assertThat(floatSlideCaptor.getAllValues(), inRange(0.0f, 1.0f));
    475         assertThat(floatSlideCaptor.getAllValues(), inAscendingOrder());
    476 
    477         // We expect that our listener will be called with specific state changes
    478         InOrder inOrder = inOrder(mockedListener);
    479         inOrder.verify(mockedListener).onDrawerStateChanged(DrawerLayout.STATE_DRAGGING);
    480         inOrder.verify(mockedListener).onDrawerStateChanged(DrawerLayout.STATE_IDLE);
    481 
    482         mDrawerLayout.removeDrawerListener(mockedListener);
    483     }
    484 
    485     @Test
    486     @LargeTest
    487     public void testDrawerListenerCallbacksOnClosingViaSwipes() {
    488         // Open the drawer so it becomes visible
    489         onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
    490 
    491         // Register a mock listener
    492         DrawerLayout.DrawerListener mockedListener = mock(DrawerLayout.DrawerListener.class);
    493         mDrawerLayout.addDrawerListener(mockedListener);
    494 
    495         // Close the drawer
    496         // Note that we're using GeneralSwipeAction instead of swipeLeft() / swipeRight().
    497         // Those Espresso actions use edge fuzzying which doesn't work well with edge-based
    498         // detection of swiping the drawers open in DrawerLayout.
    499         // It's critically important to wrap the GeneralSwipeAction to "wait" until the
    500         // DrawerLayout has settled to STATE_IDLE state before continuing to query the drawer
    501         // open / close state. This is done in DrawerLayoutActions.wrap method.
    502         onView(withId(R.id.drawer_layout)).perform(
    503                 wrap(new GeneralSwipeAction(Swipe.FAST, GeneralLocation.CENTER_RIGHT,
    504                         GeneralLocation.CENTER_LEFT, Press.FINGER)));
    505 
    506         // We expect that our listener has not been notified that the drawer has been opened
    507         verify(mockedListener, never()).onDrawerOpened(any(View.class));
    508         // We expect that our listener has been notified that the drawer has been closed
    509         // with the reference to our drawer
    510         verify(mockedListener, times(1)).onDrawerClosed(mStartDrawer);
    511 
    512         // We expect that our listener has been notified at least once on the drawer slide
    513         // event. We expect that all such callbacks pass the reference to our drawer as the first
    514         // parameter, and we capture the float slide values for further analysis
    515         ArgumentCaptor<Float> floatSlideCaptor = ArgumentCaptor.forClass(float.class);
    516         verify(mockedListener, atLeastOnce()).onDrawerSlide(eq(mStartDrawer),
    517                 floatSlideCaptor.capture());
    518         // Now we verify that calls to onDrawerSlide "gave" us a decreasing sequence of values
    519         // in [0..1] range. Note that we don't have any expectation on how many times onDrawerSlide
    520         // is called since that depends on the hardware capabilities of the device and the current
    521         // load on the CPU / GPU.
    522         assertThat(floatSlideCaptor.getAllValues(), inRange(0.0f, 1.0f));
    523         assertThat(floatSlideCaptor.getAllValues(), inDescendingOrder());
    524 
    525         // We expect that our listener will be called with specific state changes
    526         InOrder inOrder = inOrder(mockedListener);
    527         inOrder.verify(mockedListener).onDrawerStateChanged(DrawerLayout.STATE_DRAGGING);
    528         inOrder.verify(mockedListener).onDrawerStateChanged(DrawerLayout.STATE_IDLE);
    529 
    530         mDrawerLayout.removeDrawerListener(mockedListener);
    531     }
    532 
    533     @Test
    534     @LargeTest
    535     public void testDrawerLockUnlock() {
    536         assertEquals("Drawer is unlocked in initial state",
    537                 DrawerLayout.LOCK_MODE_UNLOCKED, mDrawerLayout.getDrawerLockMode(mStartDrawer));
    538 
    539         // Lock the drawer open
    540         onView(withId(R.id.drawer_layout)).perform(
    541                 setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN, GravityCompat.START));
    542         // Check that it's locked open
    543         assertEquals("Drawer is now locked open",
    544                 DrawerLayout.LOCK_MODE_LOCKED_OPEN, mDrawerLayout.getDrawerLockMode(mStartDrawer));
    545         // and also opened
    546         assertTrue("Drawer is also opened", mDrawerLayout.isDrawerOpen(mStartDrawer));
    547 
    548         // Unlock the drawer
    549         onView(withId(R.id.drawer_layout)).perform(
    550                 setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, mStartDrawer));
    551         // Check that it's still opened
    552         assertTrue("Drawer is still opened", mDrawerLayout.isDrawerOpen(mStartDrawer));
    553         // Close the drawer
    554         onView(withId(R.id.drawer_layout)).perform(closeDrawer(mStartDrawer));
    555         // Check that the drawer is unlocked
    556         assertEquals("Start drawer is now unlocked",
    557                 DrawerLayout.LOCK_MODE_UNLOCKED, mDrawerLayout.getDrawerLockMode(mStartDrawer));
    558 
    559         // Open the drawer and then clock it closed
    560         onView(withId(R.id.drawer_layout)).perform(openDrawer(mStartDrawer));
    561         onView(withId(R.id.drawer_layout)).perform(
    562                 setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.START));
    563         // Check that the drawer is locked close
    564         assertEquals("Drawer is now locked close",
    565                 DrawerLayout.LOCK_MODE_LOCKED_CLOSED,
    566                 mDrawerLayout.getDrawerLockMode(mStartDrawer));
    567         // and also closed
    568         assertFalse("Drawer is also closed", mDrawerLayout.isDrawerOpen(mStartDrawer));
    569 
    570         // Unlock the drawer
    571         onView(withId(R.id.drawer_layout)).perform(
    572                 setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, mStartDrawer));
    573         // Check that it's still closed
    574         assertFalse("Drawer is still closed", mDrawerLayout.isDrawerOpen(mStartDrawer));
    575     }
    576 }
    577