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