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}