| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.support.v7.widget; |
| |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| import android.test.AndroidTestCase; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.TextView; |
| |
| import java.util.UUID; |
| |
| public class RecyclerViewBasicTest extends AndroidTestCase { |
| |
| RecyclerView mRecyclerView; |
| |
| @Override |
| protected void setUp() throws Exception { |
| super.setUp(); |
| mRecyclerView = new RecyclerView(mContext); |
| } |
| |
| public void testMeasureWithoutLayoutManager() { |
| Throwable measureThrowable = null; |
| try { |
| measure(); |
| } catch (Throwable throwable) { |
| measureThrowable = throwable; |
| } |
| assertTrue("Calling measure without a layout manager should throw exception" |
| , measureThrowable instanceof NullPointerException); |
| } |
| |
| private void measure() { |
| mRecyclerView.measure(View.MeasureSpec.AT_MOST | 320, View.MeasureSpec.AT_MOST | 240); |
| } |
| |
| private void layout() { |
| mRecyclerView.layout(0, 0, 320, 320); |
| } |
| |
| private void safeLayout() { |
| try { |
| layout(); |
| } catch (Throwable t) { |
| |
| } |
| } |
| |
| public void testLayoutWithoutLayoutManager() throws InterruptedException { |
| MockLayoutManager layoutManager = new MockLayoutManager(); |
| mRecyclerView.setLayoutManager(layoutManager); |
| safeLayout(); |
| assertEquals("layout manager should not be called if there is no adapter attached", |
| 0, layoutManager.mLayoutCount); |
| } |
| |
| public void testLayout() throws InterruptedException { |
| MockLayoutManager layoutManager = new MockLayoutManager(); |
| mRecyclerView.setLayoutManager(layoutManager); |
| mRecyclerView.setAdapter(new MockAdapter(3)); |
| layout(); |
| assertEquals("when both layout manager and activity is set, recycler view should call" |
| + " layout manager's layout method", 1, layoutManager.mLayoutCount); |
| } |
| |
| public void testObservingAdapters() { |
| MockAdapter adapterOld = new MockAdapter(1); |
| mRecyclerView.setAdapter(adapterOld); |
| assertTrue("attached adapter should have observables", adapterOld.hasObservers()); |
| |
| MockAdapter adapterNew = new MockAdapter(2); |
| mRecyclerView.setAdapter(adapterNew); |
| assertFalse("detached adapter should lose observable", adapterOld.hasObservers()); |
| assertTrue("new adapter should have observers", adapterNew.hasObservers()); |
| |
| mRecyclerView.setAdapter(null); |
| assertNull("adapter should be removed successfully", mRecyclerView.getAdapter()); |
| assertFalse("when adapter is removed, observables should be removed too", |
| adapterNew.hasObservers()); |
| } |
| |
| public void testAdapterChangeCallbacks() { |
| MockLayoutManager layoutManager = new MockLayoutManager(); |
| mRecyclerView.setLayoutManager(layoutManager); |
| MockAdapter adapterOld = new MockAdapter(1); |
| mRecyclerView.setAdapter(adapterOld); |
| layoutManager.assertPrevNextAdapters(null, adapterOld); |
| |
| MockAdapter adapterNew = new MockAdapter(2); |
| mRecyclerView.setAdapter(adapterNew); |
| layoutManager.assertPrevNextAdapters("switching adapters should trigger correct callbacks" |
| , adapterOld, adapterNew); |
| |
| mRecyclerView.setAdapter(null); |
| layoutManager.assertPrevNextAdapters( |
| "Setting adapter null should trigger correct callbacks", |
| adapterNew, null); |
| } |
| |
| public void testSavedStateWithStatelessLayoutManager() throws InterruptedException { |
| mRecyclerView.setLayoutManager(new MockLayoutManager() { |
| @Override |
| public Parcelable onSaveInstanceState() { |
| return null; |
| } |
| }); |
| mRecyclerView.setAdapter(new MockAdapter(3)); |
| Parcel parcel = Parcel.obtain(); |
| String parcelSuffix = UUID.randomUUID().toString(); |
| Parcelable savedState = mRecyclerView.onSaveInstanceState(); |
| savedState.writeToParcel(parcel, 0); |
| parcel.writeString(parcelSuffix); |
| |
| // reset position for reading |
| parcel.setDataPosition(0); |
| RecyclerView restored = new RecyclerView(mContext); |
| restored.setLayoutManager(new MockLayoutManager()); |
| mRecyclerView.setAdapter(new MockAdapter(3)); |
| // restore |
| savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel); |
| restored.onRestoreInstanceState(savedState); |
| |
| assertEquals("Parcel reading should not go out of bounds", parcelSuffix, |
| parcel.readString()); |
| assertEquals("When unmarshalling, all of the parcel should be read", 0, parcel.dataAvail()); |
| |
| } |
| |
| public void testSavedState() throws InterruptedException { |
| MockLayoutManager mlm = new MockLayoutManager(); |
| mRecyclerView.setLayoutManager(mlm); |
| mRecyclerView.setAdapter(new MockAdapter(3)); |
| layout(); |
| Parcelable savedState = mRecyclerView.onSaveInstanceState(); |
| // we append a suffix to the parcelable to test out of bounds |
| String parcelSuffix = UUID.randomUUID().toString(); |
| Parcel parcel = Parcel.obtain(); |
| savedState.writeToParcel(parcel, 0); |
| parcel.writeString(parcelSuffix); |
| |
| // reset for reading |
| parcel.setDataPosition(0); |
| // re-create |
| savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel); |
| |
| RecyclerView restored = new RecyclerView(mContext); |
| MockLayoutManager mlmRestored = new MockLayoutManager(); |
| restored.setLayoutManager(mlmRestored); |
| restored.setAdapter(new MockAdapter(3)); |
| restored.onRestoreInstanceState(savedState); |
| |
| assertEquals("Parcel reading should not go out of bounds", parcelSuffix, |
| parcel.readString()); |
| assertEquals("When unmarshalling, all of the parcel should be read", 0, parcel.dataAvail()); |
| assertEquals("uuid in layout manager should be preserved properly", mlm.mUuid, |
| mlmRestored.mUuid); |
| assertNotSame("stateless parameter should not be preserved", mlm.mLayoutCount, |
| mlmRestored.mLayoutCount); |
| layout(); |
| } |
| |
| static class MockLayoutManager extends RecyclerView.LayoutManager { |
| |
| int mLayoutCount = 0; |
| |
| int mAdapterChangedCount = 0; |
| |
| RecyclerView.Adapter mPrevAdapter; |
| |
| RecyclerView.Adapter mNextAdapter; |
| |
| String mUuid = UUID.randomUUID().toString(); |
| |
| @Override |
| public void onAdapterChanged(RecyclerView.Adapter oldAdapter, |
| RecyclerView.Adapter newAdapter) { |
| super.onAdapterChanged(oldAdapter, newAdapter); |
| mPrevAdapter = oldAdapter; |
| mNextAdapter = newAdapter; |
| mAdapterChangedCount++; |
| } |
| |
| @Override |
| public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { |
| mLayoutCount += 1; |
| } |
| |
| @Override |
| public Parcelable onSaveInstanceState() { |
| LayoutManagerSavedState lss = new LayoutManagerSavedState(); |
| lss.mUuid = mUuid; |
| return lss; |
| } |
| |
| @Override |
| public void onRestoreInstanceState(Parcelable state) { |
| super.onRestoreInstanceState(state); |
| if (state instanceof LayoutManagerSavedState) { |
| mUuid = ((LayoutManagerSavedState) state).mUuid; |
| } |
| } |
| |
| @Override |
| public RecyclerView.LayoutParams generateDefaultLayoutParams() { |
| return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, |
| ViewGroup.LayoutParams.WRAP_CONTENT); |
| } |
| |
| public void assertPrevNextAdapters(String message, RecyclerView.Adapter prevAdapter, |
| RecyclerView.Adapter nextAdapter) { |
| assertSame(message, prevAdapter, mPrevAdapter); |
| assertSame(message, nextAdapter, mNextAdapter); |
| } |
| |
| public void assertPrevNextAdapters(RecyclerView.Adapter prevAdapter, |
| RecyclerView.Adapter nextAdapter) { |
| assertPrevNextAdapters("Adapters from onAdapterChanged callback should match", |
| prevAdapter, nextAdapter); |
| } |
| |
| @Override |
| public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, |
| RecyclerView.State state) { |
| return dx; |
| } |
| |
| @Override |
| public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, |
| RecyclerView.State state) { |
| return dy; |
| } |
| |
| @Override |
| public boolean canScrollHorizontally() { |
| return true; |
| } |
| |
| @Override |
| public boolean canScrollVertically() { |
| return true; |
| } |
| } |
| |
| static class LayoutManagerSavedState implements Parcelable { |
| |
| String mUuid; |
| |
| public LayoutManagerSavedState(Parcel in) { |
| mUuid = in.readString(); |
| } |
| |
| public LayoutManagerSavedState() { |
| |
| } |
| |
| @Override |
| public int describeContents() { |
| return 0; |
| } |
| |
| @Override |
| public void writeToParcel(Parcel dest, int flags) { |
| dest.writeString(mUuid); |
| } |
| |
| public static final Parcelable.Creator<LayoutManagerSavedState> CREATOR |
| = new Parcelable.Creator<LayoutManagerSavedState>() { |
| @Override |
| public LayoutManagerSavedState createFromParcel(Parcel in) { |
| return new LayoutManagerSavedState(in); |
| } |
| |
| @Override |
| public LayoutManagerSavedState[] newArray(int size) { |
| return new LayoutManagerSavedState[size]; |
| } |
| }; |
| } |
| |
| static class MockAdapter extends RecyclerView.Adapter { |
| |
| private int mCount = 0; |
| |
| |
| MockAdapter(int count) { |
| this.mCount = count; |
| } |
| |
| @Override |
| public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { |
| return new MockViewHolder(new TextView(parent.getContext())); |
| } |
| |
| @Override |
| public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { |
| |
| } |
| |
| @Override |
| public int getItemCount() { |
| return mCount; |
| } |
| |
| void removeItems(int start, int count) { |
| mCount -= count; |
| notifyItemRangeRemoved(start, count); |
| } |
| |
| void addItems(int start, int count) { |
| mCount += count; |
| notifyItemRangeInserted(start, count); |
| } |
| } |
| |
| static class MockViewHolder extends RecyclerView.ViewHolder { |
| |
| public MockViewHolder(View itemView) { |
| super(itemView); |
| } |
| } |
| } |