package com.airbnb.epoxy; import android.support.v7.widget.RecyclerView.AdapterDataObserver; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentMatcher; import org.robolectric.annotation.Config; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import static com.airbnb.epoxy.DiffPayload.getModelFromPayload; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNull; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @Config(sdk = 21, manifest = TestRunner.MANIFEST_PATH) @RunWith(TestRunner.class) public class DiffPayloadTest { private final List<EpoxyModel<?>> models = new ArrayList<>(); private BaseEpoxyAdapter adapter; private AdapterDataObserver observer; @Before public void before() { adapter = new BaseEpoxyAdapter() { @Override List<EpoxyModel<?>> getCurrentModels() { return models; } }; observer = spy(new AdapterDataObserver() { @Override public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { } }); adapter.registerAdapterDataObserver(observer); } @Test public void payloadsDisabled() { DiffHelper diffHelper = new DiffHelper(adapter, false); TestModel firstModel = new TestModel(); models.add(firstModel); diffHelper.notifyModelChanges(); verify(observer).onItemRangeInserted(0, 1); TestModel updatedFirstModel = firstModel.clone().incrementValue(); models.clear(); models.add(updatedFirstModel); diffHelper.notifyModelChanges(); verify(observer).onItemRangeChanged(0, 1, null); verifyNoMoreInteractions(observer); } @Test public void noPayloadsForNoChanges() { DiffHelper diffHelper = new DiffHelper(adapter, true); TestModel firstModel = new TestModel(); models.add(firstModel); diffHelper.notifyModelChanges(); verify(observer).onItemRangeInserted(0, 1); models.clear(); diffHelper.notifyModelChanges(); verify(observer).onItemRangeRemoved(0, 1); verifyNoMoreInteractions(observer); } @Test public void singlePayload() { DiffHelper diffHelper = new DiffHelper(adapter, true); TestModel firstModel = new TestModel(); models.add(firstModel); diffHelper.notifyModelChanges(); verify(observer).onItemRangeInserted(0, 1); models.clear(); TestModel changedFirstModel = firstModel.clone().incrementValue(); this.models.add(changedFirstModel); diffHelper.notifyModelChanges(); verify(observer).onItemRangeChanged(eq(0), eq(1), argThat(new DiffPayloadMatcher(firstModel))); verifyNoMoreInteractions(observer); } @Test public void batchPayload() { DiffHelper diffHelper = new DiffHelper(adapter, true); TestModel firstModel = new TestModel(); TestModel secondModel = new TestModel(); models.add(firstModel); models.add(secondModel); diffHelper.notifyModelChanges(); TestModel changedFirstModel = firstModel.clone().incrementValue(); TestModel changedSecondModel = secondModel.clone().incrementValue(); models.clear(); models.add(changedFirstModel); models.add(changedSecondModel); diffHelper.notifyModelChanges(); verify(observer) .onItemRangeChanged(eq(0), eq(2), argThat(new DiffPayloadMatcher(firstModel, secondModel))); } @Test public void multiplePayloads() { DiffHelper diffHelper = new DiffHelper(adapter, true); TestModel firstModel = new TestModel(); TestModel secondModel = new TestModel(); TestModel thirdModel = new TestModel(); models.add(firstModel); models.add(thirdModel); diffHelper.notifyModelChanges(); TestModel changedFirstModel = firstModel.clone().incrementValue(); TestModel changedThirdModel = thirdModel.clone().incrementValue(); models.clear(); models.add(changedFirstModel); models.add(secondModel); models.add(changedThirdModel); diffHelper.notifyModelChanges(); verify(observer).onItemRangeChanged(eq(0), eq(1), argThat(new DiffPayloadMatcher(firstModel))); verify(observer).onItemRangeChanged(eq(2), eq(1), argThat(new DiffPayloadMatcher(thirdModel))); } @Test public void getSingleModelFromPayload() { TestModel model = new TestModel(); List<Object> payloads = payloadsWithChangedModels(model); EpoxyModel<?> modelFromPayload = getModelFromPayload(payloads, model.id()); assertEquals(model, modelFromPayload); } @Test public void returnsNullWhenNoModelFoundInPayload() { TestModel model = new TestModel(); List<Object> payloads = payloadsWithChangedModels(model); EpoxyModel<?> modelFromPayload = getModelFromPayload(payloads, model.id() - 1); assertNull(modelFromPayload); } @Test public void returnsNullForEmptyPayload() { List<Object> payloads = new ArrayList<>(); EpoxyModel<?> modelFromPayload = getModelFromPayload(payloads, 2); assertNull(modelFromPayload); } @Test public void getMultipleModelsFromPayload() { TestModel model1 = new TestModel(); TestModel model2 = new TestModel(); List<Object> payloads = payloadsWithChangedModels(model1, model2); EpoxyModel<?> modelFromPayload1 = getModelFromPayload(payloads, model1.id()); EpoxyModel<?> modelFromPayload2 = getModelFromPayload(payloads, model2.id()); assertEquals(model1, modelFromPayload1); assertEquals(model2, modelFromPayload2); } @Test public void getSingleModelsFromMultipleDiffPayloads() { TestModel model1 = new TestModel(); DiffPayload diffPayload1 = diffPayloadWithModels(model1); TestModel model2 = new TestModel(); DiffPayload diffPayload2 = diffPayloadWithModels(model2); List<Object> payloads = payloadsWithDiffPayloads(diffPayload1, diffPayload2); EpoxyModel<?> modelFromPayload1 = getModelFromPayload(payloads, model1.id()); EpoxyModel<?> modelFromPayload2 = getModelFromPayload(payloads, model2.id()); assertEquals(model1, modelFromPayload1); assertEquals(model2, modelFromPayload2); } @Test public void getMultipleModelsFromMultipleDiffPayloads() { TestModel model1Payload1 = new TestModel(1); TestModel model2Payload1 = new TestModel(2); DiffPayload diffPayload1 = diffPayloadWithModels(model1Payload1, model2Payload1); TestModel model1Payload2 = new TestModel(3); TestModel model2Payload2 = new TestModel(4); DiffPayload diffPayload2 = diffPayloadWithModels(model1Payload2, model2Payload2); List<Object> payloads = payloadsWithDiffPayloads(diffPayload1, diffPayload2); EpoxyModel<?> model1FromPayload1 = getModelFromPayload(payloads, model1Payload1.id()); EpoxyModel<?> model2FromPayload1 = getModelFromPayload(payloads, model2Payload1.id()); EpoxyModel<?> model1FromPayload2 = getModelFromPayload(payloads, model1Payload2.id()); EpoxyModel<?> model2FromPayload2 = getModelFromPayload(payloads, model2Payload2.id()); assertEquals(model1Payload1, model1FromPayload1); assertEquals(model2Payload1, model2FromPayload1); assertEquals(model1Payload2, model1FromPayload2); assertEquals(model2Payload2, model2FromPayload2); } static class DiffPayloadMatcher implements ArgumentMatcher<DiffPayload> { private final DiffPayload expectedPayload; DiffPayloadMatcher(EpoxyModel<?>... changedModels) { List<EpoxyModel<?>> epoxyModels = Arrays.asList(changedModels); expectedPayload = new DiffPayload(epoxyModels); } @Override public boolean matches(DiffPayload argument) { return expectedPayload.equalsForTesting(argument); } } static DiffPayload diffPayloadWithModels(EpoxyModel<?>... models) { List<EpoxyModel<?>> epoxyModels = Arrays.asList(models); return new DiffPayload(epoxyModels); } static List<Object> payloadsWithDiffPayloads(DiffPayload... diffPayloads) { List<DiffPayload> payloads = Arrays.asList(diffPayloads); return new ArrayList<Object>(payloads); } static List<Object> payloadsWithChangedModels(EpoxyModel<?>... models) { return payloadsWithDiffPayloads(diffPayloadWithModels(models)); } }