RecyclerViewBasicTest.java revision d7d27e9ebe5c7325e67e1a8af265378bd2056cad
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 android.os.Parcel;
20import android.os.Parcelable;
21import android.test.AndroidTestCase;
22import android.view.View;
23import android.view.ViewGroup;
24import android.widget.TextView;
25
26import java.util.UUID;
27
28public class RecyclerViewBasicTest extends AndroidTestCase {
29
30    RecyclerView mRecyclerView;
31
32    @Override
33    protected void setUp() throws Exception {
34        super.setUp();
35        mRecyclerView = new RecyclerView(mContext);
36    }
37
38    public void testMeasureWithoutLayoutManager() {
39        Throwable measureThrowable = null;
40        try {
41            measure();
42        } catch (Throwable throwable) {
43            measureThrowable = throwable;
44        }
45        assertTrue("Calling measure without a layout manager should throw exception"
46                , measureThrowable instanceof NullPointerException);
47    }
48
49    private void measure() {
50        mRecyclerView.measure(View.MeasureSpec.AT_MOST | 320, View.MeasureSpec.AT_MOST | 240);
51    }
52
53    private void layout() {
54        mRecyclerView.layout(0, 0, 320, 320);
55    }
56
57    private void safeLayout() {
58        try {
59            layout();
60        } catch (Throwable t) {
61
62        }
63    }
64
65    public void testLayoutWithoutLayoutManager() throws InterruptedException {
66        MockLayoutManager layoutManager = new MockLayoutManager();
67        mRecyclerView.setLayoutManager(layoutManager);
68        safeLayout();
69        assertEquals("layout manager should not be called if there is no adapter attached",
70                0, layoutManager.mLayoutCount);
71    }
72
73    public void testLayout() throws InterruptedException {
74        MockLayoutManager layoutManager = new MockLayoutManager();
75        mRecyclerView.setLayoutManager(layoutManager);
76        mRecyclerView.setAdapter(new MockAdapter(3));
77        layout();
78        assertEquals("when both layout manager and activity is set, recycler view should call"
79                + " layout manager's layout method", 1, layoutManager.mLayoutCount);
80    }
81
82    public void testObservingAdapters() {
83        MockAdapter adapterOld = new MockAdapter(1);
84        mRecyclerView.setAdapter(adapterOld);
85        assertTrue("attached adapter should have observables", adapterOld.hasObservers());
86
87        MockAdapter adapterNew = new MockAdapter(2);
88        mRecyclerView.setAdapter(adapterNew);
89        assertFalse("detached adapter should lose observable", adapterOld.hasObservers());
90        assertTrue("new adapter should have observers", adapterNew.hasObservers());
91
92        mRecyclerView.setAdapter(null);
93        assertNull("adapter should be removed successfully", mRecyclerView.getAdapter());
94        assertFalse("when adapter is removed, observables should be removed too",
95                adapterNew.hasObservers());
96    }
97
98    public void testAdapterChangeCallbacks() {
99        MockLayoutManager layoutManager = new MockLayoutManager();
100        mRecyclerView.setLayoutManager(layoutManager);
101        MockAdapter adapterOld = new MockAdapter(1);
102        mRecyclerView.setAdapter(adapterOld);
103        layoutManager.assertPrevNextAdapters(null, adapterOld);
104
105        MockAdapter adapterNew = new MockAdapter(2);
106        mRecyclerView.setAdapter(adapterNew);
107        layoutManager.assertPrevNextAdapters("switching adapters should trigger correct callbacks"
108                , adapterOld, adapterNew);
109
110        mRecyclerView.setAdapter(null);
111        layoutManager.assertPrevNextAdapters(
112                "Setting adapter null should trigger correct callbacks",
113                adapterNew, null);
114    }
115
116    public void testSavedStateWithStatelessLayoutManager() throws InterruptedException {
117        mRecyclerView.setLayoutManager(new MockLayoutManager() {
118            @Override
119            public Parcelable onSaveInstanceState() {
120                return null;
121            }
122        });
123        mRecyclerView.setAdapter(new MockAdapter(3));
124        Parcel parcel = Parcel.obtain();
125        String parcelSuffix = UUID.randomUUID().toString();
126        Parcelable savedState = mRecyclerView.onSaveInstanceState();
127        savedState.writeToParcel(parcel, 0);
128        parcel.writeString(parcelSuffix);
129
130        // reset position for reading
131        parcel.setDataPosition(0);
132        RecyclerView restored = new RecyclerView(mContext);
133        restored.setLayoutManager(new MockLayoutManager());
134        mRecyclerView.setAdapter(new MockAdapter(3));
135        // restore
136        savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel);
137        restored.onRestoreInstanceState(savedState);
138
139        assertEquals("Parcel reading should not go out of bounds", parcelSuffix,
140                parcel.readString());
141        assertEquals("When unmarshalling, all of the parcel should be read", 0, parcel.dataAvail());
142
143    }
144
145    public void testSavedState() throws InterruptedException {
146        MockLayoutManager mlm = new MockLayoutManager();
147        mRecyclerView.setLayoutManager(mlm);
148        mRecyclerView.setAdapter(new MockAdapter(3));
149        layout();
150        Parcelable savedState = mRecyclerView.onSaveInstanceState();
151        // we append a suffix to the parcelable to test out of bounds
152        String parcelSuffix = UUID.randomUUID().toString();
153        Parcel parcel = Parcel.obtain();
154        savedState.writeToParcel(parcel, 0);
155        parcel.writeString(parcelSuffix);
156
157        // reset for reading
158        parcel.setDataPosition(0);
159        // re-create
160        savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel);
161
162        RecyclerView restored = new RecyclerView(mContext);
163        MockLayoutManager mlmRestored = new MockLayoutManager();
164        restored.setLayoutManager(mlmRestored);
165        restored.setAdapter(new MockAdapter(3));
166        restored.onRestoreInstanceState(savedState);
167
168        assertEquals("Parcel reading should not go out of bounds", parcelSuffix,
169                parcel.readString());
170        assertEquals("When unmarshalling, all of the parcel should be read", 0, parcel.dataAvail());
171        assertEquals("uuid in layout manager should be preserved properly", mlm.mUuid,
172                mlmRestored.mUuid);
173        assertNotSame("stateless parameter should not be preserved", mlm.mLayoutCount,
174                mlmRestored.mLayoutCount);
175        layout();
176
177
178    }
179
180    static class MockLayoutManager extends RecyclerView.LayoutManager {
181
182        int mLayoutCount = 0;
183
184        int mAdapterChangedCount = 0;
185
186        RecyclerView.Adapter mPrevAdapter;
187
188        RecyclerView.Adapter mNextAdapter;
189
190        String mUuid = UUID.randomUUID().toString();
191
192        @Override
193        public void onAdapterChanged(RecyclerView.Adapter oldAdapter,
194                RecyclerView.Adapter newAdapter) {
195            super.onAdapterChanged(oldAdapter, newAdapter);
196            mPrevAdapter = oldAdapter;
197            mNextAdapter = newAdapter;
198            mAdapterChangedCount++;
199        }
200
201        @Override
202        public void onLayoutChildren(RecyclerView.Adapter adapter, RecyclerView.Recycler recycler,
203                boolean structureChanged, RecyclerView.State state) {
204            mLayoutCount += 1;
205        }
206
207        @Override
208        public Parcelable onSaveInstanceState() {
209            LayoutManagerSavedState lss = new LayoutManagerSavedState();
210            lss.mUuid = mUuid;
211            return lss;
212        }
213
214        @Override
215        public void onRestoreInstanceState(Parcelable state) {
216            super.onRestoreInstanceState(state);
217            if (state instanceof LayoutManagerSavedState) {
218                mUuid = ((LayoutManagerSavedState) state).mUuid;
219            }
220        }
221
222        @Override
223        public RecyclerView.LayoutParams generateDefaultLayoutParams() {
224            return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
225                    ViewGroup.LayoutParams.WRAP_CONTENT);
226        }
227
228        public void assertPrevNextAdapters(String message, RecyclerView.Adapter prevAdapter,
229                RecyclerView.Adapter nextAdapter) {
230            assertSame(message, prevAdapter, mPrevAdapter);
231            assertSame(message, nextAdapter, mNextAdapter);
232        }
233
234        public void assertPrevNextAdapters(RecyclerView.Adapter prevAdapter,
235                RecyclerView.Adapter nextAdapter) {
236            assertPrevNextAdapters("Adapters from onAdapterChanged callback should match",
237                    prevAdapter, nextAdapter);
238        }
239    }
240
241    static class LayoutManagerSavedState implements Parcelable {
242
243        String mUuid;
244
245        public LayoutManagerSavedState(Parcel in) {
246            mUuid = in.readString();
247        }
248
249        public LayoutManagerSavedState() {
250
251        }
252
253        @Override
254        public int describeContents() {
255            return 0;
256        }
257
258        @Override
259        public void writeToParcel(Parcel dest, int flags) {
260            dest.writeString(mUuid);
261        }
262
263        public static final Parcelable.Creator<LayoutManagerSavedState> CREATOR
264                = new Parcelable.Creator<LayoutManagerSavedState>() {
265            @Override
266            public LayoutManagerSavedState createFromParcel(Parcel in) {
267                return new LayoutManagerSavedState(in);
268            }
269
270            @Override
271            public LayoutManagerSavedState[] newArray(int size) {
272                return new LayoutManagerSavedState[size];
273            }
274        };
275    }
276
277    static class MockAdapter extends RecyclerView.Adapter {
278
279        private int mCount = 0;
280
281
282        MockAdapter(int count) {
283            this.mCount = count;
284        }
285
286        @Override
287        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
288            return new MockViewHolder(new TextView(parent.getContext()));
289        }
290
291        @Override
292        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
293
294        }
295
296        @Override
297        public int getItemCount() {
298            return mCount;
299        }
300
301        void removeItems(int start, int count) {
302            mCount -= count;
303            notifyItemRangeRemoved(start, count);
304        }
305
306        void addItems(int start, int count) {
307            mCount += count;
308            notifyItemRangeInserted(start, count);
309        }
310    }
311
312    static class MockViewHolder extends RecyclerView.ViewHolder {
313
314        public MockViewHolder(View itemView) {
315            super(itemView);
316        }
317    }
318
319}
320
321