package com.bumptech.glide; import static com.bumptech.glide.tests.Util.cast; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import com.bumptech.glide.request.target.SizeReadyCallback; import com.bumptech.glide.request.target.Target; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.verification.VerificationMode; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE, sdk = 18) public class ListPreloaderTest { @Mock private RequestBuilder<Object> request; @Mock private RequestManager requestManager; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); } @Test public void testGetItemsIsCalledIncreasing() { final AtomicBoolean called = new AtomicBoolean(false); final AtomicInteger calledCount = new AtomicInteger(); ListPreloaderAdapter preloaderAdapter = new ListPreloaderAdapter() { @Override public List<Object> getPreloadItems(int position) { called.set(true); final int count = calledCount.getAndIncrement(); assertEquals(11 + count, position); return super.getPreloadItems(position); } }; ListPreloader<Object> preloader = new ListPreloader<>(requestManager, preloaderAdapter, preloaderAdapter, 10); preloader.onScroll(null, 1, 10, 30); assertEquals(10, calledCount.get()); } @Test public void testGetItemsIsCalledInOrderIncreasing() { final int toPreload = 10; final List<Object> objects = new ArrayList<>(); for (int i = 0; i < toPreload; i++) { objects.add(i); } ListPreloaderAdapter preloaderAdapter = new ListPreloaderAdapter() { private int expectedPosition; @Override public int[] getPreloadSize(Object item, int adapterPosition, int itemPosition) { return new int[] { 10, 10 }; } @Override public List<Object> getPreloadItems(int position) { return objects.subList(position - 11, position + 1 - 11); } @Override @SuppressWarnings("unchecked") public RequestBuilder<Object> getPreloadRequestBuilder(Object item) { assertEquals(objects.get(expectedPosition), item); expectedPosition++; return mock(RequestBuilder.class); } }; ListPreloader<Object> preloader = new ListPreloader<>(requestManager, preloaderAdapter, preloaderAdapter, toPreload); preloader.onScroll(null, 1, 10, 20); } @Test public void testGetItemsIsCalledDecreasing() { final AtomicBoolean called = new AtomicBoolean(false); final AtomicInteger calledCount = new AtomicInteger(); ListPreloaderAdapter preloaderAdapter = new ListPreloaderAdapter() { @Override public List<Object> getPreloadItems(int position) { // Ignore the preload caused from us starting at the end if (position >= 40) { return Collections.emptyList(); } final int count = calledCount.getAndIncrement(); called.set(true); assertEquals(28 - count, position); return super.getPreloadItems(position); } }; ListPreloader<Object> preloader = new ListPreloader<>(requestManager, preloaderAdapter, preloaderAdapter, 10); preloader.onScroll(null, 30, 10, 40); preloader.onScroll(null, 29, 10, 40); assertTrue(called.get()); } @Test public void testGetItemsIsCalledInOrderDecreasing() { final int toPreload = 10; final List<Object> objects = new ArrayList<>(); for (int i = 0; i < toPreload; i++) { objects.add(new Object()); } ListPreloaderAdapter preloaderAdapter = new ListPreloaderAdapter() { private int expectedPosition = toPreload - 1; @Override public int[] getPreloadSize(Object item, int adapterPosition, int itemPosition) { return new int[] { 10, 10 }; } @Override public List<Object> getPreloadItems(int position) { if (position == 40) { return null; } return objects.subList(position, position + 1); } @Override @SuppressWarnings("unchecked") public RequestBuilder<Object> getPreloadRequestBuilder(Object item) { assertEquals(objects.get(expectedPosition), item); expectedPosition--; return mock(RequestBuilder.class); } }; ListPreloader<Object> preloader = new ListPreloader<>(requestManager, preloaderAdapter, preloaderAdapter, toPreload); preloader.onScroll(null, 30, 10, 10); preloader.onScroll(null, 29, 10, 10); } @Test public void testGetItemsIsNeverCalledWithEndGreaterThanTotalItems() { final AtomicBoolean called = new AtomicBoolean(false); final AtomicInteger calledCount = new AtomicInteger(); ListPreloaderAdapter preloaderAdapter = new ListPreloaderAdapter() { @Override public List<Object> getPreloadItems(int position) { called.set(true); final int count = calledCount.getAndIncrement(); assertEquals(26 + count, position); return super.getPreloadItems(position); } }; ListPreloader<Object> preloader = new ListPreloader<>(requestManager, preloaderAdapter, preloaderAdapter, 10); preloader.onScroll(null, 16, 10, 30); assertTrue(called.get()); } @Test public void testGetItemsIsNeverCalledWithStartLessThanZero() { final AtomicBoolean called = new AtomicBoolean(false); final AtomicInteger calledCount = new AtomicInteger(); ListPreloaderAdapter preloaderAdapter = new ListPreloaderAdapter() { @Override public List<Object> getPreloadItems(int position) { if (position >= 17) { return Collections.emptyList(); } called.set(true); final int count = calledCount.getAndIncrement(); assertEquals(5 - count, position); return super.getPreloadItems(position); } }; ListPreloader<Object> preloader = new ListPreloader<>(requestManager, preloaderAdapter, preloaderAdapter, 10); preloader.onScroll(null, 7, 10, 30); preloader.onScroll(null, 6, 10, 30); assertTrue(called.get()); } @Test public void testDontPreloadItemsRepeatedlyWhileIncreasing() { final AtomicInteger called = new AtomicInteger(); ListPreloaderAdapter preloaderAdapter = new ListPreloaderAdapter() { @Override public List<Object> getPreloadItems(int position) { final int current = called.getAndIncrement(); assertEquals(11 + current, position); return super.getPreloadItems(position); } }; ListPreloader<Object> preloader = new ListPreloader<>(requestManager, preloaderAdapter, preloaderAdapter, 10); preloader.onScroll(null, 1, 10, 30); preloader.onScroll(null, 4, 10, 30); assertEquals(13, called.get()); } @Test public void testDontPreloadItemsRepeatedlyWhileDecreasing() { final AtomicInteger called = new AtomicInteger(); ListPreloaderAdapter preloaderAdapter = new ListPreloaderAdapter() { @Override public List<Object> getPreloadItems(int position) { if (position >= 20) { return Collections.emptyList(); } final int current = called.getAndIncrement(); assertEquals(19 - current, position); return super.getPreloadItems(position); } }; ListPreloader<Object> preloader = new ListPreloader<>(requestManager, preloaderAdapter, preloaderAdapter, 10); preloader.onScroll(null, 21, 10, 30); preloader.onScroll(null, 20, 10, 30); preloader.onScroll(null, 17, 10, 30); assertEquals(13, called.get()); } @Test public void testMultipleItemsForPositionIncreasing() throws NoSuchFieldException { final List<Object> objects = new ArrayList<>(); objects.add(new Object()); objects.add(new Object()); ListPreloaderAdapter preloaderAdapter = new ListPreloaderAdapter() { private int expectedPosition = (1 + 10) * 2; @Override public List<Object> getPreloadItems(int position) { return objects; } @Override public int[] getPreloadSize(Object item, int adapterPosition, int itemPosition) { assertEquals(expectedPosition / 2, adapterPosition); assertEquals(expectedPosition % 2, itemPosition); expectedPosition++; return itemPosition == 0 ? new int[] { 10, 11 } : new int[] { 20, 21 }; } @Override public RequestBuilder<Object> getPreloadRequestBuilder(Object item) { return request; } }; ListPreloader<Object> preloader = new ListPreloader<>(requestManager, preloaderAdapter, preloaderAdapter, 10); Iterable<Integer> expected = Arrays.asList(10, 11, 20, 21, 10, 11, 20, 21); preloader.onScroll(null, 1, 10, 1 + 10 + 2); List<Integer> allValues = getTargetsSizes(request, times(4)); assertEquals(expected, allValues); } @Test public void testMultipleItemsForPositionDecreasing() throws NoSuchFieldException { final List<Object> objects = new ArrayList<>(); objects.add(new Object()); objects.add(new Object()); ListPreloaderAdapter preloaderAdapter = new ListPreloaderAdapter() { private int expectedPosition = objects.size() * 2 - 1; @Override public List<Object> getPreloadItems(int position) { return objects; } @Override public int[] getPreloadSize(Object item, int adapterPosition, int itemPosition) { assertEquals(expectedPosition / 2, adapterPosition); assertEquals(expectedPosition % 2, itemPosition); expectedPosition--; return itemPosition == 0 ? new int[] { 10, 11 } : new int[] { 20, 21 }; } @Override public RequestBuilder<Object> getPreloadRequestBuilder(Object item) { return request; } }; ListPreloader<Object> preloader = new ListPreloader<>(requestManager, preloaderAdapter, preloaderAdapter, 10); Iterable<Integer> expected = Arrays.asList(20, 21, 10, 11, 20, 21, 10, 11); preloader.onScroll(null, 3, 2, 3 + 2); preloader.onScroll(null, 2, 2, 3 + 2); List<Integer> allValues = getTargetsSizes(request, times(4)); assertEquals(expected, allValues); } private <R> List<Integer> getTargetsSizes( RequestBuilder<R> requestBuilder, VerificationMode mode) { ArgumentCaptor<Integer> integerArgumentCaptor = ArgumentCaptor.forClass(Integer.class); ArgumentCaptor<Target<R>> targetArgumentCaptor = cast(ArgumentCaptor.forClass(Target.class)); SizeReadyCallback cb = mock(SizeReadyCallback.class); verify(requestBuilder, mode).into(targetArgumentCaptor.capture()); for (Target<R> target : targetArgumentCaptor.getAllValues()) { target.getSize(cb); } verify(cb, mode).onSizeReady(integerArgumentCaptor.capture(), integerArgumentCaptor.capture()); return integerArgumentCaptor.getAllValues(); } @Test public void testItemsArePreloadedWithGlide() { final List<Object> objects = new ArrayList<>(); objects.add(new Object()); objects.add(new Object()); final HashSet<Object> loadedObjects = new HashSet<>(); ListPreloaderAdapter preloaderAdapter = new ListPreloaderAdapter() { @Override public List<Object> getPreloadItems(int position) { return objects.subList(position - 11, position - 10); } @Override public RequestBuilder<Object> getPreloadRequestBuilder(Object item) { loadedObjects.add(item); return super.getPreloadRequestBuilder(item); } }; ListPreloader<Object> preloader = new ListPreloader<>(requestManager, preloaderAdapter, preloaderAdapter, 10); preloader.onScroll(null, 1, 10, 13); assertThat(loadedObjects).containsAllIn(objects); } private static class ListPreloaderAdapter implements ListPreloader.PreloadModelProvider<Object>, ListPreloader.PreloadSizeProvider<Object> { public ListPreloaderAdapter() { } @Override public List<Object> getPreloadItems(int position) { ArrayList<Object> result = new ArrayList<>(1); Collections.fill(result, new Object()); return result; } @Override @SuppressWarnings("unchecked") public RequestBuilder<Object> getPreloadRequestBuilder(Object item) { return mock(RequestBuilder.class); } @Override public int[] getPreloadSize(Object item, int adapterPosition, int itemPosition) { return new int[] { 100, 100 }; } } }