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