1ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik/*
2ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik * Copyright (C) 2017 The Android Open Source Project
3ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik *
4ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik * Licensed under the Apache License, Version 2.0 (the "License");
5ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik * you may not use this file except in compliance with the License.
6ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik * You may obtain a copy of the License at
7ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik *
8ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik *      http://www.apache.org/licenses/LICENSE-2.0
9ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik *
10ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik * Unless required by applicable law or agreed to in writing, software
11ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik * distributed under the License is distributed on an "AS IS" BASIS,
12ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik * See the License for the specific language governing permissions and
14ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik * limitations under the License.
15ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik */
16ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
17bdc4c86d3dff74f6634a38e2f7b316b0e823a2c8Alan Viverettepackage androidx.paging
18ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
19bdc4c86d3dff74f6634a38e2f7b316b0e823a2c8Alan Viveretteimport androidx.arch.core.util.Function
20ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craikimport org.junit.Assert.assertArrayEquals
21ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craikimport org.junit.Assert.assertEquals
225dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craikimport org.junit.Assert.assertFalse
23ed6541a9e1b9737dc9aed9f46ad134fdb3d47a7cChris Craikimport org.junit.Assert.assertSame
245dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craikimport org.junit.Assert.assertTrue
25ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craikimport org.junit.Test
26ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craikimport org.junit.runner.RunWith
27ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craikimport org.junit.runners.Parameterized
28ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craikimport org.mockito.Mockito.mock
29ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craikimport org.mockito.Mockito.verify
30ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craikimport org.mockito.Mockito.verifyNoMoreInteractions
31ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craikimport org.mockito.Mockito.verifyZeroInteractions
32f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craikimport java.util.concurrent.Executor
335dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik
34ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik@RunWith(Parameterized::class)
35ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craikclass ContiguousPagedListTest(private val mCounted: Boolean) {
36ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    private val mMainThread = TestExecutor()
37ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    private val mBackgroundThread = TestExecutor()
38ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
39ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    private class Item(position: Int) {
40ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        val name: String = "Item $position"
41ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
42ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        override fun toString(): String {
43ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik            return name
44ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        }
45ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    }
46ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
4767077406223e49eba5ecd0def10ca80dd6909f16Chris Craik    private inner class TestSource(val listData: List<Item> = ITEMS)
485dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik            : ContiguousDataSource<Int, Item>() {
49694588d1d059ac96142d6334ec7fce90abb7622bChris Craik        override fun dispatchLoadInitial(
50f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik                key: Int?,
51f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik                initialLoadSize: Int,
52f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik                pageSize: Int,
53f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik                enablePlaceholders: Boolean,
54f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik                mainThreadExecutor: Executor,
55f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik                receiver: PageResult.Receiver<Item>) {
565dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik
575dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik            val convertPosition = key ?: 0
58f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik            val position = Math.max(0, (convertPosition - initialLoadSize / 2))
59f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik            val data = getClampedRange(position, position + initialLoadSize)
60f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik            val trailingUnloadedCount = listData.size - position - data.size
615dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik
625dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik            if (enablePlaceholders && mCounted) {
63f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik                receiver.onPageResult(PageResult.INIT,
64f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik                        PageResult(data, position, trailingUnloadedCount, 0))
65ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik            } else {
665dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                // still must pass offset, even if not counted
67f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik                receiver.onPageResult(PageResult.INIT,
68f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik                        PageResult(data, position))
69ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik            }
70ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        }
71ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
72694588d1d059ac96142d6334ec7fce90abb7622bChris Craik        override fun dispatchLoadAfter(
73f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik                currentEndIndex: Int,
74f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik                currentEndItem: Item,
75f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik                pageSize: Int,
76f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik                mainThreadExecutor: Executor,
77f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik                receiver: PageResult.Receiver<Item>) {
785dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik            val startIndex = currentEndIndex + 1
79f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik            val data = getClampedRange(startIndex, startIndex + pageSize)
80f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik
81f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik            mainThreadExecutor.execute {
82f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik                receiver.onPageResult(PageResult.APPEND, PageResult(data, 0, 0, 0))
83f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik            }
84ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        }
85ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
86694588d1d059ac96142d6334ec7fce90abb7622bChris Craik        override fun dispatchLoadBefore(
87f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik                currentBeginIndex: Int,
88f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik                currentBeginItem: Item,
89f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik                pageSize: Int,
90f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik                mainThreadExecutor: Executor,
91f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik                receiver: PageResult.Receiver<Item>) {
92f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik
935dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik            val startIndex = currentBeginIndex - 1
94f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik            val data = getClampedRange(startIndex - pageSize + 1, startIndex + 1)
95f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik
96f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik            mainThreadExecutor.execute {
97f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik                receiver.onPageResult(PageResult.PREPEND, PageResult(data, 0, 0, 0))
98f174a1ccda73d8a42dc752a0b1c487508af2c54dChris Craik            }
99ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        }
100ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
1015dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        override fun getKey(position: Int, item: Item?): Int {
1025dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik            return 0
1035dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        }
1045dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik
1055dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        private fun getClampedRange(startInc: Int, endExc: Int): List<Item> {
1065dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik            return listData.subList(Math.max(0, startInc), Math.min(listData.size, endExc))
107ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        }
10849da2e7f2d6d92afae01d99c292dd477f3bf7ffcChris Craik
10949da2e7f2d6d92afae01d99c292dd477f3bf7ffcChris Craik        override fun <ToValue : Any?> mapByPage(function: Function<List<Item>, List<ToValue>>):
11049da2e7f2d6d92afae01d99c292dd477f3bf7ffcChris Craik                DataSource<Int, ToValue> {
11149da2e7f2d6d92afae01d99c292dd477f3bf7ffcChris Craik            throw UnsupportedOperationException()
11249da2e7f2d6d92afae01d99c292dd477f3bf7ffcChris Craik        }
11349da2e7f2d6d92afae01d99c292dd477f3bf7ffcChris Craik
11449da2e7f2d6d92afae01d99c292dd477f3bf7ffcChris Craik        override fun <ToValue : Any?> map(function: Function<Item, ToValue>):
11549da2e7f2d6d92afae01d99c292dd477f3bf7ffcChris Craik                DataSource<Int, ToValue> {
11649da2e7f2d6d92afae01d99c292dd477f3bf7ffcChris Craik            throw UnsupportedOperationException()
11749da2e7f2d6d92afae01d99c292dd477f3bf7ffcChris Craik        }
118ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    }
119ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
1205dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik    private fun verifyRange(start: Int, count: Int, actual: PagedStorage<Item>) {
121ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        if (mCounted) {
122771816bab34735dd8fb47a93085f6b86c132154cChris Craik            // assert nulls + content
123ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik            val expected = arrayOfNulls<Item>(ITEMS.size)
124ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik            System.arraycopy(ITEMS.toTypedArray(), start, expected, start, count)
125ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik            assertArrayEquals(expected, actual.toTypedArray())
126ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
127ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik            val expectedTrailing = ITEMS.size - start - count
128ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik            assertEquals(ITEMS.size, actual.size)
129771816bab34735dd8fb47a93085f6b86c132154cChris Craik            assertEquals((ITEMS.size - start - expectedTrailing),
130ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik                    actual.storageCount)
131771816bab34735dd8fb47a93085f6b86c132154cChris Craik            assertEquals(start, actual.leadingNullCount)
132ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik            assertEquals(expectedTrailing, actual.trailingNullCount)
133ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        } else {
134771816bab34735dd8fb47a93085f6b86c132154cChris Craik            assertEquals(ITEMS.subList(start, start + count), actual)
135ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
136ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik            assertEquals(count, actual.size)
137ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik            assertEquals(actual.size, actual.storageCount)
138ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik            assertEquals(0, actual.leadingNullCount)
139ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik            assertEquals(0, actual.trailingNullCount)
140ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        }
141ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    }
142ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
143ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    private fun verifyRange(start: Int, count: Int, actual: PagedList<Item>) {
144ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyRange(start, count, actual.mStorage)
145ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    }
146ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
147ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    private fun createCountedPagedList(
14867077406223e49eba5ecd0def10ca80dd6909f16Chris Craik            initialPosition: Int,
14967077406223e49eba5ecd0def10ca80dd6909f16Chris Craik            pageSize: Int = 20,
15067077406223e49eba5ecd0def10ca80dd6909f16Chris Craik            initLoadSize: Int = 40,
15167077406223e49eba5ecd0def10ca80dd6909f16Chris Craik            prefetchDistance: Int = 20,
15267077406223e49eba5ecd0def10ca80dd6909f16Chris Craik            listData: List<Item> = ITEMS,
153e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik            boundaryCallback: PagedList.BoundaryCallback<Item>? = null,
154e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik            lastLoad: Int = ContiguousPagedList.LAST_LOAD_UNSPECIFIED
1556f1f5567abe765d30fda9c8fedce5617ecdeda9cAurimas Liutikas    ): ContiguousPagedList<Int, Item> {
156ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        return ContiguousPagedList(
15767077406223e49eba5ecd0def10ca80dd6909f16Chris Craik                TestSource(listData), mMainThread, mBackgroundThread, boundaryCallback,
158ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik                PagedList.Config.Builder()
15967077406223e49eba5ecd0def10ca80dd6909f16Chris Craik                        .setInitialLoadSizeHint(initLoadSize)
16067077406223e49eba5ecd0def10ca80dd6909f16Chris Craik                        .setPageSize(pageSize)
16167077406223e49eba5ecd0def10ca80dd6909f16Chris Craik                        .setPrefetchDistance(prefetchDistance)
162ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik                        .build(),
163e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik                initialPosition,
164e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik                lastLoad)
165ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    }
166ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
167ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    @Test
168ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    fun construct() {
169ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        val pagedList = createCountedPagedList(0)
170ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyRange(0, 40, pagedList)
171ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    }
172ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
173ed6541a9e1b9737dc9aed9f46ad134fdb3d47a7cChris Craik    @Test
174ed6541a9e1b9737dc9aed9f46ad134fdb3d47a7cChris Craik    fun getDataSource() {
175ed6541a9e1b9737dc9aed9f46ad134fdb3d47a7cChris Craik        val pagedList = createCountedPagedList(0)
176ed6541a9e1b9737dc9aed9f46ad134fdb3d47a7cChris Craik        assertTrue(pagedList.dataSource is TestSource)
177ed6541a9e1b9737dc9aed9f46ad134fdb3d47a7cChris Craik
178ed6541a9e1b9737dc9aed9f46ad134fdb3d47a7cChris Craik        // snapshot keeps same DataSource
179ed6541a9e1b9737dc9aed9f46ad134fdb3d47a7cChris Craik        assertSame(pagedList.dataSource,
180ed6541a9e1b9737dc9aed9f46ad134fdb3d47a7cChris Craik                (pagedList.snapshot() as SnapshotPagedList<Item>).dataSource)
181ed6541a9e1b9737dc9aed9f46ad134fdb3d47a7cChris Craik    }
182ed6541a9e1b9737dc9aed9f46ad134fdb3d47a7cChris Craik
183771816bab34735dd8fb47a93085f6b86c132154cChris Craik    private fun verifyCallback(callback: PagedList.Callback, countedPosition: Int,
184771816bab34735dd8fb47a93085f6b86c132154cChris Craik            uncountedPosition: Int) {
185771816bab34735dd8fb47a93085f6b86c132154cChris Craik        if (mCounted) {
186771816bab34735dd8fb47a93085f6b86c132154cChris Craik            verify(callback).onChanged(countedPosition, 20)
187771816bab34735dd8fb47a93085f6b86c132154cChris Craik        } else {
188771816bab34735dd8fb47a93085f6b86c132154cChris Craik            verify(callback).onInserted(uncountedPosition, 20)
189771816bab34735dd8fb47a93085f6b86c132154cChris Craik        }
190771816bab34735dd8fb47a93085f6b86c132154cChris Craik    }
191771816bab34735dd8fb47a93085f6b86c132154cChris Craik
192ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    @Test
193ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    fun append() {
194ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        val pagedList = createCountedPagedList(0)
195ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        val callback = mock(PagedList.Callback::class.java)
196ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        pagedList.addWeakCallback(null, callback)
197ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyRange(0, 40, pagedList)
198ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyZeroInteractions(callback)
199ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
200ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        pagedList.loadAround(35)
201ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        drain()
202ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
203ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyRange(0, 60, pagedList)
204ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyCallback(callback, 40, 40)
205ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyNoMoreInteractions(callback)
206ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    }
207ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
208ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    @Test
209ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    fun prepend() {
210ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        val pagedList = createCountedPagedList(80)
211ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        val callback = mock(PagedList.Callback::class.java)
212ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        pagedList.addWeakCallback(null, callback)
213ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyRange(60, 40, pagedList)
214ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyZeroInteractions(callback)
215ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
216ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        pagedList.loadAround(if (mCounted) 65 else 5)
217ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        drain()
218ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
219ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyRange(40, 60, pagedList)
220ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyCallback(callback, 40, 0)
221ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyNoMoreInteractions(callback)
222ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    }
223ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
224ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    @Test
225ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    fun outwards() {
226ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        val pagedList = createCountedPagedList(50)
227ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        val callback = mock(PagedList.Callback::class.java)
228ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        pagedList.addWeakCallback(null, callback)
229ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyRange(30, 40, pagedList)
230ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyZeroInteractions(callback)
231ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
232ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        pagedList.loadAround(if (mCounted) 65 else 35)
233ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        drain()
234ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
235ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyRange(30, 60, pagedList)
236ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyCallback(callback, 70, 40)
237ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyNoMoreInteractions(callback)
238ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
239ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        pagedList.loadAround(if (mCounted) 35 else 5)
240ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        drain()
241ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
242ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyRange(10, 80, pagedList)
243ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyCallback(callback, 10, 0)
244ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyNoMoreInteractions(callback)
245ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    }
246ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
247ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    @Test
248ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    fun multiAppend() {
249ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        val pagedList = createCountedPagedList(0)
250ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        val callback = mock(PagedList.Callback::class.java)
251ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        pagedList.addWeakCallback(null, callback)
252ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyRange(0, 40, pagedList)
253ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyZeroInteractions(callback)
254ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
255ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        pagedList.loadAround(55)
256ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        drain()
257ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
258ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyRange(0, 80, pagedList)
259ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyCallback(callback, 40, 40)
260ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyCallback(callback, 60, 60)
261ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyNoMoreInteractions(callback)
262ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    }
263ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
264ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    @Test
265ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    fun distantPrefetch() {
26667077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        val pagedList = createCountedPagedList(0,
26767077406223e49eba5ecd0def10ca80dd6909f16Chris Craik                initLoadSize = 10, pageSize = 10, prefetchDistance = 30)
268ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        val callback = mock(PagedList.Callback::class.java)
269ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        pagedList.addWeakCallback(null, callback)
270ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyRange(0, 10, pagedList)
271ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyZeroInteractions(callback)
272ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
273ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        pagedList.loadAround(5)
274ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        drain()
275ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
276ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyRange(0, 40, pagedList)
277ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
278ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        pagedList.loadAround(6)
279ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        drain()
280ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
281ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        // although our prefetch window moves forward, no new load triggered
282ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyRange(0, 40, pagedList)
283ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    }
284ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
285ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    @Test
286ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    fun appendCallbackAddedLate() {
287ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        val pagedList = createCountedPagedList(0)
288ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyRange(0, 40, pagedList)
289ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
290ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        pagedList.loadAround(35)
291ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        drain()
292ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyRange(0, 60, pagedList)
293ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
294ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        // snapshot at 60 items
295ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        val snapshot = pagedList.snapshot() as PagedList<Item>
296ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyRange(0, 60, snapshot)
297ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
298ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        // load more items...
299ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        pagedList.loadAround(55)
300ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        drain()
301ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyRange(0, 80, pagedList)
302ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyRange(0, 60, snapshot)
303ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
304ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        // and verify the snapshot hasn't received them
305ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        val callback = mock(PagedList.Callback::class.java)
306ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        pagedList.addWeakCallback(snapshot, callback)
307ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyCallback(callback, 60, 60)
308ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyNoMoreInteractions(callback)
309ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    }
310ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
311ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    @Test
312ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    fun prependCallbackAddedLate() {
313ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        val pagedList = createCountedPagedList(80)
314ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyRange(60, 40, pagedList)
315ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
316ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        pagedList.loadAround(if (mCounted) 65 else 5)
317ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        drain()
318ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyRange(40, 60, pagedList)
319ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
320ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        // snapshot at 60 items
321ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        val snapshot = pagedList.snapshot() as PagedList<Item>
322ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyRange(40, 60, snapshot)
323ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
324ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        pagedList.loadAround(if (mCounted) 45 else 5)
325ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        drain()
326ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyRange(20, 80, pagedList)
327ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyRange(40, 60, snapshot)
328ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
329ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        val callback = mock(PagedList.Callback::class.java)
330ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        pagedList.addWeakCallback(snapshot, callback)
331ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyCallback(callback, 40, 0)
332ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        verifyNoMoreInteractions(callback)
333ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    }
334ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
33567077406223e49eba5ecd0def10ca80dd6909f16Chris Craik    @Test
336e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik    fun initialLoad_lastLoad() {
337e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik        val pagedList = createCountedPagedList(
338e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik                initialPosition = 0,
339e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik                initLoadSize = 20,
340e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik                lastLoad = 4)
341e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik        // last load is param passed
342e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik        assertEquals(4, pagedList.mLastLoad)
343e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik        verifyRange(0, 20, pagedList)
344e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik    }
345e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik
346e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik    @Test
347e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik    fun initialLoad_lastLoadComputed() {
348e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik        val pagedList = createCountedPagedList(
349e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik                initialPosition = 0,
350e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik                initLoadSize = 20,
351e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik                lastLoad = ContiguousPagedList.LAST_LOAD_UNSPECIFIED)
352e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik        // last load is middle of initial load
353e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik        assertEquals(10, pagedList.mLastLoad)
354e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik        verifyRange(0, 20, pagedList)
355e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik    }
356e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik
357e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik    @Test
3585dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik    fun initialLoadAsync() {
3595dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        // Note: ignores Parameterized param
3605dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        val asyncDataSource = AsyncListDataSource(ITEMS)
3615dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        val dataSource = asyncDataSource.wrapAsContiguousWithoutPlaceholders()
3625dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        val pagedList = ContiguousPagedList(
3635dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                dataSource, mMainThread, mBackgroundThread, null,
364e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik                PagedList.Config.Builder().setPageSize(10).build(), null,
365e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik                ContiguousPagedList.LAST_LOAD_UNSPECIFIED)
3665f9d120fb2dbcebb87c1caa171320e9246e00c10Chris Craik        val callback = mock(PagedList.Callback::class.java)
3675f9d120fb2dbcebb87c1caa171320e9246e00c10Chris Craik        pagedList.addWeakCallback(null, callback)
3685dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik
3695dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        assertTrue(pagedList.isEmpty())
3705dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        drain()
3715dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        assertTrue(pagedList.isEmpty())
3725dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        asyncDataSource.flush()
3735dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        assertTrue(pagedList.isEmpty())
3745dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        mBackgroundThread.executeAll()
3755dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        assertTrue(pagedList.isEmpty())
3765f9d120fb2dbcebb87c1caa171320e9246e00c10Chris Craik        verifyZeroInteractions(callback)
3775dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik
3785dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        // Data source defers callbacks until flush, which posts result to main thread
3795dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        mMainThread.executeAll()
3805dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        assertFalse(pagedList.isEmpty())
3815f9d120fb2dbcebb87c1caa171320e9246e00c10Chris Craik        // callback onInsert called once with initial size
3825f9d120fb2dbcebb87c1caa171320e9246e00c10Chris Craik        verify(callback).onInserted(0, pagedList.size)
3835f9d120fb2dbcebb87c1caa171320e9246e00c10Chris Craik        verifyNoMoreInteractions(callback)
3845dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik    }
3855dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik
3865dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik    @Test
3875dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik    fun addWeakCallbackEmpty() {
3885dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        // Note: ignores Parameterized param
3895dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        val asyncDataSource = AsyncListDataSource(ITEMS)
3905dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        val dataSource = asyncDataSource.wrapAsContiguousWithoutPlaceholders()
3915dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        val pagedList = ContiguousPagedList(
3925dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik                dataSource, mMainThread, mBackgroundThread, null,
393e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik                PagedList.Config.Builder().setPageSize(10).build(), null,
394e4cede1bc21d8f16cee3c0de2d77f70d7c3fd2cfChris Craik                ContiguousPagedList.LAST_LOAD_UNSPECIFIED)
3955dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        val callback = mock(PagedList.Callback::class.java)
3965dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik
3975dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        // capture empty snapshot
3985dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        val emptySnapshot = pagedList.snapshot()
3995dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        assertTrue(pagedList.isEmpty())
4005dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        assertTrue(emptySnapshot.isEmpty())
4015dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik
4025dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        // verify that adding callback notifies nothing going from empty -> empty
4035dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        pagedList.addWeakCallback(emptySnapshot, callback)
4045dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        verifyZeroInteractions(callback)
4055dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        pagedList.removeWeakCallback(callback)
4065dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik
4075dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        // data added in asynchronously
4085dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        asyncDataSource.flush()
4095dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        drain()
4105dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        assertFalse(pagedList.isEmpty())
4115dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik
4125dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        // verify that adding callback notifies insert going from empty -> content
4135dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        pagedList.addWeakCallback(emptySnapshot, callback)
4145dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        verify(callback).onInserted(0, pagedList.size)
4155dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        verifyNoMoreInteractions(callback)
4165dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik    }
4175dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik
4185dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik    @Test
41967077406223e49eba5ecd0def10ca80dd6909f16Chris Craik    fun boundaryCallback_empty() {
42067077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        @Suppress("UNCHECKED_CAST")
42167077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        val boundaryCallback =
42267077406223e49eba5ecd0def10ca80dd6909f16Chris Craik                mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<Item>
42367077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        val pagedList = createCountedPagedList(0,
42467077406223e49eba5ecd0def10ca80dd6909f16Chris Craik                listData = ArrayList(), boundaryCallback = boundaryCallback)
42567077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        assertEquals(0, pagedList.size)
42667077406223e49eba5ecd0def10ca80dd6909f16Chris Craik
42767077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        // nothing yet
42867077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        verifyNoMoreInteractions(boundaryCallback)
42967077406223e49eba5ecd0def10ca80dd6909f16Chris Craik
43067077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        // onZeroItemsLoaded posted, since creation often happens on BG thread
43167077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        drain()
43267077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        verify(boundaryCallback).onZeroItemsLoaded()
43367077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        verifyNoMoreInteractions(boundaryCallback)
43467077406223e49eba5ecd0def10ca80dd6909f16Chris Craik    }
43567077406223e49eba5ecd0def10ca80dd6909f16Chris Craik
43667077406223e49eba5ecd0def10ca80dd6909f16Chris Craik    @Test
43768d6c3eb6419ab55acd9ed91b03eb2b25e47c55bChris Craik    fun boundaryCallback_singleInitialLoad() {
43868d6c3eb6419ab55acd9ed91b03eb2b25e47c55bChris Craik        val shortList = ITEMS.subList(0, 4)
43968d6c3eb6419ab55acd9ed91b03eb2b25e47c55bChris Craik        @Suppress("UNCHECKED_CAST")
44068d6c3eb6419ab55acd9ed91b03eb2b25e47c55bChris Craik        val boundaryCallback =
44168d6c3eb6419ab55acd9ed91b03eb2b25e47c55bChris Craik                mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<Item>
44268d6c3eb6419ab55acd9ed91b03eb2b25e47c55bChris Craik        val pagedList = createCountedPagedList(0, listData = shortList,
44368d6c3eb6419ab55acd9ed91b03eb2b25e47c55bChris Craik                initLoadSize = shortList.size, boundaryCallback = boundaryCallback)
44468d6c3eb6419ab55acd9ed91b03eb2b25e47c55bChris Craik        assertEquals(shortList.size, pagedList.size)
44568d6c3eb6419ab55acd9ed91b03eb2b25e47c55bChris Craik
44668d6c3eb6419ab55acd9ed91b03eb2b25e47c55bChris Craik        // nothing yet
44768d6c3eb6419ab55acd9ed91b03eb2b25e47c55bChris Craik        verifyNoMoreInteractions(boundaryCallback)
44868d6c3eb6419ab55acd9ed91b03eb2b25e47c55bChris Craik
44968d6c3eb6419ab55acd9ed91b03eb2b25e47c55bChris Craik        // onItemAtFrontLoaded / onItemAtEndLoaded posted, since creation often happens on BG thread
45068d6c3eb6419ab55acd9ed91b03eb2b25e47c55bChris Craik        drain()
45168d6c3eb6419ab55acd9ed91b03eb2b25e47c55bChris Craik        pagedList.loadAround(0)
45268d6c3eb6419ab55acd9ed91b03eb2b25e47c55bChris Craik        drain()
45368d6c3eb6419ab55acd9ed91b03eb2b25e47c55bChris Craik        verify(boundaryCallback).onItemAtFrontLoaded(shortList.first())
45468d6c3eb6419ab55acd9ed91b03eb2b25e47c55bChris Craik        verify(boundaryCallback).onItemAtEndLoaded(shortList.last())
45568d6c3eb6419ab55acd9ed91b03eb2b25e47c55bChris Craik        verifyNoMoreInteractions(boundaryCallback)
45668d6c3eb6419ab55acd9ed91b03eb2b25e47c55bChris Craik    }
45768d6c3eb6419ab55acd9ed91b03eb2b25e47c55bChris Craik
45868d6c3eb6419ab55acd9ed91b03eb2b25e47c55bChris Craik    @Test
45967077406223e49eba5ecd0def10ca80dd6909f16Chris Craik    fun boundaryCallback_delayed() {
46067077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        @Suppress("UNCHECKED_CAST")
46167077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        val boundaryCallback =
46267077406223e49eba5ecd0def10ca80dd6909f16Chris Craik                mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<Item>
46367077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        val pagedList = createCountedPagedList(90,
46467077406223e49eba5ecd0def10ca80dd6909f16Chris Craik                initLoadSize = 20, prefetchDistance = 5, boundaryCallback = boundaryCallback)
46567077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        verifyRange(80, 20, pagedList)
46667077406223e49eba5ecd0def10ca80dd6909f16Chris Craik
46767077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        // nothing yet
46867077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        verifyZeroInteractions(boundaryCallback)
46967077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        drain()
47067077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        verifyZeroInteractions(boundaryCallback)
47167077406223e49eba5ecd0def10ca80dd6909f16Chris Craik
47267077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        // loading around last item causes onItemAtEndLoaded
47367077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        pagedList.loadAround(if (mCounted) 99 else 19)
47467077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        drain()
47567077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        verifyRange(80, 20, pagedList)
4765dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        verify(boundaryCallback).onItemAtEndLoaded(ITEMS.last())
47767077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        verifyNoMoreInteractions(boundaryCallback)
47867077406223e49eba5ecd0def10ca80dd6909f16Chris Craik
47967077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        // prepending doesn't trigger callback...
48067077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        pagedList.loadAround(if (mCounted) 80 else 0)
48167077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        drain()
48267077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        verifyRange(60, 40, pagedList)
48367077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        verifyZeroInteractions(boundaryCallback)
48467077406223e49eba5ecd0def10ca80dd6909f16Chris Craik
48567077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        // ...load rest of data, still no dispatch...
48667077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        pagedList.loadAround(if (mCounted) 60 else 0)
48767077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        drain()
48867077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        pagedList.loadAround(if (mCounted) 40 else 0)
48967077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        drain()
49067077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        pagedList.loadAround(if (mCounted) 20 else 0)
49167077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        drain()
49267077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        verifyRange(0, 100, pagedList)
49367077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        verifyZeroInteractions(boundaryCallback)
49467077406223e49eba5ecd0def10ca80dd6909f16Chris Craik
49567077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        // ... finally try prepend, see 0 items, which will dispatch front callback
49667077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        pagedList.loadAround(0)
49767077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        drain()
4985dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik        verify(boundaryCallback).onItemAtFrontLoaded(ITEMS.first())
49967077406223e49eba5ecd0def10ca80dd6909f16Chris Craik        verifyNoMoreInteractions(boundaryCallback)
50067077406223e49eba5ecd0def10ca80dd6909f16Chris Craik    }
50167077406223e49eba5ecd0def10ca80dd6909f16Chris Craik
502ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    private fun drain() {
503ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        var executed: Boolean
504ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        do {
505ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik            executed = mBackgroundThread.executeAll()
506ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik            executed = mMainThread.executeAll() || executed
507ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        } while (executed)
508ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    }
509ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
510ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    companion object {
511ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        @JvmStatic
512ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        @Parameterized.Parameters(name = "counted:{0}")
513ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        fun parameters(): Array<Array<Boolean>> {
514ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik            return arrayOf(arrayOf(true), arrayOf(false))
515ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        }
516ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik
517ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik        private val ITEMS = List(100) { Item(it) }
518ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik    }
519ac809222a8350934455ef38980d53f0b89b0f0e9Chris Craik}
520