1 /*
<lambda>null2  * Copyright 2019 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 androidx.paging.LoadType.PREPEND
20 import androidx.paging.LoadType.REFRESH
21 import androidx.paging.PageEvent.Drop
22 import kotlin.coroutines.EmptyCoroutineContext
23 import kotlin.test.Test
24 import kotlin.test.assertEquals
25 import kotlin.test.assertFailsWith
26 import kotlin.test.assertSame
27 import kotlinx.coroutines.ExperimentalCoroutinesApi
28 import kotlinx.coroutines.test.UnconfinedTestDispatcher
29 import kotlinx.coroutines.test.runTest
30 
31 internal fun <T : Any> adjacentInsertEvent(
32     isPrepend: Boolean,
33     page: List<T>,
34     originalPageOffset: Int,
35     placeholdersRemaining: Int
36 ) =
37     if (isPrepend) {
38         localPrepend(
39             pages = listOf(TransformablePage(originalPageOffset = originalPageOffset, data = page)),
40             placeholdersBefore = placeholdersRemaining,
41         )
42     } else {
43         localAppend(
44             pages = listOf(TransformablePage(originalPageOffset = originalPageOffset, data = page)),
45             placeholdersAfter = placeholdersRemaining,
46         )
47     }
48 
49 @OptIn(ExperimentalCoroutinesApi::class)
50 class PageEventTest {
51     @Test
placeholdersExceptionnull52     fun placeholdersException() {
53         assertFailsWith<IllegalArgumentException> {
54             localRefresh<Char>(
55                 pages = listOf(),
56                 placeholdersBefore = 1,
57                 placeholdersAfter = -1,
58             )
59         }
60         assertFailsWith<IllegalArgumentException> {
61             localRefresh<Char>(
62                 pages = listOf(),
63                 placeholdersBefore = -1,
64                 placeholdersAfter = 1,
65             )
66         }
67     }
68 
69     @Test
dropTypenull70     fun dropType() {
71         assertFailsWith<IllegalArgumentException> {
72             Drop<Char>(
73                 loadType = REFRESH,
74                 minPageOffset = 0,
75                 maxPageOffset = 0,
76                 placeholdersRemaining = 4
77             )
78         }
79     }
80 
81     @Test
dropRangenull82     fun dropRange() {
83         assertFailsWith<IllegalArgumentException> {
84             Drop<Char>(
85                 loadType = REFRESH,
86                 minPageOffset = 2,
87                 maxPageOffset = 0,
88                 placeholdersRemaining = 4
89             )
90         }
91     }
92 
93     @Test
dropPlaceholdersnull94     fun dropPlaceholders() {
95         assertFailsWith<IllegalArgumentException> {
96             Drop<Char>(
97                 loadType = REFRESH,
98                 minPageOffset = 0,
99                 maxPageOffset = 0,
100                 placeholdersRemaining = -1
101             )
102         }
103     }
104 
105     @Test
dropTransformnull106     fun dropTransform() =
107         runTest(UnconfinedTestDispatcher()) {
108             val drop =
109                 Drop<Char>(
110                     loadType = PREPEND,
111                     minPageOffset = 0,
112                     maxPageOffset = 0,
113                     placeholdersRemaining = 0
114                 )
115 
116             assertSame(drop, drop.map { it + 1 })
117             assertSame(drop, drop.flatMap { listOf(it, it) })
118             assertSame(drop, drop.filter { false })
119         }
120 
121     @Test
stateTransformnull122     fun stateTransform() =
123         runTest(UnconfinedTestDispatcher()) {
124             val state = localLoadStateUpdate<Char>(refreshLocal = LoadState.Loading)
125 
126             assertSame(state, state.map { it + 1 })
127             assertSame(state, state.flatMap { listOf(it, it) })
128             assertSame(state, state.filter { false })
129         }
130 
131     @Test
insertMapnull132     fun insertMap() =
133         runTest(UnconfinedTestDispatcher()) {
134             val insert =
135                 localAppend(
136                     pages = listOf(TransformablePage(listOf('a', 'b'))),
137                     placeholdersAfter = 4,
138                 )
139             assertEquals(
140                 localAppend(
141                     pages = listOf(TransformablePage(listOf("a", "b"))),
142                     placeholdersAfter = 4,
143                 ),
144                 insert.map { it.toString() }
145             )
146 
147             assertEquals(insert, insert.map { it.toString() }.map { it[0] })
148         }
149 
150     @Test
insertMapTransformednull151     fun insertMapTransformed() =
152         runTest(UnconfinedTestDispatcher()) {
153             assertEquals(
154                 localAppend(
155                     pages =
156                         listOf(
157                             TransformablePage(
158                                 originalPageOffsets = intArrayOf(0),
159                                 data = listOf("a", "b"),
160                                 hintOriginalPageOffset = 0,
161                                 hintOriginalIndices = listOf(0, 2)
162                             )
163                         ),
164                     placeholdersAfter = 4,
165                 ),
166                 localAppend(
167                         pages =
168                             listOf(
169                                 TransformablePage(
170                                     originalPageOffsets = intArrayOf(0),
171                                     data = listOf("a", "b"),
172                                     hintOriginalPageOffset = 0,
173                                     hintOriginalIndices = listOf(0, 2)
174                                 )
175                             ),
176                         placeholdersAfter = 4,
177                     )
178                     .map { it.toString() }
179             )
180         }
181 
182     @Test
insertFilternull183     fun insertFilter() =
184         runTest(UnconfinedTestDispatcher()) {
185             val insert =
186                 localAppend(
187                     pages = listOf(TransformablePage(listOf('a', 'b', 'c', 'd'))),
188                     placeholdersAfter = 4,
189                 )
190 
191             // filter out C
192             val insertNoC = insert.filter { it != 'c' }
193 
194             assertEquals(
195                 localAppend(
196                     pages =
197                         listOf(
198                             TransformablePage(
199                                 originalPageOffsets = intArrayOf(0),
200                                 data = listOf('a', 'b', 'd'),
201                                 hintOriginalPageOffset = 0,
202                                 hintOriginalIndices = listOf(0, 1, 3)
203                             )
204                         ),
205                     placeholdersAfter = 4,
206                 ),
207                 insertNoC
208             )
209 
210             // now filter out A, to validate filtration when lookup present
211             assertEquals(
212                 localAppend(
213                     pages =
214                         listOf(
215                             TransformablePage(
216                                 originalPageOffsets = intArrayOf(0),
217                                 data = listOf('b', 'd'),
218                                 hintOriginalPageOffset = 0,
219                                 hintOriginalIndices = listOf(1, 3)
220                             )
221                         ),
222                     placeholdersAfter = 4,
223                 ),
224                 insertNoC.filter { it != 'a' }
225             )
226         }
227 
228     @Test
insertFlatMapnull229     fun insertFlatMap() =
230         runTest(UnconfinedTestDispatcher()) {
231             val insert =
232                 localAppend(
233                     pages = listOf(TransformablePage(listOf('a', 'b'))),
234                     placeholdersAfter = 4,
235                 )
236 
237             val flatMapped = insert.flatMap { listOf("${it}1", "${it}2") }
238 
239             assertEquals(
240                 localAppend(
241                     pages =
242                         listOf(
243                             TransformablePage(
244                                 originalPageOffsets = intArrayOf(0),
245                                 data = listOf("a1", "a2", "b1", "b2"),
246                                 hintOriginalPageOffset = 0,
247                                 hintOriginalIndices = listOf(0, 0, 1, 1)
248                             )
249                         ),
250                     placeholdersAfter = 4,
251                 ),
252                 flatMapped
253             )
254 
255             val flatMappedAgain = flatMapped.flatMap { listOf(it, "-") }
256 
257             assertEquals(
258                 localAppend(
259                     pages =
260                         listOf(
261                             TransformablePage(
262                                 originalPageOffsets = intArrayOf(0),
263                                 data = listOf("a1", "-", "a2", "-", "b1", "-", "b2", "-"),
264                                 hintOriginalPageOffset = 0,
265                                 hintOriginalIndices = listOf(0, 0, 0, 0, 1, 1, 1, 1)
266                             )
267                         ),
268                     placeholdersAfter = 4,
269                 ),
270                 flatMappedAgain
271             )
272         }
273 
274     class StaticPagingData {
275         companion object {
initParametersnull276             fun initParameters() =
277                 listOf(
278                     listOf("a", "b", "c"),
279                     emptyList(),
280                 )
281         }
282 
283         private val presenter = TestPagingDataPresenter<String>(EmptyCoroutineContext)
284 
285         @Test fun map_nonEmpty() = map(PagingData.from(listOf("a", "b", "c")))
286 
287         @Test fun map_empty() = map(PagingData.empty())
288 
289         private fun map(pagingData: PagingData<String>) =
290             runTest(UnconfinedTestDispatcher()) {
291                 val transform = { it: String -> it + it }
292                 presenter.collectFrom(pagingData)
293                 val originalItems = presenter.snapshot().items
294                 val expectedItems = originalItems.map(transform)
295                 val transformedPagingData = pagingData.map { transform(it) }
296                 presenter.collectFrom(transformedPagingData)
297                 assertEquals(expectedItems, presenter.snapshot().items)
298             }
299 
flatMap_nonEmptynull300         @Test fun flatMap_nonEmpty() = flatMap(PagingData.from(listOf("a", "b", "c")))
301 
302         @Test fun flatMap_empty() = flatMap(PagingData.empty())
303 
304         private fun flatMap(pagingData: PagingData<String>) =
305             runTest(UnconfinedTestDispatcher()) {
306                 val transform = { it: String -> listOf(it, it) }
307                 presenter.collectFrom(pagingData)
308                 val originalItems = presenter.snapshot().items
309                 val expectedItems = originalItems.flatMap(transform)
310                 val transformedPagingData = pagingData.flatMap { transform(it) }
311                 presenter.collectFrom(transformedPagingData)
312                 assertEquals(expectedItems, presenter.snapshot().items)
313             }
314 
filter_nonEmptynull315         @Test fun filter_nonEmpty() = filter(PagingData.from(listOf("a", "b", "c")))
316 
317         @Test fun filter_empty() = filter(PagingData.empty())
318 
319         private fun filter(pagingData: PagingData<String>) =
320             runTest(UnconfinedTestDispatcher()) {
321                 val predicate = { it: String -> it != "b" }
322                 presenter.collectFrom(pagingData)
323                 val originalItems = presenter.snapshot().items
324                 val expectedItems = originalItems.filter(predicate)
325                 val transformedPagingData = pagingData.filter { predicate(it) }
326                 presenter.collectFrom(transformedPagingData)
327                 assertEquals(expectedItems, presenter.snapshot().items)
328             }
329 
330         @Test
insertSeparators_nonEmptynull331         fun insertSeparators_nonEmpty() = insertSeparators(PagingData.from(listOf("a", "b", "c")))
332 
333         @Test fun insertSeparators_empty() = insertSeparators(PagingData.empty())
334 
335         private fun insertSeparators(pagingData: PagingData<String>) =
336             runTest(UnconfinedTestDispatcher()) {
337                 val transform = { left: String?, right: String? ->
338                     if (left == null || right == null) null else "|"
339                 }
340                 presenter.collectFrom(pagingData)
341                 val originalItems = presenter.snapshot().items
342                 val expectedItems =
343                     originalItems.flatMapIndexed { index, s ->
344                         val result = mutableListOf<String>()
345                         if (index == 0) {
346                             transform(null, s)?.let(result::add)
347                         }
348                         result.add(s)
349                         transform(s, originalItems.getOrNull(index + 1))?.let(result::add)
350                         if (index == originalItems.lastIndex) {
351                             transform(s, null)?.let(result::add)
352                         }
353                         result
354                     }
355                 val transformedPagingData =
356                     pagingData.insertSeparators { left, right -> transform(left, right) }
357                 presenter.collectFrom(transformedPagingData)
358                 assertEquals(expectedItems, presenter.snapshot().items)
359             }
360     }
361 }
362