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