DrawerLayoutTest.java revision 21f78eb90b3217aa5cf69c3ffd359506468b55f4
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 */
16package android.support.v7.app;
17
18import android.os.Build;
19import android.support.test.espresso.action.GeneralLocation;
20import android.support.test.espresso.action.GeneralSwipeAction;
21import android.support.test.espresso.action.Press;
22import android.support.test.espresso.action.Swipe;
23import android.support.v4.view.GravityCompat;
24import android.support.v4.widget.DrawerLayout;
25import android.support.v7.appcompat.test.R;
26import android.support.v7.custom.CustomDrawerLayout;
27import android.test.suitebuilder.annotation.MediumTest;
28import android.test.suitebuilder.annotation.SmallTest;
29import android.view.View;
30import org.junit.Before;
31import org.junit.Test;
32import org.mockito.ArgumentCaptor;
33import org.mockito.InOrder;
34
35import static android.support.test.espresso.Espresso.onView;
36import static android.support.test.espresso.matcher.ViewMatchers.withId;
37import static android.support.v7.testutils.DrawerLayoutActions.*;
38import static android.support.v7.testutils.TestUtilsMatchers.*;
39import static org.hamcrest.MatcherAssert.assertThat;
40import static org.junit.Assert.*;
41import static org.mockito.Mockito.*;
42
43public class DrawerLayoutTest extends BaseInstrumentationTestCase<DrawerLayoutActivity> {
44    private CustomDrawerLayout mDrawerLayout;
45
46    private View mStartDrawer;
47
48    private View mContentView;
49
50    public DrawerLayoutTest() {
51        super(DrawerLayoutActivity.class);
52    }
53
54    @Before
55    public void setUp() {
56        final DrawerLayoutActivity activity = mActivityTestRule.getActivity();
57        mDrawerLayout = (CustomDrawerLayout) activity.findViewById(R.id.drawer_layout);
58        mStartDrawer = mDrawerLayout.findViewById(R.id.start_drawer);
59        mContentView = mDrawerLayout.findViewById(R.id.content);
60
61        // Close the drawer to reset the state for the next test
62        onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START));
63    }
64
65    // Tests for opening and closing the drawer and checking the open state
66
67    @Test
68    @MediumTest
69    public void testDrawerOpenCloseViaAPI() {
70        assertFalse("Initial state", mDrawerLayout.isDrawerOpen(GravityCompat.START));
71
72        for (int i = 0; i < 5; i++) {
73            onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
74            assertTrue("Opened drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
75
76            onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START));
77            assertFalse("Closed drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
78        }
79    }
80
81    @Test
82    @MediumTest
83    public void testDrawerOpenCloseWithRedundancyViaAPI() {
84        assertFalse("Initial state", mDrawerLayout.isDrawerOpen(GravityCompat.START));
85
86        for (int i = 0; i < 5; i++) {
87            onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
88            assertTrue("Opened drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
89
90            // Try opening the drawer when it's already opened
91            onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
92            assertTrue("Opened drawer is still opened #" + i,
93                    mDrawerLayout.isDrawerOpen(GravityCompat.START));
94
95            onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START));
96            assertFalse("Closed drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
97
98            // Try closing the drawer when it's already closed
99            onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START));
100            assertFalse("Closed drawer is still closed #" + i,
101                    mDrawerLayout.isDrawerOpen(GravityCompat.START));
102        }
103    }
104
105    @Test
106    @MediumTest
107    public void testDrawerOpenCloseViaSwipes() {
108        assertFalse("Initial state", mDrawerLayout.isDrawerOpen(GravityCompat.START));
109
110        // Note that we're using GeneralSwipeAction instead of swipeLeft() / swipeRight().
111        // Those Espresso actions use edge fuzzying which doesn't work well with edge-based
112        // detection of swiping the drawers open in DrawerLayout.
113        // It's critically important to wrap the GeneralSwipeAction to "wait" until the
114        // DrawerLayout has settled to STATE_IDLE state before continuing to query the drawer
115        // open / close state. This is done in DrawerLayoutActions.wrap method.
116        for (int i = 0; i < 5; i++) {
117            onView(withId(R.id.drawer_layout)).perform(
118                    wrap(new GeneralSwipeAction(Swipe.FAST, GeneralLocation.CENTER_LEFT,
119                            GeneralLocation.CENTER_RIGHT, Press.FINGER)));
120            assertTrue("Opened drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
121
122            onView(withId(R.id.drawer_layout)).perform(
123                    wrap(new GeneralSwipeAction(Swipe.FAST, GeneralLocation.CENTER_RIGHT,
124                            GeneralLocation.CENTER_LEFT, Press.FINGER)));
125            assertFalse("Closed drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
126        }
127    }
128
129    @Test
130    @MediumTest
131    public void testDrawerOpenCloseWithRedundancyViaSwipes() {
132        assertFalse("Initial state", mDrawerLayout.isDrawerOpen(GravityCompat.START));
133
134        // Note that we're using GeneralSwipeAction instead of swipeLeft() / swipeRight().
135        // Those Espresso actions use edge fuzzying which doesn't work well with edge-based
136        // detection of swiping the drawers open in DrawerLayout.
137        // It's critically important to wrap the GeneralSwipeAction to "wait" until the
138        // DrawerLayout has settled to STATE_IDLE state before continuing to query the drawer
139        // open / close state. This is done in DrawerLayoutActions.wrap method.
140        for (int i = 0; i < 5; i++) {
141            onView(withId(R.id.drawer_layout)).perform(
142                    wrap(new GeneralSwipeAction(Swipe.FAST, GeneralLocation.CENTER_LEFT,
143                            GeneralLocation.CENTER_RIGHT, Press.FINGER)));
144            assertTrue("Opened drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
145
146            // Try opening the drawer when it's already opened
147            onView(withId(R.id.drawer_layout)).perform(
148                    wrap(new GeneralSwipeAction(Swipe.FAST, GeneralLocation.CENTER_LEFT,
149                            GeneralLocation.CENTER_RIGHT, Press.FINGER)));
150            assertTrue("Opened drawer is still opened #" + i,
151                    mDrawerLayout.isDrawerOpen(GravityCompat.START));
152
153            onView(withId(R.id.drawer_layout)).perform(
154                    wrap(new GeneralSwipeAction(Swipe.FAST, GeneralLocation.CENTER_RIGHT,
155                            GeneralLocation.CENTER_LEFT, Press.FINGER)));
156            assertFalse("Closed drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
157
158            // Try closing the drawer when it's already closed
159            onView(withId(R.id.drawer_layout)).perform(
160                    wrap(new GeneralSwipeAction(Swipe.FAST, GeneralLocation.CENTER_RIGHT,
161                            GeneralLocation.CENTER_LEFT, Press.FINGER)));
162            assertFalse("Closed drawer is still closed #" + i,
163                    mDrawerLayout.isDrawerOpen(GravityCompat.START));
164        }
165    }
166
167    @Test
168    @SmallTest
169    public void testDrawerHeight() {
170        // Open the drawer so it becomes visible
171        onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
172
173        final int drawerLayoutHeight = mDrawerLayout.getHeight();
174        final int startDrawerHeight = mStartDrawer.getHeight();
175        final int contentHeight = mContentView.getHeight();
176
177        // On all devices the height of the drawer layout and the drawer should be identical.
178        assertEquals("Drawer layout and drawer heights", drawerLayoutHeight, startDrawerHeight);
179
180        if (Build.VERSION.SDK_INT < 21) {
181            // On pre-L devices the content height should be the same as the drawer layout height.
182            assertEquals("Drawer layout and content heights on pre-L",
183                    drawerLayoutHeight, contentHeight);
184        } else {
185            // Our drawer layout is configured with android:fitsSystemWindows="true" which should be
186            // respected on L+ devices to extend the drawer layout into the system status bar.
187            // The start drawer is also configured with the same attribute so it should have the
188            // same height as the drawer layout. The main content does not have that attribute
189            // specified, so it should have its height reduced by the height of the system status
190            // bar.
191
192            // Get the system window top inset that was propagated to the top-level DrawerLayout
193            // during its layout.
194            int drawerTopInset = mDrawerLayout.getSystemWindowInsetTop();
195            assertTrue("Drawer top inset is positive on L+", drawerTopInset > 0);
196            assertEquals("Drawer layout and drawer heights on L+",
197                    drawerLayoutHeight - drawerTopInset, contentHeight);
198        }
199    }
200
201    // Tests for listener(s) being notified of various events
202
203    @Test
204    @SmallTest
205    public void testDrawerListenerCallbacksOnOpeningViaAPI() {
206        // Register a mock listener
207        DrawerLayout.DrawerListener mockedListener = mock(DrawerLayout.DrawerListener.class);
208        mDrawerLayout.addDrawerListener(mockedListener);
209
210        // Open the drawer so it becomes visible
211        onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
212
213        // We expect that our listener has been notified that the drawer has been opened
214        // with the reference to our drawer
215        verify(mockedListener, times(1)).onDrawerOpened(mStartDrawer);
216        // We expect that our listener has not been notified that the drawer has been closed
217        verify(mockedListener, never()).onDrawerClosed(any(View.class));
218
219        // We expect that our listener has been notified at least once on the drawer slide
220        // event. We expect that all such callbacks pass the reference to our drawer as the first
221        // parameter, and we capture the float slide values for further analysis
222        ArgumentCaptor<Float> floatSlideCaptor = ArgumentCaptor.forClass(float.class);
223        verify(mockedListener, atLeastOnce()).onDrawerSlide(eq(mStartDrawer),
224                floatSlideCaptor.capture());
225        // Now we verify that calls to onDrawerSlide "gave" us an increasing sequence of values
226        // in [0..1] range. Note that we don't have any expectation on how many times onDrawerSlide
227        // is called since that depends on the hardware capabilities of the device and the current
228        // load on the CPU / GPU.
229        assertThat(floatSlideCaptor.getAllValues(), inRange(0.0f, 1.0f));
230        assertThat(floatSlideCaptor.getAllValues(), inAscendingOrder());
231
232        // We expect that our listener will be called with specific state changes
233        InOrder inOrder = inOrder(mockedListener);
234        inOrder.verify(mockedListener).onDrawerStateChanged(DrawerLayout.STATE_SETTLING);
235        inOrder.verify(mockedListener).onDrawerStateChanged(DrawerLayout.STATE_IDLE);
236
237        mDrawerLayout.removeDrawerListener(mockedListener);
238    }
239
240    @Test
241    @SmallTest
242    public void testDrawerListenerCallbacksOnClosingViaAPI() {
243        // Open the drawer so it becomes visible
244        onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
245
246        // Register a mock listener
247        DrawerLayout.DrawerListener mockedListener = mock(DrawerLayout.DrawerListener.class);
248        mDrawerLayout.addDrawerListener(mockedListener);
249
250        // Close the drawer
251        onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START));
252
253        // We expect that our listener has not been notified that the drawer has been opened
254        verify(mockedListener, never()).onDrawerOpened(any(View.class));
255        // We expect that our listener has been notified that the drawer has been closed
256        // with the reference to our drawer
257        verify(mockedListener, times(1)).onDrawerClosed(mStartDrawer);
258
259        // We expect that our listener has been notified at least once on the drawer slide
260        // event. We expect that all such callbacks pass the reference to our drawer as the first
261        // parameter, and we capture the float slide values for further analysis
262        ArgumentCaptor<Float> floatSlideCaptor = ArgumentCaptor.forClass(float.class);
263        verify(mockedListener, atLeastOnce()).onDrawerSlide(eq(mStartDrawer),
264                floatSlideCaptor.capture());
265        // Now we verify that calls to onDrawerSlide "gave" us a decreasing sequence of values
266        // in [0..1] range. Note that we don't have any expectation on how many times onDrawerSlide
267        // is called since that depends on the hardware capabilities of the device and the current
268        // load on the CPU / GPU.
269        assertThat(floatSlideCaptor.getAllValues(), inRange(0.0f, 1.0f));
270        assertThat(floatSlideCaptor.getAllValues(), inDescendingOrder());
271
272        // We expect that our listener will be called with specific state changes
273        InOrder inOrder = inOrder(mockedListener);
274        inOrder.verify(mockedListener).onDrawerStateChanged(DrawerLayout.STATE_SETTLING);
275        inOrder.verify(mockedListener).onDrawerStateChanged(DrawerLayout.STATE_IDLE);
276
277        mDrawerLayout.removeDrawerListener(mockedListener);
278    }
279
280    @Test
281    @SmallTest
282    public void testDrawerListenerCallbacksOnOpeningViaSwipes() {
283        // Register a mock listener
284        DrawerLayout.DrawerListener mockedListener = mock(DrawerLayout.DrawerListener.class);
285        mDrawerLayout.addDrawerListener(mockedListener);
286
287        // Open the drawer so it becomes visible
288        // Note that we're using GeneralSwipeAction instead of swipeLeft() / swipeRight().
289        // Those Espresso actions use edge fuzzying which doesn't work well with edge-based
290        // detection of swiping the drawers open in DrawerLayout.
291        // It's critically important to wrap the GeneralSwipeAction to "wait" until the
292        // DrawerLayout has settled to STATE_IDLE state before continuing to query the drawer
293        // open / close state. This is done in DrawerLayoutActions.wrap method.
294        onView(withId(R.id.drawer_layout)).perform(
295                wrap(new GeneralSwipeAction(Swipe.FAST, GeneralLocation.CENTER_LEFT,
296                        GeneralLocation.CENTER_RIGHT, Press.FINGER)));
297
298        // We expect that our listener has been notified that the drawer has been opened
299        // with the reference to our drawer
300        verify(mockedListener, times(1)).onDrawerOpened(mStartDrawer);
301        // We expect that our listener has not been notified that the drawer has been closed
302        verify(mockedListener, never()).onDrawerClosed(any(View.class));
303
304        // We expect that our listener has been notified at least once on the drawer slide
305        // event. We expect that all such callbacks pass the reference to our drawer as the first
306        // parameter, and we capture the float slide values for further analysis
307        ArgumentCaptor<Float> floatSlideCaptor = ArgumentCaptor.forClass(float.class);
308        verify(mockedListener, atLeastOnce()).onDrawerSlide(eq(mStartDrawer),
309                floatSlideCaptor.capture());
310        // Now we verify that calls to onDrawerSlide "gave" us an increasing sequence of values
311        // in [0..1] range. Note that we don't have any expectation on how many times onDrawerSlide
312        // is called since that depends on the hardware capabilities of the device and the current
313        // load on the CPU / GPU.
314        assertThat(floatSlideCaptor.getAllValues(), inRange(0.0f, 1.0f));
315        assertThat(floatSlideCaptor.getAllValues(), inAscendingOrder());
316
317        // We expect that our listener will be called with specific state changes
318        InOrder inOrder = inOrder(mockedListener);
319        inOrder.verify(mockedListener).onDrawerStateChanged(DrawerLayout.STATE_DRAGGING);
320        inOrder.verify(mockedListener).onDrawerStateChanged(DrawerLayout.STATE_IDLE);
321
322        mDrawerLayout.removeDrawerListener(mockedListener);
323    }
324
325    @Test
326    @SmallTest
327    public void testDrawerListenerCallbacksOnClosingViaSwipes() {
328        // Open the drawer so it becomes visible
329        onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
330
331        // Register a mock listener
332        DrawerLayout.DrawerListener mockedListener = mock(DrawerLayout.DrawerListener.class);
333        mDrawerLayout.addDrawerListener(mockedListener);
334
335        // Close the drawer
336        // Note that we're using GeneralSwipeAction instead of swipeLeft() / swipeRight().
337        // Those Espresso actions use edge fuzzying which doesn't work well with edge-based
338        // detection of swiping the drawers open in DrawerLayout.
339        // It's critically important to wrap the GeneralSwipeAction to "wait" until the
340        // DrawerLayout has settled to STATE_IDLE state before continuing to query the drawer
341        // open / close state. This is done in DrawerLayoutActions.wrap method.
342        onView(withId(R.id.drawer_layout)).perform(
343                wrap(new GeneralSwipeAction(Swipe.FAST, GeneralLocation.CENTER_RIGHT,
344                        GeneralLocation.CENTER_LEFT, Press.FINGER)));
345
346        // We expect that our listener has not been notified that the drawer has been opened
347        verify(mockedListener, never()).onDrawerOpened(any(View.class));
348        // We expect that our listener has been notified that the drawer has been closed
349        // with the reference to our drawer
350        verify(mockedListener, times(1)).onDrawerClosed(mStartDrawer);
351
352        // We expect that our listener has been notified at least once on the drawer slide
353        // event. We expect that all such callbacks pass the reference to our drawer as the first
354        // parameter, and we capture the float slide values for further analysis
355        ArgumentCaptor<Float> floatSlideCaptor = ArgumentCaptor.forClass(float.class);
356        verify(mockedListener, atLeastOnce()).onDrawerSlide(eq(mStartDrawer),
357                floatSlideCaptor.capture());
358        // Now we verify that calls to onDrawerSlide "gave" us a decreasing sequence of values
359        // in [0..1] range. Note that we don't have any expectation on how many times onDrawerSlide
360        // is called since that depends on the hardware capabilities of the device and the current
361        // load on the CPU / GPU.
362        assertThat(floatSlideCaptor.getAllValues(), inRange(0.0f, 1.0f));
363        assertThat(floatSlideCaptor.getAllValues(), inDescendingOrder());
364
365        // We expect that our listener will be called with specific state changes
366        InOrder inOrder = inOrder(mockedListener);
367        inOrder.verify(mockedListener).onDrawerStateChanged(DrawerLayout.STATE_DRAGGING);
368        inOrder.verify(mockedListener).onDrawerStateChanged(DrawerLayout.STATE_IDLE);
369
370        mDrawerLayout.removeDrawerListener(mockedListener);
371    }
372}
373