1 /*
2  * Copyright 2020 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 
17 package androidx.paging
18 
19 import kotlin.coroutines.CoroutineContext
20 import kotlin.coroutines.EmptyCoroutineContext
21 import kotlinx.coroutines.delay
22 import kotlinx.coroutines.withContext
23 
24 /**
25  * [PagingSource] for testing which pages through a list of conesecutive integers from 0..99 where
26  * position == key == value.
27  *
28  * Note: This class has a delay of 1000ms is built into its load method and is meant to be used with
29  * APIs from [kotlinx.coroutines.test.DelayController].
30  */
31 class TestPagingSource(
32     counted: Boolean = true,
33     override val jumpingSupported: Boolean = true,
34     val items: List<Int> = ITEMS,
35     private val loadDelay: Long = 1000,
36     private val loadContext: CoroutineContext = EmptyCoroutineContext,
37     private val placeholdersEnabled: Boolean = true,
38 ) : PagingSource<Int, Int>() {
39     var errorNextLoad = false
40     var nextLoadResult: LoadResult<Int, Int>? = null
41 
42     var getRefreshKeyResult: Int? = null
43     val getRefreshKeyCalls = mutableListOf<PagingState<Int, Int>>()
44     val loadedPages = mutableListOf<LoadResult.Page<Int, Int>>()
45 
46     init {
47         if (!counted) {
48             throw NotImplementedError(
49                 "TODO: Implement this for uncounted case, and add " +
50                     "appropriate test cases to PageFetcher, Pager, and PagerState."
51             )
52         }
53     }
54 
55     override val keyReuseSupported: Boolean
56         get() = true
57 
loadnull58     override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Int> {
59         // This delay allows tests running within DelayController APIs to control the order of
60         // execution of events.
61         delay(loadDelay)
62 
63         return withContext(loadContext) { getLoadResult(params) }
64     }
65 
getLoadResultnull66     private fun getLoadResult(params: LoadParams<Int>): LoadResult<Int, Int> {
67         val key = params.key ?: 0
68 
69         val isPrepend = params is LoadParams.Prepend
70         val start = (if (isPrepend) key - params.loadSize + 1 else key).coerceAtLeast(0)
71         val end = (if (isPrepend) key + 1 else key + params.loadSize).coerceAtMost(items.size)
72 
73         if (errorNextLoad) {
74             errorNextLoad = false
75             return LoadResult.Error(LOAD_ERROR)
76         }
77 
78         val nextLoadResult = nextLoadResult
79         if (nextLoadResult != null) {
80             this.nextLoadResult = null
81             return nextLoadResult
82         }
83 
84         return LoadResult.Page(
85                 items.subList(start, end),
86                 if (start > 0) start - 1 else null,
87                 if (end < items.size) end else null,
88                 if (placeholdersEnabled) start else Int.MIN_VALUE,
89                 if (placeholdersEnabled) (items.size - end) else Int.MIN_VALUE
90             )
91             .also { loadedPages.add(it) }
92     }
93 
getRefreshKeynull94     override fun getRefreshKey(state: PagingState<Int, Int>): Int? {
95         getRefreshKeyCalls.add(state)
96         return getRefreshKeyResult ?: state.anchorPosition
97     }
98 
99     companion object {
<lambda>null100         val ITEMS = List(100) { it }
101         val LOAD_ERROR = Exception("Exception from TestPagingSource.errorNextLoad")
102     }
103 }
104