[go: nahoru, domu]

1/*
2 * Copyright (C) 2014 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.Before;
20import org.junit.Test;
21import org.junit.runner.RunWith;
22
23import android.app.Instrumentation;
24import android.content.Context;
25import android.os.Parcel;
26import android.os.Parcelable;
27import android.os.SystemClock;
28import android.support.test.InstrumentationRegistry;
29import android.support.test.runner.AndroidJUnit4;
30import android.test.AndroidTestCase;
31import android.test.suitebuilder.annotation.SmallTest;
32import android.util.AttributeSet;
33import android.util.SparseArray;
34import android.view.MotionEvent;
35import android.view.View;
36import android.view.ViewGroup;
37import android.widget.TextView;
38
39import java.util.ArrayList;
40import java.util.List;
41import java.util.UUID;
42import static org.junit.Assert.*;
43
44@SmallTest
45@RunWith(AndroidJUnit4.class)
46public class RecyclerViewBasicTest {
47
48    RecyclerView mRecyclerView;
49
50
51    @Before
52    public void setUp() throws Exception {
53        mRecyclerView = new RecyclerView(getContext());
54    }
55
56    private Context getContext() {
57        return InstrumentationRegistry.getContext();
58    }
59
60    @Test
61    public void measureWithoutLayoutManager() {
62        measure();
63    }
64
65    private void measure() {
66        mRecyclerView.measure(View.MeasureSpec.AT_MOST | 320, View.MeasureSpec.AT_MOST | 240);
67    }
68
69    private void layout() {
70        mRecyclerView.layout(0, 0, 320, 320);
71    }
72
73    private void focusSearch() {
74        mRecyclerView.focusSearch(1);
75    }
76
77    @Test
78    public void layoutWithoutAdapter() throws InterruptedException {
79        MockLayoutManager layoutManager = new MockLayoutManager();
80        mRecyclerView.setLayoutManager(layoutManager);
81        layout();
82        assertEquals("layout manager should not be called if there is no adapter attached",
83                0, layoutManager.mLayoutCount);
84    }
85
86    public void setScrollContainer() {
87        assertEquals("RecyclerView should announce itself as scroll container for the IME to "
88                + "handle it properly", true, mRecyclerView.isScrollContainer());
89    }
90
91    @Test
92    public void layoutWithoutLayoutManager() throws InterruptedException {
93        mRecyclerView.setAdapter(new MockAdapter(20));
94        measure();
95        layout();
96    }
97
98    @Test
99    public void focusWithoutLayoutManager() throws InterruptedException {
100        mRecyclerView.setAdapter(new MockAdapter(20));
101        measure();
102        layout();
103        focusSearch();
104    }
105
106    @Test
107    public void scrollWithoutLayoutManager() throws InterruptedException {
108        mRecyclerView.setAdapter(new MockAdapter(20));
109        measure();
110        layout();
111        mRecyclerView.scrollBy(10, 10);
112    }
113
114    @Test
115    public void smoothScrollWithoutLayoutManager() throws InterruptedException {
116        mRecyclerView.setAdapter(new MockAdapter(20));
117        measure();
118        layout();
119        mRecyclerView.smoothScrollBy(10, 10);
120    }
121
122    @Test
123    public void scrollToPositionWithoutLayoutManager() throws InterruptedException {
124        mRecyclerView.setAdapter(new MockAdapter(20));
125        measure();
126        layout();
127        mRecyclerView.scrollToPosition(5);
128    }
129
130    @Test
131    public void smoothScrollToPositionWithoutLayoutManager() throws InterruptedException {
132        mRecyclerView.setAdapter(new MockAdapter(20));
133        measure();
134        layout();
135        mRecyclerView.smoothScrollToPosition(5);
136    }
137
138    @Test
139    public void interceptTouchWithoutLayoutManager() {
140        mRecyclerView.setAdapter(new MockAdapter(20));
141        measure();
142        layout();
143        assertFalse(mRecyclerView.onInterceptTouchEvent(
144                MotionEvent.obtain(SystemClock.uptimeMillis(),
145                        SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 10, 10, 0)));
146    }
147
148    @Test
149    public void onTouchWithoutLayoutManager() {
150        mRecyclerView.setAdapter(new MockAdapter(20));
151        measure();
152        layout();
153        assertFalse(mRecyclerView.onTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(),
154                SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 10, 10, 0)));
155    }
156
157    @Test
158    public void layoutSimple() throws InterruptedException {
159        MockLayoutManager layoutManager = new MockLayoutManager();
160        mRecyclerView.setLayoutManager(layoutManager);
161        mRecyclerView.setAdapter(new MockAdapter(3));
162        layout();
163        assertEquals("when both layout manager and activity is set, recycler view should call"
164                + " layout manager's layout method", 1, layoutManager.mLayoutCount);
165    }
166
167    @Test
168    public void observingAdapters() {
169        MockAdapter adapterOld = new MockAdapter(1);
170        mRecyclerView.setAdapter(adapterOld);
171        assertTrue("attached adapter should have observables", adapterOld.hasObservers());
172
173        MockAdapter adapterNew = new MockAdapter(2);
174        mRecyclerView.setAdapter(adapterNew);
175        assertFalse("detached adapter should lose observable", adapterOld.hasObservers());
176        assertTrue("new adapter should have observers", adapterNew.hasObservers());
177
178        mRecyclerView.setAdapter(null);
179        assertNull("adapter should be removed successfully", mRecyclerView.getAdapter());
180        assertFalse("when adapter is removed, observables should be removed too",
181                adapterNew.hasObservers());
182    }
183
184    @Test
185    public void adapterChangeCallbacks() {
186        MockLayoutManager layoutManager = new MockLayoutManager();
187        mRecyclerView.setLayoutManager(layoutManager);
188        MockAdapter adapterOld = new MockAdapter(1);
189        mRecyclerView.setAdapter(adapterOld);
190        layoutManager.assertPrevNextAdapters(null, adapterOld);
191
192        MockAdapter adapterNew = new MockAdapter(2);
193        mRecyclerView.setAdapter(adapterNew);
194        layoutManager.assertPrevNextAdapters("switching adapters should trigger correct callbacks"
195                , adapterOld, adapterNew);
196
197        mRecyclerView.setAdapter(null);
198        layoutManager.assertPrevNextAdapters(
199                "Setting adapter null should trigger correct callbacks",
200                adapterNew, null);
201    }
202
203    @Test
204    public void recyclerOffsetsOnMove() {
205        MockLayoutManager  layoutManager = new MockLayoutManager();
206        final List<RecyclerView.ViewHolder> recycledVhs = new ArrayList<>();
207        mRecyclerView.setLayoutManager(layoutManager);
208        MockAdapter adapter = new MockAdapter(100) {
209            @Override
210            public void onViewRecycled(RecyclerView.ViewHolder holder) {
211                super.onViewRecycled(holder);
212                recycledVhs.add(holder);
213            }
214        };
215        MockViewHolder mvh = new MockViewHolder(new TextView(getContext()));
216        mRecyclerView.setAdapter(adapter);
217        adapter.bindViewHolder(mvh, 20);
218        mRecyclerView.mRecycler.mCachedViews.add(mvh);
219        mRecyclerView.offsetPositionRecordsForRemove(10, 9, false);
220
221        mRecyclerView.offsetPositionRecordsForRemove(11, 1, false);
222        assertEquals(1, recycledVhs.size());
223        assertSame(mvh, recycledVhs.get(0));
224    }
225
226    @Test
227    public void recyclerOffsetsOnAdd() {
228        MockLayoutManager  layoutManager = new MockLayoutManager();
229        final List<RecyclerView.ViewHolder> recycledVhs = new ArrayList<>();
230        mRecyclerView.setLayoutManager(layoutManager);
231        MockAdapter adapter = new MockAdapter(100) {
232            @Override
233            public void onViewRecycled(RecyclerView.ViewHolder holder) {
234                super.onViewRecycled(holder);
235                recycledVhs.add(holder);
236            }
237        };
238        MockViewHolder mvh = new MockViewHolder(new TextView(getContext()));
239        mRecyclerView.setAdapter(adapter);
240        adapter.bindViewHolder(mvh, 20);
241        mRecyclerView.mRecycler.mCachedViews.add(mvh);
242        mRecyclerView.offsetPositionRecordsForRemove(10, 9, false);
243
244        mRecyclerView.offsetPositionRecordsForInsert(15, 10);
245        assertEquals(11, mvh.mPosition);
246    }
247
248    @Test
249    public void savedStateWithStatelessLayoutManager() throws InterruptedException {
250        mRecyclerView.setLayoutManager(new MockLayoutManager() {
251            @Override
252            public Parcelable onSaveInstanceState() {
253                return null;
254            }
255        });
256        mRecyclerView.setAdapter(new MockAdapter(3));
257        Parcel parcel = Parcel.obtain();
258        String parcelSuffix = UUID.randomUUID().toString();
259        Parcelable savedState = mRecyclerView.onSaveInstanceState();
260        savedState.writeToParcel(parcel, 0);
261        parcel.writeString(parcelSuffix);
262
263        // reset position for reading
264        parcel.setDataPosition(0);
265        RecyclerView restored = new RecyclerView(getContext());
266        restored.setLayoutManager(new MockLayoutManager());
267        mRecyclerView.setAdapter(new MockAdapter(3));
268        // restore
269        savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel);
270        restored.onRestoreInstanceState(savedState);
271
272        assertEquals("Parcel reading should not go out of bounds", parcelSuffix,
273                parcel.readString());
274        assertEquals("When unmarshalling, all of the parcel should be read", 0, parcel.dataAvail());
275
276    }
277
278    @Test
279    public void savedState() throws InterruptedException {
280        MockLayoutManager mlm = new MockLayoutManager();
281        mRecyclerView.setLayoutManager(mlm);
282        mRecyclerView.setAdapter(new MockAdapter(3));
283        layout();
284        Parcelable savedState = mRecyclerView.onSaveInstanceState();
285        // we append a suffix to the parcelable to test out of bounds
286        String parcelSuffix = UUID.randomUUID().toString();
287        Parcel parcel = Parcel.obtain();
288        savedState.writeToParcel(parcel, 0);
289        parcel.writeString(parcelSuffix);
290
291        // reset for reading
292        parcel.setDataPosition(0);
293        // re-create
294        savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel);
295
296        RecyclerView restored = new RecyclerView(getContext());
297        MockLayoutManager mlmRestored = new MockLayoutManager();
298        restored.setLayoutManager(mlmRestored);
299        restored.setAdapter(new MockAdapter(3));
300        restored.onRestoreInstanceState(savedState);
301
302        assertEquals("Parcel reading should not go out of bounds", parcelSuffix,
303                parcel.readString());
304        assertEquals("When unmarshalling, all of the parcel should be read", 0, parcel.dataAvail());
305        assertEquals("uuid in layout manager should be preserved properly", mlm.mUuid,
306                mlmRestored.mUuid);
307        assertNotSame("stateless parameter should not be preserved", mlm.mLayoutCount,
308                mlmRestored.mLayoutCount);
309        layout();
310    }
311
312
313    @Test
314    public void dontSaveChildrenState() throws InterruptedException {
315        MockLayoutManager mlm = new MockLayoutManager() {
316            @Override
317            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
318                super.onLayoutChildren(recycler, state);
319                View view = recycler.getViewForPosition(0);
320                addView(view);
321                measureChildWithMargins(view, 0, 0);
322                view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
323            }
324        };
325        mRecyclerView.setLayoutManager(mlm);
326        mRecyclerView.setAdapter(new MockAdapter(3) {
327            @Override
328            public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
329                final LoggingView itemView = new LoggingView(parent.getContext());
330                //noinspection ResourceType
331                itemView.setId(3);
332                return new MockViewHolder(itemView);
333            }
334        });
335        measure();
336        layout();
337        View view = mRecyclerView.getChildAt(0);
338        assertNotNull("test sanity", view);
339        LoggingView loggingView = (LoggingView) view;
340        SparseArray<Parcelable> container = new SparseArray<Parcelable>();
341        mRecyclerView.saveHierarchyState(container);
342        assertEquals("children's save state method should not be called", 0,
343                loggingView.getOnSavedInstanceCnt());
344    }
345
346    static class MockLayoutManager extends RecyclerView.LayoutManager {
347
348        int mLayoutCount = 0;
349
350        int mAdapterChangedCount = 0;
351
352        RecyclerView.Adapter mPrevAdapter;
353
354        RecyclerView.Adapter mNextAdapter;
355
356        String mUuid = UUID.randomUUID().toString();
357
358        @Override
359        public void onAdapterChanged(RecyclerView.Adapter oldAdapter,
360                RecyclerView.Adapter newAdapter) {
361            super.onAdapterChanged(oldAdapter, newAdapter);
362            mPrevAdapter = oldAdapter;
363            mNextAdapter = newAdapter;
364            mAdapterChangedCount++;
365        }
366
367        @Override
368        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
369            mLayoutCount += 1;
370        }
371
372        @Override
373        public Parcelable onSaveInstanceState() {
374            LayoutManagerSavedState lss = new LayoutManagerSavedState();
375            lss.mUuid = mUuid;
376            return lss;
377        }
378
379        @Override
380        public void onRestoreInstanceState(Parcelable state) {
381            super.onRestoreInstanceState(state);
382            if (state instanceof LayoutManagerSavedState) {
383                mUuid = ((LayoutManagerSavedState) state).mUuid;
384            }
385        }
386
387        @Override
388        public RecyclerView.LayoutParams generateDefaultLayoutParams() {
389            return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
390                    ViewGroup.LayoutParams.WRAP_CONTENT);
391        }
392
393        public void assertPrevNextAdapters(String message, RecyclerView.Adapter prevAdapter,
394                RecyclerView.Adapter nextAdapter) {
395            assertSame(message, prevAdapter, mPrevAdapter);
396            assertSame(message, nextAdapter, mNextAdapter);
397        }
398
399        public void assertPrevNextAdapters(RecyclerView.Adapter prevAdapter,
400                RecyclerView.Adapter nextAdapter) {
401            assertPrevNextAdapters("Adapters from onAdapterChanged callback should match",
402                    prevAdapter, nextAdapter);
403        }
404
405        @Override
406        public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
407                RecyclerView.State state) {
408            return dx;
409        }
410
411        @Override
412        public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
413                RecyclerView.State state) {
414            return dy;
415        }
416
417        @Override
418        public boolean canScrollHorizontally() {
419            return true;
420        }
421
422        @Override
423        public boolean canScrollVertically() {
424            return true;
425        }
426    }
427
428    static class LayoutManagerSavedState implements Parcelable {
429
430        String mUuid;
431
432        public LayoutManagerSavedState(Parcel in) {
433            mUuid = in.readString();
434        }
435
436        public LayoutManagerSavedState() {
437
438        }
439
440        @Override
441        public int describeContents() {
442            return 0;
443        }
444
445        @Override
446        public void writeToParcel(Parcel dest, int flags) {
447            dest.writeString(mUuid);
448        }
449
450        public static final Parcelable.Creator<LayoutManagerSavedState> CREATOR
451                = new Parcelable.Creator<LayoutManagerSavedState>() {
452            @Override
453            public LayoutManagerSavedState createFromParcel(Parcel in) {
454                return new LayoutManagerSavedState(in);
455            }
456
457            @Override
458            public LayoutManagerSavedState[] newArray(int size) {
459                return new LayoutManagerSavedState[size];
460            }
461        };
462    }
463
464    static class MockAdapter extends RecyclerView.Adapter {
465
466        private int mCount = 0;
467
468
469        MockAdapter(int count) {
470            this.mCount = count;
471        }
472
473        @Override
474        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
475            return new MockViewHolder(new TextView(parent.getContext()));
476        }
477
478        @Override
479        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
480
481        }
482
483        @Override
484        public int getItemCount() {
485            return mCount;
486        }
487
488        void removeItems(int start, int count) {
489            mCount -= count;
490            notifyItemRangeRemoved(start, count);
491        }
492
493        void addItems(int start, int count) {
494            mCount += count;
495            notifyItemRangeInserted(start, count);
496        }
497    }
498
499    static class MockViewHolder extends RecyclerView.ViewHolder {
500        public Object mItem;
501        public MockViewHolder(View itemView) {
502            super(itemView);
503        }
504    }
505
506    static class LoggingView extends TextView {
507        private int mOnSavedInstanceCnt = 0;
508
509        public LoggingView(Context context) {
510            super(context);
511        }
512
513        public LoggingView(Context context, AttributeSet attrs) {
514            super(context, attrs);
515        }
516
517        public LoggingView(Context context, AttributeSet attrs, int defStyleAttr) {
518            super(context, attrs, defStyleAttr);
519        }
520
521        @Override
522        public Parcelable onSaveInstanceState() {
523            mOnSavedInstanceCnt ++;
524            return super.onSaveInstanceState();
525        }
526
527        public int getOnSavedInstanceCnt() {
528            return mOnSavedInstanceCnt;
529        }
530    }
531}