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 17 package android.support.v7.widget; 18 19 import junit.framework.AssertionFailedError; 20 import junit.framework.TestResult; 21 22 import org.junit.Before; 23 import org.junit.Test; 24 import org.junit.runner.RunWith; 25 import org.junit.runners.JUnit4; 26 27 import android.test.AndroidTestCase; 28 import android.util.Log; 29 import android.view.View; 30 import android.widget.TextView; 31 32 import java.util.ArrayList; 33 import java.util.LinkedList; 34 import java.util.List; 35 import java.util.Queue; 36 import java.util.Random; 37 import java.util.concurrent.atomic.AtomicInteger; 38 39 import static android.support.v7.widget.RecyclerView.*; 40 41 @RunWith(JUnit4.class) 42 public class AdapterHelperTest extends AndroidTestCase { 43 44 private static final boolean DEBUG = false; 45 46 private boolean mCollectLogs = false; 47 48 private static final String TAG = "AHT"; 49 50 List<MockViewHolder> mViewHolders; 51 52 AdapterHelper mAdapterHelper; 53 54 List<AdapterHelper.UpdateOp> mFirstPassUpdates, mSecondPassUpdates; 55 56 TestAdapter mTestAdapter; 57 58 TestAdapter mPreProcessClone; // we clone adapter pre-process to run operations to see result 59 60 private List<TestAdapter.Item> mPreLayoutItems; 61 62 private StringBuilder mLog = new StringBuilder(); 63 64 @Override run(TestResult result)65 public void run(TestResult result) { 66 super.run(result); 67 if (!result.wasSuccessful()) { 68 result.addFailure(this, new AssertionFailedError(mLog.toString())); 69 } 70 } 71 72 @Before cleanState()73 public void cleanState() { 74 mLog.setLength(0); 75 mPreLayoutItems = new ArrayList<TestAdapter.Item>(); 76 mViewHolders = new ArrayList<MockViewHolder>(); 77 mFirstPassUpdates = new ArrayList<AdapterHelper.UpdateOp>(); 78 mSecondPassUpdates = new ArrayList<AdapterHelper.UpdateOp>(); 79 mPreProcessClone = null; 80 mAdapterHelper = new AdapterHelper(new AdapterHelper.Callback() { 81 @Override 82 public RecyclerView.ViewHolder findViewHolder(int position) { 83 for (ViewHolder vh : mViewHolders) { 84 if (vh.mPosition == position && !vh.isRemoved()) { 85 return vh; 86 } 87 } 88 return null; 89 } 90 91 @Override 92 public void offsetPositionsForRemovingInvisible(int positionStart, int itemCount) { 93 final int positionEnd = positionStart + itemCount; 94 for (ViewHolder holder : mViewHolders) { 95 if (holder.mPosition >= positionEnd) { 96 holder.offsetPosition(-itemCount, true); 97 } else if (holder.mPosition >= positionStart) { 98 holder.flagRemovedAndOffsetPosition(positionStart - 1, -itemCount, true); 99 } 100 } 101 } 102 103 @Override 104 public void offsetPositionsForRemovingLaidOutOrNewView(int positionStart, 105 int itemCount) { 106 final int positionEnd = positionStart + itemCount; 107 for (ViewHolder holder : mViewHolders) { 108 if (holder.mPosition >= positionEnd) { 109 holder.offsetPosition(-itemCount, false); 110 } else if (holder.mPosition >= positionStart) { 111 holder.flagRemovedAndOffsetPosition(positionStart - 1, -itemCount, false); 112 } 113 } 114 } 115 116 @Override 117 public void markViewHoldersUpdated(int positionStart, int itemCount, Object payload) { 118 final int positionEnd = positionStart + itemCount; 119 for (ViewHolder holder : mViewHolders) { 120 if (holder.mPosition >= positionStart && holder.mPosition < positionEnd) { 121 holder.addFlags(ViewHolder.FLAG_UPDATE); 122 holder.addChangePayload(payload); 123 } 124 } 125 } 126 127 @Override 128 public void onDispatchFirstPass(AdapterHelper.UpdateOp updateOp) { 129 if (DEBUG) { 130 log("first pass:" + updateOp.toString()); 131 } 132 for (ViewHolder viewHolder : mViewHolders) { 133 for (int i = 0; i < updateOp.itemCount; i++) { 134 // events are dispatched before view holders are updated for consistency 135 assertFalse("update op should not match any existing view holders", 136 viewHolder.getLayoutPosition() == updateOp.positionStart + i); 137 } 138 } 139 140 mFirstPassUpdates.add(updateOp); 141 } 142 143 @Override 144 public void onDispatchSecondPass(AdapterHelper.UpdateOp updateOp) { 145 if (DEBUG) { 146 log("second pass:" + updateOp.toString()); 147 } 148 mSecondPassUpdates.add(updateOp); 149 } 150 151 @Override 152 public void offsetPositionsForAdd(int positionStart, int itemCount) { 153 for (ViewHolder holder : mViewHolders) { 154 if (holder != null && holder.mPosition >= positionStart) { 155 holder.offsetPosition(itemCount, false); 156 } 157 } 158 } 159 160 @Override 161 public void offsetPositionsForMove(int from, int to) { 162 final int start, end, inBetweenOffset; 163 if (from < to) { 164 start = from; 165 end = to; 166 inBetweenOffset = -1; 167 } else { 168 start = to; 169 end = from; 170 inBetweenOffset = 1; 171 } 172 for (ViewHolder holder : mViewHolders) { 173 if (holder == null || holder.mPosition < start || holder.mPosition > end) { 174 continue; 175 } 176 if (holder.mPosition == from) { 177 holder.offsetPosition(to - from, false); 178 } else { 179 holder.offsetPosition(inBetweenOffset, false); 180 } 181 } 182 } 183 }, true); 184 } 185 log(String msg)186 void log(String msg) { 187 if (mCollectLogs) { 188 mLog.append(msg).append("\n"); 189 } else { 190 Log.d(TAG, msg); 191 } 192 } 193 setupBasic(int count, int visibleStart, int visibleCount)194 void setupBasic(int count, int visibleStart, int visibleCount) { 195 if (DEBUG) { 196 log("setupBasic(" + count + "," + visibleStart + "," + visibleCount + ");"); 197 } 198 mTestAdapter = new TestAdapter(count, mAdapterHelper); 199 for (int i = 0; i < visibleCount; i++) { 200 addViewHolder(visibleStart + i); 201 } 202 mPreProcessClone = mTestAdapter.createCopy(); 203 } 204 addViewHolder(int position)205 private void addViewHolder(int position) { 206 MockViewHolder viewHolder = new MockViewHolder( 207 new TextView(getContext())); 208 viewHolder.mPosition = position; 209 viewHolder.mItem = mTestAdapter.mItems.get(position); 210 mViewHolders.add(viewHolder); 211 } 212 213 @Test testChangeAll()214 public void testChangeAll() throws Exception { 215 try { 216 setupBasic(5, 0, 3); 217 up(0, 5); 218 mAdapterHelper.preProcess(); 219 } catch (Throwable t) { 220 throw new Exception(mLog.toString()); 221 } 222 } 223 224 @Test testFindPositionOffsetInPreLayout()225 public void testFindPositionOffsetInPreLayout() { 226 setupBasic(50, 25, 10); 227 rm(24, 5); 228 mAdapterHelper.preProcess(); 229 // since 25 is invisible, we offset by one while checking 230 assertEquals("find position for view 23", 231 23, mAdapterHelper.findPositionOffset(23)); 232 assertEquals("find position for view 24", 233 -1, mAdapterHelper.findPositionOffset(24)); 234 assertEquals("find position for view 25", 235 -1, mAdapterHelper.findPositionOffset(25)); 236 assertEquals("find position for view 26", 237 -1, mAdapterHelper.findPositionOffset(26)); 238 assertEquals("find position for view 27", 239 -1, mAdapterHelper.findPositionOffset(27)); 240 assertEquals("find position for view 28", 241 24, mAdapterHelper.findPositionOffset(28)); 242 assertEquals("find position for view 29", 243 25, mAdapterHelper.findPositionOffset(29)); 244 } 245 246 @Test testSinglePass()247 public void testSinglePass() { 248 setupBasic(10, 2, 3); 249 add(2, 1); 250 rm(1, 2); 251 add(1, 5); 252 mAdapterHelper.consumeUpdatesInOnePass(); 253 assertDispatch(0, 3); 254 } 255 256 @Test testDeleteVisible()257 public void testDeleteVisible() { 258 setupBasic(10, 2, 3); 259 rm(2, 1); 260 preProcess(); 261 assertDispatch(0, 1); 262 } 263 264 @Test testDeleteInvisible()265 public void testDeleteInvisible() { 266 setupBasic(10, 3, 4); 267 rm(2, 1); 268 preProcess(); 269 assertDispatch(1, 0); 270 } 271 272 @Test testAddCount()273 public void testAddCount() { 274 setupBasic(0, 0, 0); 275 add(0, 1); 276 assertEquals(1, mAdapterHelper.mPendingUpdates.size()); 277 } 278 279 @Test testDeleteCount()280 public void testDeleteCount() { 281 setupBasic(1, 0, 0); 282 rm(0, 1); 283 assertEquals(1, mAdapterHelper.mPendingUpdates.size()); 284 } 285 286 @Test testAddProcess()287 public void testAddProcess() { 288 setupBasic(0, 0, 0); 289 add(0, 1); 290 preProcess(); 291 assertEquals(0, mAdapterHelper.mPendingUpdates.size()); 292 } 293 294 @Test testAddRemoveSeparate()295 public void testAddRemoveSeparate() { 296 setupBasic(10, 2, 2); 297 add(6, 1); 298 rm(5, 1); 299 preProcess(); 300 assertDispatch(1, 1); 301 } 302 303 @Test testScenario1()304 public void testScenario1() { 305 setupBasic(10, 3, 2); 306 rm(4, 1); 307 rm(3, 1); 308 rm(3, 1); 309 preProcess(); 310 assertDispatch(1, 2); 311 } 312 313 @Test testDivideDelete()314 public void testDivideDelete() { 315 setupBasic(10, 3, 4); 316 rm(2, 2); 317 preProcess(); 318 assertDispatch(1, 1); 319 } 320 321 @Test testScenario2()322 public void testScenario2() { 323 setupBasic(10, 3, 3); // 3-4-5 324 add(4, 2); // 3 a b 4 5 325 rm(0, 1); // (0) 3(2) a(3) b(4) 4(3) 5(4) 326 rm(1, 3); // (1,2) (x) a(1) b(2) 4(3) 327 preProcess(); 328 assertDispatch(2, 2); 329 } 330 331 @Test testScenario3()332 public void testScenario3() { 333 setupBasic(10, 2, 2); 334 rm(0, 5); 335 preProcess(); 336 assertDispatch(2, 1); 337 assertOps(mFirstPassUpdates, rmOp(0, 2), rmOp(2, 1)); 338 assertOps(mSecondPassUpdates, rmOp(0, 2)); 339 } 340 // TODO test MOVE then remove items in between. 341 // TODO test MOVE then remove it, make sure it is not dispatched 342 343 @Test testScenario4()344 public void testScenario4() { 345 setupBasic(5, 0, 5); 346 // 0 1 2 3 4 347 // 0 1 2 a b 3 4 348 // 0 2 a b 3 4 349 // 0 c d 2 a b 3 4 350 // 0 c d 2 a 4 351 // c d 2 a 4 352 // pre: 0 1 2 3 4 353 add(3, 2); 354 rm(1, 1); 355 add(1, 2); 356 rm(5, 2); 357 rm(0, 1); 358 preProcess(); 359 } 360 361 @Test testScenario5()362 public void testScenario5() { 363 setupBasic(5, 0, 5); 364 // 0 1 2 3 4 365 // 0 1 2 a b 3 4 366 // 0 1 b 3 4 367 // pre: 0 1 2 3 4 368 // pre w/ adap: 0 1 2 b 3 4 369 add(3, 2); 370 rm(2, 2); 371 preProcess(); 372 } 373 374 @Test testScenario6()375 public void testScenario6() { 376 // setupBasic(47, 19, 24); 377 // mv(11, 12); 378 // add(24, 16); 379 // rm(9, 3); 380 setupBasic(10, 5, 3); 381 mv(2, 3); 382 add(6, 4); 383 rm(4, 1); 384 preProcess(); 385 } 386 387 @Test testScenario8()388 public void testScenario8() { 389 setupBasic(68, 51, 13); 390 mv(22, 11); 391 mv(22, 52); 392 rm(37, 19); 393 add(12, 38); 394 preProcess(); 395 } 396 397 @Test testScenario9()398 public void testScenario9() { 399 setupBasic(44, 3, 7); 400 add(7, 21); 401 rm(31, 3); 402 rm(32, 11); 403 mv(29, 5); 404 mv(30, 32); 405 add(25, 32); 406 rm(15, 66); 407 preProcess(); 408 } 409 410 @Test testScenario10()411 public void testScenario10() { 412 setupBasic(14, 10, 3); 413 rm(4, 4); 414 add(5, 11); 415 mv(5, 18); 416 rm(2, 9); 417 preProcess(); 418 } 419 420 @Test testScenario11()421 public void testScenario11() { 422 setupBasic(78, 3, 64); 423 mv(34, 28); 424 add(1, 11); 425 rm(9, 74); 426 preProcess(); 427 } 428 429 @Test testScenario12()430 public void testScenario12() { 431 setupBasic(38, 9, 7); 432 rm(26, 3); 433 mv(29, 15); 434 rm(30, 1); 435 preProcess(); 436 } 437 438 @Test testScenario13()439 public void testScenario13() { 440 setupBasic(49, 41, 3); 441 rm(30, 13); 442 add(4, 10); 443 mv(3, 38); 444 mv(20, 17); 445 rm(18, 23); 446 preProcess(); 447 } 448 449 @Test testScenario14()450 public void testScenario14() { 451 setupBasic(24, 3, 11); 452 rm(2, 15); 453 mv(2, 1); 454 add(2, 34); 455 add(11, 3); 456 rm(10, 25); 457 rm(13, 6); 458 rm(4, 4); 459 rm(6, 4); 460 preProcess(); 461 } 462 463 @Test testScenario15()464 public void testScenario15() { 465 setupBasic(10, 8, 1); 466 mv(6, 1); 467 mv(1, 4); 468 rm(3, 1); 469 preProcess(); 470 } 471 472 @Test testScenario16()473 public void testScenario16() { 474 setupBasic(10, 3, 3); 475 rm(2, 1); 476 rm(1, 7); 477 rm(0, 1); 478 preProcess(); 479 } 480 481 @Test testScenario17()482 public void testScenario17() { 483 setupBasic(10, 8, 1); 484 mv(1, 0); 485 mv(5, 1); 486 rm(1, 7); 487 preProcess(); 488 } 489 490 @Test testScenario18()491 public void testScenario18() throws InterruptedException { 492 setupBasic(10, 1, 4); 493 add(2, 11); 494 rm(16, 1); 495 add(3, 1); 496 rm(9, 10); 497 preProcess(); 498 } 499 500 @Test testScenario19()501 public void testScenario19() { 502 setupBasic(10, 8, 1); 503 mv(9, 7); 504 mv(9, 3); 505 rm(5, 4); 506 preProcess(); 507 } 508 509 @Test testScenario20()510 public void testScenario20() { 511 setupBasic(10, 7, 1); 512 mv(9, 1); 513 mv(3, 9); 514 rm(7, 2); 515 preProcess(); 516 } 517 518 @Test testScenario21()519 public void testScenario21() { 520 setupBasic(10, 5, 2); 521 mv(1, 0); 522 mv(9, 1); 523 rm(2, 3); 524 preProcess(); 525 } 526 527 @Test testScenario22()528 public void testScenario22() { 529 setupBasic(10, 7, 2); 530 add(2, 16); 531 mv(20, 9); 532 rm(17, 6); 533 preProcess(); 534 } 535 536 @Test testScenario23()537 public void testScenario23() { 538 setupBasic(10, 5, 3); 539 mv(9, 6); 540 add(4, 15); 541 rm(21, 3); 542 preProcess(); 543 } 544 545 @Test testScenario24()546 public void testScenario24() { 547 setupBasic(10, 1, 6); 548 add(6, 5); 549 mv(14, 6); 550 rm(7, 6); 551 preProcess(); 552 } 553 554 @Test testScenario25()555 public void testScenario25() { 556 setupBasic(10, 3, 4); 557 mv(3, 9); 558 rm(5, 4); 559 preProcess(); 560 } 561 562 @Test testScenario25a()563 public void testScenario25a() { 564 setupBasic(10, 3, 4); 565 rm(6, 4); 566 mv(3, 5); 567 preProcess(); 568 } 569 570 @Test testScenario26()571 public void testScenario26() { 572 setupBasic(10, 4, 4); 573 rm(3, 5); 574 mv(2, 0); 575 mv(1, 0); 576 rm(1, 1); 577 mv(0, 2); 578 preProcess(); 579 } 580 581 @Test testScenario27()582 public void testScenario27() { 583 setupBasic(10, 0, 3); 584 mv(9, 4); 585 mv(8, 4); 586 add(7, 6); 587 rm(5, 5); 588 preProcess(); 589 } 590 591 @Test testScenerio28()592 public void testScenerio28() { 593 setupBasic(10, 4, 1); 594 mv(8, 6); 595 rm(8, 1); 596 mv(7, 5); 597 rm(3, 3); 598 rm(1, 4); 599 preProcess(); 600 } 601 602 @Test testScenerio29()603 public void testScenerio29() { 604 setupBasic(10, 6, 3); 605 mv(3, 6); 606 up(6, 2); 607 add(5, 5); 608 } 609 610 @Test testScenerio30()611 public void testScenerio30() throws InterruptedException { 612 mCollectLogs = true; 613 setupBasic(10, 3, 1); 614 rm(3, 2); 615 rm(2, 5); 616 preProcess(); 617 } 618 619 @Test testScenerio31()620 public void testScenerio31() throws InterruptedException { 621 mCollectLogs = true; 622 setupBasic(10, 3, 1); 623 rm(3, 1); 624 rm(2, 3); 625 preProcess(); 626 } 627 628 @Test testScenerio32()629 public void testScenerio32() { 630 setupBasic(10, 8, 1); 631 add(9, 2); 632 add(7, 39); 633 up(0, 39); 634 mv(36, 20); 635 add(1, 48); 636 mv(22, 98); 637 mv(96, 29); 638 up(36, 29); 639 add(60, 36); 640 add(127, 34); 641 rm(142, 22); 642 up(12, 69); 643 up(116, 13); 644 up(118, 19); 645 mv(94, 69); 646 up(98, 21); 647 add(89, 18); 648 rm(94, 70); 649 up(71, 8); 650 rm(54, 26); 651 add(2, 20); 652 mv(78, 84); 653 mv(56, 2); 654 mv(1, 79); 655 rm(76, 7); 656 rm(57, 12); 657 rm(30, 27); 658 add(24, 13); 659 add(21, 5); 660 rm(11, 27); 661 rm(32, 1); 662 up(0, 5); 663 mv(14, 9); 664 rm(15, 12); 665 up(19, 1); 666 rm(7, 1); 667 mv(10, 4); 668 up(4, 3); 669 rm(16, 1); 670 up(13, 5); 671 up(2, 8); 672 add(10, 19); 673 add(15, 42); 674 preProcess(); 675 } 676 677 @Test testScenerio33()678 public void testScenerio33() throws Throwable { 679 try { 680 mCollectLogs = true; 681 setupBasic(10, 7, 1); 682 mv(0, 6); 683 up(0, 7); 684 preProcess(); 685 } catch (Throwable t) { 686 throw new Throwable(t.getMessage() + "\n" + mLog.toString()); 687 } 688 } 689 690 @Test testScenerio34()691 public void testScenerio34() { 692 setupBasic(10, 6, 1); 693 mv(9, 7); 694 rm(5, 2); 695 up(4, 3); 696 preProcess(); 697 } 698 699 @Test testScenerio35()700 public void testScenerio35() { 701 setupBasic(10, 4, 4); 702 mv(1, 4); 703 up(2, 7); 704 up(0, 1); 705 preProcess(); 706 } 707 708 @Test testScenerio36()709 public void testScenerio36() { 710 setupBasic(10, 7, 2); 711 rm(4, 1); 712 mv(1, 6); 713 up(4, 4); 714 preProcess(); 715 } 716 717 @Test testScenerio37()718 public void testScenerio37() throws Throwable { 719 try { 720 mCollectLogs = true; 721 setupBasic(10, 5, 2); 722 mv(3, 6); 723 rm(4, 4); 724 rm(3, 2); 725 preProcess(); 726 } catch (Throwable t) { 727 throw new Throwable(t.getMessage() + "\n" + mLog.toString()); 728 } 729 } 730 731 @Test testScenerio38()732 public void testScenerio38() { 733 setupBasic(10, 2, 2); 734 add(0, 24); 735 rm(26, 4); 736 rm(1, 24); 737 preProcess(); 738 } 739 740 @Test testScenerio39()741 public void testScenerio39() { 742 setupBasic(10, 7, 1); 743 mv(0, 2); 744 rm(8, 1); 745 rm(2, 6); 746 preProcess(); 747 } 748 749 @Test testScenerio40()750 public void testScenerio40() { 751 setupBasic(10, 5, 3); 752 rm(5, 4); 753 mv(0, 5); 754 rm(2, 3); 755 preProcess(); 756 } 757 758 @Test testScenerio41()759 public void testScenerio41() { 760 setupBasic(10, 7, 2); 761 mv(4, 9); 762 rm(0, 6); 763 rm(0, 1); 764 preProcess(); 765 } 766 767 @Test testScenerio42()768 public void testScenerio42() { 769 setupBasic(10, 6, 2); 770 mv(5, 9); 771 rm(5, 1); 772 rm(2, 6); 773 preProcess(); 774 } 775 776 @Test testScenerio43()777 public void testScenerio43() { 778 setupBasic(10, 1, 6); 779 mv(6, 8); 780 rm(3, 5); 781 up(3, 1); 782 preProcess(); 783 } 784 785 @Test testScenerio44()786 public void testScenerio44() { 787 setupBasic(10, 5, 2); 788 mv(6, 4); 789 mv(4, 1); 790 rm(5, 3); 791 preProcess(); 792 } 793 794 @Test testScenerio45()795 public void testScenerio45() { 796 setupBasic(10, 4, 2); 797 rm(1, 4); 798 preProcess(); 799 } 800 801 @Test testScenerio46()802 public void testScenerio46() { 803 setupBasic(10, 4, 3); 804 up(6, 1); 805 mv(8, 0); 806 rm(2, 7); 807 preProcess(); 808 } 809 810 @Test testMoveAdded()811 public void testMoveAdded() { 812 setupBasic(10, 2, 2); 813 add(3, 5); 814 mv(4, 2); 815 preProcess(); 816 } 817 818 @Test testPayloads()819 public void testPayloads() { 820 setupBasic(10, 2, 2); 821 up(3, 3, "payload"); 822 preProcess(); 823 assertOps(mFirstPassUpdates, upOp(4, 2, "payload")); 824 assertOps(mSecondPassUpdates, upOp(3, 1, "payload")); 825 } 826 827 @Test testRandom()828 public void testRandom() throws Throwable { 829 mCollectLogs = true; 830 Random random = new Random(System.nanoTime()); 831 for (int i = 0; i < 100; i++) { 832 try { 833 Log.d(TAG, "running random test " + i); 834 randomTest(random, Math.max(40, 10 + nextInt(random, i))); 835 } catch (Throwable t) { 836 throw new Throwable("failure at random test " + i + "\n" + t.getMessage() 837 + "\n" + mLog.toString(), t); 838 } 839 } 840 } 841 randomTest(Random random, int opCount)842 public void randomTest(Random random, int opCount) { 843 cleanState(); 844 if (DEBUG) { 845 log("randomTest"); 846 } 847 final int count = 10;// + nextInt(random,100); 848 final int start = nextInt(random, count - 1); 849 final int layoutCount = Math.max(1, nextInt(random, count - start)); 850 setupBasic(count, start, layoutCount); 851 852 while (opCount-- > 0) { 853 final int op = nextInt(random, 5); 854 switch (op) { 855 case 0: 856 if (mTestAdapter.mItems.size() > 1) { 857 int s = nextInt(random, mTestAdapter.mItems.size() - 1); 858 int len = Math.max(1, nextInt(random, mTestAdapter.mItems.size() - s)); 859 rm(s, len); 860 } 861 break; 862 case 1: 863 int s = mTestAdapter.mItems.size() == 0 ? 0 : 864 nextInt(random, mTestAdapter.mItems.size()); 865 add(s, nextInt(random, 50)); 866 break; 867 case 2: 868 if (mTestAdapter.mItems.size() >= 2) { 869 int from = nextInt(random, mTestAdapter.mItems.size()); 870 int to; 871 do { 872 to = nextInt(random, mTestAdapter.mItems.size()); 873 } while (to == from); 874 mv(from, to); 875 } 876 break; 877 case 3: 878 if (mTestAdapter.mItems.size() > 1) { 879 s = nextInt(random, mTestAdapter.mItems.size() - 1); 880 int len = Math.max(1, nextInt(random, mTestAdapter.mItems.size() - s)); 881 up(s, len); 882 } 883 break; 884 case 4: 885 if (mTestAdapter.mItems.size() > 1) { 886 s = nextInt(random, mTestAdapter.mItems.size() - 1); 887 int len = Math.max(1, nextInt(random, mTestAdapter.mItems.size() - s)); 888 up(s, len, Integer.toString(s)); 889 } 890 break; 891 } 892 } 893 preProcess(); 894 } 895 nextInt(Random random, int n)896 int nextInt(Random random, int n) { 897 if (n == 0) { 898 return 0; 899 } 900 return random.nextInt(n); 901 } 902 assertOps(List<AdapterHelper.UpdateOp> actual, AdapterHelper.UpdateOp... expected)903 public void assertOps(List<AdapterHelper.UpdateOp> actual, 904 AdapterHelper.UpdateOp... expected) { 905 assertEquals(expected.length, actual.size()); 906 for (int i = 0; i < expected.length; i++) { 907 assertEquals(expected[i], actual.get(i)); 908 } 909 } 910 assertDispatch(int firstPass, int secondPass)911 void assertDispatch(int firstPass, int secondPass) { 912 assertEquals(firstPass, mFirstPassUpdates.size()); 913 assertEquals(secondPass, mSecondPassUpdates.size()); 914 } 915 preProcess()916 void preProcess() { 917 for (MockViewHolder vh : mViewHolders) { 918 final int ind = mTestAdapter.mItems.indexOf(vh.mItem); 919 assertEquals("actual adapter position should match", ind, 920 mAdapterHelper.applyPendingUpdatesToPosition(vh.mPosition)); 921 } 922 mAdapterHelper.preProcess(); 923 for (int i = 0; i < mPreProcessClone.mItems.size(); i++) { 924 TestAdapter.Item item = mPreProcessClone.mItems.get(i); 925 final int preLayoutIndex = mPreLayoutItems.indexOf(item); 926 final int endIndex = mTestAdapter.mItems.indexOf(item); 927 if (preLayoutIndex != -1) { 928 assertEquals("find position offset should work properly for existing elements" + i 929 + " at pre layout position " + preLayoutIndex + " and post layout position " 930 + endIndex, endIndex, mAdapterHelper.findPositionOffset(preLayoutIndex)); 931 } 932 } 933 // make sure visible view holders still have continuous positions 934 final StringBuilder vhLogBuilder = new StringBuilder(); 935 for (ViewHolder vh : mViewHolders) { 936 vhLogBuilder.append("\n").append(vh.toString()); 937 } 938 if (mViewHolders.size() > 0) { 939 final String vhLog = vhLogBuilder.toString(); 940 final int start = mViewHolders.get(0).getLayoutPosition(); 941 for (int i = 1; i < mViewHolders.size(); i++) { 942 assertEquals("view holder positions should be continious in pre-layout" + vhLog, 943 start + i, mViewHolders.get(i).getLayoutPosition()); 944 } 945 } 946 mAdapterHelper.consumePostponedUpdates(); 947 // now assert these two adapters have identical data. 948 mPreProcessClone.applyOps(mFirstPassUpdates, mTestAdapter); 949 mPreProcessClone.applyOps(mSecondPassUpdates, mTestAdapter); 950 assertAdaptersEqual(mTestAdapter, mPreProcessClone); 951 } 952 assertAdaptersEqual(TestAdapter a1, TestAdapter a2)953 private void assertAdaptersEqual(TestAdapter a1, TestAdapter a2) { 954 assertEquals(a1.mItems.size(), a2.mItems.size()); 955 for (int i = 0; i < a1.mItems.size(); i++) { 956 TestAdapter.Item item = a1.mItems.get(i); 957 assertSame(item, a2.mItems.get(i)); 958 assertEquals(0, item.getUpdateCount()); 959 } 960 assertEquals(0, a1.mPendingAdded.size()); 961 assertEquals(0, a2.mPendingAdded.size()); 962 } 963 op(int cmd, int start, int count)964 AdapterHelper.UpdateOp op(int cmd, int start, int count) { 965 return new AdapterHelper.UpdateOp(cmd, start, count, null); 966 } 967 op(int cmd, int start, int count, Object payload)968 AdapterHelper.UpdateOp op(int cmd, int start, int count, Object payload) { 969 return new AdapterHelper.UpdateOp(cmd, start, count, payload); 970 } 971 addOp(int start, int count)972 AdapterHelper.UpdateOp addOp(int start, int count) { 973 return op(AdapterHelper.UpdateOp.ADD, start, count); 974 } 975 rmOp(int start, int count)976 AdapterHelper.UpdateOp rmOp(int start, int count) { 977 return op(AdapterHelper.UpdateOp.REMOVE, start, count); 978 } 979 upOp(int start, int count, Object payload)980 AdapterHelper.UpdateOp upOp(int start, int count, Object payload) { 981 return op(AdapterHelper.UpdateOp.UPDATE, start, count, payload); 982 } 983 add(int start, int count)984 void add(int start, int count) { 985 if (DEBUG) { 986 log("add(" + start + "," + count + ");"); 987 } 988 mTestAdapter.add(start, count); 989 } 990 isItemLaidOut(int pos)991 boolean isItemLaidOut(int pos) { 992 for (ViewHolder viewHolder : mViewHolders) { 993 if (viewHolder.mOldPosition == pos) { 994 return true; 995 } 996 } 997 return false; 998 } 999 mv(int from, int to)1000 private void mv(int from, int to) { 1001 if (DEBUG) { 1002 log("mv(" + from + "," + to + ");"); 1003 } 1004 mTestAdapter.move(from, to); 1005 } 1006 rm(int start, int count)1007 void rm(int start, int count) { 1008 if (DEBUG) { 1009 log("rm(" + start + "," + count + ");"); 1010 } 1011 for (int i = start; i < start + count; i++) { 1012 if (!isItemLaidOut(i)) { 1013 TestAdapter.Item item = mTestAdapter.mItems.get(i); 1014 mPreLayoutItems.remove(item); 1015 } 1016 } 1017 mTestAdapter.remove(start, count); 1018 } 1019 up(int start, int count)1020 void up(int start, int count) { 1021 if (DEBUG) { 1022 log("up(" + start + "," + count + ");"); 1023 } 1024 mTestAdapter.update(start, count); 1025 } 1026 up(int start, int count, Object payload)1027 void up(int start, int count, Object payload) { 1028 if (DEBUG) { 1029 log("up(" + start + "," + count + "," + payload + ");"); 1030 } 1031 mTestAdapter.update(start, count, payload); 1032 } 1033 1034 static class TestAdapter { 1035 1036 List<Item> mItems; 1037 1038 final AdapterHelper mAdapterHelper; 1039 1040 Queue<Item> mPendingAdded; 1041 TestAdapter(int initialCount, AdapterHelper container)1042 public TestAdapter(int initialCount, AdapterHelper container) { 1043 mItems = new ArrayList<Item>(); 1044 mAdapterHelper = container; 1045 mPendingAdded = new LinkedList<Item>(); 1046 for (int i = 0; i < initialCount; i++) { 1047 mItems.add(new Item()); 1048 } 1049 } 1050 add(int index, int count)1051 public void add(int index, int count) { 1052 for (int i = 0; i < count; i++) { 1053 Item item = new Item(); 1054 mPendingAdded.add(item); 1055 mItems.add(index + i, item); 1056 } 1057 mAdapterHelper.addUpdateOp(new AdapterHelper.UpdateOp( 1058 AdapterHelper.UpdateOp.ADD, index, count, null 1059 )); 1060 } 1061 move(int from, int to)1062 public void move(int from, int to) { 1063 mItems.add(to, mItems.remove(from)); 1064 mAdapterHelper.addUpdateOp(new AdapterHelper.UpdateOp( 1065 AdapterHelper.UpdateOp.MOVE, from, to, null 1066 )); 1067 } 1068 remove(int index, int count)1069 public void remove(int index, int count) { 1070 for (int i = 0; i < count; i++) { 1071 mItems.remove(index); 1072 } 1073 mAdapterHelper.addUpdateOp(new AdapterHelper.UpdateOp( 1074 AdapterHelper.UpdateOp.REMOVE, index, count, null 1075 )); 1076 } 1077 update(int index, int count)1078 public void update(int index, int count) { 1079 update(index, count, null); 1080 } 1081 update(int index, int count, Object payload)1082 public void update(int index, int count, Object payload) { 1083 for (int i = 0; i < count; i++) { 1084 mItems.get(index + i).update(payload); 1085 } 1086 mAdapterHelper.addUpdateOp(new AdapterHelper.UpdateOp( 1087 AdapterHelper.UpdateOp.UPDATE, index, count, payload 1088 )); 1089 } 1090 createCopy()1091 protected TestAdapter createCopy() { 1092 TestAdapter adapter = new TestAdapter(0, mAdapterHelper); 1093 for (Item item : mItems) { 1094 adapter.mItems.add(item); 1095 } 1096 return adapter; 1097 } 1098 applyOps(List<AdapterHelper.UpdateOp> updates, TestAdapter dataSource)1099 public void applyOps(List<AdapterHelper.UpdateOp> updates, 1100 TestAdapter dataSource) { 1101 for (AdapterHelper.UpdateOp op : updates) { 1102 switch (op.cmd) { 1103 case AdapterHelper.UpdateOp.ADD: 1104 for (int i = 0; i < op.itemCount; i++) { 1105 mItems.add(op.positionStart + i, dataSource.consumeNextAdded()); 1106 } 1107 break; 1108 case AdapterHelper.UpdateOp.REMOVE: 1109 for (int i = 0; i < op.itemCount; i++) { 1110 mItems.remove(op.positionStart); 1111 } 1112 break; 1113 case AdapterHelper.UpdateOp.UPDATE: 1114 for (int i = 0; i < op.itemCount; i++) { 1115 mItems.get(op.positionStart + i).handleUpdate(op.payload); 1116 } 1117 break; 1118 case AdapterHelper.UpdateOp.MOVE: 1119 mItems.add(op.itemCount, mItems.remove(op.positionStart)); 1120 break; 1121 } 1122 } 1123 } 1124 consumeNextAdded()1125 private Item consumeNextAdded() { 1126 return mPendingAdded.remove(); 1127 } 1128 createFakeItemAt(int fakeAddedItemIndex)1129 public void createFakeItemAt(int fakeAddedItemIndex) { 1130 Item fakeItem = new Item(); 1131 ((LinkedList<Item>) mPendingAdded).add(fakeAddedItemIndex, fakeItem); 1132 } 1133 1134 public static class Item { 1135 1136 private static AtomicInteger itemCounter = new AtomicInteger(); 1137 1138 private final int id; 1139 1140 private int mVersionCount = 0; 1141 1142 private ArrayList<Object> mPayloads = new ArrayList<Object>(); 1143 Item()1144 public Item() { 1145 id = itemCounter.incrementAndGet(); 1146 } 1147 update(Object payload)1148 public void update(Object payload) { 1149 mPayloads.add(payload); 1150 mVersionCount++; 1151 } 1152 handleUpdate(Object payload)1153 public void handleUpdate(Object payload) { 1154 assertSame(payload, mPayloads.get(0)); 1155 mPayloads.remove(0); 1156 mVersionCount--; 1157 } 1158 getUpdateCount()1159 public int getUpdateCount() { 1160 return mVersionCount; 1161 } 1162 } 1163 } 1164 waitForDebugger()1165 void waitForDebugger() { 1166 android.os.Debug.waitForDebugger(); 1167 } 1168 1169 static class MockViewHolder extends RecyclerView.ViewHolder { 1170 public Object mItem; MockViewHolder(View itemView)1171 public MockViewHolder(View itemView) { 1172 super(itemView); 1173 } 1174 } 1175 } 1176