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