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 */
16
17package android.support.v7.widget;
18
19import org.junit.Test;
20import org.junit.runner.RunWith;
21import org.junit.runners.Parameterized;
22
23import android.graphics.Rect;
24import android.test.suitebuilder.annotation.MediumTest;
25
26import java.util.ArrayList;
27import java.util.List;
28import java.util.Map;
29
30import static android.support.v7.widget.StaggeredGridLayoutManager.GAP_HANDLING_NONE;
31import static org.junit.Assert.assertNull;
32
33@RunWith(Parameterized.class)
34@MediumTest
35public class StaggeredGridLayoutManagerGapTest extends BaseStaggeredGridLayoutManagerTest {
36    private final Config mConfig;
37    private final int mDeletePosition;
38    private final int mDeleteCount;
39
40    public StaggeredGridLayoutManagerGapTest(Config config, int deletePosition, int deleteCount) {
41        mConfig = config;
42        mDeletePosition = deletePosition;
43        mDeleteCount = deleteCount;
44    }
45
46    @Parameterized.Parameters(name = "config={0} deletePos={1} deleteCount={2}")
47    public static List<Object[]> getParams() throws CloneNotSupportedException {
48        List<Config> variations = createBaseVariations();
49        List<Object[]> params = new ArrayList<>();
50        for (Config config : variations) {
51            for (int deleteCount = 1; deleteCount < config.mSpanCount * 2; deleteCount++) {
52                for (int deletePosition = config.mSpanCount - 1;
53                        deletePosition < config.mSpanCount + 2; deletePosition++) {
54                    params.add(new Object[]{config.clone(), deletePosition, deleteCount});
55                }
56            }
57        }
58        return params;
59    }
60
61    @Test
62    public void gapAtTheBeginningOfTheListTest() throws Throwable {
63        if (mConfig.mSpanCount < 2 || mConfig.mGapStrategy == GAP_HANDLING_NONE) {
64            return;
65        }
66        if (mConfig.mItemCount < 100) {
67            mConfig.itemCount(100);
68        }
69        setupByConfig(mConfig);
70        final RecyclerView.Adapter adapter = mAdapter;
71        waitFirstLayout();
72        // scroll far away
73        smoothScrollToPosition(mConfig.mItemCount / 2);
74        checkForMainThreadException();
75        // assert to be deleted child is not visible
76        assertNull(" test sanity, to be deleted child should be invisible",
77                mRecyclerView.findViewHolderForLayoutPosition(mDeletePosition));
78        // delete the child and notify
79        mAdapter.deleteAndNotify(mDeletePosition, mDeleteCount);
80        getInstrumentation().waitForIdleSync();
81        mLayoutManager.expectLayouts(1);
82        smoothScrollToPosition(0);
83        mLayoutManager.waitForLayout(2);
84        checkForMainThreadException();
85        // due to data changes, first item may become visible before others which will cause
86        // smooth scrolling to stop. Triggering it twice more is a naive hack.
87        // Until we have time to consider it as a bug, this is the only workaround.
88        smoothScrollToPosition(0);
89        Thread.sleep(500);
90        checkForMainThreadException();
91        smoothScrollToPosition(0);
92        Thread.sleep(500);
93        checkForMainThreadException();
94        // some animations should happen and we should recover layout
95        final Map<Item, Rect> actualCoords = mLayoutManager.collectChildCoordinates();
96
97        // now layout another RV with same adapter
98        removeRecyclerView();
99        setupByConfig(mConfig);
100        mRecyclerView.setAdapter(adapter);// use same adapter so that items can be matched
101        waitFirstLayout();
102        final Map<Item, Rect> desiredCoords = mLayoutManager.collectChildCoordinates();
103        assertRectSetsEqual(" when an item from the start of the list is deleted, "
104                        + "layout should recover the state once scrolling is stopped",
105                desiredCoords, actualCoords);
106        checkForMainThreadException();
107    }
108}
109