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    static class MockLayoutManager extends RecyclerView.LayoutManager {
179
180        int mLayoutCount = 0;
181
182        int mAdapterChangedCount = 0;
183
184        RecyclerView.Adapter mPrevAdapter;
185
186        RecyclerView.Adapter mNextAdapter;
187
188        String mUuid = UUID.randomUUID().toString();
189
190        @Override
191        public void onAdapterChanged(RecyclerView.Adapter oldAdapter,
192                RecyclerView.Adapter newAdapter) {
193            super.onAdapterChanged(oldAdapter, newAdapter);
194            mPrevAdapter = oldAdapter;
195            mNextAdapter = newAdapter;
196            mAdapterChangedCount++;
197        }
198
199        @Override
200        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
201            mLayoutCount += 1;
202        }
203
204        @Override
205        public Parcelable onSaveInstanceState() {
206            LayoutManagerSavedState lss = new LayoutManagerSavedState();
207            lss.mUuid = mUuid;
208            return lss;
209        }
210
211        @Override
212        public void onRestoreInstanceState(Parcelable state) {
213            super.onRestoreInstanceState(state);
214            if (state instanceof LayoutManagerSavedState) {
215                mUuid = ((LayoutManagerSavedState) state).mUuid;
216            }
217        }
218
219        @Override
220        public RecyclerView.LayoutParams generateDefaultLayoutParams() {
221            return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
222                    ViewGroup.LayoutParams.WRAP_CONTENT);
223        }
224
225        public void assertPrevNextAdapters(String message, RecyclerView.Adapter prevAdapter,
226                RecyclerView.Adapter nextAdapter) {
227            assertSame(message, prevAdapter, mPrevAdapter);
228            assertSame(message, nextAdapter, mNextAdapter);
229        }
230
231        public void assertPrevNextAdapters(RecyclerView.Adapter prevAdapter,
232                RecyclerView.Adapter nextAdapter) {
233            assertPrevNextAdapters("Adapters from onAdapterChanged callback should match",
234                    prevAdapter, nextAdapter);
235        }
236
237        @Override
238        public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
239                RecyclerView.State state) {
240            return dx;
241        }
242
243        @Override
244        public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
245                RecyclerView.State state) {
246            return dy;
247        }
248
249        @Override
250        public boolean canScrollHorizontally() {
251            return true;
252        }
253
254        @Override
255        public boolean canScrollVertically() {
256            return true;
257        }
258    }
259
260    static class LayoutManagerSavedState implements Parcelable {
261
262        String mUuid;
263
264        public LayoutManagerSavedState(Parcel in) {
265            mUuid = in.readString();
266        }
267
268        public LayoutManagerSavedState() {
269
270        }
271
272        @Override
273        public int describeContents() {
274            return 0;
275        }
276
277        @Override
278        public void writeToParcel(Parcel dest, int flags) {
279            dest.writeString(mUuid);
280        }
281
282        public static final Parcelable.Creator<LayoutManagerSavedState> CREATOR
283                = new Parcelable.Creator<LayoutManagerSavedState>() {
284            @Override
285            public LayoutManagerSavedState createFromParcel(Parcel in) {
286                return new LayoutManagerSavedState(in);
287            }
288
289            @Override
290            public LayoutManagerSavedState[] newArray(int size) {
291                return new LayoutManagerSavedState[size];
292            }
293        };
294    }
295
296    static class MockAdapter extends RecyclerView.Adapter {
297
298        private int mCount = 0;
299
300
301        MockAdapter(int count) {
302            this.mCount = count;
303        }
304
305        @Override
306        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
307            return new MockViewHolder(new TextView(parent.getContext()));
308        }
309
310        @Override
311        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
312
313        }
314
315        @Override
316        public int getItemCount() {
317            return mCount;
318        }
319
320        void removeItems(int start, int count) {
321            mCount -= count;
322            notifyItemRangeRemoved(start, count);
323        }
324
325        void addItems(int start, int count) {
326            mCount += count;
327            notifyItemRangeInserted(start, count);
328        }
329    }
330
331    static class MockViewHolder extends RecyclerView.ViewHolder {
332
333        public MockViewHolder(View itemView) {
334            super(itemView);
335        }
336    }
337}