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