SwipeTest.java revision 4cb6b032217d1a8f55f0bc9aace47d7b314a635c
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 updatePage(currentPage, expectedValues[currentPage], activity); 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 static void updatePage(final int pageIx, final int newValue, 113 final BaseActivity activity) { 114 activity.runOnUiThread(new Runnable() { 115 @Override 116 public void run() { 117 activity.updatePage(pageIx, newValue); 118 } 119 }); 120 } 121 122 private void assertStateCorrect(int expectedValue, BaseActivity activity) { 123 onView(allOf(withId(R.id.text_view), isDisplayed())).check( 124 matches(withText(String.valueOf(expectedValue)))); 125 activity.validateState(); 126 } 127 128 @Parameterized.Parameters(name = "{0}") 129 public static List<TestConfig> getParams() { 130 List<TestConfig> tests = new ArrayList<>(); 131 132 if (RANDOM_PASS_ENABLED) { // run locally after making larger changes 133 tests.addAll(generateRandomTests()); 134 } 135 136 for (Class<? extends BaseActivity> activityClass : TEST_ACTIVITIES_ALL) { 137 tests.add(new TestConfig("full pass", asList(1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1, 0), 138 NO_CONFIG_CHANGES, NO_MUTATIONS, 8, activityClass)); 139 140 tests.add(new TestConfig("swipe beyond edge pages", 141 asList(0, 0, 1, 2, 3, 3, 3, 2, 1, 0, 0, 0), NO_CONFIG_CHANGES, NO_MUTATIONS, 4, 142 activityClass)); 143 144 tests.add(new TestConfig("config change", asList(1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1, 0), 145 asList(3, 5, 7), NO_MUTATIONS, 7, activityClass)); 146 147 tests.add( 148 new TestConfig("regression1", asList(1, 2, 3, 2, 1, 2, 3, 4), NO_CONFIG_CHANGES, 149 NO_MUTATIONS, 10, activityClass)); 150 151 tests.add(new TestConfig("regression2", asList(1, 2, 3, 4, 3, 2, 1, 2, 3, 4, 5), 152 NO_CONFIG_CHANGES, NO_MUTATIONS, 10, activityClass)); 153 154 tests.add(new TestConfig("regression3", asList(1, 2, 3, 2, 1, 2, 3, 2, 1, 0), 155 NO_CONFIG_CHANGES, NO_MUTATIONS, 10, activityClass)); 156 } 157 158 // mutations only apply to Fragment state persistence 159 tests.add(new TestConfig("mutations", asList(1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1, 0), 160 singletonList(8), 161 asList(Pair.create(0, 999), Pair.create(1, 100), Pair.create(3, 300), 162 Pair.create(5, 500)), 7, FragmentAdapterActivity.class)); 163 164 return checkTestNamesUnique(tests); 165 } 166 167 private static Collection<TestConfig> generateRandomTests() { 168 List<TestConfig> result = new ArrayList<>(); 169 170 int id = 0; 171 for (int i = 0; i < 10; i++) { 172 // both adapters 173 for (Class<? extends BaseActivity> adapterClass : TEST_ACTIVITIES_ALL) { 174 result.add(createRandomTest(id++, 8, 50, 0, 0, 0.875, adapterClass)); 175 result.add(createRandomTest(id++, 8, 10, 0.5, 0, 0.875, adapterClass)); 176 } 177 178 // fragment adapter specific 179 result.add( 180 createRandomTest(id++, 8, 50, 0, 0.125, 0.875, FragmentAdapterActivity.class)); 181 result.add(createRandomTest(id++, 8, 10, 0.5, 0.125, 0.875, 182 FragmentAdapterActivity.class)); 183 } 184 185 return result; 186 } 187 188 /** 189 * @param advanceProbability determines the probability of a swipe direction being towards 190 * the next edge - e.g. if we start from the left, it's the 191 * probability that the next swipe will go right. <p> Setting it to 192 * values closer to 0.5 results in a lot of back and forth, while 193 * setting it closer to 1.0 results in going edge to edge with few 194 * back-swipes. 195 */ 196 @SuppressWarnings("SameParameterValue") 197 private static TestConfig createRandomTest(int id, int totalPages, int sequenceLength, 198 double configChangeProbability, double mutationProbability, double advanceProbability, 199 Class<? extends BaseActivity> activityClass) { 200 Random random = new Random(); 201 202 List<Integer> pageSequence = new ArrayList<>(); 203 List<Integer> configChanges = new ArrayList<>(); 204 List<Pair<Integer, Integer>> stepToNewValue = new ArrayList<>(); 205 206 int pageIx = 0; 207 Double goRightProbability = null; 208 for (int currentStep = 0; currentStep < sequenceLength; currentStep++) { 209 if (random.nextDouble() < configChangeProbability) { 210 configChanges.add(currentStep); 211 } 212 213 if (random.nextDouble() < mutationProbability) { 214 stepToNewValue.add(Pair.create(currentStep, random.nextInt(10_000))); 215 } 216 217 boolean goRight; 218 if (pageIx == 0) { 219 goRight = true; 220 goRightProbability = advanceProbability; 221 } else if (pageIx == totalPages - 1) { // last page 222 goRight = false; 223 goRightProbability = 1 - advanceProbability; 224 } else { 225 goRight = random.nextDouble() < goRightProbability; 226 } 227 pageSequence.add(goRight ? ++pageIx : --pageIx); 228 } 229 230 return new TestConfig("random_" + id, pageSequence, configChanges, stepToNewValue, 231 totalPages, activityClass); 232 } 233 234 private static List<TestConfig> checkTestNamesUnique(List<TestConfig> configs) { 235 Set<String> names = new HashSet<>(); 236 for (TestConfig config : configs) { 237 names.add(config.toString()); 238 } 239 assertThat(names.size(), is(configs.size())); 240 return configs; 241 } 242 243 @Before 244 public void setUp() throws Exception { 245 Log.i(getClass().getSimpleName(), mTestConfig.toFullSpecString()); 246 247 mActivityTestRule = new ActivityTestRule<>(mTestConfig.mActivityClass, true, false); 248 mActivityTestRule.launchActivity(BaseActivity.createIntent(mTestConfig.mTotalPages)); 249 250 ViewPager2 viewPager = mActivityTestRule.getActivity().findViewById(R.id.view_pager); 251 RecyclerView recyclerView = (RecyclerView) viewPager.getChildAt(0); // HACK 252 mSwiper = new PageSwiper(mTestConfig.mTotalPages, recyclerView); 253 254 if (Build.VERSION.SDK_INT < 16) { // TODO(b/71500143): remove temporary workaround 255 RecyclerView mRecyclerView = (RecyclerView) viewPager.getChildAt(0); 256 mRecyclerView.setOverScrollMode(OVER_SCROLL_NEVER); 257 } 258 259 onView(withId(R.id.view_pager)).check(matches(isDisplayed())); 260 } 261 262 private static class TestConfig { 263 final String mMessage; 264 final List<Integer> mPageSequence; 265 final Set<Integer> mConfigChangeSteps; 266 /** {@link Map.Entry#getKey()} = step, {@link Map.Entry#getValue()} = new value */ 267 final Map<Integer, Integer> mStepToNewValue; 268 final int mTotalPages; 269 final Class<? extends BaseActivity> mActivityClass; 270 271 /** 272 * @param stepToNewValue {@link Pair#first} = step, {@link Pair#second} = new value 273 */ 274 TestConfig(String message, List<Integer> pageSequence, 275 Collection<Integer> configChangeSteps, 276 List<Pair<Integer, Integer>> stepToNewValue, 277 int totalPages, 278 Class<? extends BaseActivity> activityClass) { 279 mMessage = message; 280 mPageSequence = pageSequence; 281 mConfigChangeSteps = new HashSet<>(configChangeSteps); 282 mStepToNewValue = mapFromPairList(stepToNewValue); 283 mTotalPages = totalPages; 284 mActivityClass = activityClass; 285 } 286 287 private static Map<Integer, Integer> mapFromPairList(List<Pair<Integer, Integer>> list) { 288 Map<Integer, Integer> result = new HashMap<>(); 289 for (Pair<Integer, Integer> pair : list) { 290 Integer prevValueAtKey = result.put(pair.first, pair.second); 291 assertThat("there should be only one value defined for a key", prevValueAtKey, 292 equalTo(null)); 293 } 294 return result; 295 } 296 297 @Override 298 public String toString() { 299 return mActivityClass.getSimpleName() + ": " + mMessage; 300 } 301 302 String toFullSpecString() { 303 return String.format( 304 "Test: %s\nPage sequence: %s\nTotal pages: %s\nMutations {step1:newValue1, " 305 + "step2:newValue2, ...}: %s", 306 toString(), 307 mPageSequence, 308 mTotalPages, 309 mStepToNewValue.toString().replace('=', ':')); 310 } 311 } 312} 313