[go: nahoru, domu]

blob: dd4dcaa05c6a2fdaf0bd214e05a05b1998ea9786 [file] [log] [blame]
Chris Craike1178ed2017-09-29 17:50:00 -07001/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Chris Craikef346ae2017-08-28 17:42:07 -070017package android.arch.paging
Chris Craik2e9d5132017-08-24 18:10:23 -070018
19import org.junit.Assert.assertEquals
Chris Craik771816b2017-10-26 13:02:34 -070020import org.junit.Assert.assertNotNull
Chris Craik2e9d5132017-08-24 18:10:23 -070021import org.junit.Assert.assertTrue
Chris Craikf174a1c2017-11-28 11:11:32 -080022import org.junit.Assert.fail
Chris Craik2e9d5132017-08-24 18:10:23 -070023import org.junit.Test
24import org.junit.runner.RunWith
25import org.junit.runners.JUnit4
Chris Craike1178ed2017-09-29 17:50:00 -070026import org.mockito.ArgumentCaptor
Chris Craik5dc2fd42017-10-30 10:47:38 -070027import org.mockito.ArgumentMatchers.anyInt
Chris Craik771816b2017-10-26 13:02:34 -070028import org.mockito.Mockito.mock
29import org.mockito.Mockito.verify
30import org.mockito.Mockito.verifyNoMoreInteractions
Chris Craik2e9d5132017-08-24 18:10:23 -070031
32@RunWith(JUnit4::class)
Chris Craikb632de52017-11-30 16:09:04 -080033class ItemKeyedDataSourceTest {
Chris Craik2e9d5132017-08-24 18:10:23 -070034
35 // ----- STANDARD -----
36
Chris Craik771816b2017-10-26 13:02:34 -070037 private fun loadInitial(dataSource: ItemDataSource, key: Key?, initialLoadSize: Int,
Chris Craik5dc2fd42017-10-30 10:47:38 -070038 enablePlaceholders: Boolean): PageResult<Item> {
Chris Craik771816b2017-10-26 13:02:34 -070039 @Suppress("UNCHECKED_CAST")
Chris Craik5dc2fd42017-10-30 10:47:38 -070040 val receiver = mock(PageResult.Receiver::class.java) as PageResult.Receiver<Item>
Chris Craik771816b2017-10-26 13:02:34 -070041 @Suppress("UNCHECKED_CAST")
42 val captor = ArgumentCaptor.forClass(PageResult::class.java)
Chris Craik5dc2fd42017-10-30 10:47:38 -070043 as ArgumentCaptor<PageResult<Item>>
Chris Craik771816b2017-10-26 13:02:34 -070044
Chris Craik694588d2017-11-28 16:19:46 -080045 dataSource.dispatchLoadInitial(key, initialLoadSize,
Chris Craikf174a1c2017-11-28 11:11:32 -080046 /* ignored pageSize */ 10, enablePlaceholders, FailExecutor(), receiver)
Chris Craik5dc2fd42017-10-30 10:47:38 -070047
48 verify(receiver).onPageResult(anyInt(), captor.capture())
Chris Craik771816b2017-10-26 13:02:34 -070049 verifyNoMoreInteractions(receiver)
50 assertNotNull(captor.value)
51 return captor.value
52 }
53
Chris Craik2e9d5132017-08-24 18:10:23 -070054 @Test
Chris Craikfd4fa4a2017-08-31 14:08:37 -070055 fun loadInitial() {
56 val dataSource = ItemDataSource()
Chris Craik771816b2017-10-26 13:02:34 -070057 val result = loadInitial(dataSource, dataSource.getKey(ITEMS_BY_NAME_ID[49]), 10, true)
Chris Craikfd4fa4a2017-08-31 14:08:37 -070058
Chris Craik771816b2017-10-26 13:02:34 -070059 assertEquals(45, result.leadingNulls)
Chris Craik5dc2fd42017-10-30 10:47:38 -070060 assertEquals(ITEMS_BY_NAME_ID.subList(45, 55), result.page)
Chris Craik771816b2017-10-26 13:02:34 -070061 assertEquals(45, result.trailingNulls)
Chris Craikfd4fa4a2017-08-31 14:08:37 -070062 }
63
64 @Test
65 fun loadInitial_keyMatchesSingleItem() {
66 val dataSource = ItemDataSource(items = ITEMS_BY_NAME_ID.subList(0, 1))
67
68 // this is tricky, since load after and load before with the passed key will fail
Chris Craik771816b2017-10-26 13:02:34 -070069 val result = loadInitial(dataSource, dataSource.getKey(ITEMS_BY_NAME_ID[0]), 20, true)
Chris Craikfd4fa4a2017-08-31 14:08:37 -070070
Chris Craik771816b2017-10-26 13:02:34 -070071 assertEquals(0, result.leadingNulls)
Chris Craik5dc2fd42017-10-30 10:47:38 -070072 assertEquals(ITEMS_BY_NAME_ID.subList(0, 1), result.page)
Chris Craik771816b2017-10-26 13:02:34 -070073 assertEquals(0, result.trailingNulls)
Chris Craikfd4fa4a2017-08-31 14:08:37 -070074 }
75
76 @Test
77 fun loadInitial_keyMatchesLastItem() {
78 val dataSource = ItemDataSource()
79
80 // tricky, because load after key is empty, so another load before and load after required
81 val key = dataSource.getKey(ITEMS_BY_NAME_ID.last())
Chris Craik771816b2017-10-26 13:02:34 -070082 val result = loadInitial(dataSource, key, 20, true)
Chris Craikfd4fa4a2017-08-31 14:08:37 -070083
Chris Craik5dc2fd42017-10-30 10:47:38 -070084 assertEquals(90, result.leadingNulls)
85 assertEquals(ITEMS_BY_NAME_ID.subList(90, 100), result.page)
Chris Craik771816b2017-10-26 13:02:34 -070086 assertEquals(0, result.trailingNulls)
Chris Craikfd4fa4a2017-08-31 14:08:37 -070087 }
88
89 @Test
90 fun loadInitial_nullKey() {
Chris Craik2e9d5132017-08-24 18:10:23 -070091 val dataSource = ItemDataSource()
92
Chris Craik694588d2017-11-28 16:19:46 -080093 // dispatchLoadInitial(null, count) == dispatchLoadInitial(count)
Chris Craik771816b2017-10-26 13:02:34 -070094 val result = loadInitial(dataSource, null, 10, true)
Chris Craik2e9d5132017-08-24 18:10:23 -070095
Chris Craik771816b2017-10-26 13:02:34 -070096 assertEquals(0, result.leadingNulls)
Chris Craik5dc2fd42017-10-30 10:47:38 -070097 assertEquals(ITEMS_BY_NAME_ID.subList(0, 10), result.page)
Chris Craik771816b2017-10-26 13:02:34 -070098 assertEquals(90, result.trailingNulls)
Chris Craik2e9d5132017-08-24 18:10:23 -070099 }
100
101 @Test
Chris Craikfd4fa4a2017-08-31 14:08:37 -0700102 fun loadInitial_keyPastEndOfList() {
Chris Craik2e9d5132017-08-24 18:10:23 -0700103 val dataSource = ItemDataSource()
104
105 // if key is past entire data set, should return last items in data set
106 val key = Key("fz", 0)
Chris Craik771816b2017-10-26 13:02:34 -0700107 val result = loadInitial(dataSource, key, 10, true)
Chris Craik2e9d5132017-08-24 18:10:23 -0700108
Chris Craikfd4fa4a2017-08-31 14:08:37 -0700109 // NOTE: ideally we'd load 10 items here, but it adds complexity and unpredictability to
110 // do: load after was empty, so pass full size to load before, since this can incur larger
111 // loads than requested (see keyMatchesLastItem test)
Chris Craik771816b2017-10-26 13:02:34 -0700112 assertEquals(95, result.leadingNulls)
Chris Craik5dc2fd42017-10-30 10:47:38 -0700113 assertEquals(ITEMS_BY_NAME_ID.subList(95, 100), result.page)
Chris Craik771816b2017-10-26 13:02:34 -0700114 assertEquals(0, result.trailingNulls)
Chris Craik2e9d5132017-08-24 18:10:23 -0700115 }
116
117 // ----- UNCOUNTED -----
118
119 @Test
Chris Craikfd4fa4a2017-08-31 14:08:37 -0700120 fun loadInitial_disablePlaceholders() {
121 val dataSource = ItemDataSource()
122
Chris Craik694588d2017-11-28 16:19:46 -0800123 // dispatchLoadInitial(key, count) == null padding, loadAfter(key, count), null padding
Chris Craikfd4fa4a2017-08-31 14:08:37 -0700124 val key = dataSource.getKey(ITEMS_BY_NAME_ID[49])
Chris Craik771816b2017-10-26 13:02:34 -0700125 val result = loadInitial(dataSource, key, 10, false)
Chris Craikfd4fa4a2017-08-31 14:08:37 -0700126
Chris Craik771816b2017-10-26 13:02:34 -0700127 assertEquals(0, result.leadingNulls)
Chris Craik5dc2fd42017-10-30 10:47:38 -0700128 assertEquals(ITEMS_BY_NAME_ID.subList(45, 55), result.page)
Chris Craik771816b2017-10-26 13:02:34 -0700129 assertEquals(0, result.trailingNulls)
Chris Craikfd4fa4a2017-08-31 14:08:37 -0700130 }
131
132 @Test
133 fun loadInitial_uncounted() {
134 val dataSource = ItemDataSource(counted = false)
135
Chris Craik694588d2017-11-28 16:19:46 -0800136 // dispatchLoadInitial(key, count) == null padding, loadAfter(key, count), null padding
Chris Craikfd4fa4a2017-08-31 14:08:37 -0700137 val key = dataSource.getKey(ITEMS_BY_NAME_ID[49])
Chris Craik771816b2017-10-26 13:02:34 -0700138 val result = loadInitial(dataSource, key, 10, true)
Chris Craikfd4fa4a2017-08-31 14:08:37 -0700139
Chris Craik771816b2017-10-26 13:02:34 -0700140 assertEquals(0, result.leadingNulls)
Chris Craik5dc2fd42017-10-30 10:47:38 -0700141 assertEquals(ITEMS_BY_NAME_ID.subList(45, 55), result.page)
Chris Craik771816b2017-10-26 13:02:34 -0700142 assertEquals(0, result.trailingNulls)
Chris Craikfd4fa4a2017-08-31 14:08:37 -0700143 }
144
145 @Test
146 fun loadInitial_nullKey_uncounted() {
Chris Craik2e9d5132017-08-24 18:10:23 -0700147 val dataSource = ItemDataSource(counted = false)
148
Chris Craik694588d2017-11-28 16:19:46 -0800149 // dispatchLoadInitial(null, count) == dispatchLoadInitial(count)
Chris Craik771816b2017-10-26 13:02:34 -0700150 val result = loadInitial(dataSource, null, 10, true)
Chris Craik2e9d5132017-08-24 18:10:23 -0700151
Chris Craik771816b2017-10-26 13:02:34 -0700152 assertEquals(0, result.leadingNulls)
Chris Craik5dc2fd42017-10-30 10:47:38 -0700153 assertEquals(ITEMS_BY_NAME_ID.subList(0, 10), result.page)
Chris Craik771816b2017-10-26 13:02:34 -0700154 assertEquals(0, result.trailingNulls)
Chris Craik2e9d5132017-08-24 18:10:23 -0700155 }
156
Chris Craik2e9d5132017-08-24 18:10:23 -0700157 // ----- EMPTY -----
158
159 @Test
Chris Craikfd4fa4a2017-08-31 14:08:37 -0700160 fun loadInitial_empty() {
161 val dataSource = ItemDataSource(items = ArrayList())
162
Chris Craik694588d2017-11-28 16:19:46 -0800163 // dispatchLoadInitial(key, count) == null padding, loadAfter(key, count), null padding
Chris Craikfd4fa4a2017-08-31 14:08:37 -0700164 val key = dataSource.getKey(ITEMS_BY_NAME_ID[49])
Chris Craik771816b2017-10-26 13:02:34 -0700165 val result = loadInitial(dataSource, key, 10, true)
Chris Craik2e9d5132017-08-24 18:10:23 -0700166
Chris Craik771816b2017-10-26 13:02:34 -0700167 assertEquals(0, result.leadingNulls)
Chris Craik5dc2fd42017-10-30 10:47:38 -0700168 assertTrue(result.page.isEmpty())
Chris Craik771816b2017-10-26 13:02:34 -0700169 assertEquals(0, result.trailingNulls)
Chris Craik2e9d5132017-08-24 18:10:23 -0700170 }
171
172 @Test
Chris Craikfd4fa4a2017-08-31 14:08:37 -0700173 fun loadInitial_nullKey_empty() {
174 val dataSource = ItemDataSource(items = ArrayList())
Chris Craik771816b2017-10-26 13:02:34 -0700175 val result = loadInitial(dataSource, null, 10, true)
Chris Craik2e9d5132017-08-24 18:10:23 -0700176
Chris Craik771816b2017-10-26 13:02:34 -0700177 assertEquals(0, result.leadingNulls)
Chris Craik5dc2fd42017-10-30 10:47:38 -0700178 assertTrue(result.page.isEmpty())
Chris Craik771816b2017-10-26 13:02:34 -0700179 assertEquals(0, result.trailingNulls)
Chris Craik2e9d5132017-08-24 18:10:23 -0700180 }
181
Chris Craike1178ed2017-09-29 17:50:00 -0700182 // ----- Other behavior -----
183
184 @Test
185 fun loadBefore() {
186 val dataSource = ItemDataSource()
187 @Suppress("UNCHECKED_CAST")
Chris Craikb632de52017-11-30 16:09:04 -0800188 val callback = mock(ItemKeyedDataSource.LoadCallback::class.java)
189 as ItemKeyedDataSource.LoadCallback<Item>
Chris Craike1178ed2017-09-29 17:50:00 -0700190
Chris Craik694588d2017-11-28 16:19:46 -0800191 dataSource.loadBefore(
Chris Craikb632de52017-11-30 16:09:04 -0800192 ItemKeyedDataSource.LoadParams(dataSource.getKey(ITEMS_BY_NAME_ID[5]), 5), callback)
Chris Craike1178ed2017-09-29 17:50:00 -0700193
194 @Suppress("UNCHECKED_CAST")
Chris Craik5dc2fd42017-10-30 10:47:38 -0700195 val argument = ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<Item>>
196 verify(callback).onResult(argument.capture())
197 verifyNoMoreInteractions(callback)
Chris Craike1178ed2017-09-29 17:50:00 -0700198
199 val observed = argument.value
200
Chris Craik5dc2fd42017-10-30 10:47:38 -0700201 assertEquals(ITEMS_BY_NAME_ID.subList(0, 5), observed)
Chris Craike1178ed2017-09-29 17:50:00 -0700202 }
203
Chris Craik2e9d5132017-08-24 18:10:23 -0700204 internal data class Key(val name: String, val id: Int)
205
206 internal data class Item(
207 val name: String, val id: Int, val balance: Double, val address: String)
208
Chris Craik5dc2fd42017-10-30 10:47:38 -0700209 internal class ItemDataSource(private val counted: Boolean = true,
210 private val items: List<Item> = ITEMS_BY_NAME_ID)
Chris Craikb632de52017-11-30 16:09:04 -0800211 : ItemKeyedDataSource<Key, Item>() {
Chris Craik5dc2fd42017-10-30 10:47:38 -0700212
Chris Craik694588d2017-11-28 16:19:46 -0800213 override fun loadInitial(
214 params: LoadInitialParams<Key>,
215 callback: LoadInitialCallback<Item>) {
216 val key = params.requestedInitialKey ?: Key("", Integer.MAX_VALUE)
217 val start = Math.max(0, findFirstIndexAfter(key) - params.requestedLoadSize / 2)
218 val endExclusive = Math.min(start + params.requestedLoadSize, items.size)
219
220 if (params.placeholdersEnabled && counted) {
Chris Craik5dc2fd42017-10-30 10:47:38 -0700221 callback.onResult(items.subList(start, endExclusive), start, items.size)
222 } else {
223 callback.onResult(items.subList(start, endExclusive))
224 }
225 }
226
Chris Craik694588d2017-11-28 16:19:46 -0800227 override fun loadAfter(params: LoadParams<Key>, callback: LoadCallback<Item>) {
228 val start = findFirstIndexAfter(params.key)
229 val endExclusive = Math.min(start + params.requestedLoadSize, items.size)
Chris Craik5dc2fd42017-10-30 10:47:38 -0700230
231 callback.onResult(items.subList(start, endExclusive))
232 }
233
Chris Craik694588d2017-11-28 16:19:46 -0800234 override fun loadBefore(params: LoadParams<Key>, callback: LoadCallback<Item>) {
235 val firstIndexBefore = findFirstIndexBefore(params.key)
Chris Craik5dc2fd42017-10-30 10:47:38 -0700236 val endExclusive = Math.max(0, firstIndexBefore + 1)
Chris Craik694588d2017-11-28 16:19:46 -0800237 val start = Math.max(0, firstIndexBefore - params.requestedLoadSize + 1)
Chris Craik5dc2fd42017-10-30 10:47:38 -0700238
239 callback.onResult(items.subList(start, endExclusive))
240 }
Chris Craikfd4fa4a2017-08-31 14:08:37 -0700241
Chris Craik2e9d5132017-08-24 18:10:23 -0700242 override fun getKey(item: Item): Key {
243 return Key(item.name, item.id)
244 }
245
Chris Craik5dc2fd42017-10-30 10:47:38 -0700246 private fun findFirstIndexAfter(key: Key): Int {
Chris Craikfd4fa4a2017-08-31 14:08:37 -0700247 return items.indices.firstOrNull {
248 KEY_COMPARATOR.compare(key, getKey(items[it])) < 0
249 } ?: items.size
Chris Craik2e9d5132017-08-24 18:10:23 -0700250 }
251
Chris Craik5dc2fd42017-10-30 10:47:38 -0700252 private fun findFirstIndexBefore(key: Key): Int {
Chris Craikfd4fa4a2017-08-31 14:08:37 -0700253 return items.indices.reversed().firstOrNull {
254 KEY_COMPARATOR.compare(key, getKey(items[it])) > 0
Chris Craik2e9d5132017-08-24 18:10:23 -0700255 } ?: -1
256 }
Chris Craik2e9d5132017-08-24 18:10:23 -0700257 }
258
Chris Craikb632de52017-11-30 16:09:04 -0800259 private fun performLoadInitial(
Chris Craik7f66b3c2017-12-08 11:13:26 -0800260 invalidateDataSource: Boolean = false,
Chris Craikb632de52017-11-30 16:09:04 -0800261 callbackInvoker: (callback: ItemKeyedDataSource.LoadInitialCallback<String>) -> Unit) {
262 val dataSource = object : ItemKeyedDataSource<String, String>() {
Chris Craikf174a1c2017-11-28 11:11:32 -0800263 override fun getKey(item: String): String {
264 return ""
265 }
266
267 override fun loadInitial(
Chris Craik694588d2017-11-28 16:19:46 -0800268 params: LoadInitialParams<String>,
269 callback: LoadInitialCallback<String>) {
Chris Craik7f66b3c2017-12-08 11:13:26 -0800270 if (invalidateDataSource) {
271 // invalidate data source so it's invalid when onResult() called
272 invalidate()
273 }
Chris Craikf174a1c2017-11-28 11:11:32 -0800274 callbackInvoker(callback)
275 }
276
Chris Craik694588d2017-11-28 16:19:46 -0800277 override fun loadAfter(params: LoadParams<String>, callback: LoadCallback<String>) {
278 fail("loadAfter not expected")
Chris Craikf174a1c2017-11-28 11:11:32 -0800279 }
280
Chris Craik694588d2017-11-28 16:19:46 -0800281 override fun loadBefore(params: LoadParams<String>, callback: LoadCallback<String>) {
282 fail("loadBefore not expected")
Chris Craikf174a1c2017-11-28 11:11:32 -0800283 }
284 }
285
286 ContiguousPagedList<String, String>(
287 dataSource, FailExecutor(), FailExecutor(), null,
288 PagedList.Config.Builder()
289 .setPageSize(10)
290 .build(),
Chris Craike4cede12018-02-23 10:06:39 -0800291 "",
292 ContiguousPagedList.LAST_LOAD_UNSPECIFIED)
Chris Craikf174a1c2017-11-28 11:11:32 -0800293 }
294
295 @Test
Chris Craikb632de52017-11-30 16:09:04 -0800296 fun loadInitialCallbackSuccess() = performLoadInitial {
Chris Craik694588d2017-11-28 16:19:46 -0800297 // LoadInitialCallback correct usage
Chris Craikf174a1c2017-11-28 11:11:32 -0800298 it.onResult(listOf("a", "b"), 0, 2)
299 }
300
301 @Test
Chris Craikb632de52017-11-30 16:09:04 -0800302 fun loadInitialCallbackNotPageSizeMultiple() = performLoadInitial {
Chris Craik694588d2017-11-28 16:19:46 -0800303 // Keyed LoadInitialCallback *can* accept result that's not a multiple of page size
Chris Craikf174a1c2017-11-28 11:11:32 -0800304 val elevenLetterList = List(11) { "" + 'a' + it }
305 it.onResult(elevenLetterList, 0, 12)
306 }
307
308 @Test(expected = IllegalArgumentException::class)
Chris Craikb632de52017-11-30 16:09:04 -0800309 fun loadInitialCallbackListTooBig() = performLoadInitial {
Chris Craik694588d2017-11-28 16:19:46 -0800310 // LoadInitialCallback can't accept pos + list > totalCount
Chris Craikf174a1c2017-11-28 11:11:32 -0800311 it.onResult(listOf("a", "b", "c"), 0, 2)
312 }
313
314 @Test(expected = IllegalArgumentException::class)
Chris Craikb632de52017-11-30 16:09:04 -0800315 fun loadInitialCallbackPositionTooLarge() = performLoadInitial {
Chris Craik694588d2017-11-28 16:19:46 -0800316 // LoadInitialCallback can't accept pos + list > totalCount
Chris Craikf174a1c2017-11-28 11:11:32 -0800317 it.onResult(listOf("a", "b"), 1, 2)
318 }
319
320 @Test(expected = IllegalArgumentException::class)
Chris Craikb632de52017-11-30 16:09:04 -0800321 fun loadInitialCallbackPositionNegative() = performLoadInitial {
Chris Craik694588d2017-11-28 16:19:46 -0800322 // LoadInitialCallback can't accept negative position
Chris Craikf174a1c2017-11-28 11:11:32 -0800323 it.onResult(listOf("a", "b", "c"), -1, 2)
324 }
325
326 @Test(expected = IllegalArgumentException::class)
Chris Craikb632de52017-11-30 16:09:04 -0800327 fun loadInitialCallbackEmptyCannotHavePlaceholders() = performLoadInitial {
Chris Craik694588d2017-11-28 16:19:46 -0800328 // LoadInitialCallback can't accept empty result unless data set is empty
Chris Craikf174a1c2017-11-28 11:11:32 -0800329 it.onResult(emptyList(), 0, 2)
330 }
331
Chris Craik7f66b3c2017-12-08 11:13:26 -0800332 @Test
333 fun initialLoadCallbackInvalidThreeArg() = performLoadInitial(invalidateDataSource = true) {
334 // LoadInitialCallback doesn't throw on invalid args if DataSource is invalid
335 it.onResult(emptyList(), 0, 1)
336 }
337
Chris Craik792ed302018-03-06 16:28:38 -0800338 private abstract class WrapperDataSource<K, A, B>(private val source: ItemKeyedDataSource<K, A>)
339 : ItemKeyedDataSource<K, B>() {
340 private val invalidatedCallback = DataSource.InvalidatedCallback {
341 invalidate()
342 removeCallback()
343 }
344
345 init {
346 source.addInvalidatedCallback(invalidatedCallback)
347 }
348
349 private fun removeCallback() {
350 removeInvalidatedCallback(invalidatedCallback)
351 }
352
353 override fun loadInitial(params: LoadInitialParams<K>, callback: LoadInitialCallback<B>) {
354 source.loadInitial(params, object : LoadInitialCallback<A>() {
355 override fun onResult(data: List<A>, position: Int, totalCount: Int) {
356 callback.onResult(convert(data), position, totalCount)
357 }
358
359 override fun onResult(data: MutableList<A>) {
360 callback.onResult(convert(data))
361 }
362 })
363 }
364
365 override fun loadAfter(params: LoadParams<K>, callback: LoadCallback<B>) {
366 source.loadAfter(params, object : LoadCallback<A>() {
367 override fun onResult(data: MutableList<A>) {
368 callback.onResult(convert(data))
369 }
370 })
371 }
372
373 override fun loadBefore(params: LoadParams<K>, callback: LoadCallback<B>) {
374 source.loadBefore(params, object : LoadCallback<A>() {
375 override fun onResult(data: MutableList<A>) {
376 callback.onResult(convert(data))
377 }
378 })
379 }
380
381 protected abstract fun convert(source: List<A>): List<B>
382 }
383
384 private data class DecoratedItem(val item: Item)
385
386 private class DecoratedWrapperDataSource(private val source: ItemKeyedDataSource<Key, Item>)
387 : WrapperDataSource<Key, Item, DecoratedItem>(source) {
388 override fun convert(source: List<Item>): List<DecoratedItem> {
389 return source.map { DecoratedItem(it) }
390 }
391
392 override fun getKey(item: DecoratedItem): Key {
393 return source.getKey(item.item)
394 }
395 }
396
397 @Test
398 fun simpleWrappedDataSource() {
399 // verify that it's possible to wrap an ItemKeyedDataSource, and add info to its data
400
401 val orig = ItemDataSource(items = ITEMS_BY_NAME_ID)
402 val wrapper = DecoratedWrapperDataSource(orig)
403
404 // load initial
405 @Suppress("UNCHECKED_CAST")
406 val loadInitialCallback = mock(ItemKeyedDataSource.LoadInitialCallback::class.java)
407 as ItemKeyedDataSource.LoadInitialCallback<DecoratedItem>
408 val initKey = orig.getKey(ITEMS_BY_NAME_ID.first())
409 wrapper.loadInitial(ItemKeyedDataSource.LoadInitialParams(initKey, 10, false),
410 loadInitialCallback)
411 verify(loadInitialCallback).onResult(
412 ITEMS_BY_NAME_ID.subList(0, 10).map { DecoratedItem(it) })
413 verifyNoMoreInteractions(loadInitialCallback)
414
415 @Suppress("UNCHECKED_CAST")
416 val loadCallback = mock(ItemKeyedDataSource.LoadCallback::class.java)
417 as ItemKeyedDataSource.LoadCallback<DecoratedItem>
418 val key = orig.getKey(ITEMS_BY_NAME_ID[20])
419 // load after
420 wrapper.loadAfter(ItemKeyedDataSource.LoadParams(key, 10), loadCallback)
421 verify(loadCallback).onResult(ITEMS_BY_NAME_ID.subList(21, 31).map { DecoratedItem(it) })
422 verifyNoMoreInteractions(loadCallback)
423
424 // load before
425 wrapper.loadBefore(ItemKeyedDataSource.LoadParams(key, 10), loadCallback)
426 verify(loadCallback).onResult(ITEMS_BY_NAME_ID.subList(10, 20).map { DecoratedItem(it) })
427 verifyNoMoreInteractions(loadCallback)
428 }
429
Chris Craik2e9d5132017-08-24 18:10:23 -0700430 companion object {
Aurimas Liutikas6f1f5562017-11-17 10:05:57 -0800431 private val ITEM_COMPARATOR = compareBy<Item>({ it.name }).thenByDescending({ it.id })
432 private val KEY_COMPARATOR = compareBy<Key>({ it.name }).thenByDescending({ it.id })
Chris Craik2e9d5132017-08-24 18:10:23 -0700433
Chris Craikac809222017-10-17 15:40:21 -0700434 private val ITEMS_BY_NAME_ID = List(100) {
435 val names = Array(10) { "f" + ('a' + it) }
Chris Craik2e9d5132017-08-24 18:10:23 -0700436 Item(names[it % 10],
Aurimas Liutikas6f1f5562017-11-17 10:05:57 -0800437 it,
438 Math.random() * 1000,
439 (Math.random() * 200).toInt().toString() + " fake st.")
Chris Craikac809222017-10-17 15:40:21 -0700440 }.sortedWith(ITEM_COMPARATOR)
Chris Craik2e9d5132017-08-24 18:10:23 -0700441 }
442}