1 /* 2 * Copyright 2018 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 package androidx.viewpager.widget; 17 18 import static android.support.test.espresso.Espresso.onView; 19 import static android.support.test.espresso.action.ViewActions.pressKey; 20 import static android.support.test.espresso.action.ViewActions.swipeLeft; 21 import static android.support.test.espresso.action.ViewActions.swipeRight; 22 import static android.support.test.espresso.assertion.PositionAssertions.isBelow; 23 import static android.support.test.espresso.assertion.PositionAssertions.isBottomAlignedWith; 24 import static android.support.test.espresso.assertion.PositionAssertions.isLeftAlignedWith; 25 import static android.support.test.espresso.assertion.PositionAssertions.isRightAlignedWith; 26 import static android.support.test.espresso.assertion.PositionAssertions.isTopAlignedWith; 27 import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist; 28 import static android.support.test.espresso.assertion.ViewAssertions.matches; 29 import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant; 30 import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; 31 import static android.support.test.espresso.matcher.ViewMatchers.withId; 32 import static android.support.test.espresso.matcher.ViewMatchers.withText; 33 import static android.support.v4.testutils.TestUtilsAssertions.hasDisplayedChildren; 34 import static android.support.v4.testutils.TestUtilsMatchers.backgroundColor; 35 import static android.support.v4.testutils.TestUtilsMatchers.centerAlignedInParent; 36 import static android.support.v4.testutils.TestUtilsMatchers.endAlignedToParent; 37 import static android.support.v4.testutils.TestUtilsMatchers.isOfClass; 38 import static android.support.v4.testutils.TestUtilsMatchers.startAlignedToParent; 39 40 import static org.hamcrest.MatcherAssert.assertThat; 41 import static org.hamcrest.Matchers.allOf; 42 import static org.hamcrest.Matchers.is; 43 import static org.hamcrest.core.IsNot.not; 44 import static org.junit.Assert.assertEquals; 45 import static org.junit.Assert.assertFalse; 46 import static org.junit.Assert.assertTrue; 47 import static org.mockito.Mockito.anyInt; 48 import static org.mockito.Mockito.atLeastOnce; 49 import static org.mockito.Mockito.mock; 50 import static org.mockito.Mockito.never; 51 import static org.mockito.Mockito.times; 52 import static org.mockito.Mockito.verify; 53 54 import android.app.Activity; 55 import android.graphics.Color; 56 import android.support.test.espresso.ViewAction; 57 import android.support.test.espresso.action.EspressoKey; 58 import android.support.test.filters.FlakyTest; 59 import android.support.test.filters.LargeTest; 60 import android.support.test.filters.MediumTest; 61 import android.support.test.rule.ActivityTestRule; 62 import android.support.v4.testutils.TestUtilsMatchers; 63 import android.text.TextUtils; 64 import android.util.Pair; 65 import android.view.KeyEvent; 66 import android.view.View; 67 import android.view.ViewGroup; 68 import android.widget.Button; 69 import android.widget.LinearLayout; 70 import android.widget.TextView; 71 72 import androidx.viewpager.test.R; 73 74 import org.junit.After; 75 import org.junit.Assert; 76 import org.junit.Before; 77 import org.junit.Rule; 78 import org.junit.Test; 79 import org.mockito.ArgumentCaptor; 80 81 import java.util.ArrayList; 82 import java.util.List; 83 84 /** 85 * Base class for testing <code>ViewPager</code>. Most of the testing logic should be in this 86 * class as it is independent on the specific pager title implementation (interactive or non 87 * interactive). 88 * 89 * Testing logic that does depend on the specific pager title implementation is pushed into the 90 * extending classes in <code>assertStripInteraction()</code> method. 91 */ 92 public abstract class BaseViewPagerTest<T extends Activity> { 93 @Rule 94 public final ActivityTestRule<T> mActivityTestRule; 95 96 private static final int DIRECTION_LEFT = -1; 97 private static final int DIRECTION_RIGHT = 1; 98 protected ViewPager mViewPager; 99 100 protected static class BasePagerAdapter<Q> extends PagerAdapter { 101 protected ArrayList<Pair<String, Q>> mEntries = new ArrayList<>(); 102 add(String title, Q content)103 public void add(String title, Q content) { 104 mEntries.add(new Pair<>(title, content)); 105 } 106 107 @Override getCount()108 public int getCount() { 109 return mEntries.size(); 110 } 111 configureInstantiatedItem(View view, int position)112 protected void configureInstantiatedItem(View view, int position) { 113 switch (position) { 114 case 0: 115 view.setId(R.id.page_0); 116 break; 117 case 1: 118 view.setId(R.id.page_1); 119 break; 120 case 2: 121 view.setId(R.id.page_2); 122 break; 123 case 3: 124 view.setId(R.id.page_3); 125 break; 126 case 4: 127 view.setId(R.id.page_4); 128 break; 129 case 5: 130 view.setId(R.id.page_5); 131 break; 132 case 6: 133 view.setId(R.id.page_6); 134 break; 135 case 7: 136 view.setId(R.id.page_7); 137 break; 138 case 8: 139 view.setId(R.id.page_8); 140 break; 141 case 9: 142 view.setId(R.id.page_9); 143 break; 144 } 145 } 146 147 @Override destroyItem(ViewGroup container, int position, Object object)148 public void destroyItem(ViewGroup container, int position, Object object) { 149 // The adapter is also responsible for removing the view. 150 container.removeView(((ViewHolder) object).view); 151 } 152 153 @Override getItemPosition(Object object)154 public int getItemPosition(Object object) { 155 return ((ViewHolder) object).position; 156 } 157 158 @Override isViewFromObject(View view, Object object)159 public boolean isViewFromObject(View view, Object object) { 160 return ((ViewHolder) object).view == view; 161 } 162 163 @Override getPageTitle(int position)164 public CharSequence getPageTitle(int position) { 165 return mEntries.get(position).first; 166 } 167 168 protected static class ViewHolder { 169 final View view; 170 final int position; 171 ViewHolder(View view, int position)172 public ViewHolder(View view, int position) { 173 this.view = view; 174 this.position = position; 175 } 176 } 177 } 178 179 protected static class ColorPagerAdapter extends BasePagerAdapter<Integer> { 180 @Override instantiateItem(ViewGroup container, int position)181 public Object instantiateItem(ViewGroup container, int position) { 182 final View view = new View(container.getContext()); 183 view.setBackgroundColor(mEntries.get(position).second); 184 configureInstantiatedItem(view, position); 185 186 // Unlike ListView adapters, the ViewPager adapter is responsible 187 // for adding the view to the container. 188 container.addView(view); 189 190 return new ViewHolder(view, position); 191 } 192 } 193 194 protected static class TextPagerAdapter extends BasePagerAdapter<String> { 195 @Override instantiateItem(ViewGroup container, int position)196 public Object instantiateItem(ViewGroup container, int position) { 197 final TextView view = new TextView(container.getContext()); 198 view.setText(mEntries.get(position).second); 199 configureInstantiatedItem(view, position); 200 201 // Unlike ListView adapters, the ViewPager adapter is responsible 202 // for adding the view to the container. 203 container.addView(view); 204 205 return new ViewHolder(view, position); 206 } 207 } 208 209 protected static class ButtonPagerAdapter extends BasePagerAdapter<Integer> { 210 private ArrayList<Button[]> mButtons = new ArrayList<>(); 211 212 @Override add(String title, Integer content)213 public void add(String title, Integer content) { 214 super.add(title, content); 215 mButtons.add(new Button[3]); 216 } 217 218 @Override instantiateItem(ViewGroup container, int position)219 public Object instantiateItem(ViewGroup container, int position) { 220 final LinearLayout view = new LinearLayout(container.getContext()); 221 view.setBackgroundColor(mEntries.get(position).second); 222 view.setOrientation(LinearLayout.HORIZONTAL); 223 configureInstantiatedItem(view, position); 224 225 for (int i = 0; i < 3; ++i) { 226 Button but = new Button(container.getContext()); 227 but.setText("" + i); 228 but.setFocusableInTouchMode(true); 229 view.addView(but, ViewGroup.LayoutParams.WRAP_CONTENT, 230 ViewGroup.LayoutParams.WRAP_CONTENT); 231 mButtons.get(position)[i] = but; 232 } 233 234 // Unlike ListView adapters, the ViewPager adapter is responsible 235 // for adding the view to the container. 236 container.addView(view); 237 238 return new ViewHolder(view, position); 239 } 240 getButton(int page, int idx)241 public View getButton(int page, int idx) { 242 return mButtons.get(page)[idx]; 243 } 244 } 245 BaseViewPagerTest(Class<T> activityClass)246 public BaseViewPagerTest(Class<T> activityClass) { 247 mActivityTestRule = new ActivityTestRule<T>(activityClass); 248 } 249 250 @Before setUp()251 public void setUp() throws Exception { 252 final T activity = mActivityTestRule.getActivity(); 253 mViewPager = (ViewPager) activity.findViewById(R.id.pager); 254 255 ColorPagerAdapter adapter = new ColorPagerAdapter(); 256 adapter.add("Red", Color.RED); 257 adapter.add("Green", Color.GREEN); 258 adapter.add("Blue", Color.BLUE); 259 onView(withId(R.id.pager)).perform( 260 ViewPagerActions.setAdapter(adapter), ViewPagerActions.scrollToPage(0, false)); 261 } 262 263 @After tearDown()264 public void tearDown() throws Exception { 265 onView(withId(R.id.pager)).perform(ViewPagerActions.setAdapter(null)); 266 } 267 verifyPageSelections(boolean smoothScroll)268 private void verifyPageSelections(boolean smoothScroll) { 269 assertEquals("Initial state", 0, mViewPager.getCurrentItem()); 270 271 ViewPager.OnPageChangeListener mockPageChangeListener = 272 mock(ViewPager.OnPageChangeListener.class); 273 mViewPager.addOnPageChangeListener(mockPageChangeListener); 274 275 onView(withId(R.id.pager)).perform(ViewPagerActions.scrollRight(smoothScroll)); 276 assertEquals("Scroll right", 1, mViewPager.getCurrentItem()); 277 verify(mockPageChangeListener, times(1)).onPageSelected(1); 278 279 onView(withId(R.id.pager)).perform(ViewPagerActions.scrollRight(smoothScroll)); 280 assertEquals("Scroll right", 2, mViewPager.getCurrentItem()); 281 verify(mockPageChangeListener, times(1)).onPageSelected(2); 282 283 // Try "scrolling" beyond the last page and test that we're still on the last page. 284 onView(withId(R.id.pager)).perform(ViewPagerActions.scrollRight(smoothScroll)); 285 assertEquals("Scroll right beyond last page", 2, mViewPager.getCurrentItem()); 286 // We're still on this page, so we shouldn't have been called again with index 2 287 verify(mockPageChangeListener, times(1)).onPageSelected(2); 288 289 onView(withId(R.id.pager)).perform(ViewPagerActions.scrollLeft(smoothScroll)); 290 assertEquals("Scroll left", 1, mViewPager.getCurrentItem()); 291 // Verify that this is the second time we're called on index 1 292 verify(mockPageChangeListener, times(2)).onPageSelected(1); 293 294 onView(withId(R.id.pager)).perform(ViewPagerActions.scrollLeft(smoothScroll)); 295 assertEquals("Scroll left", 0, mViewPager.getCurrentItem()); 296 // Verify that this is the first time we're called on index 0 297 verify(mockPageChangeListener, times(1)).onPageSelected(0); 298 299 // Try "scrolling" beyond the first page and test that we're still on the first page. 300 onView(withId(R.id.pager)).perform(ViewPagerActions.scrollLeft(smoothScroll)); 301 assertEquals("Scroll left beyond first page", 0, mViewPager.getCurrentItem()); 302 // We're still on this page, so we shouldn't have been called again with index 0 303 verify(mockPageChangeListener, times(1)).onPageSelected(0); 304 305 // Unregister our listener 306 mViewPager.removeOnPageChangeListener(mockPageChangeListener); 307 308 // Go from index 0 to index 2 309 onView(withId(R.id.pager)).perform(ViewPagerActions.scrollToPage(2, smoothScroll)); 310 assertEquals("Scroll to last page", 2, mViewPager.getCurrentItem()); 311 // Our listener is not registered anymore, so we shouldn't have been called with index 2 312 verify(mockPageChangeListener, times(1)).onPageSelected(2); 313 314 // And back to 0 315 onView(withId(R.id.pager)).perform(ViewPagerActions.scrollToPage(0, smoothScroll)); 316 assertEquals("Scroll to first page", 0, mViewPager.getCurrentItem()); 317 // Our listener is not registered anymore, so we shouldn't have been called with index 0 318 verify(mockPageChangeListener, times(1)).onPageSelected(0); 319 320 // Verify the overall sequence of calls to onPageSelected of our listener 321 ArgumentCaptor<Integer> pageSelectedCaptor = ArgumentCaptor.forClass(int.class); 322 verify(mockPageChangeListener, times(4)).onPageSelected(pageSelectedCaptor.capture()); 323 assertThat(pageSelectedCaptor.getAllValues(), TestUtilsMatchers.matches(1, 2, 1, 0)); 324 } 325 326 @Test 327 @MediumTest testPageSelectionsImmediate()328 public void testPageSelectionsImmediate() { 329 verifyPageSelections(false); 330 } 331 332 @Test 333 @LargeTest testPageSelectionsSmooth()334 public void testPageSelectionsSmooth() { 335 verifyPageSelections(true); 336 } 337 verifyPageChangeViewActions(ViewAction next, ViewAction previous)338 private void verifyPageChangeViewActions(ViewAction next, ViewAction previous) { 339 assertEquals("Initial state", 0, mViewPager.getCurrentItem()); 340 assertFalse(mViewPager.canScrollHorizontally(DIRECTION_LEFT)); 341 assertTrue(mViewPager.canScrollHorizontally(DIRECTION_RIGHT)); 342 343 ViewPager.OnPageChangeListener mockPageChangeListener = 344 mock(ViewPager.OnPageChangeListener.class); 345 mViewPager.addOnPageChangeListener(mockPageChangeListener); 346 347 onView(withId(R.id.pager)).perform(next); 348 assertEquals("Move to next page", 1, mViewPager.getCurrentItem()); 349 verify(mockPageChangeListener, times(1)).onPageSelected(1); 350 assertTrue(mViewPager.canScrollHorizontally(DIRECTION_LEFT)); 351 assertTrue(mViewPager.canScrollHorizontally(DIRECTION_RIGHT)); 352 353 onView(withId(R.id.pager)).perform(next); 354 assertEquals("Move to next page", 2, mViewPager.getCurrentItem()); 355 verify(mockPageChangeListener, times(1)).onPageSelected(2); 356 assertTrue(mViewPager.canScrollHorizontally(DIRECTION_LEFT)); 357 assertFalse(mViewPager.canScrollHorizontally(DIRECTION_RIGHT)); 358 359 // Try swiping beyond the last page and test that we're still on the last page. 360 onView(withId(R.id.pager)).perform(next); 361 assertEquals("Attempt to move to next page beyond last page", 2, 362 mViewPager.getCurrentItem()); 363 // We're still on this page, so we shouldn't have been called again with index 2 364 verify(mockPageChangeListener, times(1)).onPageSelected(2); 365 assertTrue(mViewPager.canScrollHorizontally(DIRECTION_LEFT)); 366 assertFalse(mViewPager.canScrollHorizontally(DIRECTION_RIGHT)); 367 368 onView(withId(R.id.pager)).perform(previous); 369 assertEquals("Move to previous page", 1, mViewPager.getCurrentItem()); 370 // Verify that this is the second time we're called on index 1 371 verify(mockPageChangeListener, times(2)).onPageSelected(1); 372 assertTrue(mViewPager.canScrollHorizontally(DIRECTION_LEFT)); 373 assertTrue(mViewPager.canScrollHorizontally(DIRECTION_RIGHT)); 374 375 onView(withId(R.id.pager)).perform(previous); 376 assertEquals("Move to previous page", 0, mViewPager.getCurrentItem()); 377 // Verify that this is the first time we're called on index 0 378 verify(mockPageChangeListener, times(1)).onPageSelected(0); 379 assertFalse(mViewPager.canScrollHorizontally(DIRECTION_LEFT)); 380 assertTrue(mViewPager.canScrollHorizontally(DIRECTION_RIGHT)); 381 382 // Try swiping beyond the first page and test that we're still on the first page. 383 onView(withId(R.id.pager)).perform(previous); 384 assertEquals("Attempt to move to previous page beyond first page", 0, 385 mViewPager.getCurrentItem()); 386 // We're still on this page, so we shouldn't have been called again with index 0 387 verify(mockPageChangeListener, times(1)).onPageSelected(0); 388 assertFalse(mViewPager.canScrollHorizontally(DIRECTION_LEFT)); 389 assertTrue(mViewPager.canScrollHorizontally(DIRECTION_RIGHT)); 390 391 mViewPager.removeOnPageChangeListener(mockPageChangeListener); 392 393 // Verify the overall sequence of calls to onPageSelected of our listener 394 ArgumentCaptor<Integer> pageSelectedCaptor = ArgumentCaptor.forClass(int.class); 395 verify(mockPageChangeListener, times(4)).onPageSelected(pageSelectedCaptor.capture()); 396 assertThat(pageSelectedCaptor.getAllValues(), TestUtilsMatchers.matches(1, 2, 1, 0)); 397 } 398 399 @Test 400 @LargeTest testPageSwipes()401 public void testPageSwipes() { 402 verifyPageChangeViewActions(ViewPagerActions.wrap(swipeLeft()), ViewPagerActions.wrap(swipeRight())); 403 } 404 405 @Test 406 @LargeTest testArrowPageChanges()407 public void testArrowPageChanges() { 408 verifyPageChangeViewActions( 409 ViewPagerActions.arrowScroll(View.FOCUS_RIGHT), ViewPagerActions.arrowScroll(View.FOCUS_LEFT)); 410 } 411 412 @Test 413 @LargeTest testPageSwipesComposite()414 public void testPageSwipesComposite() { 415 assertEquals("Initial state", 0, mViewPager.getCurrentItem()); 416 417 onView(withId(R.id.pager)).perform(ViewPagerActions.wrap(swipeLeft()), ViewPagerActions.wrap(swipeLeft())); 418 assertEquals("Swipe twice left", 2, mViewPager.getCurrentItem()); 419 420 onView(withId(R.id.pager)).perform(ViewPagerActions.wrap(swipeLeft()), ViewPagerActions.wrap(swipeRight())); 421 assertEquals("Swipe left beyond last page and then right", 1, mViewPager.getCurrentItem()); 422 423 onView(withId(R.id.pager)).perform( 424 ViewPagerActions.wrap(swipeRight()), ViewPagerActions.wrap(swipeRight())); 425 assertEquals("Swipe right and then right beyond first page", 0, 426 mViewPager.getCurrentItem()); 427 428 onView(withId(R.id.pager)).perform( 429 ViewPagerActions.wrap(swipeRight()), ViewPagerActions.wrap(swipeLeft())); 430 assertEquals("Swipe right beyond first page and then left", 1, mViewPager.getCurrentItem()); 431 } 432 verifyPageContent(boolean smoothScroll)433 private void verifyPageContent(boolean smoothScroll) { 434 assertEquals("Initial state", 0, mViewPager.getCurrentItem()); 435 436 // Verify the displayed content to match the initial adapter - with 3 pages and each 437 // one rendered as a View. 438 439 // Page #0 should be displayed, page #1 should not be displayed and page #2 should not exist 440 // yet as it's outside of the offscreen window limit. 441 onView(withId(R.id.page_0)).check(matches(allOf( 442 isOfClass(View.class), 443 isDisplayed(), 444 backgroundColor(Color.RED)))); 445 onView(withId(R.id.page_1)).check(matches(not(isDisplayed()))); 446 onView(withId(R.id.page_2)).check(doesNotExist()); 447 448 // Scroll one page to select page #1 449 onView(withId(R.id.pager)).perform(ViewPagerActions.scrollRight(smoothScroll)); 450 assertEquals("Scroll right", 1, mViewPager.getCurrentItem()); 451 // Pages #0 / #2 should not be displayed, page #1 should be displayed. 452 onView(withId(R.id.page_0)).check(matches(not(isDisplayed()))); 453 onView(withId(R.id.page_1)).check(matches(allOf( 454 isOfClass(View.class), 455 isDisplayed(), 456 backgroundColor(Color.GREEN)))); 457 onView(withId(R.id.page_2)).check(matches(not(isDisplayed()))); 458 459 // Scroll one more page to select page #2 460 onView(withId(R.id.pager)).perform(ViewPagerActions.scrollRight(smoothScroll)); 461 assertEquals("Scroll right again", 2, mViewPager.getCurrentItem()); 462 // Page #0 should not exist as it's bumped to the outside of the offscreen window limit, 463 // page #1 should not be displayed, page #2 should be displayed. 464 onView(withId(R.id.page_0)).check(doesNotExist()); 465 onView(withId(R.id.page_1)).check(matches(not(isDisplayed()))); 466 onView(withId(R.id.page_2)).check(matches(allOf( 467 isOfClass(View.class), 468 isDisplayed(), 469 backgroundColor(Color.BLUE)))); 470 } 471 472 @Test 473 @MediumTest testPageContentImmediate()474 public void testPageContentImmediate() { 475 verifyPageContent(false); 476 } 477 478 @Test 479 @LargeTest testPageContentSmooth()480 public void testPageContentSmooth() { 481 verifyPageContent(true); 482 } 483 verifyAdapterChange(boolean smoothScroll)484 private void verifyAdapterChange(boolean smoothScroll) { 485 // Verify that we have the expected initial adapter 486 PagerAdapter initialAdapter = mViewPager.getAdapter(); 487 assertEquals("Initial adapter class", ColorPagerAdapter.class, initialAdapter.getClass()); 488 assertEquals("Initial adapter page count", 3, initialAdapter.getCount()); 489 490 // Create a new adapter 491 TextPagerAdapter newAdapter = new TextPagerAdapter(); 492 newAdapter.add("Title 0", "Body 0"); 493 newAdapter.add("Title 1", "Body 1"); 494 newAdapter.add("Title 2", "Body 2"); 495 newAdapter.add("Title 3", "Body 3"); 496 onView(withId(R.id.pager)).perform( 497 ViewPagerActions.setAdapter(newAdapter), ViewPagerActions.scrollToPage(0, smoothScroll)); 498 499 // Verify the displayed content to match the newly set adapter - with 4 pages and each 500 // one rendered as a TextView. 501 502 // Page #0 should be displayed, page #1 should not be displayed and pages #2 / #3 should not 503 // exist yet as they're outside of the offscreen window limit. 504 onView(withId(R.id.page_0)).check(matches(allOf( 505 isOfClass(TextView.class), 506 isDisplayed(), 507 withText("Body 0")))); 508 onView(withId(R.id.page_1)).check(matches(not(isDisplayed()))); 509 onView(withId(R.id.page_2)).check(doesNotExist()); 510 onView(withId(R.id.page_3)).check(doesNotExist()); 511 512 // Scroll one page to select page #1 513 onView(withId(R.id.pager)).perform(ViewPagerActions.scrollRight(smoothScroll)); 514 assertEquals("Scroll right", 1, mViewPager.getCurrentItem()); 515 // Pages #0 / #2 should not be displayed, page #1 should be displayed, page #3 is still 516 // outside the offscreen limit. 517 onView(withId(R.id.page_0)).check(matches(not(isDisplayed()))); 518 onView(withId(R.id.page_1)).check(matches(allOf( 519 isOfClass(TextView.class), 520 isDisplayed(), 521 withText("Body 1")))); 522 onView(withId(R.id.page_2)).check(matches(not(isDisplayed()))); 523 onView(withId(R.id.page_3)).check(doesNotExist()); 524 525 // Scroll one more page to select page #2 526 onView(withId(R.id.pager)).perform(ViewPagerActions.scrollRight(smoothScroll)); 527 assertEquals("Scroll right again", 2, mViewPager.getCurrentItem()); 528 // Page #0 should not exist as it's bumped to the outside of the offscreen window limit, 529 // pages #1 / #3 should not be displayed, page #2 should be displayed. 530 onView(withId(R.id.page_0)).check(doesNotExist()); 531 onView(withId(R.id.page_1)).check(matches(not(isDisplayed()))); 532 onView(withId(R.id.page_2)).check(matches(allOf( 533 isOfClass(TextView.class), 534 isDisplayed(), 535 withText("Body 2")))); 536 onView(withId(R.id.page_3)).check(matches(not(isDisplayed()))); 537 538 // Scroll one more page to select page #2 539 onView(withId(R.id.pager)).perform(ViewPagerActions.scrollRight(smoothScroll)); 540 assertEquals("Scroll right one more time", 3, mViewPager.getCurrentItem()); 541 // Pages #0 / #1 should not exist as they're bumped to the outside of the offscreen window 542 // limit, page #2 should not be displayed, page #3 should be displayed. 543 onView(withId(R.id.page_0)).check(doesNotExist()); 544 onView(withId(R.id.page_1)).check(doesNotExist()); 545 onView(withId(R.id.page_2)).check(matches(not(isDisplayed()))); 546 onView(withId(R.id.page_3)).check(matches(allOf( 547 isOfClass(TextView.class), 548 isDisplayed(), 549 withText("Body 3")))); 550 } 551 552 @Test 553 @MediumTest testAdapterChangeImmediate()554 public void testAdapterChangeImmediate() { 555 verifyAdapterChange(false); 556 } 557 558 @Test 559 @LargeTest testAdapterChangeSmooth()560 public void testAdapterChangeSmooth() { 561 verifyAdapterChange(true); 562 } 563 verifyTitleStripLayout(String expectedStartTitle, String expectedSelectedTitle, String expectedEndTitle, int selectedPageId)564 private void verifyTitleStripLayout(String expectedStartTitle, String expectedSelectedTitle, 565 String expectedEndTitle, int selectedPageId) { 566 // Check that the title strip spans the whole width of the pager and is aligned to 567 // its top 568 onView(withId(R.id.titles)).check(isLeftAlignedWith(withId(R.id.pager))); 569 onView(withId(R.id.titles)).check(isRightAlignedWith(withId(R.id.pager))); 570 onView(withId(R.id.titles)).check(isTopAlignedWith(withId(R.id.pager))); 571 572 // Check that the currently selected page spans the whole width of the pager and is below 573 // the title strip 574 onView(withId(selectedPageId)).check(isLeftAlignedWith(withId(R.id.pager))); 575 onView(withId(selectedPageId)).check(isRightAlignedWith(withId(R.id.pager))); 576 onView(withId(selectedPageId)).check(isBelow(withId(R.id.titles))); 577 onView(withId(selectedPageId)).check(isBottomAlignedWith(withId(R.id.pager))); 578 579 boolean hasStartTitle = !TextUtils.isEmpty(expectedStartTitle); 580 boolean hasEndTitle = !TextUtils.isEmpty(expectedEndTitle); 581 582 // Check that the title strip shows the expected number of children (tab titles) 583 int nonNullTitles = (hasStartTitle ? 1 : 0) + 1 + (hasEndTitle ? 1 : 0); 584 onView(withId(R.id.titles)).check(hasDisplayedChildren(nonNullTitles)); 585 586 if (hasStartTitle) { 587 // Check that the title for the start page is displayed at the start edge of its parent 588 // (title strip) 589 onView(withId(R.id.titles)).check(matches(hasDescendant( 590 allOf(withText(expectedStartTitle), isDisplayed(), startAlignedToParent())))); 591 } 592 // Check that the title for the selected page is displayed centered in its parent 593 // (title strip) 594 onView(withId(R.id.titles)).check(matches(hasDescendant( 595 allOf(withText(expectedSelectedTitle), isDisplayed(), centerAlignedInParent())))); 596 if (hasEndTitle) { 597 // Check that the title for the end page is displayed at the end edge of its parent 598 // (title strip) 599 onView(withId(R.id.titles)).check(matches(hasDescendant( 600 allOf(withText(expectedEndTitle), isDisplayed(), endAlignedToParent())))); 601 } 602 } 603 verifyPagerStrip(boolean smoothScroll)604 private void verifyPagerStrip(boolean smoothScroll) { 605 // Set an adapter with 5 pages 606 final ColorPagerAdapter adapter = new ColorPagerAdapter(); 607 adapter.add("Red", Color.RED); 608 adapter.add("Green", Color.GREEN); 609 adapter.add("Blue", Color.BLUE); 610 adapter.add("Yellow", Color.YELLOW); 611 adapter.add("Magenta", Color.MAGENTA); 612 onView(withId(R.id.pager)).perform(ViewPagerActions.setAdapter(adapter), 613 ViewPagerActions.scrollToPage(0, smoothScroll)); 614 615 // Check that the pager has a title strip 616 onView(withId(R.id.pager)).check(matches(hasDescendant(withId(R.id.titles)))); 617 // Check that the title strip is displayed and is of the expected class 618 onView(withId(R.id.titles)).check(matches(allOf( 619 isDisplayed(), isOfClass(getStripClass())))); 620 621 // The following block tests the overall layout of tab strip and main pager content 622 // (vertical stacking), the content of the tab strip (showing texts for the selected 623 // tab and the ones on its left / right) as well as the alignment of the content in the 624 // tab strip (selected in center, others on left and right). 625 626 // Check the content and alignment of title strip for selected page #0 627 verifyTitleStripLayout(null, "Red", "Green", R.id.page_0); 628 629 // Scroll one page to select page #1 and check layout / content of title strip 630 onView(withId(R.id.pager)).perform(ViewPagerActions.scrollRight(smoothScroll)); 631 verifyTitleStripLayout("Red", "Green", "Blue", R.id.page_1); 632 633 // Scroll one page to select page #2 and check layout / content of title strip 634 onView(withId(R.id.pager)).perform(ViewPagerActions.scrollRight(smoothScroll)); 635 verifyTitleStripLayout("Green", "Blue", "Yellow", R.id.page_2); 636 637 // Scroll one page to select page #3 and check layout / content of title strip 638 onView(withId(R.id.pager)).perform(ViewPagerActions.scrollRight(smoothScroll)); 639 verifyTitleStripLayout("Blue", "Yellow", "Magenta", R.id.page_3); 640 641 // Scroll one page to select page #4 and check layout / content of title strip 642 onView(withId(R.id.pager)).perform(ViewPagerActions.scrollRight(smoothScroll)); 643 verifyTitleStripLayout("Yellow", "Magenta", null, R.id.page_4); 644 645 // Scroll back to page #0 646 onView(withId(R.id.pager)).perform(ViewPagerActions.scrollToPage(0, smoothScroll)); 647 648 assertStripInteraction(smoothScroll); 649 } 650 651 @Test 652 @LargeTest testPagerStripImmediate()653 public void testPagerStripImmediate() { 654 verifyPagerStrip(false); 655 } 656 657 @Test 658 @LargeTest testPagerStripSmooth()659 public void testPagerStripSmooth() { 660 verifyPagerStrip(true); 661 } 662 663 /** 664 * Returns the class of the pager strip. 665 */ getStripClass()666 protected abstract Class getStripClass(); 667 668 /** 669 * Checks assertions that are specific to the pager strip implementation (interactive or 670 * non interactive). 671 */ assertStripInteraction(boolean smoothScroll)672 protected abstract void assertStripInteraction(boolean smoothScroll); 673 674 /** 675 * Helper method that performs the specified action on the <code>ViewPager</code> and then 676 * checks the sequence of calls to the page change listener based on the specified expected 677 * scroll state changes. 678 * 679 * If that expected list is empty, this method verifies that there were no calls to 680 * onPageScrollStateChanged when the action was performed. Otherwise it verifies that the actual 681 * sequence of calls to onPageScrollStateChanged matches the expected (specified) one. 682 */ verifyScrollStateChange(ViewAction viewAction, int... expectedScrollStateChanges)683 private void verifyScrollStateChange(ViewAction viewAction, int... expectedScrollStateChanges) { 684 ViewPager.OnPageChangeListener mockPageChangeListener = 685 mock(ViewPager.OnPageChangeListener.class); 686 mViewPager.addOnPageChangeListener(mockPageChangeListener); 687 688 // Perform our action 689 onView(withId(R.id.pager)).perform(viewAction); 690 691 int expectedScrollStateChangeCount = (expectedScrollStateChanges != null) ? 692 expectedScrollStateChanges.length : 0; 693 694 if (expectedScrollStateChangeCount == 0) { 695 verify(mockPageChangeListener, never()).onPageScrollStateChanged(anyInt()); 696 } else { 697 ArgumentCaptor<Integer> pageScrollStateCaptor = ArgumentCaptor.forClass(int.class); 698 verify(mockPageChangeListener, times(expectedScrollStateChangeCount)). 699 onPageScrollStateChanged(pageScrollStateCaptor.capture()); 700 assertThat(pageScrollStateCaptor.getAllValues(), 701 TestUtilsMatchers.matches(expectedScrollStateChanges)); 702 } 703 704 // Remove our mock listener to get back to clean state for the next test 705 mViewPager.removeOnPageChangeListener(mockPageChangeListener); 706 } 707 708 @Test 709 @MediumTest testPageScrollStateChangedImmediate()710 public void testPageScrollStateChangedImmediate() { 711 // Note that all the actions tested in this method are immediate (no scrolling) and 712 // as such we test that we do not get any calls to onPageScrollStateChanged in any of them 713 714 // Select one page to the right 715 verifyScrollStateChange(ViewPagerActions.scrollRight(false)); 716 // Select one more page to the right 717 verifyScrollStateChange(ViewPagerActions.scrollRight(false)); 718 // Select one page to the left 719 verifyScrollStateChange(ViewPagerActions.scrollLeft(false)); 720 // Select one more page to the left 721 verifyScrollStateChange(ViewPagerActions.scrollLeft(false)); 722 // Select last page 723 verifyScrollStateChange(ViewPagerActions.scrollToLast(false)); 724 // Select first page 725 verifyScrollStateChange(ViewPagerActions.scrollToFirst(false)); 726 } 727 728 @Test 729 @LargeTest testPageScrollStateChangedSmooth()730 public void testPageScrollStateChangedSmooth() { 731 // Note that all the actions tested in this method use smooth scrolling and as such we test 732 // that we get the matching calls to onPageScrollStateChanged 733 final int[] expectedScrollStateChanges = new int[] { 734 ViewPager.SCROLL_STATE_SETTLING, ViewPager.SCROLL_STATE_IDLE 735 }; 736 737 // Select one page to the right 738 verifyScrollStateChange(ViewPagerActions.scrollRight(true), expectedScrollStateChanges); 739 // Select one more page to the right 740 verifyScrollStateChange(ViewPagerActions.scrollRight(true), expectedScrollStateChanges); 741 // Select one page to the left 742 verifyScrollStateChange(ViewPagerActions.scrollLeft(true), expectedScrollStateChanges); 743 // Select one more page to the left 744 verifyScrollStateChange(ViewPagerActions.scrollLeft(true), expectedScrollStateChanges); 745 // Select last page 746 verifyScrollStateChange(ViewPagerActions.scrollToLast(true), expectedScrollStateChanges); 747 // Select first page 748 verifyScrollStateChange(ViewPagerActions.scrollToFirst(true), expectedScrollStateChanges); 749 } 750 751 @Test 752 @LargeTest testPageScrollStateChangedSwipe()753 public void testPageScrollStateChangedSwipe() { 754 // Note that all the actions tested in this method use swiping and as such we test 755 // that we get the matching calls to onPageScrollStateChanged 756 final int[] expectedScrollStateChanges = new int[] { ViewPager.SCROLL_STATE_DRAGGING, 757 ViewPager.SCROLL_STATE_SETTLING, ViewPager.SCROLL_STATE_IDLE }; 758 759 // Swipe one page to the left 760 verifyScrollStateChange(ViewPagerActions.wrap(swipeLeft()), expectedScrollStateChanges); 761 assertEquals("Swipe left", 1, mViewPager.getCurrentItem()); 762 763 // Swipe one more page to the left 764 verifyScrollStateChange(ViewPagerActions.wrap(swipeLeft()), expectedScrollStateChanges); 765 assertEquals("Swipe left", 2, mViewPager.getCurrentItem()); 766 767 // Swipe one page to the right 768 verifyScrollStateChange(ViewPagerActions.wrap(swipeRight()), expectedScrollStateChanges); 769 assertEquals("Swipe right", 1, mViewPager.getCurrentItem()); 770 771 // Swipe one more page to the right 772 verifyScrollStateChange(ViewPagerActions.wrap(swipeRight()), expectedScrollStateChanges); 773 assertEquals("Swipe right", 0, mViewPager.getCurrentItem()); 774 } 775 776 /** 777 * Helper method to verify the internal consistency of values passed to 778 * {@link ViewPager.OnPageChangeListener#onPageScrolled} callback when we go from a page with 779 * lower index to a page with higher index. 780 * 781 * @param startPageIndex Index of the starting page. 782 * @param endPageIndex Index of the ending page. 783 * @param pageWidth Page width in pixels. 784 * @param positions List of "position" values passed to all 785 * {@link ViewPager.OnPageChangeListener#onPageScrolled} calls. 786 * @param positionOffsets List of "positionOffset" values passed to all 787 * {@link ViewPager.OnPageChangeListener#onPageScrolled} calls. 788 * @param positionOffsetPixels List of "positionOffsetPixel" values passed to all 789 * {@link ViewPager.OnPageChangeListener#onPageScrolled} calls. 790 */ verifyScrollCallbacksToHigherPage(int startPageIndex, int endPageIndex, int pageWidth, List<Integer> positions, List<Float> positionOffsets, List<Integer> positionOffsetPixels)791 private void verifyScrollCallbacksToHigherPage(int startPageIndex, int endPageIndex, 792 int pageWidth, List<Integer> positions, List<Float> positionOffsets, 793 List<Integer> positionOffsetPixels) { 794 int callbackCount = positions.size(); 795 796 // The last entry in all three lists must match the index of the end page 797 Assert.assertEquals("Position at last index", 798 endPageIndex, (int) positions.get(callbackCount - 1)); 799 Assert.assertEquals("Position offset at last index", 800 0.0f, positionOffsets.get(callbackCount - 1), 0.0f); 801 Assert.assertEquals("Position offset pixel at last index", 802 0, (int) positionOffsetPixels.get(callbackCount - 1)); 803 804 // If this was our only callback, return. This can happen on immediate page change 805 // or on very slow devices. 806 if (callbackCount == 1) { 807 return; 808 } 809 810 // If we have additional callbacks, verify that the values provided to our callback reflect 811 // a valid sequence of events going from startPageIndex to endPageIndex. 812 for (int i = 0; i < callbackCount - 1; i++) { 813 // Page position must be between start page and end page 814 int pagePositionCurr = positions.get(i); 815 if ((pagePositionCurr < startPageIndex) || (pagePositionCurr > endPageIndex)) { 816 Assert.fail("Position at #" + i + " is " + pagePositionCurr + 817 ", but should be between " + startPageIndex + " and " + endPageIndex); 818 } 819 820 // Page position sequence cannot be decreasing 821 int pagePositionNext = positions.get(i + 1); 822 if (pagePositionCurr > pagePositionNext) { 823 Assert.fail("Position at #" + i + " is " + pagePositionCurr + 824 " and then decreases to " + pagePositionNext + " at #" + (i + 1)); 825 } 826 827 // Position offset must be in [0..1) range (inclusive / exclusive) 828 float positionOffsetCurr = positionOffsets.get(i); 829 if ((positionOffsetCurr < 0.0f) || (positionOffsetCurr >= 1.0f)) { 830 Assert.fail("Position offset at #" + i + " is " + positionOffsetCurr + 831 ", but should be in [0..1) range"); 832 } 833 834 // Position pixel offset must be in [0..pageWidth) range (inclusive / exclusive) 835 int positionOffsetPixelCurr = positionOffsetPixels.get(i); 836 if ((positionOffsetPixelCurr < 0.0f) || (positionOffsetPixelCurr >= pageWidth)) { 837 Assert.fail("Position pixel offset at #" + i + " is " + positionOffsetCurr + 838 ", but should be in [0.." + pageWidth + ") range"); 839 } 840 841 // Position pixel offset must match the position offset and page width within 842 // a one-pixel tolerance range 843 Assert.assertEquals("Position pixel offset at #" + i + " is " + 844 positionOffsetPixelCurr + ", but doesn't match position offset which is" + 845 positionOffsetCurr + " and page width which is " + pageWidth, 846 positionOffsetPixelCurr, positionOffsetCurr * pageWidth, 1.0f); 847 848 // If we stay on the same page between this index and the next one, both position 849 // offset and position pixel offset must increase 850 if (pagePositionNext == pagePositionCurr) { 851 float positionOffsetNext = positionOffsets.get(i + 1); 852 // Note that since position offset sequence is float, we are checking for strict 853 // increasing 854 if (positionOffsetNext <= positionOffsetCurr) { 855 Assert.fail("Position offset at #" + i + " is " + positionOffsetCurr + 856 " and at #" + (i + 1) + " is " + positionOffsetNext + 857 ". Since both are for page " + pagePositionCurr + 858 ", they cannot decrease"); 859 } 860 861 int positionOffsetPixelNext = positionOffsetPixels.get(i + 1); 862 // Note that since position offset pixel sequence is the mapping of position offset 863 // into screen pixels, we can get two (or more) callbacks with strictly increasing 864 // position offsets that are converted into the same pixel value. This is why here 865 // we are checking for non-strict increasing 866 if (positionOffsetPixelNext < positionOffsetPixelCurr) { 867 Assert.fail("Position offset pixel at #" + i + " is " + 868 positionOffsetPixelCurr + " and at #" + (i + 1) + " is " + 869 positionOffsetPixelNext + ". Since both are for page " + 870 pagePositionCurr + ", they cannot decrease"); 871 } 872 } 873 } 874 } 875 876 /** 877 * Helper method to verify the internal consistency of values passed to 878 * {@link ViewPager.OnPageChangeListener#onPageScrolled} callback when we go from a page with 879 * higher index to a page with lower index. 880 * 881 * @param startPageIndex Index of the starting page. 882 * @param endPageIndex Index of the ending page. 883 * @param pageWidth Page width in pixels. 884 * @param positions List of "position" values passed to all 885 * {@link ViewPager.OnPageChangeListener#onPageScrolled} calls. 886 * @param positionOffsets List of "positionOffset" values passed to all 887 * {@link ViewPager.OnPageChangeListener#onPageScrolled} calls. 888 * @param positionOffsetPixels List of "positionOffsetPixel" values passed to all 889 * {@link ViewPager.OnPageChangeListener#onPageScrolled} calls. 890 */ verifyScrollCallbacksToLowerPage(int startPageIndex, int endPageIndex, int pageWidth, List<Integer> positions, List<Float> positionOffsets, List<Integer> positionOffsetPixels)891 private void verifyScrollCallbacksToLowerPage(int startPageIndex, int endPageIndex, 892 int pageWidth, List<Integer> positions, List<Float> positionOffsets, 893 List<Integer> positionOffsetPixels) { 894 int callbackCount = positions.size(); 895 896 // The last entry in all three lists must match the index of the end page 897 Assert.assertEquals("Position at last index", 898 endPageIndex, (int) positions.get(callbackCount - 1)); 899 Assert.assertEquals("Position offset at last index", 900 0.0f, positionOffsets.get(callbackCount - 1), 0.0f); 901 Assert.assertEquals("Position offset pixel at last index", 902 0, (int) positionOffsetPixels.get(callbackCount - 1)); 903 904 // If this was our only callback, return. This can happen on immediate page change 905 // or on very slow devices. 906 if (callbackCount == 1) { 907 return; 908 } 909 910 // If we have additional callbacks, verify that the values provided to our callback reflect 911 // a valid sequence of events going from startPageIndex to endPageIndex. 912 for (int i = 0; i < callbackCount - 1; i++) { 913 // Page position must be between start page and end page 914 int pagePositionCurr = positions.get(i); 915 if ((pagePositionCurr > startPageIndex) || (pagePositionCurr < endPageIndex)) { 916 Assert.fail("Position at #" + i + " is " + pagePositionCurr + 917 ", but should be between " + endPageIndex + " and " + startPageIndex); 918 } 919 920 // Page position sequence cannot be increasing 921 int pagePositionNext = positions.get(i + 1); 922 if (pagePositionCurr < pagePositionNext) { 923 Assert.fail("Position at #" + i + " is " + pagePositionCurr + 924 " and then increases to " + pagePositionNext + " at #" + (i + 1)); 925 } 926 927 // Position offset must be in [0..1) range (inclusive / exclusive) 928 float positionOffsetCurr = positionOffsets.get(i); 929 if ((positionOffsetCurr < 0.0f) || (positionOffsetCurr >= 1.0f)) { 930 Assert.fail("Position offset at #" + i + " is " + positionOffsetCurr + 931 ", but should be in [0..1) range"); 932 } 933 934 // Position pixel offset must be in [0..pageWidth) range (inclusive / exclusive) 935 int positionOffsetPixelCurr = positionOffsetPixels.get(i); 936 if ((positionOffsetPixelCurr < 0.0f) || (positionOffsetPixelCurr >= pageWidth)) { 937 Assert.fail("Position pixel offset at #" + i + " is " + positionOffsetCurr + 938 ", but should be in [0.." + pageWidth + ") range"); 939 } 940 941 // Position pixel offset must match the position offset and page width within 942 // a one-pixel tolerance range 943 Assert.assertEquals("Position pixel offset at #" + i + " is " + 944 positionOffsetPixelCurr + ", but doesn't match position offset which is" + 945 positionOffsetCurr + " and page width which is " + pageWidth, 946 positionOffsetPixelCurr, positionOffsetCurr * pageWidth, 1.0f); 947 948 // If we stay on the same page between this index and the next one, both position 949 // offset and position pixel offset must decrease 950 if (pagePositionNext == pagePositionCurr) { 951 float positionOffsetNext = positionOffsets.get(i + 1); 952 // Note that since position offset sequence is float, we are checking for strict 953 // decreasing 954 if (positionOffsetNext >= positionOffsetCurr) { 955 Assert.fail("Position offset at #" + i + " is " + positionOffsetCurr + 956 " and at #" + (i + 1) + " is " + positionOffsetNext + 957 ". Since both are for page " + pagePositionCurr + 958 ", they cannot increase"); 959 } 960 961 int positionOffsetPixelNext = positionOffsetPixels.get(i + 1); 962 // Note that since position offset pixel sequence is the mapping of position offset 963 // into screen pixels, we can get two (or more) callbacks with strictly decreasing 964 // position offsets that are converted into the same pixel value. This is why here 965 // we are checking for non-strict decreasing 966 if (positionOffsetPixelNext > positionOffsetPixelCurr) { 967 Assert.fail("Position offset pixel at #" + i + " is " + 968 positionOffsetPixelCurr + " and at #" + (i + 1) + " is " + 969 positionOffsetPixelNext + ". Since both are for page " + 970 pagePositionCurr + ", they cannot increase"); 971 } 972 } 973 } 974 } 975 verifyScrollCallbacksToHigherPage(ViewAction viewAction, int expectedEndPageIndex)976 private void verifyScrollCallbacksToHigherPage(ViewAction viewAction, 977 int expectedEndPageIndex) { 978 final int startPageIndex = mViewPager.getCurrentItem(); 979 980 ViewPager.OnPageChangeListener mockPageChangeListener = 981 mock(ViewPager.OnPageChangeListener.class); 982 mViewPager.addOnPageChangeListener(mockPageChangeListener); 983 984 // Perform our action 985 onView(withId(R.id.pager)).perform(viewAction); 986 987 final int endPageIndex = mViewPager.getCurrentItem(); 988 Assert.assertEquals("Current item after action", expectedEndPageIndex, endPageIndex); 989 990 ArgumentCaptor<Integer> positionCaptor = ArgumentCaptor.forClass(int.class); 991 ArgumentCaptor<Float> positionOffsetCaptor = ArgumentCaptor.forClass(float.class); 992 ArgumentCaptor<Integer> positionOffsetPixelsCaptor = ArgumentCaptor.forClass(int.class); 993 verify(mockPageChangeListener, atLeastOnce()).onPageScrolled(positionCaptor.capture(), 994 positionOffsetCaptor.capture(), positionOffsetPixelsCaptor.capture()); 995 996 verifyScrollCallbacksToHigherPage(startPageIndex, endPageIndex, mViewPager.getWidth(), 997 positionCaptor.getAllValues(), positionOffsetCaptor.getAllValues(), 998 positionOffsetPixelsCaptor.getAllValues()); 999 1000 // Remove our mock listener to get back to clean state for the next test 1001 mViewPager.removeOnPageChangeListener(mockPageChangeListener); 1002 } 1003 verifyScrollCallbacksToLowerPage(ViewAction viewAction, int expectedEndPageIndex)1004 private void verifyScrollCallbacksToLowerPage(ViewAction viewAction, 1005 int expectedEndPageIndex) { 1006 final int startPageIndex = mViewPager.getCurrentItem(); 1007 1008 ViewPager.OnPageChangeListener mockPageChangeListener = 1009 mock(ViewPager.OnPageChangeListener.class); 1010 mViewPager.addOnPageChangeListener(mockPageChangeListener); 1011 1012 // Perform our action 1013 onView(withId(R.id.pager)).perform(viewAction); 1014 1015 final int endPageIndex = mViewPager.getCurrentItem(); 1016 Assert.assertEquals("Current item after action", expectedEndPageIndex, endPageIndex); 1017 1018 ArgumentCaptor<Integer> positionCaptor = ArgumentCaptor.forClass(int.class); 1019 ArgumentCaptor<Float> positionOffsetCaptor = ArgumentCaptor.forClass(float.class); 1020 ArgumentCaptor<Integer> positionOffsetPixelsCaptor = ArgumentCaptor.forClass(int.class); 1021 verify(mockPageChangeListener, atLeastOnce()).onPageScrolled(positionCaptor.capture(), 1022 positionOffsetCaptor.capture(), positionOffsetPixelsCaptor.capture()); 1023 1024 verifyScrollCallbacksToLowerPage(startPageIndex, endPageIndex, mViewPager.getWidth(), 1025 positionCaptor.getAllValues(), positionOffsetCaptor.getAllValues(), 1026 positionOffsetPixelsCaptor.getAllValues()); 1027 1028 // Remove our mock listener to get back to clean state for the next test 1029 mViewPager.removeOnPageChangeListener(mockPageChangeListener); 1030 } 1031 1032 @Test 1033 @MediumTest testPageScrollPositionChangesImmediate()1034 public void testPageScrollPositionChangesImmediate() { 1035 // Scroll one page to the right 1036 verifyScrollCallbacksToHigherPage(ViewPagerActions.scrollRight(false), 1); 1037 // Scroll one more page to the right 1038 verifyScrollCallbacksToHigherPage(ViewPagerActions.scrollRight(false), 2); 1039 // Scroll one page to the left 1040 verifyScrollCallbacksToLowerPage(ViewPagerActions.scrollLeft(false), 1); 1041 // Scroll one more page to the left 1042 verifyScrollCallbacksToLowerPage(ViewPagerActions.scrollLeft(false), 0); 1043 1044 // Scroll to the last page 1045 verifyScrollCallbacksToHigherPage(ViewPagerActions.scrollToLast(false), 2); 1046 // Scroll to the first page 1047 verifyScrollCallbacksToLowerPage(ViewPagerActions.scrollToFirst(false), 0); 1048 } 1049 1050 @Test 1051 @LargeTest testPageScrollPositionChangesSmooth()1052 public void testPageScrollPositionChangesSmooth() { 1053 // Scroll one page to the right 1054 verifyScrollCallbacksToHigherPage(ViewPagerActions.scrollRight(true), 1); 1055 // Scroll one more page to the right 1056 verifyScrollCallbacksToHigherPage(ViewPagerActions.scrollRight(true), 2); 1057 // Scroll one page to the left 1058 verifyScrollCallbacksToLowerPage(ViewPagerActions.scrollLeft(true), 1); 1059 // Scroll one more page to the left 1060 verifyScrollCallbacksToLowerPage(ViewPagerActions.scrollLeft(true), 0); 1061 1062 // Scroll to the last page 1063 verifyScrollCallbacksToHigherPage(ViewPagerActions.scrollToLast(true), 2); 1064 // Scroll to the first page 1065 verifyScrollCallbacksToLowerPage(ViewPagerActions.scrollToFirst(true), 0); 1066 } 1067 1068 @Test 1069 @LargeTest testPageScrollPositionChangesSwipe()1070 public void testPageScrollPositionChangesSwipe() { 1071 // Swipe one page to the left 1072 verifyScrollCallbacksToHigherPage(ViewPagerActions.wrap(swipeLeft()), 1); 1073 // Swipe one more page to the left 1074 verifyScrollCallbacksToHigherPage(ViewPagerActions.wrap(swipeLeft()), 2); 1075 // Swipe one page to the right 1076 verifyScrollCallbacksToLowerPage(ViewPagerActions.wrap(swipeRight()), 1); 1077 // Swipe one more page to the right 1078 verifyScrollCallbacksToLowerPage(ViewPagerActions.wrap(swipeRight()), 0); 1079 } 1080 1081 @FlakyTest(bugId = 38260187) 1082 @Test 1083 @LargeTest testKeyboardNavigation()1084 public void testKeyboardNavigation() { 1085 ButtonPagerAdapter adapter = new ButtonPagerAdapter(); 1086 adapter.add("Red", Color.RED); 1087 adapter.add("Green", Color.GREEN); 1088 adapter.add("Blue", Color.BLUE); 1089 onView(withId(R.id.pager)).perform( 1090 ViewPagerActions.setAdapter(adapter), ViewPagerActions.scrollToPage(0, false)); 1091 View firstButton = adapter.getButton(0, 0); 1092 firstButton.requestFocus(); 1093 assertTrue(firstButton.isFocused()); 1094 assertEquals(0, mViewPager.getCurrentItem()); 1095 1096 // Normal arrows should traverse contents first 1097 onView(is(firstButton)).perform(pressKey(KeyEvent.KEYCODE_DPAD_RIGHT)); 1098 assertEquals(0, mViewPager.getCurrentItem()); 1099 assertTrue(adapter.getButton(0, 1).isFocused()); 1100 1101 // Alt arrows should change page even if there are more focusables in that direction 1102 onView(is(adapter.getButton(0, 1))).perform(pressKey(new EspressoKey.Builder() 1103 .withAltPressed(true).withKeyCode(KeyEvent.KEYCODE_DPAD_RIGHT).build())); 1104 assertEquals(1, mViewPager.getCurrentItem()); 1105 assertTrue(adapter.getButton(1, 0).isFocused()); 1106 1107 // Normal arrows should change page if there are no more focusables in that direction 1108 onView(is(adapter.getButton(1, 0))).perform(pressKey(KeyEvent.KEYCODE_DPAD_LEFT)); 1109 assertEquals(0, mViewPager.getCurrentItem()); 1110 } 1111 } 1112