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