SwipeTest.java revision a6b9e5ba410f128dc61ca899153ab05b16ae5023
1/* 2 * Copyright (C) 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 17package androidx.viewpager2.widget; 18 19import static android.support.test.espresso.Espresso.onView; 20import static android.support.test.espresso.assertion.ViewAssertions.matches; 21import static android.support.test.espresso.matcher.ViewMatchers.assertThat; 22import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; 23import static android.support.test.espresso.matcher.ViewMatchers.withId; 24import static android.support.test.espresso.matcher.ViewMatchers.withText; 25import static android.view.View.OVER_SCROLL_NEVER; 26 27import static org.hamcrest.CoreMatchers.allOf; 28import static org.hamcrest.CoreMatchers.equalTo; 29import static org.hamcrest.CoreMatchers.is; 30 31import static java.util.Arrays.asList; 32import static java.util.Collections.singletonList; 33 34import android.os.Build; 35import android.support.test.filters.LargeTest; 36import android.support.test.rule.ActivityTestRule; 37import android.util.Log; 38import android.util.Pair; 39 40import androidx.recyclerview.widget.RecyclerView; 41import androidx.testutils.FragmentActivityUtils; 42import androidx.viewpager2.test.R; 43import androidx.viewpager2.widget.swipe.BaseActivity; 44import androidx.viewpager2.widget.swipe.FragmentAdapterActivity; 45import androidx.viewpager2.widget.swipe.PageSwiper; 46import androidx.viewpager2.widget.swipe.ViewAdapterActivity; 47 48import org.junit.Before; 49import org.junit.Test; 50import org.junit.runner.RunWith; 51import org.junit.runners.Parameterized; 52 53import java.util.ArrayList; 54import java.util.Collection; 55import java.util.Collections; 56import java.util.HashMap; 57import java.util.HashSet; 58import java.util.List; 59import java.util.Map; 60import java.util.Random; 61import java.util.Set; 62 63@LargeTest 64@RunWith(Parameterized.class) 65public class SwipeTest { 66 private static final List<Class<? extends BaseActivity>> TEST_ACTIVITIES_ALL = asList( 67 ViewAdapterActivity.class, FragmentAdapterActivity.class); 68 private static final Set<Integer> NO_CONFIG_CHANGES = Collections.emptySet(); 69 private static final List<Pair<Integer, Integer>> NO_MUTATIONS = Collections.emptyList(); 70 private static final boolean RANDOM_PASS_ENABLED = false; 71 72 private final TestConfig mTestConfig; 73 private ActivityTestRule<? extends BaseActivity> mActivityTestRule; 74 private PageSwiper mSwiper; 75 76 public SwipeTest(TestConfig testConfig) { 77 mTestConfig = testConfig; 78 } 79 80 @Test 81 public void test() throws Throwable { 82 BaseActivity activity = mActivityTestRule.getActivity(); 83 84 final int[] expectedValues = new int[mTestConfig.mTotalPages]; 85 for (int i = 0; i < mTestConfig.mTotalPages; i++) { 86 expectedValues[i] = i; 87 } 88 89 int currentPage = 0, currentStep = 0; 90 assertStateCorrect(expectedValues[currentPage], activity); 91 for (int nextPage : mTestConfig.mPageSequence) { 92 // value change 93 if (mTestConfig.mStepToNewValue.containsKey(currentStep)) { 94 expectedValues[currentPage] = mTestConfig.mStepToNewValue.get(currentStep); 95 activity.updatePage(currentPage, expectedValues[currentPage]); 96 assertStateCorrect(expectedValues[currentPage], activity); 97 } 98 99 // config change 100 if (mTestConfig.mConfigChangeSteps.contains(currentStep++)) { 101 activity = FragmentActivityUtils.recreateActivity(mActivityTestRule, activity); 102 assertStateCorrect(expectedValues[currentPage], activity); 103 } 104 105 // page swipe 106 mSwiper.swipe(currentPage, nextPage); 107 currentPage = nextPage; 108 assertStateCorrect(expectedValues[currentPage], activity); 109 } 110 } 111 112 private void assertStateCorrect(int expectedValue, BaseActivity activity) { 113 onView(allOf(withId(R.id.text_view), isDisplayed())).check( 114 matches(withText(String.valueOf(expectedValue)))); 115 activity.validateState(); 116 } 117 118 @Parameterized.Parameters(name = "{0}") 119 public static List<TestConfig> getParams() { 120 List<TestConfig> tests = new ArrayList<>(); 121 122 if (RANDOM_PASS_ENABLED) { // run locally after making larger changes 123 tests.addAll(generateRandomTests()); 124 } 125 126 for (Class<? extends BaseActivity> activityClass : TEST_ACTIVITIES_ALL) { 127 tests.add(new TestConfig("full pass", asList(1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1, 0), 128 NO_CONFIG_CHANGES, NO_MUTATIONS, 8, activityClass)); 129 130 tests.add(new TestConfig("swipe beyond edge pages", 131 asList(0, 0, 1, 2, 3, 3, 3, 2, 1, 0, 0, 0), NO_CONFIG_CHANGES, NO_MUTATIONS, 4, 132 activityClass)); 133 134 tests.add(new TestConfig("config change", asList(1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1, 0), 135 asList(3, 5, 7), NO_MUTATIONS, 7, activityClass)); 136 137 tests.add( 138 new TestConfig("regression1", asList(1, 2, 3, 2, 1, 2, 3, 4), NO_CONFIG_CHANGES, 139 NO_MUTATIONS, 10, activityClass)); 140 141 tests.add(new TestConfig("regression2", asList(1, 2, 3, 4, 3, 2, 1, 2, 3, 4, 5), 142 NO_CONFIG_CHANGES, NO_MUTATIONS, 10, activityClass)); 143 144 tests.add(new TestConfig("regression3", asList(1, 2, 3, 2, 1, 2, 3, 2, 1, 0), 145 NO_CONFIG_CHANGES, NO_MUTATIONS, 10, activityClass)); 146 } 147 148 // mutations only apply to Fragment state persistence 149 tests.add(new TestConfig("mutations", asList(1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1, 0), 150 singletonList(8), 151 asList(Pair.create(0, 999), Pair.create(1, 100), Pair.create(3, 300), 152 Pair.create(5, 500)), 7, FragmentAdapterActivity.class)); 153 154 return checkTestNamesUnique(tests); 155 } 156 157 private static Collection<TestConfig> generateRandomTests() { 158 List<TestConfig> result = new ArrayList<>(); 159 160 int id = 0; 161 for (int i = 0; i < 10; i++) { 162 // both adapters 163 for (Class<? extends BaseActivity> adapterClass : TEST_ACTIVITIES_ALL) { 164 result.add(createRandomTest(id++, 8, 50, 0, 0, 0.875, adapterClass)); 165 result.add(createRandomTest(id++, 8, 10, 0.5, 0, 0.875, adapterClass)); 166 } 167 168 // fragment adapter specific 169 result.add( 170 createRandomTest(id++, 8, 50, 0, 0.125, 0.875, FragmentAdapterActivity.class)); 171 result.add(createRandomTest(id++, 8, 10, 0.5, 0.125, 0.875, 172 FragmentAdapterActivity.class)); 173 } 174 175 return result; 176 } 177 178 /** 179 * @param advanceProbability determines the probability of a swipe direction being towards 180 * the next edge - e.g. if we start from the left, it's the 181 * probability that the next swipe will go right. <p> Setting it to 182 * values closer to 0.5 results in a lot of back and forth, while 183 * setting it closer to 1.0 results in going edge to edge with few 184 * back-swipes. 185 */ 186 @SuppressWarnings("SameParameterValue") 187 private static TestConfig createRandomTest(int id, int totalPages, int sequenceLength, 188 double configChangeProbability, double mutationProbability, double advanceProbability, 189 Class<? extends BaseActivity> activityClass) { 190 Random random = new Random(); 191 192 List<Integer> pageSequence = new ArrayList<>(); 193 List<Integer> configChanges = new ArrayList<>(); 194 List<Pair<Integer, Integer>> stepToNewValue = new ArrayList<>(); 195 196 int pageIx = 0; 197 Double goRightProbability = null; 198 for (int currentStep = 0; currentStep < sequenceLength; currentStep++) { 199 if (random.nextDouble() < configChangeProbability) { 200 configChanges.add(currentStep); 201 } 202 203 if (random.nextDouble() < mutationProbability) { 204 stepToNewValue.add(Pair.create(currentStep, random.nextInt(10_000))); 205 } 206 207 boolean goRight; 208 if (pageIx == 0) { 209 goRight = true; 210 goRightProbability = advanceProbability; 211 } else if (pageIx == totalPages - 1) { // last page 212 goRight = false; 213 goRightProbability = 1 - advanceProbability; 214 } else { 215 goRight = random.nextDouble() < goRightProbability; 216 } 217 pageSequence.add(goRight ? ++pageIx : --pageIx); 218 } 219 220 return new TestConfig("random_" + id, pageSequence, configChanges, stepToNewValue, 221 totalPages, activityClass); 222 } 223 224 private static List<TestConfig> checkTestNamesUnique(List<TestConfig> configs) { 225 Set<String> names = new HashSet<>(); 226 for (TestConfig config : configs) { 227 names.add(config.toString()); 228 } 229 assertThat(names.size(), is(configs.size())); 230 return configs; 231 } 232 233 @Before 234 public void setUp() throws Exception { 235 Log.i(getClass().getSimpleName(), mTestConfig.toFullSpecString()); 236 237 mActivityTestRule = new ActivityTestRule<>(mTestConfig.mActivityClass, true, false); 238 mActivityTestRule.launchActivity(BaseActivity.createIntent(mTestConfig.mTotalPages)); 239 240 ViewPager2 viewPager = mActivityTestRule.getActivity().findViewById(R.id.view_pager); 241 RecyclerView recyclerView = (RecyclerView) viewPager.getChildAt(0); // HACK 242 mSwiper = new PageSwiper(mTestConfig.mTotalPages, recyclerView); 243 244 if (Build.VERSION.SDK_INT < 16) { // TODO(b/71500143): remove temporary workaround 245 RecyclerView mRecyclerView = (RecyclerView) viewPager.getChildAt(0); 246 mRecyclerView.setOverScrollMode(OVER_SCROLL_NEVER); 247 } 248 249 onView(withId(R.id.view_pager)).check(matches(isDisplayed())); 250 } 251 252 private static class TestConfig { 253 final String mMessage; 254 final List<Integer> mPageSequence; 255 final Set<Integer> mConfigChangeSteps; 256 /** {@link Map.Entry#getKey()} = step, {@link Map.Entry#getValue()} = new value */ 257 final Map<Integer, Integer> mStepToNewValue; 258 final int mTotalPages; 259 final Class<? extends BaseActivity> mActivityClass; 260 261 /** 262 * @param stepToNewValue {@link Pair#first} = step, {@link Pair#second} = new value 263 */ 264 TestConfig(String message, List<Integer> pageSequence, 265 Collection<Integer> configChangeSteps, 266 List<Pair<Integer, Integer>> stepToNewValue, 267 int totalPages, 268 Class<? extends BaseActivity> activityClass) { 269 mMessage = message; 270 mPageSequence = pageSequence; 271 mConfigChangeSteps = new HashSet<>(configChangeSteps); 272 mStepToNewValue = mapFromPairList(stepToNewValue); 273 mTotalPages = totalPages; 274 mActivityClass = activityClass; 275 } 276 277 private static Map<Integer, Integer> mapFromPairList(List<Pair<Integer, Integer>> list) { 278 Map<Integer, Integer> result = new HashMap<>(); 279 for (Pair<Integer, Integer> pair : list) { 280 Integer prevValueAtKey = result.put(pair.first, pair.second); 281 assertThat("there should be only one value defined for a key", prevValueAtKey, 282 equalTo(null)); 283 } 284 return result; 285 } 286 287 @Override 288 public String toString() { 289 return mActivityClass.getSimpleName() + ": " + mMessage; 290 } 291 292 String toFullSpecString() { 293 return String.format( 294 "Test: %s\nPage sequence: %s\nTotal pages: %s\nMutations {step1:newValue1, " 295 + "step2:newValue2, ...}: %s", 296 toString(), 297 mPageSequence, 298 mTotalPages, 299 mStepToNewValue.toString().replace('=', ':')); 300 } 301 } 302} 303