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