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