| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.support.v7.util; |
| |
| import android.support.annotation.Nullable; |
| import android.support.test.filters.SmallTest; |
| |
| import junit.framework.TestCase; |
| |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Queue; |
| import java.util.Random; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| @RunWith(JUnit4.class) |
| @SmallTest |
| public class SortedListTest extends TestCase { |
| |
| SortedList<Item> mList; |
| List<Pair> mAdditions = new ArrayList<Pair>(); |
| List<Pair> mRemovals = new ArrayList<Pair>(); |
| List<Pair> mMoves = new ArrayList<Pair>(); |
| List<Pair> mUpdates = new ArrayList<Pair>(); |
| private boolean mPayloadChanges = false; |
| List<PayloadChange> mPayloadUpdates = new ArrayList<>(); |
| Queue<AssertListStateRunnable> mCallbackRunnables; |
| List<Event> mEvents = new ArrayList<>(); |
| private SortedList.Callback<Item> mCallback; |
| InsertedCallback<Item> mInsertedCallback; |
| ChangedCallback<Item> mChangedCallback; |
| |
| private Comparator<? super Item> sItemComparator = new Comparator<Item>() { |
| @Override |
| public int compare(Item o1, Item o2) { |
| return mCallback.compare(o1, o2); |
| } |
| }; |
| |
| private abstract class InsertedCallback<T> { |
| public abstract void onInserted(int position, int count); |
| } |
| |
| private abstract class ChangedCallback<T> { |
| public abstract void onChanged(int position, int count); |
| } |
| |
| @Override |
| @Before |
| public void setUp() throws Exception { |
| super.setUp(); |
| |
| mCallback = new SortedList.Callback<Item>() { |
| @Override |
| public int compare(Item o1, Item o2) { |
| return o1.cmpField < o2.cmpField ? -1 : (o1.cmpField == o2.cmpField ? 0 : 1); |
| } |
| |
| @Override |
| public void onInserted(int position, int count) { |
| mEvents.add(new Event(TYPE.ADD, position, count)); |
| mAdditions.add(new Pair(position, count)); |
| if (mInsertedCallback != null) { |
| mInsertedCallback.onInserted(position, count); |
| } |
| pollAndRun(mCallbackRunnables); |
| } |
| |
| @Override |
| public void onRemoved(int position, int count) { |
| mEvents.add(new Event(TYPE.REMOVE, position, count)); |
| mRemovals.add(new Pair(position, count)); |
| pollAndRun(mCallbackRunnables); |
| } |
| |
| @Override |
| public void onMoved(int fromPosition, int toPosition) { |
| mEvents.add(new Event(TYPE.MOVE, fromPosition, toPosition)); |
| mMoves.add(new Pair(fromPosition, toPosition)); |
| } |
| |
| @Override |
| public void onChanged(int position, int count) { |
| mEvents.add(new Event(TYPE.CHANGE, position, count)); |
| mUpdates.add(new Pair(position, count)); |
| if (mChangedCallback != null) { |
| mChangedCallback.onChanged(position, count); |
| } |
| pollAndRun(mCallbackRunnables); |
| } |
| |
| @Override |
| public void onChanged(int position, int count, Object payload) { |
| if (mPayloadChanges) { |
| mPayloadUpdates.add(new PayloadChange(position, count, payload)); |
| } else { |
| onChanged(position, count); |
| } |
| } |
| |
| @Override |
| public boolean areContentsTheSame(Item oldItem, Item newItem) { |
| return oldItem.data == newItem.data; |
| } |
| |
| @Override |
| public boolean areItemsTheSame(Item item1, Item item2) { |
| return item1.id == item2.id; |
| } |
| |
| @Nullable |
| @Override |
| public Object getChangePayload(Item item1, Item item2) { |
| if (mPayloadChanges) { |
| return item2.data; |
| } |
| return null; |
| } |
| }; |
| mList = new SortedList<Item>(Item.class, mCallback); |
| } |
| |
| private void pollAndRun(Queue<AssertListStateRunnable> queue) { |
| if (queue != null) { |
| Runnable runnable = queue.poll(); |
| assertNotNull(runnable); |
| runnable.run(); |
| } |
| } |
| |
| @Test |
| public void testValidMethodsDuringOnInsertedCallbackFromEmptyList() { |
| |
| final Item[] items = |
| new Item[] {new Item(0), new Item(1), new Item(2)}; |
| |
| final AtomicInteger atomicInteger = new AtomicInteger(0); |
| mInsertedCallback = new InsertedCallback<Item>() { |
| @Override |
| public void onInserted(int position, int count) { |
| for (int i = 0; i < count; i++) { |
| assertEquals(mList.get(i), items[i]); |
| assertEquals(mList.indexOf(items[i]), i); |
| atomicInteger.incrementAndGet(); |
| } |
| } |
| }; |
| |
| mList.add(items[0]); |
| mList.clear(); |
| mList.addAll(items, false); |
| assertEquals(4, atomicInteger.get()); |
| } |
| |
| @Test |
| public void testEmpty() { |
| assertEquals("empty", mList.size(), 0); |
| } |
| |
| @Test |
| public void testAdd() { |
| Item item = new Item(1); |
| assertEquals(insert(item), 0); |
| assertEquals(size(), 1); |
| assertTrue(mAdditions.contains(new Pair(0, 1))); |
| Item item2 = new Item(2); |
| item2.cmpField = item.cmpField + 1; |
| assertEquals(insert(item2), 1); |
| assertEquals(size(), 2); |
| assertTrue(mAdditions.contains(new Pair(1, 1))); |
| Item item3 = new Item(3); |
| item3.cmpField = item.cmpField - 1; |
| mAdditions.clear(); |
| assertEquals(insert(item3), 0); |
| assertEquals(size(), 3); |
| assertTrue(mAdditions.contains(new Pair(0, 1))); |
| } |
| |
| @Test |
| public void testAddDuplicate() { |
| Item item = new Item(1); |
| Item item2 = new Item(item.id); |
| insert(item); |
| assertEquals(0, insert(item2)); |
| assertEquals(1, size()); |
| assertEquals(1, mAdditions.size()); |
| assertEquals(0, mUpdates.size()); |
| } |
| |
| @Test |
| public void testRemove() { |
| Item item = new Item(1); |
| assertFalse(remove(item)); |
| assertEquals(0, mRemovals.size()); |
| insert(item); |
| assertTrue(remove(item)); |
| assertEquals(1, mRemovals.size()); |
| assertTrue(mRemovals.contains(new Pair(0, 1))); |
| assertEquals(0, size()); |
| assertFalse(remove(item)); |
| assertEquals(1, mRemovals.size()); |
| } |
| |
| @Test |
| public void testRemove2() { |
| Item item = new Item(1); |
| Item item2 = new Item(2, 1, 1); |
| insert(item); |
| assertFalse(remove(item2)); |
| assertEquals(0, mRemovals.size()); |
| } |
| |
| @Test |
| public void clearTest() { |
| insert(new Item(1)); |
| insert(new Item(2)); |
| assertEquals(2, mList.size()); |
| mList.clear(); |
| assertEquals(0, mList.size()); |
| insert(new Item(3)); |
| assertEquals(1, mList.size()); |
| } |
| |
| @Test |
| public void testBatch() { |
| mList.beginBatchedUpdates(); |
| for (int i = 0; i < 5; i++) { |
| mList.add(new Item(i)); |
| } |
| assertEquals(0, mAdditions.size()); |
| mList.endBatchedUpdates(); |
| assertTrue(mAdditions.contains(new Pair(0, 5))); |
| } |
| |
| @Test |
| public void testRandom() throws Throwable { |
| Random random = new Random(System.nanoTime()); |
| List<Item> copy = new ArrayList<Item>(); |
| StringBuilder log = new StringBuilder(); |
| int id = 1; |
| try { |
| for (int i = 0; i < 10000; i++) { |
| switch (random.nextInt(3)) { |
| case 0://ADD |
| Item item = new Item(id++); |
| copy.add(item); |
| insert(item); |
| log.append("add ").append(item).append("\n"); |
| break; |
| case 1://REMOVE |
| if (copy.size() > 0) { |
| int index = random.nextInt(mList.size()); |
| item = mList.get(index); |
| log.append("remove ").append(item).append("\n"); |
| assertTrue(copy.remove(item)); |
| assertTrue(mList.remove(item)); |
| } |
| break; |
| case 2://UPDATE |
| if (copy.size() > 0) { |
| int index = random.nextInt(mList.size()); |
| item = mList.get(index); |
| // TODO this cannot work |
| Item newItem = |
| new Item(item.id, item.cmpField, random.nextInt(1000)); |
| while (newItem.data == item.data) { |
| newItem.data = random.nextInt(1000); |
| } |
| log.append("update ").append(item).append(" to ").append(newItem) |
| .append("\n"); |
| int itemIndex = mList.add(newItem); |
| copy.remove(item); |
| copy.add(newItem); |
| assertSame(mList.get(itemIndex), newItem); |
| assertNotSame(mList.get(index), item); |
| } |
| break; |
| case 3:// UPDATE AT |
| if (copy.size() > 0) { |
| int index = random.nextInt(mList.size()); |
| item = mList.get(index); |
| Item newItem = new Item(item.id, random.nextInt(), random.nextInt()); |
| mList.updateItemAt(index, newItem); |
| copy.remove(item); |
| copy.add(newItem); |
| log.append("update at ").append(index).append(" ").append(item) |
| .append(" to ").append(newItem).append("\n"); |
| } |
| } |
| int lastCmp = Integer.MIN_VALUE; |
| for (int index = 0; index < copy.size(); index++) { |
| assertFalse(mList.indexOf(copy.get(index)) == SortedList.INVALID_POSITION); |
| assertTrue(mList.get(index).cmpField >= lastCmp); |
| lastCmp = mList.get(index).cmpField; |
| assertTrue(copy.contains(mList.get(index))); |
| } |
| |
| for (int index = 0; index < mList.size(); index++) { |
| assertNotNull(mList.mData[index]); |
| } |
| for (int index = mList.size(); index < mList.mData.length; index++) { |
| assertNull(mList.mData[index]); |
| } |
| } |
| } catch (Throwable t) { |
| Collections.sort(copy, sItemComparator); |
| log.append("Items:\n"); |
| for (Item item : copy) { |
| log.append(item).append("\n"); |
| } |
| log.append("SortedList:\n"); |
| for (int i = 0; i < mList.size(); i++) { |
| log.append(mList.get(i)).append("\n"); |
| } |
| |
| throw new Throwable(" \nlog:\n" + log.toString(), t); |
| } |
| } |
| |
| private static Item[] createItems(int idFrom, int idTo, int idStep) { |
| final int count = (idTo - idFrom) / idStep + 1; |
| Item[] items = new Item[count]; |
| int id = idFrom; |
| for (int i = 0; i < count; i++) { |
| Item item = new Item(id); |
| items[i] = item; |
| id += idStep; |
| } |
| return items; |
| } |
| |
| private static Item[] createItemsFromInts(int ... ints) { |
| Item[] items = new Item[ints.length]; |
| for (int i = ints.length - 1; i >= 0; i--) { |
| items[i] = new Item(ints[i]); |
| } |
| return items; |
| } |
| |
| private static Item[] shuffle(Item[] items) { |
| Random random = new Random(System.nanoTime()); |
| final int count = items.length; |
| for (int i = 0; i < count; i++) { |
| int pos1 = random.nextInt(count); |
| int pos2 = random.nextInt(count); |
| if (pos1 != pos2) { |
| Item temp = items[pos1]; |
| items[pos1] = items[pos2]; |
| items[pos2] = temp; |
| } |
| } |
| return items; |
| } |
| |
| private void assertIntegrity(int size, String context) { |
| assertEquals(context + ": incorrect size", size, size()); |
| int rangeStart = 0; |
| for (int i = 0; i < size(); i++) { |
| Item item = mList.get(i); |
| assertNotNull(context + ": get returned null @" + i, item); |
| assertEquals(context + ": incorrect indexOf result @" + i, i, mList.indexOf(item)); |
| if (i == 0) { |
| continue; |
| } |
| |
| final int compare = mCallback.compare(mList.get(i - 1), item); |
| assertTrue(context + ": incorrect sorting order @" + i, compare <= 0); |
| |
| if (compare == 0) { |
| for (int j = rangeStart; j < i; j++) { |
| assertFalse(context + ": duplicates found @" + j + " and " + i, |
| mCallback.areItemsTheSame(mList.get(j), item)); |
| } |
| } else { |
| rangeStart = i; |
| } |
| } |
| } |
| |
| private void assertSequentialOrder() { |
| for (int i = 0; i < size(); i++) { |
| assertEquals(i, mList.get(i).cmpField); |
| } |
| } |
| |
| @Test |
| public void testAddAllMerge() throws Throwable { |
| mList.addAll(new Item[0]); |
| assertIntegrity(0, "addAll, empty list, empty input"); |
| assertEquals(0, mAdditions.size()); |
| |
| // Add first 5 even numbers. Test adding to an empty list. |
| mList.addAll(createItems(0, 8, 2)); |
| assertIntegrity(5, "addAll, empty list, non-empty input"); |
| assertEquals(1, mAdditions.size()); |
| assertTrue(mAdditions.contains(new Pair(0, 5))); |
| |
| mList.addAll(new Item[0]); |
| assertIntegrity(5, "addAll, non-empty list, empty input"); |
| assertEquals(1, mAdditions.size()); |
| |
| // Add 5 more even numbers, shuffled (test pre-sorting). |
| mList.addAll(shuffle(createItems(10, 18, 2))); |
| assertIntegrity(10, "addAll, shuffled input"); |
| assertEquals(2, mAdditions.size()); |
| assertTrue(mAdditions.contains(new Pair(5, 5))); |
| |
| // Add 5 more even numbers, reversed (test pre-sorting). |
| mList.addAll(shuffle(createItems(28, 20, -2))); |
| assertIntegrity(15, "addAll, reversed input"); |
| assertEquals(3, mAdditions.size()); |
| assertTrue(mAdditions.contains(new Pair(10, 5))); |
| |
| // Add first 10 odd numbers. |
| // Test the merge when the new items run out first. |
| mList.addAll(createItems(1, 19, 2)); |
| assertIntegrity(25, "addAll, merging in the middle"); |
| assertEquals(13, mAdditions.size()); |
| for (int i = 1; i <= 19; i += 2) { |
| assertTrue(mAdditions.contains(new Pair(i, 1))); |
| } |
| |
| // Add 10 more odd numbers. |
| // Test the merge when the old items run out first. |
| mList.addAll(createItems(21, 39, 2)); |
| assertIntegrity(35, "addAll, merging at the end"); |
| assertEquals(18, mAdditions.size()); |
| for (int i = 21; i <= 27; i += 2) { |
| assertTrue(mAdditions.contains(new Pair(i, 1))); |
| } |
| assertTrue(mAdditions.contains(new Pair(29, 6))); |
| |
| // Add 5 more even numbers. |
| mList.addAll(createItems(30, 38, 2)); |
| assertIntegrity(40, "addAll, merging more"); |
| assertEquals(23, mAdditions.size()); |
| for (int i = 30; i <= 38; i += 2) { |
| assertTrue(mAdditions.contains(new Pair(i, 1))); |
| } |
| |
| assertEquals(0, mMoves.size()); |
| assertEquals(0, mUpdates.size()); |
| assertEquals(0, mRemovals.size()); |
| |
| assertSequentialOrder(); |
| } |
| |
| @Test |
| public void testAddAllUpdates() throws Throwable { |
| // Add first 5 even numbers. |
| Item[] evenItems = createItems(0, 8, 2); |
| for (Item item : evenItems) { |
| item.data = 1; |
| } |
| mList.addAll(evenItems); |
| assertEquals(5, size()); |
| assertEquals(1, mAdditions.size()); |
| assertTrue(mAdditions.contains(new Pair(0, 5))); |
| assertEquals(0, mUpdates.size()); |
| |
| Item[] sameEvenItems = createItems(0, 8, 2); |
| for (Item item : sameEvenItems) { |
| item.data = 1; |
| } |
| mList.addAll(sameEvenItems); |
| assertEquals(1, mAdditions.size()); |
| assertEquals(0, mUpdates.size()); |
| |
| Item[] newEvenItems = createItems(0, 8, 2); |
| for (Item item : newEvenItems) { |
| item.data = 2; |
| } |
| mList.addAll(newEvenItems); |
| assertEquals(5, size()); |
| assertEquals(1, mAdditions.size()); |
| assertEquals(1, mUpdates.size()); |
| assertTrue(mUpdates.contains(new Pair(0, 5))); |
| for (int i = 0; i < 5; i++) { |
| assertEquals(2, mList.get(i).data); |
| } |
| |
| // Add all numbers from 0 to 9 |
| Item[] sequentialItems = createItems(0, 9, 1); |
| for (Item item : sequentialItems) { |
| item.data = 3; |
| } |
| mList.addAll(sequentialItems); |
| |
| // Odd numbers should have been added. |
| assertEquals(6, mAdditions.size()); |
| for (int i = 0; i < 5; i++) { |
| assertTrue(mAdditions.contains(new Pair(i * 2 + 1, 1))); |
| } |
| |
| // All even items should have been updated. |
| assertEquals(6, mUpdates.size()); |
| for (int i = 0; i < 5; i++) { |
| assertTrue(mUpdates.contains(new Pair(i * 2, 1))); |
| } |
| |
| assertEquals(10, size()); |
| |
| // All items should have the latest data value. |
| for (int i = 0; i < 10; i++) { |
| assertEquals(3, mList.get(i).data); |
| } |
| assertEquals(0, mMoves.size()); |
| assertEquals(0, mRemovals.size()); |
| assertSequentialOrder(); |
| } |
| |
| @Test |
| public void testAddAllWithDuplicates() throws Throwable { |
| final int maxCmpField = 5; |
| final int idsPerCmpField = 10; |
| final int maxUniqueId = maxCmpField * idsPerCmpField; |
| final int maxGeneration = 5; |
| |
| Item[] items = new Item[maxUniqueId * maxGeneration]; |
| |
| int index = 0; |
| for (int generation = 0; generation < maxGeneration; generation++) { |
| int uniqueId = 0; |
| for (int cmpField = 0; cmpField < maxCmpField; cmpField++) { |
| for (int id = 0; id < idsPerCmpField; id++) { |
| Item item = new Item(uniqueId++, cmpField, generation); |
| items[index++] = item; |
| } |
| } |
| } |
| |
| mList.addAll(items); |
| |
| assertIntegrity(maxUniqueId, "addAll with duplicates"); |
| |
| // Check that the most recent items have made it to the list. |
| for (int i = 0; i != size(); i++) { |
| Item item = mList.get(i); |
| assertEquals(maxGeneration - 1, item.data); |
| } |
| } |
| |
| @Test |
| public void testAddAllFast() throws Throwable { |
| mList.addAll(new Item[0], true); |
| assertIntegrity(0, "addAll(T[],boolean), empty list, with empty input"); |
| assertEquals(0, mAdditions.size()); |
| |
| mList.addAll(createItems(0, 9, 1), true); |
| assertIntegrity(10, "addAll(T[],boolean), empty list, non-empty input"); |
| assertEquals(1, mAdditions.size()); |
| assertTrue(mAdditions.contains(new Pair(0, 10))); |
| |
| mList.addAll(new Item[0], true); |
| assertEquals(1, mAdditions.size()); |
| assertIntegrity(10, "addAll(T[],boolean), non-empty list, empty input"); |
| |
| mList.addAll(createItems(10, 19, 1), true); |
| assertEquals(2, mAdditions.size()); |
| assertTrue(mAdditions.contains(new Pair(10, 10))); |
| assertIntegrity(20, "addAll(T[],boolean), non-empty list, non-empty input"); |
| } |
| |
| @Test |
| public void testAddAllCollection() throws Throwable { |
| Collection<Item> itemList = new ArrayList<Item>(); |
| for (int i = 0; i < 5; i++) { |
| itemList.add(new Item(i)); |
| } |
| mList.addAll(itemList); |
| |
| assertEquals(1, mAdditions.size()); |
| assertTrue(mAdditions.contains(new Pair(0, itemList.size()))); |
| assertIntegrity(itemList.size(), "addAll on collection"); |
| } |
| |
| @Test |
| public void testAddAllStableSort() { |
| int id = 0; |
| Item item = new Item(id++, 0, 0); |
| mList.add(item); |
| |
| // Create a few items with the same sort order. |
| Item[] items = new Item[3]; |
| for (int i = 0; i < 3; i++) { |
| items[i] = new Item(id++, item.cmpField, 0); |
| assertEquals(0, mCallback.compare(item, items[i])); |
| } |
| |
| mList.addAll(items); |
| assertEquals(1 + items.length, size()); |
| |
| // Check that the order has been preserved. |
| for (int i = 0; i < size(); i++) { |
| assertEquals(i, mList.get(i).id); |
| } |
| } |
| |
| |
| @Test |
| public void testAddAllAccessFromCallbacks() { |
| // Add first 5 even numbers. |
| Item[] evenItems = createItems(0, 8, 2); |
| for (Item item : evenItems) { |
| item.data = 1; |
| } |
| |
| |
| mInsertedCallback = new InsertedCallback<Item>() { |
| @Override |
| public void onInserted(int position, int count) { |
| assertEquals(0, position); |
| assertEquals(5, count); |
| for (int i = 0; i < count; i++) { |
| assertEquals(i * 2, mList.get(i).id); |
| } |
| assertIntegrity(5, "onInserted(" + position + ", " + count + ")"); |
| |
| } |
| }; |
| |
| mList.addAll(evenItems); |
| assertEquals(1, mAdditions.size()); |
| assertEquals(0, mUpdates.size()); |
| |
| // Add all numbers from 0 to 9. This should trigger 5 change and 5 insert notifications. |
| Item[] sequentialItems = createItems(0, 9, 1); |
| for (Item item : sequentialItems) { |
| item.data = 2; |
| } |
| |
| mChangedCallback = new ChangedCallback<Item>() { |
| int expectedSize = 5; |
| |
| @Override |
| public void onChanged(int position, int count) { |
| assertEquals(1, count); |
| assertEquals(position, mList.get(position).id); |
| assertIntegrity(++expectedSize, "onChanged(" + position + ")"); |
| } |
| }; |
| |
| mInsertedCallback = new InsertedCallback<Item>() { |
| int expectedSize = 5; |
| |
| @Override |
| public void onInserted(int position, int count) { |
| assertEquals(1, count); |
| assertEquals(position, mList.get(position).id); |
| assertIntegrity(++expectedSize, "onInserted(" + position + ")"); |
| } |
| }; |
| |
| mList.addAll(sequentialItems); |
| assertEquals(6, mAdditions.size()); |
| assertEquals(5, mUpdates.size()); |
| } |
| |
| @Test |
| public void testModificationFromCallbackThrows() { |
| final Item extraItem = new Item(0); |
| |
| Item[] items = createItems(1, 5, 2); |
| for (Item item : items) { |
| item.data = 1; |
| } |
| mList.addAll(items); |
| |
| mInsertedCallback = new InsertedCallback<Item>() { |
| @Override |
| public void onInserted(int position, int count) { |
| try { |
| mList.add(new Item(1)); |
| fail("add must throw from within a callback"); |
| } catch (IllegalStateException e) { |
| } |
| try { |
| mList.addAll(createItems(0, 0, 1)); |
| fail("addAll must throw from within a callback"); |
| } catch (IllegalStateException e) { |
| } |
| try { |
| mList.addAll(createItems(0, 0, 1), true); |
| fail("addAll(T[],boolean) must throw from within a callback"); |
| } catch (IllegalStateException e) { |
| } |
| try { |
| mList.remove(extraItem); |
| fail("remove must throw from within a callback"); |
| } catch (IllegalStateException e) { |
| } |
| try { |
| mList.removeItemAt(0); |
| fail("removeItemAt must throw from within a callback"); |
| } catch (IllegalStateException e) { |
| } |
| try { |
| mList.updateItemAt(0, extraItem); |
| fail("updateItemAt must throw from within a callback"); |
| } catch (IllegalStateException e) { |
| } |
| try { |
| mList.recalculatePositionOfItemAt(0); |
| fail("recalculatePositionOfItemAt must throw from within a callback"); |
| } catch (IllegalStateException e) { |
| } |
| try { |
| mList.clear(); |
| fail("recalculatePositionOfItemAt must throw from within a callback"); |
| } catch (IllegalStateException e) { |
| } |
| } |
| }; |
| |
| // Make sure that the last one notification is change, so that the above callback is |
| // not called from endBatchUpdates when the nested alls are actually OK. |
| items = createItems(1, 5, 1); |
| for (Item item : items) { |
| item.data = 2; |
| } |
| mList.addAll(items); |
| assertIntegrity(5, "Modification from callback"); |
| } |
| |
| @Test |
| public void testAddAllOutsideBatchedUpdates() { |
| mList.add(new Item(1)); |
| assertEquals(1, mAdditions.size()); |
| mList.add(new Item(2)); |
| assertEquals(2, mAdditions.size()); |
| mList.addAll(new Item(3), new Item(4)); |
| assertEquals(3, mAdditions.size()); |
| mList.add(new Item(5)); |
| assertEquals(4, mAdditions.size()); |
| mList.add(new Item(6)); |
| assertEquals(5, mAdditions.size()); |
| } |
| |
| @Test |
| public void testAddAllInsideBatchedUpdates() { |
| mList.beginBatchedUpdates(); |
| |
| mList.add(new Item(1)); |
| assertEquals(0, mAdditions.size()); |
| mList.add(new Item(2)); |
| assertEquals(0, mAdditions.size()); |
| mList.addAll(new Item(3), new Item(4)); |
| assertEquals(0, mAdditions.size()); |
| mList.add(new Item(5)); |
| assertEquals(0, mAdditions.size()); |
| mList.add(new Item(6)); |
| assertEquals(0, mAdditions.size()); |
| |
| mList.endBatchedUpdates(); |
| |
| assertEquals(1, mAdditions.size()); |
| assertTrue(mAdditions.contains(new Pair(0, 6))); |
| } |
| |
| @Test |
| public void testAddExistingItemCallsChangeWithPayload() { |
| mList.addAll( |
| new Item(1), |
| new Item(2), |
| new Item(3) |
| ); |
| mPayloadChanges = true; |
| |
| // add an item with the same id but a new data field i.e. send an update |
| final Item twoUpdate = new Item(2); |
| twoUpdate.data = 1337; |
| mList.add(twoUpdate); |
| assertEquals(1, mPayloadUpdates.size()); |
| final PayloadChange update = mPayloadUpdates.get(0); |
| assertEquals(1, update.position); |
| assertEquals(1, update.count); |
| assertEquals(1337, update.payload); |
| assertEquals(3, size()); |
| } |
| |
| @Test |
| public void testUpdateItemCallsChangeWithPayload() { |
| mList.addAll( |
| new Item(1), |
| new Item(2), |
| new Item(3) |
| ); |
| mPayloadChanges = true; |
| |
| // add an item with the same id but a new data field i.e. send an update |
| final Item twoUpdate = new Item(2); |
| twoUpdate.data = 1337; |
| mList.updateItemAt(1, twoUpdate); |
| assertEquals(1, mPayloadUpdates.size()); |
| final PayloadChange update = mPayloadUpdates.get(0); |
| assertEquals(1, update.position); |
| assertEquals(1, update.count); |
| assertEquals(1337, update.payload); |
| assertEquals(3, size()); |
| assertEquals(1337, mList.get(1).data); |
| } |
| |
| @Test |
| public void testAddMultipleExistingItemCallsChangeWithPayload() { |
| mList.addAll( |
| new Item(1), |
| new Item(2), |
| new Item(3) |
| ); |
| mPayloadChanges = true; |
| |
| // add two items with the same ids but a new data fields i.e. send two updates |
| final Item twoUpdate = new Item(2); |
| twoUpdate.data = 222; |
| final Item threeUpdate = new Item(3); |
| threeUpdate.data = 333; |
| mList.addAll(twoUpdate, threeUpdate); |
| assertEquals(2, mPayloadUpdates.size()); |
| final PayloadChange update1 = mPayloadUpdates.get(0); |
| assertEquals(1, update1.position); |
| assertEquals(1, update1.count); |
| assertEquals(222, update1.payload); |
| final PayloadChange update2 = mPayloadUpdates.get(1); |
| assertEquals(2, update2.position); |
| assertEquals(1, update2.count); |
| assertEquals(333, update2.payload); |
| assertEquals(3, size()); |
| } |
| |
| @Test |
| public void replaceAll_mayModifyInputFalse_doesNotModify() { |
| mList.addAll( |
| new Item(1), |
| new Item(2) |
| ); |
| Item replacement0 = new Item(4); |
| Item replacement1 = new Item(3); |
| Item[] replacements = new Item[]{ |
| replacement0, |
| replacement1 |
| }; |
| |
| mList.replaceAll(replacements, false); |
| |
| assertSame(replacement0, replacements[0]); |
| assertSame(replacement1, replacements[1]); |
| } |
| |
| @Test |
| public void replaceAll_varArgs_isEquivalentToDefault() { |
| mList.addAll( |
| new Item(1), |
| new Item(2) |
| ); |
| Item replacement0 = new Item(3); |
| Item replacement1 = new Item(4); |
| |
| mList.replaceAll(replacement0, replacement1); |
| |
| assertEquals(mList.get(0), replacement0); |
| assertEquals(mList.get(1), replacement1); |
| assertEquals(2, mList.size()); |
| } |
| |
| @Test |
| public void replaceAll_collection_isEquivalentToDefaultWithMayModifyInputFalse() { |
| mList.addAll( |
| new Item(1), |
| new Item(2) |
| ); |
| Item replacement0 = new Item(4); |
| Item replacement1 = new Item(3); |
| List<Item> replacements = new ArrayList<>(); |
| replacements.add(replacement0); |
| replacements.add(replacement1); |
| |
| mList.replaceAll(replacements); |
| |
| assertEquals(mList.get(0), replacement1); |
| assertEquals(mList.get(1), replacement0); |
| assertSame(replacements.get(0), replacement0); |
| assertSame(replacements.get(1), replacement1); |
| assertEquals(2, mList.size()); |
| } |
| |
| @Test |
| public void replaceAll_callsChangeWithPayload() { |
| mList.addAll( |
| new Item(1), |
| new Item(2), |
| new Item(3) |
| ); |
| mPayloadChanges = true; |
| final Item twoUpdate = new Item(2); |
| twoUpdate.data = 222; |
| final Item threeUpdate = new Item(3); |
| threeUpdate.data = 333; |
| |
| mList.replaceAll(twoUpdate, threeUpdate); |
| |
| assertEquals(2, mPayloadUpdates.size()); |
| final PayloadChange update1 = mPayloadUpdates.get(0); |
| assertEquals(0, update1.position); |
| assertEquals(1, update1.count); |
| assertEquals(222, update1.payload); |
| final PayloadChange update2 = mPayloadUpdates.get(1); |
| assertEquals(1, update2.position); |
| assertEquals(1, update2.count); |
| assertEquals(333, update2.payload); |
| } |
| |
| @Test |
| public void replaceAll_totallyEquivalentData_worksCorrectly() { |
| Item[] items1 = createItemsFromInts(1, 2, 3); |
| Item[] items2 = createItemsFromInts(1, 2, 3); |
| mList.addAll(items1); |
| mEvents.clear(); |
| |
| mList.replaceAll(items2); |
| |
| assertEquals(0, mEvents.size()); |
| assertTrue(sortedListEquals(mList, items2)); |
| } |
| |
| @Test |
| public void replaceAll_removalsAndAdds1_worksCorrectly() { |
| Item[] items1 = createItemsFromInts(1, 3, 5); |
| Item[] items2 = createItemsFromInts(2, 4); |
| mList.addAll(items1); |
| mEvents.clear(); |
| |
| mCallbackRunnables = new LinkedList<>(); |
| mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(2, 3, 5))); |
| mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(2, 5))); |
| mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(2, 4, 5))); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| |
| mList.replaceAll(items2); |
| |
| assertEquals(new Event(TYPE.REMOVE, 0, 1), mEvents.get(0)); |
| assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(1)); |
| assertEquals(new Event(TYPE.REMOVE, 1, 1), mEvents.get(2)); |
| assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(3)); |
| assertEquals(new Event(TYPE.REMOVE, 2, 1), mEvents.get(4)); |
| assertEquals(5, mEvents.size()); |
| assertTrue(sortedListEquals(mList, items2)); |
| assertTrue(mCallbackRunnables.isEmpty()); |
| } |
| |
| @Test |
| public void replaceAll_removalsAndAdds2_worksCorrectly() { |
| Item[] items1 = createItemsFromInts(2, 4); |
| Item[] items2 = createItemsFromInts(1, 3, 5); |
| mList.addAll(items1); |
| mEvents.clear(); |
| |
| mCallbackRunnables = new LinkedList<>(); |
| mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(1, 4))); |
| mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(1, 3, 4))); |
| mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(1, 3))); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| |
| mList.replaceAll(items2); |
| |
| assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(0)); |
| assertEquals(new Event(TYPE.REMOVE, 1, 1), mEvents.get(1)); |
| assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(2)); |
| assertEquals(new Event(TYPE.REMOVE, 2, 1), mEvents.get(3)); |
| assertEquals(new Event(TYPE.ADD, 2, 1), mEvents.get(4)); |
| assertEquals(5, mEvents.size()); |
| assertTrue(sortedListEquals(mList, items2)); |
| assertTrue(mCallbackRunnables.isEmpty()); |
| } |
| |
| @Test |
| public void replaceAll_removalsAndAdds3_worksCorrectly() { |
| Item[] items1 = createItemsFromInts(1, 3, 5); |
| Item[] items2 = createItemsFromInts(2, 3, 4); |
| mList.addAll(items1); |
| mEvents.clear(); |
| |
| mCallbackRunnables = new LinkedList<>(); |
| mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(2, 3, 5))); |
| mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(2, 3, 4, 5))); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| |
| mList.replaceAll(items2); |
| |
| assertEquals(new Event(TYPE.REMOVE, 0, 1), mEvents.get(0)); |
| assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(1)); |
| assertEquals(new Event(TYPE.ADD, 2, 1), mEvents.get(2)); |
| assertEquals(new Event(TYPE.REMOVE, 3, 1), mEvents.get(3)); |
| assertEquals(4, mEvents.size()); |
| assertTrue(sortedListEquals(mList, items2)); |
| assertTrue(mCallbackRunnables.isEmpty()); |
| } |
| |
| @Test |
| public void replaceAll_removalsAndAdds4_worksCorrectly() { |
| Item[] items1 = createItemsFromInts(2, 3, 4); |
| Item[] items2 = createItemsFromInts(1, 3, 5); |
| mList.addAll(items1); |
| mEvents.clear(); |
| |
| mCallbackRunnables = new LinkedList<>(); |
| mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(1, 3, 4))); |
| mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(1, 3))); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| |
| mList.replaceAll(items2); |
| |
| assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(0)); |
| assertEquals(new Event(TYPE.REMOVE, 1, 1), mEvents.get(1)); |
| assertEquals(new Event(TYPE.REMOVE, 2, 1), mEvents.get(2)); |
| assertEquals(new Event(TYPE.ADD, 2, 1), mEvents.get(3)); |
| assertEquals(4, mEvents.size()); |
| assertTrue(sortedListEquals(mList, items2)); |
| assertTrue(mCallbackRunnables.isEmpty()); |
| } |
| |
| @Test |
| public void replaceAll_removalsAndAdds5_worksCorrectly() { |
| Item[] items1 = createItemsFromInts(1, 2, 3); |
| Item[] items2 = createItemsFromInts(3, 4, 5); |
| mList.addAll(items1); |
| mEvents.clear(); |
| |
| mCallbackRunnables = new LinkedList<>(); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| |
| mList.replaceAll(items2); |
| |
| assertEquals(new Event(TYPE.REMOVE, 0, 2), mEvents.get(0)); |
| assertEquals(new Event(TYPE.ADD, 1, 2), mEvents.get(1)); |
| assertEquals(2, mEvents.size()); |
| assertTrue(sortedListEquals(mList, items2)); |
| assertTrue(mCallbackRunnables.isEmpty()); |
| } |
| |
| @Test |
| public void replaceAll_removalsAndAdds6_worksCorrectly() { |
| Item[] items1 = createItemsFromInts(3, 4, 5); |
| Item[] items2 = createItemsFromInts(1, 2, 3); |
| mList.addAll(items1); |
| mEvents.clear(); |
| |
| mCallbackRunnables = new LinkedList<>(); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| |
| mList.replaceAll(items2); |
| |
| assertEquals(new Event(TYPE.ADD, 0, 2), mEvents.get(0)); |
| assertEquals(new Event(TYPE.REMOVE, 3, 2), mEvents.get(1)); |
| assertEquals(2, mEvents.size()); |
| assertTrue(sortedListEquals(mList, items2)); |
| assertTrue(mCallbackRunnables.isEmpty()); |
| } |
| |
| @Test |
| public void replaceAll_move1_worksCorrectly() { |
| Item[] items1 = createItemsFromInts(1, 2, 3); |
| Item[] items2 = new Item[]{ |
| new Item(2), |
| new Item(3), |
| new Item(1, 4, 1)}; |
| mList.addAll(items1); |
| mEvents.clear(); |
| |
| mCallbackRunnables = new LinkedList<>(); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| |
| mList.replaceAll(items2); |
| |
| assertEquals(new Event(TYPE.REMOVE, 0, 1), mEvents.get(0)); |
| assertEquals(new Event(TYPE.ADD, 2, 1), mEvents.get(1)); |
| assertEquals(2, mEvents.size()); |
| assertTrue(sortedListEquals(mList, items2)); |
| assertTrue(mCallbackRunnables.isEmpty()); |
| } |
| |
| @Test |
| public void replaceAll_move2_worksCorrectly() { |
| Item[] items1 = createItemsFromInts(1, 2, 3); |
| Item[] items2 = new Item[]{ |
| new Item(3, 0, 3), |
| new Item(1), |
| new Item(2)}; |
| mList.addAll(items1); |
| mEvents.clear(); |
| |
| mCallbackRunnables = new LinkedList<>(); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| |
| mList.replaceAll(items2); |
| |
| assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(0)); |
| assertEquals(new Event(TYPE.REMOVE, 3, 1), mEvents.get(1)); |
| assertEquals(2, mEvents.size()); |
| assertTrue(sortedListEquals(mList, items2)); |
| assertTrue(mCallbackRunnables.isEmpty()); |
| } |
| |
| @Test |
| public void replaceAll_move3_worksCorrectly() { |
| Item[] items1 = createItemsFromInts(1, 3, 5, 7, 9); |
| Item[] items2 = new Item[]{ |
| new Item(3, 0, 3), |
| new Item(1), |
| new Item(5), |
| new Item(9), |
| new Item(7, 10, 7), |
| }; |
| mList.addAll(items1); |
| mEvents.clear(); |
| |
| mCallbackRunnables = new LinkedList<>(); |
| mCallbackRunnables.add(new AssertListStateRunnable( |
| new Item(3, 0, 3), |
| new Item(1), |
| new Item(5), |
| new Item(7), |
| new Item(9) |
| )); |
| mCallbackRunnables.add(new AssertListStateRunnable( |
| new Item(3, 0, 3), |
| new Item(1), |
| new Item(5), |
| new Item(9) |
| )); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| |
| mList.replaceAll(items2); |
| |
| assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(0)); |
| assertEquals(new Event(TYPE.REMOVE, 2, 1), mEvents.get(1)); |
| assertEquals(new Event(TYPE.REMOVE, 3, 1), mEvents.get(2)); |
| assertEquals(new Event(TYPE.ADD, 4, 1), mEvents.get(3)); |
| assertEquals(4, mEvents.size()); |
| assertTrue(sortedListEquals(mList, items2)); |
| assertTrue(mCallbackRunnables.isEmpty()); |
| } |
| |
| @Test |
| public void replaceAll_move4_worksCorrectly() { |
| Item[] items1 = createItemsFromInts(1, 3, 5, 7, 9); |
| Item[] items2 = new Item[]{ |
| new Item(3), |
| new Item(1, 4, 1), |
| new Item(5), |
| new Item(9, 6, 9), |
| new Item(7), |
| }; |
| mList.addAll(items1); |
| mEvents.clear(); |
| |
| mCallbackRunnables = new LinkedList<>(); |
| mCallbackRunnables.add(new AssertListStateRunnable( |
| new Item(3), |
| new Item(1, 4, 1), |
| new Item(5), |
| new Item(7), |
| new Item(9) |
| )); |
| mCallbackRunnables.add(new AssertListStateRunnable( |
| new Item(3), |
| new Item(1, 4, 1), |
| new Item(5), |
| new Item(9, 6, 9), |
| new Item(7), |
| new Item(9) |
| )); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| |
| mList.replaceAll(items2); |
| |
| assertEquals(new Event(TYPE.REMOVE, 0, 1), mEvents.get(0)); |
| assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(1)); |
| assertEquals(new Event(TYPE.ADD, 3, 1), mEvents.get(2)); |
| assertEquals(new Event(TYPE.REMOVE, 5, 1), mEvents.get(3)); |
| assertEquals(4, mEvents.size()); |
| assertTrue(sortedListEquals(mList, items2)); |
| assertTrue(mCallbackRunnables.isEmpty()); |
| } |
| |
| @Test |
| public void replaceAll_move5_worksCorrectly() { |
| Item[] items1 = createItemsFromInts(1, 3, 5, 7, 9); |
| Item[] items2 = new Item[]{ |
| new Item(9, 1, 9), |
| new Item(7, 3, 7), |
| new Item(5), |
| new Item(3, 7, 3), |
| new Item(1, 9, 1), |
| }; |
| mList.addAll(items1); |
| mEvents.clear(); |
| |
| mCallbackRunnables = new LinkedList<>(); |
| mCallbackRunnables.add(new AssertListStateRunnable( |
| new Item(9, 1, 9), |
| new Item(3), |
| new Item(5), |
| new Item(7), |
| new Item(9) |
| )); |
| mCallbackRunnables.add(new AssertListStateRunnable( |
| new Item(9, 1, 9), |
| new Item(5), |
| new Item(7), |
| new Item(9) |
| )); |
| mCallbackRunnables.add(new AssertListStateRunnable( |
| new Item(9, 1, 9), |
| new Item(7, 3, 7), |
| new Item(5), |
| new Item(7), |
| new Item(9) |
| )); |
| mCallbackRunnables.add(new AssertListStateRunnable( |
| new Item(9, 1, 9), |
| new Item(7, 3, 7), |
| new Item(5), |
| new Item(9) |
| )); |
| mCallbackRunnables.add(new AssertListStateRunnable( |
| new Item(9, 1, 9), |
| new Item(7, 3, 7), |
| new Item(5), |
| new Item(3, 7, 3), |
| new Item(9) |
| )); |
| mCallbackRunnables.add(new AssertListStateRunnable( |
| new Item(9, 1, 9), |
| new Item(7, 3, 7), |
| new Item(5), |
| new Item(3, 7, 3) |
| )); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| |
| mList.replaceAll(items2); |
| |
| assertEquals(new Event(TYPE.REMOVE, 0, 1), mEvents.get(0)); |
| assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(1)); |
| assertEquals(new Event(TYPE.REMOVE, 1, 1), mEvents.get(2)); |
| assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(3)); |
| assertEquals(new Event(TYPE.REMOVE, 3, 1), mEvents.get(4)); |
| assertEquals(new Event(TYPE.ADD, 3, 1), mEvents.get(5)); |
| assertEquals(new Event(TYPE.REMOVE, 4, 1), mEvents.get(6)); |
| assertEquals(new Event(TYPE.ADD, 4, 1), mEvents.get(7)); |
| assertEquals(8, mEvents.size()); |
| assertTrue(sortedListEquals(mList, items2)); |
| assertTrue(mCallbackRunnables.isEmpty()); |
| } |
| |
| @Test |
| public void replaceAll_orderSameItemDifferent_worksCorrectly() { |
| Item[] items1 = new Item[]{ |
| new Item(1), |
| new Item(2, 3, 2), |
| new Item(5) |
| }; |
| Item[] items2 = new Item[]{ |
| new Item(1), |
| new Item(4, 3, 4), |
| new Item(5) |
| }; |
| mList.addAll(items1); |
| mEvents.clear(); |
| |
| mCallbackRunnables = new LinkedList<>(); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| |
| mList.replaceAll(items2); |
| |
| assertEquals(new Event(TYPE.REMOVE, 1, 1), mEvents.get(0)); |
| assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(1)); |
| assertEquals(2, mEvents.size()); |
| assertTrue(sortedListEquals(mList, items2)); |
| assertTrue(mCallbackRunnables.isEmpty()); |
| } |
| |
| @Test |
| public void replaceAll_orderSameItemSameContentsDifferent_worksCorrectly() { |
| Item[] items1 = new Item[]{ |
| new Item(1), |
| new Item(3, 3, 2), |
| new Item(5) |
| }; |
| Item[] items2 = new Item[]{ |
| new Item(1), |
| new Item(3, 3, 4), |
| new Item(5) |
| }; |
| mList.addAll(items1); |
| mEvents.clear(); |
| |
| mCallbackRunnables = new LinkedList<>(); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| |
| mList.replaceAll(items2); |
| |
| assertEquals(new Event(TYPE.CHANGE, 1, 1), mEvents.get(0)); |
| assertEquals(1, mEvents.size()); |
| assertTrue(sortedListEquals(mList, items2)); |
| assertTrue(mCallbackRunnables.isEmpty()); |
| } |
| |
| @Test |
| public void replaceAll_allTypesOfChanges1_worksCorrectly() { |
| Item[] items1 = createItemsFromInts(2, 5, 6); |
| Item[] items2 = new Item[]{ |
| new Item(1), |
| new Item(3, 2, 3), |
| new Item(6, 6, 7) |
| }; |
| mList.addAll(items1); |
| mEvents.clear(); |
| |
| mCallbackRunnables = new LinkedList<>(); |
| mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(1, 5, 6))); |
| mCallbackRunnables.add(new AssertListStateRunnable( |
| new Item(1), |
| new Item(3, 2, 3), |
| new Item(5), |
| new Item(6) |
| )); |
| mCallbackRunnables.add(new AssertListStateRunnable( |
| new Item(1), |
| new Item(3, 2, 3), |
| new Item(6) |
| )); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| |
| mList.replaceAll(items2); |
| |
| assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(0)); |
| assertEquals(new Event(TYPE.REMOVE, 1, 1), mEvents.get(1)); |
| assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(2)); |
| assertEquals(new Event(TYPE.REMOVE, 2, 1), mEvents.get(3)); |
| assertEquals(new Event(TYPE.CHANGE, 2, 1), mEvents.get(4)); |
| assertEquals(5, mEvents.size()); |
| assertTrue(sortedListEquals(mList, items2)); |
| assertTrue(mCallbackRunnables.isEmpty()); |
| } |
| |
| @Test |
| public void replaceAll_allTypesOfChanges2_worksCorrectly() { |
| Item[] items1 = createItemsFromInts(1, 4, 6); |
| Item[] items2 = new Item[]{ |
| new Item(1, 1, 2), |
| new Item(3), |
| new Item(5, 4, 5) |
| }; |
| mList.addAll(items1); |
| mEvents.clear(); |
| |
| mCallbackRunnables = new LinkedList<>(); |
| mCallbackRunnables.add(new AssertListStateRunnable( |
| new Item(1, 1, 2), |
| new Item(3), |
| new Item(4), |
| new Item(6) |
| )); |
| mCallbackRunnables.add(new AssertListStateRunnable( |
| new Item(1, 1, 2), |
| new Item(3), |
| new Item(6) |
| )); |
| mCallbackRunnables.add(new AssertListStateRunnable( |
| new Item(1, 1, 2), |
| new Item(3), |
| new Item(5, 4, 5), |
| new Item(6) |
| )); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| |
| mList.replaceAll(items2); |
| |
| assertEquals(new Event(TYPE.CHANGE, 0, 1), mEvents.get(0)); |
| assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(1)); |
| assertEquals(new Event(TYPE.REMOVE, 2, 1), mEvents.get(2)); |
| assertEquals(new Event(TYPE.ADD, 2, 1), mEvents.get(3)); |
| assertEquals(new Event(TYPE.REMOVE, 3, 1), mEvents.get(4)); |
| assertEquals(5, mEvents.size()); |
| assertTrue(sortedListEquals(mList, items2)); |
| assertTrue(mCallbackRunnables.isEmpty()); |
| } |
| |
| @Test |
| public void replaceAll_allTypesOfChanges3_worksCorrectly() { |
| Item[] items1 = createItemsFromInts(1, 2); |
| Item[] items2 = new Item[]{ |
| new Item(2, 2, 3), |
| new Item(3, 2, 4), |
| new Item(5) |
| }; |
| mList.addAll(items1); |
| mEvents.clear(); |
| |
| mCallbackRunnables = new LinkedList<>(); |
| mCallbackRunnables.add(new AssertListStateRunnable( |
| new Item(2, 2, 3) |
| )); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| |
| mList.replaceAll(items2); |
| |
| assertEquals(new Event(TYPE.REMOVE, 0, 1), mEvents.get(0)); |
| assertEquals(new Event(TYPE.CHANGE, 0, 1), mEvents.get(1)); |
| assertEquals(new Event(TYPE.ADD, 1, 2), mEvents.get(2)); |
| assertEquals(3, mEvents.size()); |
| assertTrue(sortedListEquals(mList, items2)); |
| assertTrue(mCallbackRunnables.isEmpty()); |
| } |
| |
| @Test |
| public void replaceAll_newItemsAreIdentical_resultIsDeduped() { |
| Item[] items = createItemsFromInts(1, 1); |
| mList.replaceAll(items); |
| |
| assertEquals(new Item(1), mList.get(0)); |
| assertEquals(1, mList.size()); |
| } |
| |
| @Test |
| public void replaceAll_newItemsUnsorted_resultIsSorted() { |
| Item[] items = createItemsFromInts(2, 1); |
| mList.replaceAll(items); |
| |
| assertEquals(new Item(1), mList.get(0)); |
| assertEquals(new Item(2), mList.get(1)); |
| assertEquals(2, mList.size()); |
| } |
| |
| @Test |
| public void replaceAll_calledAfterBeginBatchedUpdates_worksCorrectly() { |
| Item[] items1 = createItemsFromInts(1, 2, 3); |
| Item[] items2 = createItemsFromInts(4, 5, 6); |
| mList.addAll(items1); |
| mEvents.clear(); |
| |
| mCallbackRunnables = new LinkedList<>(); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| mCallbackRunnables.add(new AssertListStateRunnable(items2)); |
| |
| mList.beginBatchedUpdates(); |
| mList.replaceAll(items2); |
| mList.endBatchedUpdates(); |
| |
| assertEquals(new Event(TYPE.REMOVE, 0, 3), mEvents.get(0)); |
| assertEquals(new Event(TYPE.ADD, 0, 3), mEvents.get(1)); |
| assertEquals(2, mEvents.size()); |
| assertTrue(sortedListEquals(mList, items2)); |
| assertTrue(mCallbackRunnables.isEmpty()); |
| } |
| |
| private int size() { |
| return mList.size(); |
| } |
| |
| private int insert(Item item) { |
| return mList.add(item); |
| } |
| |
| private boolean remove(Item item) { |
| return mList.remove(item); |
| } |
| |
| static class Item { |
| |
| final int id; |
| int cmpField; |
| int data; |
| |
| Item(int allFields) { |
| this(allFields, allFields, allFields); |
| } |
| |
| Item(int id, int compField, int data) { |
| this.id = id; |
| this.cmpField = compField; |
| this.data = data; |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| |
| Item item = (Item) o; |
| |
| return id == item.id && cmpField == item.cmpField && data == item.data; |
| } |
| |
| @Override |
| public String toString() { |
| return "Item(id=" + id + ", cmpField=" + cmpField + ", data=" + data + ')'; |
| } |
| } |
| |
| private static final class Pair { |
| |
| final int first, second; |
| |
| public Pair(int first) { |
| this.first = first; |
| this.second = Integer.MIN_VALUE; |
| } |
| |
| public Pair(int first, int second) { |
| this.first = first; |
| this.second = second; |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) { |
| return true; |
| } |
| if (o == null || getClass() != o.getClass()) { |
| return false; |
| } |
| |
| Pair pair = (Pair) o; |
| |
| if (first != pair.first) { |
| return false; |
| } |
| if (second != pair.second) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = first; |
| result = 31 * result + second; |
| return result; |
| } |
| } |
| |
| private enum TYPE { |
| ADD, REMOVE, MOVE, CHANGE |
| } |
| |
| private final class AssertListStateRunnable implements Runnable { |
| |
| private Item[] mExpectedItems; |
| |
| AssertListStateRunnable(Item... expectedItems) { |
| this.mExpectedItems = expectedItems; |
| } |
| |
| @Override |
| public void run() { |
| try { |
| assertEquals(mExpectedItems.length, mList.size()); |
| for (int i = mExpectedItems.length - 1; i >= 0; i--) { |
| assertEquals(mExpectedItems[i], mList.get(i)); |
| assertEquals(i, mList.indexOf(mExpectedItems[i])); |
| } |
| } catch (AssertionError assertionError) { |
| throw new AssertionError( |
| assertionError.getMessage() |
| + "\nExpected: " |
| + Arrays.toString(mExpectedItems) |
| + "\nActual: " |
| + sortedListToString(mList)); |
| } |
| } |
| } |
| |
| private static final class Event { |
| private final TYPE mType; |
| private final int mVal1; |
| private final int mVal2; |
| |
| Event(TYPE type, int val1, int val2) { |
| this.mType = type; |
| this.mVal1 = val1; |
| this.mVal2 = val2; |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| Event that = (Event) o; |
| return mType == that.mType && mVal1 == that.mVal1 && mVal2 == that.mVal2; |
| } |
| |
| @Override |
| public String toString() { |
| return "Event(" + mType + ", " + mVal1 + ", " + mVal2 + ")"; |
| } |
| } |
| |
| private <T> boolean sortedListEquals(SortedList<T> sortedList, T[] array) { |
| if (sortedList.size() != array.length) { |
| return false; |
| } |
| for (int i = sortedList.size() - 1; i >= 0; i--) { |
| if (!sortedList.get(i).equals(array[i])) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private static String sortedListToString(SortedList sortedList) { |
| StringBuilder stringBuilder = new StringBuilder("["); |
| int size = sortedList.size(); |
| for (int i = 0; i < size; i++) { |
| stringBuilder.append(sortedList.get(i).toString() + ", "); |
| } |
| stringBuilder.delete(stringBuilder.length() - 2, stringBuilder.length()); |
| stringBuilder.append("]"); |
| return stringBuilder.toString(); |
| } |
| |
| private static final class PayloadChange { |
| public final int position; |
| public final int count; |
| public final Object payload; |
| |
| PayloadChange(int position, int count, Object payload) { |
| this.position = position; |
| this.count = count; |
| this.payload = payload; |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| |
| PayloadChange payloadChange = (PayloadChange) o; |
| |
| if (position != payloadChange.position) return false; |
| if (count != payloadChange.count) return false; |
| return payload != null ? payload.equals(payloadChange.payload) |
| : payloadChange.payload == null; |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = position; |
| result = 31 * result + count; |
| result = 31 * result + (payload != null ? payload.hashCode() : 0); |
| return result; |
| } |
| } |
| } |