1/*
2 * Copyright 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.core.widget;
18
19import static org.hamcrest.CoreMatchers.is;
20import static org.hamcrest.MatcherAssert.assertThat;
21
22import android.graphics.Rect;
23import android.graphics.drawable.GradientDrawable;
24import android.os.Parcelable;
25import android.support.test.filters.SmallTest;
26import android.support.test.runner.AndroidJUnit4;
27import android.support.v4.BaseInstrumentationTestCase;
28import android.view.KeyEvent;
29import android.view.View;
30import android.view.ViewGroup;
31
32import androidx.core.test.R;
33import androidx.core.view.NestedScrollingParent2;
34
35import org.junit.Test;
36import org.junit.runner.RunWith;
37
38import java.util.concurrent.CountDownLatch;
39import java.util.concurrent.TimeUnit;
40
41/**
42 * So far these tests only cover {@code NestedScrollView}'s implementation of
43 * {@link NestedScrollingParent2} and the backwards compatibility of {@code NestedScrollView}'s
44 * implementation of {@link androidx.core.view.NestedScrollingParent} for the methods that
45 * {@link NestedScrollingParent2} overloads.
46 */
47
48@RunWith(AndroidJUnit4.class)
49@SmallTest
50public class NestedScrollViewTest extends
51        BaseInstrumentationTestCase<TestContentViewActivity> {
52
53    private NestedScrollView mNestedScrollView;
54    private View mChild;
55
56    public NestedScrollViewTest() {
57        super(TestContentViewActivity.class);
58    }
59
60    @Test
61    public void getBottomFadingEdgeStrength_childBottomIsBelowParentWithoutMargins_isCorrect() {
62        setup(200);
63        mNestedScrollView.setVerticalFadingEdgeEnabled(true);
64        measureAndLayout(100);
65
66        float expected = mNestedScrollView.getBottomFadingEdgeStrength();
67
68        assertThat(expected, is(1.0f));
69    }
70
71    @Test
72    public void getBottomFadingEdgeStrength_childBottomIsBelowParentDuetoMargins_isCorrect() {
73        setup(100);
74        mNestedScrollView.setVerticalFadingEdgeEnabled(true);
75        setChildMargins(100, 0);
76        measureAndLayout(100);
77
78        float expected = mNestedScrollView.getBottomFadingEdgeStrength();
79
80        assertThat(expected, is(1.0f));
81    }
82
83    @Test
84    public void getBottomFadingEdgeStrength_childIsAboveButMarginIsBelowParent_isCorrect() {
85        setup(100);
86        mNestedScrollView.setVerticalFadingEdgeEnabled(true);
87        setChildMargins(0, 100);
88        measureAndLayout(100);
89
90        float expected = mNestedScrollView.getBottomFadingEdgeStrength();
91
92        assertThat(expected, is(1.0f));
93    }
94
95    @Test
96    public void getBottomFadingEdgeStrength_childBottomIsAboveParentAndNoMargin_isZero() {
97        setup(100);
98        mNestedScrollView.setVerticalFadingEdgeEnabled(true);
99        measureAndLayout(100);
100
101        float expected = mNestedScrollView.getBottomFadingEdgeStrength();
102
103        assertThat(expected, is(0f));
104    }
105
106    @Test
107    public void onMeasure_fillViewPortEnabledChildSmallButWithMarginBig_childMeasuredCorrectly() {
108        setup(50);
109        setChildMargins(25, 25);
110        mNestedScrollView.setFillViewport(true);
111
112        measure(100);
113
114        assertThat(mChild.getMeasuredHeight(), is(50));
115    }
116
117    @Test
118    public void onMeasure_fillViewPortEnabledChildSmallWithMargins_childMeasuredCorrectly() {
119        setup(50);
120        setChildMargins(20, 20);
121        mNestedScrollView.setFillViewport(true);
122
123        measure(100);
124
125        assertThat(mChild.getMeasuredHeight(), is(60));
126    }
127
128    @Test
129    public void onMeasure_fillViewPortEnabledChildSmallNoMargins_childMeasuredCorrectly() {
130        setup(50);
131        setChildMargins(0, 0);
132        mNestedScrollView.setFillViewport(true);
133
134        measure(100);
135
136        assertThat(mChild.getMeasuredHeight(), is(100));
137    }
138
139    @Test
140    public void executeKeyEvent_spaceBarCanScrollDueToMargins_scrolls() {
141        setup(75);
142        setChildMargins(0, 50);
143        mNestedScrollView.setSmoothScrollingEnabled(false);
144        measureAndLayout(100);
145        KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SPACE);
146
147        mNestedScrollView.executeKeyEvent(keyEvent);
148
149        assertThat(mNestedScrollView.getScrollY(), is(25));
150    }
151
152    @Test
153    public void pageScroll_takesAccountOfMargin() {
154        setup(75);
155        setChildMargins(20, 30);
156        mNestedScrollView.setSmoothScrollingEnabled(false);
157        measureAndLayout(100);
158
159        mNestedScrollView.pageScroll(View.FOCUS_DOWN);
160
161        assertThat(mNestedScrollView.getScrollY(), is(25));
162    }
163
164    @Test
165    public void getScrollRange_takesAccountOfMargin() {
166        setup(100);
167        setChildMargins(20, 30);
168        measureAndLayout(100);
169
170        int expected = mNestedScrollView.getScrollRange();
171
172        assertThat(expected, is(50));
173    }
174
175    @Test
176    public void fullScroll_scrollsToEndOfMargin() {
177        setup(300);
178        setChildMargins(20, 30);
179        mNestedScrollView.setSmoothScrollingEnabled(false);
180        measureAndLayout(100);
181
182        mNestedScrollView.fullScroll(View.FOCUS_DOWN);
183
184        assertThat(mNestedScrollView.getScrollY(), is(250));
185    }
186
187    @Test
188    public void arrowScroll_canScrollHalfDownDueToSizeAndMargin_scrollsHalfDown() {
189        setup(130);
190        setChildMargins(10, 20);
191        mNestedScrollView.setSmoothScrollingEnabled(false);
192        measureAndLayout(100);
193
194        mNestedScrollView.arrowScroll(View.FOCUS_DOWN);
195
196        assertThat(mNestedScrollView.getScrollY(), is(50));
197    }
198
199    @Test
200    public void arrowScroll_canScrollQuarterDownDueToSizeAndMargin_scrollsQuarterDown() {
201        setup(75);
202        setChildMargins(25, 25);
203        mNestedScrollView.setSmoothScrollingEnabled(false);
204        measureAndLayout(100);
205
206        mNestedScrollView.arrowScroll(View.FOCUS_DOWN);
207
208        assertThat(mNestedScrollView.getScrollY(), is(25));
209    }
210
211    @Test
212    public void arrowScroll_canOnlyScrollQuarterUp_scrollsQuarterUp() {
213        setup(75);
214        setChildMargins(25, 25);
215        mNestedScrollView.setSmoothScrollingEnabled(false);
216        measureAndLayout(100);
217
218        mNestedScrollView.scrollTo(0, 25);
219        mNestedScrollView.arrowScroll(View.FOCUS_UP);
220
221        assertThat(mNestedScrollView.getScrollY(), is(0));
222    }
223
224    @Test
225    public void arrowScroll_canScroll_returnsTrue() {
226        setup(75);
227        setChildMargins(20, 30);
228        mNestedScrollView.setSmoothScrollingEnabled(false);
229        measureAndLayout(100);
230
231        boolean actualResult = mNestedScrollView.arrowScroll(View.FOCUS_DOWN);
232
233        assertThat(actualResult, is(true));
234    }
235
236    @Test
237    public void arrowScroll_cantScroll_returnsFalse() {
238        setup(50);
239        setChildMargins(25, 25);
240        mNestedScrollView.setSmoothScrollingEnabled(false);
241        measureAndLayout(100);
242
243        boolean actualResult = mNestedScrollView.arrowScroll(View.FOCUS_DOWN);
244
245        assertThat(actualResult, is(false));
246    }
247
248    //SmoothScrollBy
249
250    @Test
251    public void smoothScrollBy_scrollsEntireDistanceIncludingMargins() throws Throwable {
252        setup(200);
253        setChildMargins(20, 30);
254        attachToActivity(100);
255
256        final CountDownLatch countDownLatch = new CountDownLatch(1);
257        final int expectedTarget = 150;
258        final int scrollDistance = 150;
259        mActivityTestRule.runOnUiThread(new Runnable() {
260            @Override
261            public void run() {
262                mNestedScrollView.setOnScrollChangeListener(
263                        new NestedScrollView.OnScrollChangeListener() {
264
265                            @Override
266                            public void onScrollChange(NestedScrollView v, int scrollX, int scrollY,
267                                    int oldScrollX, int oldScrollY) {
268                                if (scrollY == expectedTarget) {
269                                    countDownLatch.countDown();
270                                }
271                            }
272                        });
273                mNestedScrollView.smoothScrollBy(0, scrollDistance);
274            }
275        });
276        assertThat(countDownLatch.await(2000, TimeUnit.SECONDS), is(true));
277
278        assertThat(mNestedScrollView.getScrollY(), is(expectedTarget));
279    }
280
281    @Test
282    public void computeVerticalScrollRange_takesAccountOfMargin() {
283        setup(200);
284        setChildMargins(20, 30);
285        measureAndLayout(100);
286
287        int actual = mNestedScrollView.computeVerticalScrollRange();
288
289        assertThat(actual, is(250));
290    }
291
292    @Test
293    public void computeScrollDeltaToGetChildRectOnScreen_marginRespectedToMakeRoomForFadingEdge() {
294        setup(200);
295        setChildMargins(0, 1);
296        mNestedScrollView.setVerticalFadingEdgeEnabled(true);
297        mNestedScrollView.setFadingEdgeLength(25);
298        measureAndLayout(100);
299        Rect rect = new Rect(0, 175, 100, 200);
300
301        int actual = mNestedScrollView.computeScrollDeltaToGetChildRectOnScreen(rect);
302
303        assertThat(actual, is(101));
304    }
305
306    @Test
307    public void computeScrollDeltaToGetChildRectOnScreen_fadingEdgeNoMargin_clampsToEnd() {
308        setup(200);
309        setChildMargins(0, 0);
310        mNestedScrollView.setVerticalFadingEdgeEnabled(true);
311        mNestedScrollView.setFadingEdgeLength(25);
312        measureAndLayout(100);
313        Rect rect = new Rect(0, 175, 100, 200);
314
315        int actual = mNestedScrollView.computeScrollDeltaToGetChildRectOnScreen(rect);
316
317        assertThat(actual, is(100));
318    }
319
320    @Test
321    public void onLayout_canScrollDistanceFromSavedInstanceStateDueToMargins_scrollsDistance() {
322
323        // Arrange.
324
325        setup(200);
326        setChildMargins(0, 0);
327        measureAndLayout(100);
328        mNestedScrollView.scrollTo(0, 100);
329        Parcelable savedState = mNestedScrollView.onSaveInstanceState();
330
331        setup(100);
332        setChildMargins(25, 75);
333        mNestedScrollView.onRestoreInstanceState(savedState);
334
335        // Act.
336
337        measureAndLayout(100);
338
339        // Assert
340
341        assertThat(mNestedScrollView.getScrollY(), is(100));
342    }
343
344    @Test
345    public void scrollTo_childHasMargins_scrollsToEndOfMargins() {
346        setup(100);
347        setChildMargins(25, 75);
348        mNestedScrollView.setSmoothScrollingEnabled(false);
349        measureAndLayout(100);
350
351        mNestedScrollView.scrollTo(0, 100);
352
353        assertThat(mNestedScrollView.getScrollY(), is(100));
354    }
355
356    private void setup(int childHeight) {
357        mChild = new View(mActivityTestRule.getActivity());
358        mChild.setMinimumWidth(100);
359        mChild.setMinimumHeight(childHeight);
360        mChild.setBackgroundDrawable(
361                new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM,
362                        new int[]{0xFFFF0000, 0xFF00FF00}));
363
364        mNestedScrollView = new NestedScrollView(mActivityTestRule.getActivity());
365        mNestedScrollView.setBackgroundColor(0xFF0000FF);
366        mNestedScrollView.addView(mChild);
367    }
368
369    private void setChildMargins(int top, int bottom) {
370        NestedScrollView.LayoutParams childLayoutParams =
371                new NestedScrollView.LayoutParams(100, 100);
372        childLayoutParams.topMargin = top;
373        childLayoutParams.bottomMargin = bottom;
374        mChild.setLayoutParams(childLayoutParams);
375    }
376
377    private void attachToActivity(int nestedScrollViewHeight) throws Throwable {
378        mNestedScrollView.setLayoutParams(new ViewGroup.LayoutParams(100, nestedScrollViewHeight));
379
380        final TestContentView testContentView =
381                mActivityTestRule.getActivity().findViewById(R.id.testContentView);
382        testContentView.expectLayouts(1);
383        mActivityTestRule.runOnUiThread(new Runnable() {
384            @Override
385            public void run() {
386                testContentView.addView(mNestedScrollView);
387            }
388        });
389        testContentView.awaitLayouts(2);
390    }
391
392    private void measure(int height) {
393        int measureSpecWidth =
394                View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
395        int measureSpecHeight =
396                View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
397        mNestedScrollView.measure(measureSpecWidth, measureSpecHeight);
398    }
399
400    private void measureAndLayout(int height) {
401        measure(height);
402        mNestedScrollView.layout(0, 0, 100, height);
403    }
404}
405