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