1/*
2 * Copyright (C) 2017 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 static org.junit.Assert.assertEquals;
20import static org.junit.Assert.assertNull;
21
22import android.content.Context;
23import android.support.test.InstrumentationRegistry;
24import android.support.test.filters.SmallTest;
25import android.view.View;
26
27import org.junit.Before;
28import org.junit.Test;
29import org.junit.runner.RunWith;
30import org.junit.runners.JUnit4;
31
32
33@SmallTest
34@RunWith(JUnit4.class)
35public class ViewBoundsCheckTest {
36
37
38    private static final String TAG = "ViewBoundsCheckTest";
39    private Context mContext;
40
41    /** Case #1:
42     * Parent:                    [2.......................8]
43     Views: [-3...-1] [-1...1] [1...3] [3...5] [5...7] [7...9] [9...11] [11...13]
44     */
45    int[] mParentBound1 = {2, 8};
46    int[][] mChildrenBound1 = {{-3, -1}, {-1, 1}, {1, 3}, {3, 5}, {5, 7}, {7, 9}, {9, 11},
47            {11, 13}};
48
49    /** Case #2:
50     * Parent:                  [1...................7]
51     Views: [-3...-1] [-1...1][1...3] [3...5] [5...7] [7...9] [9...11]
52     */
53    int[] mParentBound2 = {1, 7};
54    int[][] mChildrenBound2 = {{-3, -1}, {-1, 1}, {1, 3}, {3, 5}, {5, 7}, {7, 9}, {9, 11}};
55
56    View mParent;
57    View[] mChildren;
58
59    private final ViewBoundsCheck.Callback mBoundCheckCallback =
60            new ViewBoundsCheck.Callback() {
61                @Override
62                public int getChildCount() {
63                    return mChildren.length;
64                }
65
66                @Override
67                public View getParent() {
68                    return mParent;
69                }
70
71                @Override
72                public View getChildAt(int index) {
73                    return mChildren[index];
74                }
75
76                @Override
77                public int getParentStart() {
78                    return mParent.getLeft();
79                }
80
81                @Override
82                public int getParentEnd() {
83                    return mParent.getRight();
84                }
85
86                @Override
87                public int getChildStart(View view) {
88                    return view.getLeft();
89                }
90
91                @Override
92                public int getChildEnd(View view) {
93                    return view.getRight();
94                }
95            };
96
97    ViewBoundsCheck mBoundCheck = new ViewBoundsCheck(mBoundCheckCallback);
98
99    @Before
100    public void setUp() throws Exception {
101        mContext = InstrumentationRegistry.getContext();
102    }
103
104    private void setUpViews(int[] parentBound, int[][] childrenBound) {
105        mParent = new View(mContext);
106        mParent.setLeft(parentBound[0]);
107        mParent.setRight(parentBound[1]);
108        mChildren = new View[childrenBound.length];
109        for (int i = 0; i < childrenBound.length; i++) {
110            mChildren[i] = new View(mContext);
111            mChildren[i].setLeft(childrenBound[i][0]);
112            mChildren[i].setRight(childrenBound[i][1]);
113        }
114    }
115
116    @Test
117    public void firstFullyVisibleChildFromStart() {
118        setUpViews(mParentBound1, mChildrenBound1);
119        @ViewBoundsCheck.ViewBounds int preferredBoundsFlag = ViewBoundsCheck.FLAG_CVS_GT_PVS
120                | ViewBoundsCheck.FLAG_CVS_EQ_PVS | ViewBoundsCheck.FLAG_CVE_LT_PVE
121                | ViewBoundsCheck.FLAG_CVE_EQ_PVE;
122        @ViewBoundsCheck.ViewBounds int acceptableBoundsFlag = 0;
123        View view = mBoundCheck.findOneViewWithinBoundFlags(0, mChildren.length,
124                preferredBoundsFlag, acceptableBoundsFlag);
125        assertEquals("The first fully visible child from start should be returned", 3,
126                view.getLeft());
127        assertEquals("The first fully visible child from start should be returned", 5,
128                view.getRight());
129    }
130
131    @Test
132    public void firstFullyVisibleChildFromEnd() {
133        setUpViews(mParentBound1, mChildrenBound1);
134        @ViewBoundsCheck.ViewBounds int preferredBoundsFlag = ViewBoundsCheck.FLAG_CVS_GT_PVS
135                | ViewBoundsCheck.FLAG_CVS_EQ_PVS | ViewBoundsCheck.FLAG_CVE_LT_PVE
136                | ViewBoundsCheck.FLAG_CVE_EQ_PVE;
137        @ViewBoundsCheck.ViewBounds int acceptableBoundsFlag = 0;
138        View view = mBoundCheck.findOneViewWithinBoundFlags(mChildren.length - 1, -1,
139                preferredBoundsFlag, acceptableBoundsFlag);
140        assertEquals("The first fully visible child from end should be returned", 5,
141                view.getLeft());
142        assertEquals("The first fully visible child from end should be returned", 7,
143                view.getRight());
144    }
145
146    @Test
147    public void firstPartiallyOrFullyVisibleChildFromStartWithViewBoundsNotAligned() {
148        setUpViews(mParentBound1, mChildrenBound1);
149        // These set of flags are used in LinearLayoutManager#findOneVisibleChild
150        @ViewBoundsCheck.ViewBounds int preferredBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVE
151                | ViewBoundsCheck.FLAG_CVE_GT_PVS);
152        @ViewBoundsCheck.ViewBounds int acceptableBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVE
153                | ViewBoundsCheck.FLAG_CVE_GT_PVS);
154        View view = mBoundCheck.findOneViewWithinBoundFlags(0, mChildren.length,
155                preferredBoundsFlag, acceptableBoundsFlag);
156        assertEquals("The first partially visible child from start should be returned", 1,
157                view.getLeft());
158        assertEquals("The first partially visible child from start should be returned", 3,
159                view.getRight());
160    }
161
162    @Test
163    public void firstPartiallyOrFullyVisibleChildFromStartWithViewBoundsAligned() {
164        setUpViews(mParentBound2, mChildrenBound2);
165        // These set of flags are used in LinearLayoutManager#findOneVisibleChild
166        @ViewBoundsCheck.ViewBounds int preferredBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVE
167                | ViewBoundsCheck.FLAG_CVE_GT_PVS);
168        @ViewBoundsCheck.ViewBounds int acceptableBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVE
169                | ViewBoundsCheck.FLAG_CVE_GT_PVS);
170        View view = mBoundCheck.findOneViewWithinBoundFlags(0, mChildren.length,
171                preferredBoundsFlag, acceptableBoundsFlag);
172        assertEquals("The first partially visible child from start should be returned", 1,
173                view.getLeft());
174        assertEquals("The first partially visible child from start should be returned", 3,
175                view.getRight());
176    }
177
178    @Test
179    public void firstPartiallyOrFullyVisibleChildFromEndWithViewBoundsNotAligned() {
180        setUpViews(mParentBound1, mChildrenBound1);
181        // These set of flags are used in LinearLayoutManager#findOneVisibleChild
182        @ViewBoundsCheck.ViewBounds int preferredBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVE
183                | ViewBoundsCheck.FLAG_CVE_GT_PVS);
184        @ViewBoundsCheck.ViewBounds int acceptableBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVE
185                | ViewBoundsCheck.FLAG_CVE_GT_PVS);
186        View view = mBoundCheck.findOneViewWithinBoundFlags(mChildren.length - 1, -1,
187                preferredBoundsFlag, acceptableBoundsFlag);
188        assertEquals("The first partially visible child from end should be returned", 7,
189                view.getLeft());
190        assertEquals("The first partially visible child from end should be returned", 9,
191                view.getRight());
192    }
193
194    @Test
195    public void firstPartiallyOrFullyVisibleChildFromEndWithViewBoundsAligned() {
196        setUpViews(mParentBound2, mChildrenBound2);
197        // These set of flags are used in LinearLayoutManager#findOneVisibleChild
198        @ViewBoundsCheck.ViewBounds int preferredBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVE
199                | ViewBoundsCheck.FLAG_CVE_GT_PVS);
200        @ViewBoundsCheck.ViewBounds int acceptableBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVE
201                | ViewBoundsCheck.FLAG_CVE_GT_PVS);
202        View view = mBoundCheck.findOneViewWithinBoundFlags(mChildren.length - 1, -1,
203                preferredBoundsFlag, acceptableBoundsFlag);
204        assertEquals("The first partially visible child from end should be returned", 5,
205                view.getLeft());
206        assertEquals("The first partially visible child from end should be returned", 7,
207                view.getRight());
208    }
209
210    @Test
211    public void lastFullyInvisibleChildFromStart() {
212        setUpViews(mParentBound2, mChildrenBound2);
213        @ViewBoundsCheck.ViewBounds int  preferredBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVS
214                | ViewBoundsCheck.FLAG_CVE_LT_PVE | ViewBoundsCheck.FLAG_CVE_GT_PVS);
215        @ViewBoundsCheck.ViewBounds int  acceptableBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVS
216                | ViewBoundsCheck.FLAG_CVE_LT_PVE);
217        View view = mBoundCheck.findOneViewWithinBoundFlags(0, mChildren.length,
218                preferredBoundsFlag, acceptableBoundsFlag);
219        assertEquals("The last fully invisible child from start should be returned", -1,
220                view.getLeft());
221        assertEquals("TThe last fully invisible child from start should be returned", 1,
222                view.getRight());
223    }
224
225    @Test
226    public void lastFullyInvisibleChildFromEnd() {
227        setUpViews(mParentBound2, mChildrenBound2);
228        @ViewBoundsCheck.ViewBounds int preferredBoundsFlag = (ViewBoundsCheck.FLAG_CVE_GT_PVE
229                | ViewBoundsCheck.FLAG_CVS_GT_PVS | ViewBoundsCheck.FLAG_CVS_LT_PVE);
230        @ViewBoundsCheck.ViewBounds int acceptableBoundsFlag = (ViewBoundsCheck.FLAG_CVE_GT_PVE
231                | ViewBoundsCheck.FLAG_CVS_GT_PVS);
232        View view = mBoundCheck.findOneViewWithinBoundFlags(mChildren.length - 1, -1,
233                preferredBoundsFlag, acceptableBoundsFlag);
234        assertEquals("The last fully invisible child from end should be returned", 7,
235                view.getLeft());
236        assertEquals("TThe last fully invisible child from end should be returned", 9,
237                view.getRight());
238    }
239
240    @Test
241    public void noViewsFoundWithinGivenBounds() {
242        setUpViews(mParentBound1, mChildrenBound1);
243        // create a view whose bounds cover its parent. Since no such view exist in the example
244        // layout, null should be returned.
245        @ViewBoundsCheck.ViewBounds int preferredBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVS
246                | ViewBoundsCheck.FLAG_CVE_GT_PVE);
247        @ViewBoundsCheck.ViewBounds int acceptableBoundsFlag = preferredBoundsFlag;
248        View view = mBoundCheck.findOneViewWithinBoundFlags(0, mChildren.length,
249                preferredBoundsFlag, acceptableBoundsFlag);
250        assertNull("Null should be returned since no views are within the given bounds",
251                view);
252    }
253
254}
255