AdapterHelperTest.java revision e4fde6825bba479c9b030feb8f810694d46b2f06
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.addFlags(ViewHolder.FLAG_REMOVED); 96 holder.offsetPosition(-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.addFlags(ViewHolder.FLAG_REMOVED); 110 holder.offsetPosition(-itemCount, false); 111 } 112 } 113 } 114 115 @Override 116 public void markViewHoldersUpdated(int positionStart, int itemCount) { 117 final int positionEnd = positionStart + itemCount; 118 for (ViewHolder holder : mViewHolders) { 119 if (holder.mPosition >= positionStart && holder.mPosition < positionEnd) { 120 holder.addFlags(ViewHolder.FLAG_UPDATE); 121 } 122 } 123 } 124 125 @Override 126 public void onDispatchFirstPass(AdapterHelper.UpdateOp updateOp) { 127 if (DEBUG) { 128 log("first pass:" + updateOp.toString()); 129 } 130 for (ViewHolder viewHolder : mViewHolders) { 131 for (int i = 0; i < updateOp.itemCount; i ++) { 132 assertFalse("update op should not match any existing view holders", 133 viewHolder.getPosition() == 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 holder.offsetPosition(inBetweenOffset, false); 174 } 175 } 176 }, true) { 177 @Override 178 void createFakeAddForRemovedMove(int adapterIndex, int pendingUpdateIndex) { 179 int addsBefore = 0; 180 for (int i = 0; i < pendingUpdateIndex; i ++) { 181 final UpdateOp updateOp = mPendingUpdates.get(i); 182 if (updateOp.cmd == UpdateOp.ADD) { 183 addsBefore += updateOp.itemCount; 184 } 185 } 186 mTestAdapter.createFakeItemAt(addsBefore); 187 super.createFakeAddForRemovedMove(adapterIndex, pendingUpdateIndex); 188 } 189 }; 190 } 191 192 void log(String msg) { 193 if (mCollectLogs) { 194 mLog.append(msg).append("\n"); 195 } else { 196 Log.d(TAG, msg); 197 } 198 } 199 200 void setupBasic(int count, int visibleStart, int visibleCount) { 201 if (DEBUG) { 202 log("setupBasic(" + count + "," + visibleStart + "," + visibleCount + ");"); 203 } 204 mTestAdapter = new TestAdapter(count, mAdapterHelper); 205 for (int i = 0; i < visibleCount; i++) { 206 addViewHolder(visibleStart + i); 207 } 208 mPreProcessClone = mTestAdapter.createCopy(); 209 } 210 211 private void addViewHolder(int posiiton) { 212 ViewHolder viewHolder = new RecyclerViewBasicTest.MockViewHolder( 213 new TextView(getContext())); 214 viewHolder.mPosition = posiiton; 215 mViewHolders.add(viewHolder); 216 } 217 218 public void testChangeAll() throws Exception { 219 try { 220 setupBasic(5, 0, 3); 221 up(0, 5); 222 mAdapterHelper.preProcess(); 223 } catch (Throwable t) { 224 throw new Exception(mLog.toString()); 225 } 226 } 227 228 public void testFindPositionOffsetInPreLayout() { 229 setupBasic(50, 25, 10); 230 rm(24, 5); 231 mAdapterHelper.preProcess(); 232 // since 25 is invisible, we offset by one while checking 233 assertEquals("find position for view 23", 234 23, mAdapterHelper.findPositionOffset(23)); 235 assertEquals("find position for view 24", 236 -1, mAdapterHelper.findPositionOffset(24)); 237 assertEquals("find position for view 25", 238 -1, mAdapterHelper.findPositionOffset(25)); 239 assertEquals("find position for view 26", 240 -1, mAdapterHelper.findPositionOffset(26)); 241 assertEquals("find position for view 27", 242 -1, mAdapterHelper.findPositionOffset(27)); 243 assertEquals("find position for view 28", 244 24, mAdapterHelper.findPositionOffset(28)); 245 assertEquals("find position for view 29", 246 25, mAdapterHelper.findPositionOffset(29)); 247 } 248 249 public void testSinglePass() { 250 setupBasic(10, 2, 3); 251 add(2, 1); 252 rm(1, 2); 253 add(1, 5); 254 mAdapterHelper.consumeUpdatesInOnePass(); 255 assertDispatch(0, 3); 256 } 257 258 public void testDeleteVisible() { 259 setupBasic(10, 2, 3); 260 rm(2, 1); 261 preProcess(); 262 assertDispatch(0, 1); 263 } 264 265 public void testDeleteInvisible() { 266 setupBasic(10, 3, 4); 267 rm(2, 1); 268 preProcess(); 269 assertDispatch(1, 0); 270 } 271 272 public void testAddCount() { 273 setupBasic(0, 0, 0); 274 add(0, 1); 275 assertEquals(1, mAdapterHelper.mPendingUpdates.size()); 276 } 277 278 public void testDeleteCount() { 279 setupBasic(1, 0, 0); 280 rm(0, 1); 281 assertEquals(1, mAdapterHelper.mPendingUpdates.size()); 282 } 283 284 public void testAddProcess() { 285 setupBasic(0, 0, 0); 286 add(0, 1); 287 preProcess(); 288 assertEquals(0, mAdapterHelper.mPendingUpdates.size()); 289 } 290 291 public void testAddRemoveSeparate() { 292 setupBasic(10, 2, 2); 293 add(6, 1); 294 rm(5, 1); 295 preProcess(); 296 assertDispatch(1, 1); 297 } 298 299 public void testScenario1() { 300 setupBasic(10, 3, 2); 301 rm(4, 1); 302 rm(3, 1); 303 rm(3, 1); 304 preProcess(); 305 assertDispatch(1, 2); 306 } 307 308 public void testDivideDelete() { 309 setupBasic(10, 3, 4); 310 rm(2, 2); 311 preProcess(); 312 assertDispatch(1, 1); 313 } 314 315 public void testScenario2() { 316 setupBasic(10, 3, 3); // 3-4-5 317 add(4, 2); // 3 a b 4 5 318 rm(0, 1); // (0) 3(2) a(3) b(4) 4(3) 5(4) 319 rm(1, 3); // (1,2) (x) a(1) b(2) 4(3) 320 preProcess(); 321 assertDispatch(2, 2); 322 } 323 324 public void testScenario3() { 325 setupBasic(10, 2, 2); 326 rm(0, 5); 327 preProcess(); 328 assertDispatch(2, 1); 329 assertOps(mFirstPassUpdates, rmOp(0, 2), rmOp(2, 1)); 330 assertOps(mSecondPassUpdates, rmOp(0, 2)); 331 } 332 // TODO test MOVE then remove items in between. 333 // TODO test MOVE then remove it, make sure it is not dispatched 334 335 public void testScenario4() { 336 setupBasic(5, 0, 5); 337 // 0 1 2 3 4 338 // 0 1 2 a b 3 4 339 // 0 2 a b 3 4 340 // 0 c d 2 a b 3 4 341 // 0 c d 2 a 4 342 // c d 2 a 4 343 // pre: 0 1 2 3 4 344 add(3, 2); 345 rm(1, 1); 346 add(1, 2); 347 rm(5, 2); 348 rm(0, 1); 349 preProcess(); 350 } 351 352 public void testScenario5() { 353 setupBasic(5, 0, 5); 354 // 0 1 2 3 4 355 // 0 1 2 a b 3 4 356 // 0 1 b 3 4 357 // pre: 0 1 2 3 4 358 // pre w/ adap: 0 1 2 b 3 4 359 add(3, 2); 360 rm(2, 2); 361 preProcess(); 362 } 363 364 public void testScenario6() { 365// setupBasic(47, 19, 24); 366// mv(11, 12); 367// add(24, 16); 368// rm(9, 3); 369 setupBasic(10, 5, 3); 370 mv(2, 3); 371 add(6, 4); 372 rm(4, 1); 373 preProcess(); 374 } 375 376 public void testScenario8() { 377 setupBasic(68, 51, 13); 378 mv(22, 11); 379 mv(22, 52); 380 rm(37, 19); 381 add(12, 38); 382 preProcess(); 383 } 384 385 public void testScenario9() { 386 setupBasic(44, 3, 7); 387 add(7, 21); 388 rm(31, 3); 389 rm(32, 11); 390 mv(29, 5); 391 mv(30, 32); 392 add(25, 32); 393 rm(15, 66); 394 preProcess(); 395 } 396 397 public void testScenario10() { 398 setupBasic(14, 10, 3); 399 rm(4, 4); 400 add(5, 11); 401 mv(5, 18); 402 rm(2, 9); 403 preProcess(); 404 } 405 406 public void testScenario11() { 407 setupBasic(78, 3, 64); 408 mv(34, 28); 409 add(1, 11); 410 rm(9, 74); 411 preProcess(); 412 } 413 414 public void testScenario12() { 415 setupBasic(38, 9, 7); 416 rm(26, 3); 417 mv(29, 15); 418 rm(30, 1); 419 preProcess(); 420 } 421 422 public void testScenario13() { 423 setupBasic(49, 41, 3); 424 rm(30, 13); 425 add(4, 10); 426 mv(3, 38); 427 mv(20, 17); 428 rm(18, 23); 429 preProcess(); 430 } 431 432 public void testScenario14() { 433 setupBasic(24, 3, 11); 434 rm(2, 15); 435 mv(2, 1); 436 add(2, 34); 437 add(11, 3); 438 rm(10, 25); 439 rm(13, 6); 440 rm(4, 4); 441 rm(6, 4); 442 preProcess(); 443 } 444 445 public void testScenario15() { 446 setupBasic(10, 8, 1); 447 mv(6, 1); 448 mv(1, 4); 449 rm(3, 1); 450 preProcess(); 451 } 452 453 public void testScenario16() { 454 setupBasic(10, 3, 3); 455 rm(2, 1); 456 rm(1, 7); 457 rm(0, 1); 458 preProcess(); 459 } 460 461 public void testScenario17() { 462 setupBasic(10, 8, 1); 463 mv(1, 0); 464 mv(5, 1); 465 rm(1, 7); 466 preProcess(); 467 } 468 469 public void testScenario18() throws InterruptedException { 470 setupBasic(10, 1, 4); 471 add(2, 11); 472 rm(16, 1); 473 add(3, 1); 474 rm(9, 10); 475 preProcess(); 476 } 477 478 public void testScenario19() { 479 setupBasic(10, 8, 1); 480 mv(9, 7); 481 mv(9, 3); 482 rm(5,4); 483 preProcess(); 484 } 485 486 public void testScenario20() { 487 setupBasic(10,7,1); 488 mv(9,1); 489 mv(3,9); 490 rm(7,2); 491 preProcess(); 492 } 493 494 public void testScenario21() { 495 setupBasic(10,5,2); 496 mv(1,0); 497 mv(9,1); 498 rm(2,3); 499 preProcess(); 500 } 501 502 public void testScenario22() { 503 setupBasic(10,7,2); 504 add(2, 16); 505 mv(20,9); 506 rm(17,6); 507 preProcess(); 508 } 509 510 public void testScenario23() { 511 setupBasic(10,5,3); 512 mv(9, 6); 513 add(4, 15); 514 rm(21,3); 515 preProcess(); 516 } 517 518 public void testScenario24() { 519 setupBasic(10,1,6); 520 add(6, 5); 521 mv(14, 6); 522 rm(7,6); 523 preProcess(); 524 } 525 526 public void testScenario25() { 527 setupBasic(10,3,4); 528 mv(3, 9); 529 mv(2,9); 530 rm(5,4); 531 preProcess(); 532 } 533 534 public void testScenario26() { 535 setupBasic(10,4,4); 536 rm(3,5); 537 mv(2, 0); 538 mv(1,0); 539 rm(1, 1); 540 mv(0, 2); 541 preProcess(); 542 } 543 544 public void testScenario27() { 545 setupBasic(10, 0, 3); 546 mv(9,4); 547 mv(8,4); 548 add(7, 6); 549 rm(5, 5); 550 preProcess(); 551 } 552 553 public void testScenerio28() { 554 setupBasic(10,4,1); 555 mv(8, 6); 556 rm(8, 1); 557 mv(7,5); 558 rm(3, 3); 559 rm(1,4); 560 preProcess(); 561 } 562 563 public void testScenerio29() { 564 setupBasic(10, 6, 3); 565 mv(3, 6); 566 up(6,2); 567 add(5, 5); 568 } 569 570 public void testScenerio30() { 571 mCollectLogs = true; 572 setupBasic(10,3,1); 573 rm(3,2); 574 rm(2,5); 575 preProcess(); 576 } 577 578 public void testMoveAdded() { 579 setupBasic(10, 2, 2); 580 add(3, 5); 581 mv(4, 2); 582 preProcess(); 583 } 584 585 public void testRandom() throws Throwable { 586 mCollectLogs = true; 587 Random random = new Random(System.nanoTime()); 588 for (int i = 0; i < 1000; i++) { 589 try { 590 randomTest(random, i + 10); 591 } catch (Throwable t) { 592 throw new Throwable(t.getMessage() + "\n" + mLog.toString(), t); 593 } 594 } 595 } 596 597 public void randomTest(Random random, int opCount) { 598 cleanState(); 599 if (DEBUG) { 600 log("randomTest"); 601 } 602 final int count = 10;// + nextInt(random,100); 603 final int start = nextInt(random, count - 1); 604 final int layoutCount = Math.max(1, nextInt(random, count - start)); 605 setupBasic(count, start, layoutCount); 606 607 while (opCount-- > 0) { 608 final int op = nextInt(random, 4); 609 switch (op) { 610 case 0: 611 if (mTestAdapter.mItems.size() > 1) { 612 int s = nextInt(random, mTestAdapter.mItems.size() - 1); 613 int len = Math.max(1, nextInt(random, mTestAdapter.mItems.size() - s)); 614 rm(s, len); 615 } 616 break; 617 case 1: 618 int s = mTestAdapter.mItems.size() == 0 ? 0 : 619 nextInt(random, mTestAdapter.mItems.size()); 620 add(s, nextInt(random, 50)); 621 break; 622 case 2: 623 if (mTestAdapter.mItems.size() >= 2) { 624 int from = nextInt(random, mTestAdapter.mItems.size()); 625 int to; 626 do { 627 to = nextInt(random, mTestAdapter.mItems.size()); 628 } while (to == from); 629 mv(from, to); 630 } 631 break; 632 case 3: 633 if (mTestAdapter.mItems.size() > 1) { 634 s = nextInt(random, mTestAdapter.mItems.size() - 1); 635 int len = Math.max(1, nextInt(random, mTestAdapter.mItems.size() - s)); 636 up(s, len); 637 } 638 break; 639 } 640 } 641 preProcess(); 642 } 643 644 int nextInt(Random random, int n) { 645 if (n == 0) { 646 return 0; 647 } 648 return random.nextInt(n); 649 } 650 651 public void assertOps(List<AdapterHelper.UpdateOp> actual, 652 AdapterHelper.UpdateOp... expected) { 653 assertEquals(expected.length, actual.size()); 654 for (int i = 0; i < expected.length; i++) { 655 assertEquals(expected[i], actual.get(i)); 656 } 657 } 658 659 void assertDispatch(int firstPass, int secondPass) { 660 assertEquals(firstPass, mFirstPassUpdates.size()); 661 assertEquals(secondPass, mSecondPassUpdates.size()); 662 } 663 664 void preProcess() { 665 mAdapterHelper.preProcess(); 666 for (int i = 0; i < mPreProcessClone.mItems.size(); i++) { 667 TestAdapter.Item item = mPreProcessClone.mItems.get(i); 668 final int preLayoutIndex = mPreLayoutItems.indexOf(item); 669 final int endIndex = mTestAdapter.mItems.indexOf(item); 670 if (preLayoutIndex != -1) { 671 assertEquals("find position offset should work properly for existing elements" + i 672 + " at pre layout position " + preLayoutIndex + " and post layout position " 673 + endIndex, endIndex, mAdapterHelper.findPositionOffset(preLayoutIndex)); 674 } 675 } 676 mAdapterHelper.consumePostponedUpdates(); 677 // now assert these two adapters have identical data. 678 mPreProcessClone.applyOps(mFirstPassUpdates, mTestAdapter); 679 mPreProcessClone.applyOps(mSecondPassUpdates, mTestAdapter); 680 assertAdaptersEqual(mTestAdapter, mPreProcessClone); 681 } 682 683 private void assertAdaptersEqual(TestAdapter a1, TestAdapter a2) { 684 assertEquals(a1.mItems.size(), a2.mItems.size()); 685 for (int i = 0; i < a1.mItems.size(); i++) { 686 TestAdapter.Item item = a1.mItems.get(i); 687 assertSame(item, a2.mItems.get(i)); 688 assertEquals(0, item.getUpdateCount()); 689 } 690 assertEquals(0, a1.mPendingAdded.size()); 691 assertEquals(0, a2.mPendingAdded.size()); 692 } 693 694 AdapterHelper.UpdateOp op(int cmd, int start, int count) { 695 return new AdapterHelper.UpdateOp(cmd, start, count); 696 } 697 698 AdapterHelper.UpdateOp addOp(int start, int count) { 699 return op(AdapterHelper.UpdateOp.ADD, start, count); 700 } 701 702 AdapterHelper.UpdateOp rmOp(int start, int count) { 703 return op(AdapterHelper.UpdateOp.REMOVE, start, count); 704 } 705 706 AdapterHelper.UpdateOp upOp(int start, int count) { 707 return op(AdapterHelper.UpdateOp.UPDATE, start, count); 708 } 709 710 void add(int start, int count) { 711 if (DEBUG) { 712 log("add(" + start + "," + count + ");"); 713 } 714 mTestAdapter.add(start, count); 715 } 716 717 boolean isItemLaidOut(int pos) { 718 for (ViewHolder viewHolder : mViewHolders) { 719 if (viewHolder.mOldPosition == pos) { 720 return true; 721 } 722 } 723 return false; 724 } 725 726 private void mv(int from, int to) { 727 if (DEBUG) { 728 log("mv(" + from + "," + to + ");"); 729 } 730 mTestAdapter.move(from, to); 731 } 732 733 void rm(int start, int count) { 734 if (DEBUG) { 735 log("rm(" + start + "," + count + ");"); 736 } 737 for (int i = start; i < start + count; i++) { 738 if (!isItemLaidOut(i)) { 739 TestAdapter.Item item = mTestAdapter.mItems.get(i); 740 mPreLayoutItems.remove(item); 741 } 742 } 743 mTestAdapter.remove(start, count); 744 } 745 746 void up(int start, int count) { 747 if (DEBUG) { 748 log("up(" + start + "," + count + ");"); 749 } 750 mTestAdapter.update(start, count); 751 } 752 753 static class TestAdapter { 754 755 List<Item> mItems; 756 757 final AdapterHelper mAdapterHelper; 758 759 Queue<Item> mPendingAdded; 760 761 public TestAdapter(int initialCount, AdapterHelper container) { 762 mItems = new ArrayList<Item>(); 763 mAdapterHelper = container; 764 mPendingAdded = new LinkedList<Item>(); 765 for (int i = 0; i < initialCount; i++) { 766 mItems.add(new Item()); 767 } 768 } 769 770 public void add(int index, int count) { 771 for (int i = 0; i < count; i++) { 772 Item item = new Item(); 773 mPendingAdded.add(item); 774 mItems.add(index + i, item); 775 } 776 mAdapterHelper.addUpdateOp(new AdapterHelper.UpdateOp( 777 AdapterHelper.UpdateOp.ADD, index, count 778 )); 779 } 780 781 public void move(int from, int to) { 782 mItems.add(to, mItems.remove(from)); 783 mAdapterHelper.addUpdateOp(new AdapterHelper.UpdateOp( 784 AdapterHelper.UpdateOp.MOVE, from, to 785 )); 786 } 787 public void remove(int index, int count) { 788 for (int i = 0; i < count; i++) { 789 mItems.remove(index); 790 } 791 mAdapterHelper.addUpdateOp(new AdapterHelper.UpdateOp( 792 AdapterHelper.UpdateOp.REMOVE, index, count 793 )); 794 } 795 796 public void update(int index, int count) { 797 for (int i = 0; i < count; i++) { 798 mItems.get(index + i).update(); 799 } 800 mAdapterHelper.addUpdateOp(new AdapterHelper.UpdateOp( 801 AdapterHelper.UpdateOp.UPDATE, index, count 802 )); 803 } 804 805 protected TestAdapter createCopy() { 806 TestAdapter adapter = new TestAdapter(0, mAdapterHelper); 807 for (Item item : mItems) { 808 adapter.mItems.add(item); 809 } 810 return adapter; 811 } 812 813 public void applyOps(List<AdapterHelper.UpdateOp> updates, 814 TestAdapter dataSource) { 815 for (AdapterHelper.UpdateOp op : updates) { 816 switch (op.cmd) { 817 case AdapterHelper.UpdateOp.ADD: 818 for (int i = 0; i < op.itemCount; i++) { 819 mItems.add(op.positionStart + i, dataSource.consumeNextAdded()); 820 } 821 break; 822 case AdapterHelper.UpdateOp.REMOVE: 823 for (int i = 0; i < op.itemCount; i++) { 824 mItems.remove(op.positionStart); 825 } 826 break; 827 case AdapterHelper.UpdateOp.UPDATE: 828 for (int i = 0; i < op.itemCount; i++) { 829 mItems.get(i).handleUpdate(); 830 } 831 break; 832 case AdapterHelper.UpdateOp.MOVE: 833 mItems.add(op.itemCount, mItems.remove(op.positionStart)); 834 break; 835 } 836 } 837 } 838 839 private Item consumeNextAdded() { 840 return mPendingAdded.remove(); 841 } 842 843 public void createFakeItemAt(int fakeAddedItemIndex) { 844 Item fakeItem = new Item(); 845 ((LinkedList<Item>)mPendingAdded).add(fakeAddedItemIndex, fakeItem); 846 } 847 848 public static class Item { 849 850 private static AtomicInteger itemCounter = new AtomicInteger(); 851 852 private final int id; 853 854 private int mVersionCount = 0; 855 856 private int mUpdateCount; 857 858 public Item() { 859 id = itemCounter.incrementAndGet(); 860 } 861 862 public void update() { 863 mVersionCount++; 864 } 865 866 public void handleUpdate() { 867 mVersionCount--; 868 } 869 870 public int getUpdateCount() { 871 return mUpdateCount; 872 } 873 } 874 } 875} 876