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.kruth.assertThat
20 import androidx.paging.LoadType.APPEND
21 import androidx.paging.LoadType.PREPEND
22 import androidx.paging.LoadType.REFRESH
23 import androidx.paging.PagingSource.LoadResult.Page.Companion.COUNT_UNDEFINED
24 import androidx.paging.RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
25 import androidx.paging.RemoteMediator.InitializeAction.SKIP_INITIAL_REFRESH
26 import androidx.paging.RemoteMediatorMock.LoadEvent
27 import androidx.paging.TestPagingSource.Companion.LOAD_ERROR
28 import androidx.paging.internal.AtomicBoolean
29 import kotlin.test.Test
30 import kotlin.test.assertEquals
31 import kotlin.test.fail
32 import kotlinx.coroutines.ExperimentalCoroutinesApi
33 import kotlinx.coroutines.delay
34 import kotlinx.coroutines.launch
35 import kotlinx.coroutines.test.StandardTestDispatcher
36 import kotlinx.coroutines.test.TestScope
37 import kotlinx.coroutines.test.UnconfinedTestDispatcher
38 import kotlinx.coroutines.test.advanceTimeBy
39 import kotlinx.coroutines.test.advanceUntilIdle
40 import kotlinx.coroutines.test.runCurrent
41 import kotlinx.coroutines.test.runTest
42 
43 @OptIn(ExperimentalCoroutinesApi::class, ExperimentalPagingApi::class)
44 class RemoteMediatorAccessorTest {
45     private val testScope = TestScope(UnconfinedTestDispatcher())
46     private var mockStateId = 0
47 
48     // creates a unique state using the anchor position to be able to do equals check in assertions
49     private fun createMockState(anchorPosition: Int? = mockStateId++): PagingState<Int, Int> {
50         return PagingState(
51             pages = listOf(),
52             anchorPosition = anchorPosition,
53             config = PagingConfig(10),
54             leadingPlaceholderCount = COUNT_UNDEFINED
55         )
56     }
57 
58     @Test
59     fun requestLoadIfRefreshAllowed_noop() =
60         testScope.runTest {
61             val remoteMediator =
62                 RemoteMediatorMock(loadDelay = 100).apply {
63                     initializeResult = SKIP_INITIAL_REFRESH
64                 }
65             val remoteMediatorAccessor = createAccessor(remoteMediator)
66             val pagingState = PagingState<Int, Int>(listOf(), null, PagingConfig(1), 0)
67 
68             remoteMediatorAccessor.requestRefreshIfAllowed(pagingState)
69             advanceUntilIdle()
70             assertThat(remoteMediator.newLoadEvents).isEmpty()
71         }
72 
73     @Test
74     fun requestLoadIfRefreshAllowed_simple() =
75         testScope.runTest {
76             val remoteMediator =
77                 RemoteMediatorMock(loadDelay = 100).apply {
78                     initializeResult = LAUNCH_INITIAL_REFRESH
79                 }
80             val remoteMediatorAccessor = createAccessor(remoteMediator)
81             val pagingState = PagingState<Int, Int>(listOf(), null, PagingConfig(1), 0)
82 
83             remoteMediatorAccessor.allowRefresh()
84             remoteMediatorAccessor.requestRefreshIfAllowed(pagingState)
85             advanceUntilIdle()
86             assertThat(remoteMediator.newLoadEvents)
87                 .containsExactly(LoadEvent(loadType = REFRESH, state = pagingState))
88 
89             // allowRefresh should only allow one successful request to go through.
90             remoteMediatorAccessor.requestRefreshIfAllowed(pagingState)
91             advanceUntilIdle()
92             assertThat(remoteMediator.newLoadEvents).isEmpty()
93         }
94 
95     @Test
96     fun requestLoadIfRefreshAllowed_retry() =
97         testScope.runTest {
98             val remoteMediator =
99                 RemoteMediatorMock(loadDelay = 100).apply {
100                     initializeResult = LAUNCH_INITIAL_REFRESH
101                 }
102             val remoteMediatorAccessor = createAccessor(remoteMediator)
103             val pagingState = PagingState<Int, Int>(listOf(), null, PagingConfig(1), 0)
104 
105             remoteMediator.loadCallback = { _, _ ->
106                 RemoteMediator.MediatorResult.Error(Exception())
107             }
108 
109             remoteMediatorAccessor.allowRefresh()
110             remoteMediatorAccessor.requestRefreshIfAllowed(pagingState)
111             advanceUntilIdle()
112             assertThat(remoteMediator.newLoadEvents)
113                 .containsExactly(LoadEvent(loadType = REFRESH, state = pagingState))
114 
115             remoteMediator.loadCallback = { _, _ ->
116                 RemoteMediator.MediatorResult.Success(endOfPaginationReached = false)
117             }
118 
119             remoteMediatorAccessor.retryFailed(pagingState)
120             advanceUntilIdle()
121             assertThat(remoteMediator.newLoadEvents)
122                 .containsExactly(LoadEvent(loadType = REFRESH, state = pagingState))
123         }
124 
125     @Test
126     fun requestLoad_queuesBoundaryBehindRefresh() =
127         testScope.runTest {
128             val remoteMediator = RemoteMediatorMock(loadDelay = 100)
129             val remoteMediatorAccessor = createAccessor(remoteMediator)
130             val firstState = createMockState()
131             val secondState = createMockState()
132 
133             remoteMediatorAccessor.requestLoad(REFRESH, firstState)
134             advanceTimeBy(50) // Start remote refresh, but do not let it finish.
135             assertThat(remoteMediator.newLoadEvents).containsExactly(LoadEvent(REFRESH, firstState))
136             assertThat(remoteMediatorAccessor.state.value)
137                 .isEqualTo(
138                     LoadStates(
139                         refresh = LoadState.Loading,
140                         prepend = LoadState.NotLoading.Incomplete,
141                         append = LoadState.NotLoading.Incomplete
142                     )
143                 )
144 
145             // Queue a boundary requests, but it should not launch since refresh is running.
146             remoteMediatorAccessor.requestLoad(PREPEND, firstState)
147             remoteMediatorAccessor.requestLoad(APPEND, firstState)
148             assertThat(remoteMediator.newLoadEvents).isEmpty()
149             assertThat(remoteMediatorAccessor.state.value)
150                 .isEqualTo(
151                     LoadStates(
152                         refresh = LoadState.Loading,
153                         prepend = LoadState.Loading,
154                         append = LoadState.Loading
155                     )
156                 )
157 
158             // Queue more boundary requests, but with an updated PagingState.
159             remoteMediatorAccessor.requestLoad(PREPEND, secondState)
160             remoteMediatorAccessor.requestLoad(APPEND, secondState)
161             assertThat(remoteMediator.newLoadEvents).isEmpty()
162             assertThat(remoteMediatorAccessor.state.value)
163                 .isEqualTo(
164                     LoadStates(
165                         refresh = LoadState.Loading,
166                         prepend = LoadState.Loading,
167                         append = LoadState.Loading
168                     )
169                 )
170 
171             // Now wait until all queued requests finish running
172             advanceUntilIdle()
173             assertThat(remoteMediator.newLoadEvents)
174                 .containsExactly(
175                     LoadEvent(PREPEND, secondState),
176                     LoadEvent(APPEND, secondState),
177                 )
178             assertThat(remoteMediatorAccessor.state.value)
179                 .isEqualTo(
180                     LoadStates(
181                         refresh = LoadState.NotLoading.Incomplete,
182                         prepend = LoadState.NotLoading.Incomplete,
183                         append = LoadState.NotLoading.Incomplete
184                     )
185                 )
186         }
187 
188     @Test
189     fun requestLoad_cancelledBoundaryRetriesAfterRefresh() =
190         testScope.runTest {
191             val remoteMediator =
192                 RemoteMediatorMock(loadDelay = 100).apply {
193                     initializeResult = SKIP_INITIAL_REFRESH
194                 }
195             val remoteMediatorAccessor = createAccessor(remoteMediator)
196             val firstState = createMockState()
197 
198             // Launch boundary calls, but do not let them finish.
199             remoteMediatorAccessor.requestLoad(PREPEND, firstState)
200             // Single runner should prevent append from triggering, but it should still be queued.
201             remoteMediatorAccessor.requestLoad(APPEND, firstState)
202             advanceTimeBy(50)
203             assertThat(remoteMediator.newLoadEvents)
204                 .containsExactly(
205                     LoadEvent(PREPEND, firstState),
206                 )
207 
208             // Launch refresh, which should cancel running boundary calls
209             remoteMediatorAccessor.requestLoad(REFRESH, firstState)
210             advanceTimeBy(50)
211             assertThat(remoteMediator.newLoadEvents).containsExactly(LoadEvent(REFRESH, firstState))
212 
213             // Let refresh finish, retrying cancelled boundary calls
214             advanceUntilIdle()
215             assertThat(remoteMediator.newLoadEvents)
216                 .containsExactly(
217                     LoadEvent(PREPEND, firstState),
218                     LoadEvent(APPEND, firstState),
219                 )
220         }
221 
222     @Test
223     fun requestLoad_queuesBoundaryAfterRefreshFails() =
224         testScope.runTest {
225             val firstState = createMockState()
226             val secondState = createMockState()
227             val remoteMediator =
228                 RemoteMediatorMock(loadDelay = 100).apply {
229                     initializeResult = RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
230                     loadCallback = { loadType, state ->
231                         // Only error out on first refresh.
232                         if (loadType == REFRESH && state == firstState) {
233                             RemoteMediator.MediatorResult.Error(throwable = LOAD_ERROR)
234                         } else {
235                             RemoteMediator.MediatorResult.Success(endOfPaginationReached = false)
236                         }
237                     }
238                 }
239             val remoteMediatorAccessor = createAccessor(remoteMediator)
240 
241             // Queue up some remote boundary calls, which will not run immediately because they
242             // depend on refresh.
243             remoteMediatorAccessor.requestLoad(PREPEND, firstState)
244             remoteMediatorAccessor.requestLoad(APPEND, firstState)
245 
246             // Trigger refresh, letting it fail.
247             remoteMediatorAccessor.requestLoad(REFRESH, firstState)
248             advanceUntilIdle()
249             // Boundary calls should be queued, but not started.
250             assertThat(remoteMediator.newLoadEvents)
251                 .containsExactly(
252                     LoadEvent(REFRESH, firstState),
253                 )
254             // Although boundary calls are queued, they should not trigger or update LoadState since
255             // they are waiting for refresh to succeed.
256             assertThat(remoteMediatorAccessor.state.value)
257                 .isEqualTo(
258                     LoadStates(
259                         refresh = LoadState.Error(LOAD_ERROR),
260                         prepend = LoadState.NotLoading.Incomplete,
261                         append = LoadState.NotLoading.Incomplete
262                     )
263                 )
264 
265             // Let refresh finish, triggering queued boundary calls.
266             remoteMediatorAccessor.retryFailed(secondState)
267             advanceUntilIdle()
268             assertThat(remoteMediator.newLoadEvents)
269                 .containsExactly(
270                     LoadEvent(REFRESH, secondState),
271                     LoadEvent(PREPEND, firstState),
272                     LoadEvent(APPEND, firstState),
273                 )
274             assertThat(remoteMediatorAccessor.state.value)
275                 .isEqualTo(
276                     LoadStates(
277                         refresh = LoadState.NotLoading.Incomplete,
278                         prepend = LoadState.NotLoading.Incomplete,
279                         append = LoadState.NotLoading.Incomplete
280                     )
281                 )
282         }
283 
284     @Test
285     fun requestLoad_refreshEndOfPaginationReachedClearsBoundaryCalls() =
286         testScope.runTest {
287             val remoteMediator =
288                 RemoteMediatorMock(loadDelay = 100).apply {
289                     initializeResult = RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
290                     loadCallback = { _, _ ->
291                         RemoteMediator.MediatorResult.Success(endOfPaginationReached = true)
292                     }
293                 }
294             val remoteMediatorAccessor = createAccessor(remoteMediator)
295             val firstState = createMockState()
296 
297             // Queue up some remote boundary calls, which will not run immediately because they
298             // depend on refresh.
299             remoteMediatorAccessor.requestLoad(PREPEND, firstState)
300             remoteMediatorAccessor.requestLoad(APPEND, firstState)
301 
302             // Trigger refresh and let it mark endOfPaginationReached
303             remoteMediatorAccessor.requestLoad(REFRESH, firstState)
304             advanceUntilIdle()
305 
306             // Ensure boundary calls are not triggered since they should be cleared by
307             // endOfPaginationReached from refresh.
308             assertThat(remoteMediator.newLoadEvents).containsExactly(LoadEvent(REFRESH, firstState))
309             // Although boundary calls are queued, they should not trigger or update LoadState since
310             // they are waiting for refresh.
311             assertThat(remoteMediatorAccessor.state.value)
312                 .isEqualTo(
313                     LoadStates(
314                         refresh = LoadState.NotLoading.Incomplete,
315                         prepend = LoadState.NotLoading.Complete,
316                         append = LoadState.NotLoading.Complete
317                     )
318                 )
319         }
320 
321     @Test
322     fun load_reportsPrependLoadState() =
323         testScope.runTest {
324             val emptyState =
325                 PagingState<Int, Int>(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
326             val remoteMediator = RemoteMediatorMock(loadDelay = 1000)
327             val remoteMediatorAccessor = createAccessor(remoteMediator)
328 
329             // Assert initial state is NotLoading.Incomplete.
330             assertEquals(
331                 LoadStates.IDLE.copy(prepend = LoadState.NotLoading.Incomplete),
332                 remoteMediatorAccessor.state.value,
333             )
334 
335             // Start a PREPEND load.
336             remoteMediatorAccessor.requestLoad(
337                 loadType = PREPEND,
338                 pagingState = emptyState,
339             )
340 
341             // Assert state is immediately set to Loading.
342             assertEquals(
343                 LoadStates.IDLE.copy(prepend = LoadState.Loading),
344                 remoteMediatorAccessor.state.value,
345             )
346 
347             // Wait for load to finish.
348             advanceUntilIdle()
349 
350             // Assert state is set to NotLoading.Incomplete.
351             assertEquals(
352                 LoadStates.IDLE.copy(prepend = LoadState.NotLoading.Incomplete),
353                 remoteMediatorAccessor.state.value,
354             )
355 
356             // Start a PREPEND load which results in endOfPaginationReached = true.
357             remoteMediator.loadCallback = { _, _ ->
358                 RemoteMediator.MediatorResult.Success(endOfPaginationReached = true)
359             }
360             remoteMediatorAccessor.requestLoad(
361                 loadType = PREPEND,
362                 pagingState = emptyState,
363             )
364 
365             // Wait for load to finish.
366             advanceUntilIdle()
367 
368             // Assert state is set to NotLoading.Incomplete.
369             assertEquals(
370                 LoadStates.IDLE.copy(prepend = LoadState.NotLoading.Complete),
371                 remoteMediatorAccessor.state.value,
372             )
373         }
374 
375     @Test
376     fun load_reportsAppendLoadState() =
377         testScope.runTest {
378             val emptyState =
379                 PagingState<Int, Int>(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
380             val remoteMediator = RemoteMediatorMock(loadDelay = 1000)
381             val remoteMediatorAccessor = createAccessor(remoteMediator)
382 
383             // Assert initial state is NotLoading.Incomplete.
384             assertEquals(
385                 LoadStates.IDLE.copy(prepend = LoadState.NotLoading.Incomplete),
386                 remoteMediatorAccessor.state.value,
387             )
388 
389             // Start a APPEND load.
390             remoteMediatorAccessor.requestLoad(
391                 loadType = APPEND,
392                 pagingState = emptyState,
393             )
394 
395             // Assert state is immediately set to Loading.
396             assertEquals(
397                 LoadStates.IDLE.copy(append = LoadState.Loading),
398                 remoteMediatorAccessor.state.value,
399             )
400 
401             // Wait for load to finish.
402             advanceUntilIdle()
403 
404             // Assert state is set to NotLoading.Incomplete.
405             assertEquals(
406                 LoadStates.IDLE.copy(append = LoadState.NotLoading.Incomplete),
407                 remoteMediatorAccessor.state.value,
408             )
409 
410             // Start a APPEND load which results in endOfPaginationReached = true.
411             remoteMediator.loadCallback = { _, _ ->
412                 RemoteMediator.MediatorResult.Success(endOfPaginationReached = true)
413             }
414             remoteMediatorAccessor.requestLoad(
415                 loadType = APPEND,
416                 pagingState = emptyState,
417             )
418 
419             // Wait for load to finish.
420             advanceUntilIdle()
421 
422             // Assert state is set to NotLoading.Incomplete.
423             assertEquals(
424                 LoadStates.IDLE.copy(append = LoadState.NotLoading.Complete),
425                 remoteMediatorAccessor.state.value,
426             )
427         }
428 
429     @Test
430     fun load_conflatesPrepend() =
431         testScope.runTest {
432             val remoteMediator = RemoteMediatorMock(loadDelay = 1000)
433             val remoteMediatorAccessor = createAccessor(remoteMediator)
434 
435             remoteMediatorAccessor.requestLoad(
436                 loadType = PREPEND,
437                 pagingState = PagingState(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
438             )
439 
440             remoteMediatorAccessor.requestLoad(
441                 loadType = PREPEND,
442                 pagingState = PagingState(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
443             )
444 
445             // Assert that exactly one load request was started.
446             assertEquals(1, remoteMediator.newLoadEvents.size)
447 
448             // Fast-forward time until both load requests jobs complete.
449             advanceUntilIdle()
450 
451             // Assert that the second load request was skipped since it was launched while the first
452             // load request was still running.
453             assertEquals(0, remoteMediator.newLoadEvents.size)
454         }
455 
456     @Test
457     fun load_conflatesAppend() =
458         testScope.runTest {
459             val remoteMediator = RemoteMediatorMock(loadDelay = 1000)
460             val remoteMediatorAccessor = createAccessor(remoteMediator)
461 
462             remoteMediatorAccessor.requestLoad(
463                 loadType = APPEND,
464                 pagingState = PagingState(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
465             )
466 
467             remoteMediatorAccessor.requestLoad(
468                 loadType = APPEND,
469                 pagingState = PagingState(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
470             )
471 
472             // Assert that exactly one load request was started.
473             assertEquals(1, remoteMediator.newLoadEvents.size)
474 
475             // Fast-forward time until both load requests jobs complete.
476             advanceUntilIdle()
477 
478             // Assert that the second load request was skipped since it was launched while the first
479             // load request was still running.
480             assertEquals(0, remoteMediator.newLoadEvents.size)
481         }
482 
483     @Test
484     fun load_conflatesRefresh() =
485         testScope.runTest {
486             val remoteMediator = RemoteMediatorMock(loadDelay = 1000)
487             val remoteMediatorAccessor = createAccessor(remoteMediator)
488 
489             remoteMediatorAccessor.requestLoad(
490                 loadType = REFRESH,
491                 pagingState = PagingState(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
492             )
493 
494             remoteMediatorAccessor.requestLoad(
495                 loadType = REFRESH,
496                 pagingState = PagingState(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
497             )
498 
499             // Assert that exactly one load request was started.
500             assertEquals(1, remoteMediator.newLoadEvents.size)
501 
502             // Fast-forward time until both load requests jobs complete.
503             advanceUntilIdle()
504 
505             // Assert that the second load request was skipped since it was launched while the first
506             // load request was still running.
507             assertEquals(0, remoteMediator.newLoadEvents.size)
508         }
509 
510     @Test
511     fun load_concurrentInitializeJobCancelsBoundaryJobs() =
512         testScope.runTest {
513             val emptyState =
514                 PagingState<Int, Int>(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
515             val remoteMediator =
516                 object : RemoteMediatorMock(loadDelay = 1000) {
517                     var loading = AtomicBoolean(false)
518 
519                     override suspend fun load(
520                         loadType: LoadType,
521                         state: PagingState<Int, Int>
522                     ): MediatorResult {
523                         if (!loading.compareAndSet(false, true)) fail("Concurrent load")
524 
525                         return try {
526                             super.load(loadType, state)
527                         } finally {
528                             loading.set(false)
529                         }
530                     }
531                 }
532             val remoteMediatorAccessor = createAccessor(remoteMediator)
533 
534             remoteMediatorAccessor.requestLoad(loadType = PREPEND, pagingState = emptyState)
535 
536             remoteMediatorAccessor.requestLoad(loadType = APPEND, pagingState = emptyState)
537 
538             // Start prependJob and appendJob, but do not let them finish.
539             advanceTimeBy(500)
540 
541             // Assert that only the PREPEND RemoteMediator.load() call was made.
542             assertEquals(listOf(LoadEvent(PREPEND, emptyState)), remoteMediator.newLoadEvents)
543 
544             // Start refreshJob
545             remoteMediatorAccessor.requestLoad(loadType = REFRESH, pagingState = emptyState)
546 
547             // Give prependJob enough time to be cancelled and refresh started due to higher
548             // priority
549             advanceTimeBy(500)
550 
551             assertEquals(listOf(LoadEvent(REFRESH, emptyState)), remoteMediator.newLoadEvents)
552             // assert that all of them are in loading state as we don't know if refresh will succeed
553             // if refresh fails, we would retry append / prepend
554             assertEquals(
555                 LoadStates(
556                     refresh = LoadState.Loading,
557                     append = LoadState.Loading,
558                     prepend = LoadState.Loading
559                 ),
560                 remoteMediatorAccessor.state.value
561             )
562 
563             // Wait for all outstanding / queued jobs to finish.
564             advanceUntilIdle()
565 
566             // Assert all outstanding / queued jobs finished.
567             assertEquals(
568                 LoadStates(
569                     refresh = LoadState.NotLoading.Incomplete,
570                     append = LoadState.NotLoading.Incomplete,
571                     prepend = LoadState.NotLoading.Incomplete
572                 ),
573                 remoteMediatorAccessor.state.value
574             )
575 
576             // Queued boundary requests should be triggered, even though they are out-of-date.
577             assertThat(remoteMediator.newLoadEvents)
578                 .containsExactly(
579                     LoadEvent(PREPEND, emptyState),
580                     LoadEvent(APPEND, emptyState),
581                 )
582         }
583 
584     @Test
585     fun load_concurrentBoundaryJobsRunsSerially() =
586         testScope.runTest {
587             val emptyState =
588                 PagingState<Int, Int>(listOf(), null, PagingConfig(10), COUNT_UNDEFINED)
589             val remoteMediator =
590                 object : RemoteMediatorMock(loadDelay = 1000) {
591                     var loading = AtomicBoolean(false)
592 
593                     override suspend fun load(
594                         loadType: LoadType,
595                         state: PagingState<Int, Int>
596                     ): MediatorResult {
597                         if (!loading.compareAndSet(false, true)) fail("Concurrent load")
598 
599                         return try {
600                             super.load(loadType, state)
601                         } finally {
602                             loading.set(false)
603                         }
604                     }
605                 }
606 
607             val remoteMediatorAccessor = createAccessor(remoteMediator)
608 
609             remoteMediatorAccessor.requestLoad(loadType = PREPEND, pagingState = emptyState)
610 
611             remoteMediatorAccessor.requestLoad(loadType = APPEND, pagingState = emptyState)
612 
613             // Assert that only one job runs due to second job joining the first before starting.
614             assertEquals(1, remoteMediator.newLoadEvents.size)
615 
616             // Advance some time, but not enough to finish first load.
617             advanceTimeBy(500)
618             assertEquals(0, remoteMediator.newLoadEvents.size)
619 
620             // Assert that second job starts after first finishes.
621             advanceTimeBy(500)
622             runCurrent()
623             assertEquals(1, remoteMediator.newLoadEvents.size)
624 
625             // Allow second job to finish.
626             advanceTimeBy(1000)
627         }
628 
629     @Test
630     fun ignoreAppendPrependWhenRefreshIsRequired() {
631         val remoteMediatorMock =
632             RemoteMediatorMock().apply {
633                 initializeResult = RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
634             }
635         val accessor = testScope.createAccessor(remoteMediatorMock)
636         accessor.requestLoad(APPEND, createMockState())
637         accessor.requestLoad(PREPEND, createMockState())
638         testScope.advanceUntilIdle()
639         assertThat(remoteMediatorMock.loadEvents).isEmpty()
640     }
641 
642     @Test
643     fun allowAppendPrependWhenRefreshIsNotRequired() {
644         val remoteMediatorMock =
645             RemoteMediatorMock().apply { initializeResult = SKIP_INITIAL_REFRESH }
646         val accessor = testScope.createAccessor(remoteMediatorMock)
647 
648         val appendState = createMockState(1)
649         val prependState = createMockState(2)
650         accessor.requestLoad(APPEND, appendState)
651         accessor.requestLoad(PREPEND, prependState)
652         testScope.advanceUntilIdle()
653         assertThat(remoteMediatorMock.loadEvents)
654             .containsExactly(LoadEvent(APPEND, appendState), LoadEvent(PREPEND, prependState))
655     }
656 
657     @Test
658     fun ignoreAppendPrependBeforeRefresh() {
659         val remoteMediatorMock =
660             RemoteMediatorMock(loadDelay = 100).apply {
661                 initializeResult = RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
662             }
663         val testScope = TestScope(StandardTestDispatcher())
664         val accessor = testScope.createAccessor(remoteMediatorMock)
665 
666         val refreshState = createMockState()
667         accessor.requestLoad(REFRESH, refreshState)
668         // no requests yet since scope is not triggered
669         assertThat(remoteMediatorMock.loadEvents).isEmpty()
670 
671         // advance enough to trigger the request, not enough to complete
672         testScope.advanceTimeBy(50)
673         // these should be ignored
674         accessor.requestLoad(REFRESH, createMockState())
675 
676         testScope.advanceTimeBy(1)
677         val appendState = createMockState()
678         accessor.requestLoad(APPEND, appendState)
679 
680         val prependState = createMockState()
681         testScope.advanceTimeBy(1)
682         accessor.requestLoad(PREPEND, prependState)
683 
684         assertThat(remoteMediatorMock.newLoadEvents)
685             .containsExactly(LoadEvent(REFRESH, refreshState))
686 
687         // now advance enough that we can accept append prepend
688         testScope.advanceUntilIdle()
689         // queued append/prepend should be executed afterwards.
690         assertThat(remoteMediatorMock.newLoadEvents)
691             .containsExactly(
692                 LoadEvent(APPEND, appendState),
693                 LoadEvent(PREPEND, prependState),
694             )
695 
696         val otherPrependState = createMockState()
697         val otherAppendState = createMockState()
698         accessor.requestLoad(PREPEND, otherPrependState)
699         accessor.requestLoad(APPEND, otherAppendState)
700 
701         testScope.advanceTimeBy(50)
702         assertThat(remoteMediatorMock.newLoadEvents)
703             .containsExactly(LoadEvent(PREPEND, otherPrependState))
704         // while prepend running, any more requests should be ignored
705         accessor.requestLoad(PREPEND, createMockState())
706         testScope.advanceTimeBy(10)
707         assertThat(remoteMediatorMock.newLoadEvents).isEmpty()
708 
709         testScope.advanceTimeBy(41)
710         assertThat(remoteMediatorMock.newLoadEvents)
711             .containsExactly(LoadEvent(APPEND, otherAppendState))
712         accessor.requestLoad(APPEND, createMockState())
713         // while append running, any more requests should be ignored
714         accessor.requestLoad(APPEND, createMockState())
715         testScope.advanceUntilIdle()
716         assertThat(remoteMediatorMock.newLoadEvents).isEmpty()
717 
718         // now the work is done, we can add more
719         val newAppendState = createMockState()
720         accessor.requestLoad(APPEND, newAppendState)
721         testScope.advanceUntilIdle()
722         assertThat(remoteMediatorMock.newLoadEvents)
723             .containsExactly(LoadEvent(APPEND, newAppendState))
724     }
725 
726     @Test
727     fun dropAppendPrependIfRefreshIsTriggered() {
728         val remoteMediatorMock =
729             RemoteMediatorMock(loadDelay = 100).apply { initializeResult = SKIP_INITIAL_REFRESH }
730         val accessor = testScope.createAccessor(remoteMediatorMock)
731         val initialAppend = createMockState()
732         accessor.requestLoad(APPEND, initialAppend)
733         testScope.advanceTimeBy(50)
734         assertThat(remoteMediatorMock.newLoadEvents)
735             .containsExactly(LoadEvent(APPEND, initialAppend))
736         // now before that append finishes, trigger a refresh
737         val newRefresh = createMockState()
738         accessor.requestLoad(REFRESH, newRefresh)
739         testScope.advanceTimeBy(10)
740         // check that we immediately get the new refresh because we'll cancel the append
741         assertThat(remoteMediatorMock.newLoadEvents).containsExactly(LoadEvent(REFRESH, newRefresh))
742         assertThat(remoteMediatorMock.incompleteEvents)
743             .containsExactly(LoadEvent(APPEND, initialAppend))
744     }
745 
746     @Test
747     fun loadEvents() {
748         val remoteMediatorMock =
749             RemoteMediatorMock(loadDelay = 100).apply { initializeResult = SKIP_INITIAL_REFRESH }
750         val accessor = testScope.createAccessor(remoteMediatorMock)
751 
752         // Initial state
753         assertThat(accessor.state.value).isEqualTo(LoadStates.IDLE)
754 
755         // Append request should go through since it doesn't require refresh
756         val firstAppendState = createMockState()
757         accessor.requestLoad(APPEND, firstAppendState)
758         testScope.advanceTimeBy(40)
759         assertThat(accessor.state.value)
760             .isEqualTo(
761                 LoadStates(
762                     refresh = LoadState.NotLoading.Incomplete,
763                     prepend = LoadState.NotLoading.Incomplete,
764                     append = LoadState.Loading
765                 )
766             )
767         assertThat(remoteMediatorMock.newLoadEvents)
768             .containsExactly(LoadEvent(APPEND, firstAppendState))
769 
770         // Trigger refresh, cancelling remote append
771         val firstRefreshState = createMockState()
772         accessor.requestLoad(REFRESH, firstRefreshState)
773         testScope.advanceTimeBy(1)
774         assertThat(accessor.state.value)
775             .isEqualTo(
776                 LoadStates(
777                     refresh = LoadState.Loading,
778                     prepend = LoadState.NotLoading.Incomplete,
779                     append = LoadState.Loading
780                 )
781             )
782         // advance enough to complete refresh
783         testScope.advanceUntilIdle()
784         // assert that we receive refresh, and append is retried since it was cancelled
785         assertThat(remoteMediatorMock.newLoadEvents)
786             .containsExactly(
787                 LoadEvent(REFRESH, firstRefreshState),
788                 LoadEvent(APPEND, firstAppendState),
789             )
790         assertThat(accessor.state.value)
791             .isEqualTo(
792                 LoadStates(
793                     refresh = LoadState.NotLoading.Incomplete,
794                     prepend = LoadState.NotLoading.Incomplete,
795                     append = LoadState.NotLoading.Incomplete
796                 )
797             )
798 
799         val appendState = createMockState()
800         accessor.requestLoad(APPEND, appendState)
801         val prependState = createMockState()
802         accessor.requestLoad(PREPEND, prependState)
803         testScope.advanceTimeBy(50)
804         // both states should be set to loading even though prepend is not really running
805         assertThat(accessor.state.value)
806             .isEqualTo(
807                 LoadStates(
808                     refresh = LoadState.NotLoading.Incomplete,
809                     prepend = LoadState.Loading,
810                     append = LoadState.Loading
811                 )
812             )
813         assertThat(remoteMediatorMock.newLoadEvents).containsExactly(LoadEvent(APPEND, appendState))
814         // advance enough to trigger prepend
815         testScope.advanceTimeBy(51)
816         assertThat(accessor.state.value)
817             .isEqualTo(
818                 LoadStates(
819                     refresh = LoadState.NotLoading.Incomplete,
820                     prepend = LoadState.Loading,
821                     append = LoadState.NotLoading.Incomplete
822                 )
823             )
824         assertThat(remoteMediatorMock.newLoadEvents)
825             .containsExactly(LoadEvent(PREPEND, prependState))
826         testScope.advanceUntilIdle()
827         val exception = Throwable()
828         remoteMediatorMock.loadCallback = { type, _ ->
829             if (type == PREPEND) {
830                 RemoteMediator.MediatorResult.Error(exception)
831             } else {
832                 null
833             }
834         }
835         accessor.requestLoad(APPEND, createMockState())
836         accessor.requestLoad(PREPEND, createMockState())
837         testScope.advanceUntilIdle()
838         assertThat(accessor.state.value)
839             .isEqualTo(
840                 LoadStates(
841                     refresh = LoadState.NotLoading.Incomplete,
842                     prepend = LoadState.Error(exception),
843                     append = LoadState.NotLoading.Incomplete
844                 )
845             )
846         // now complete append, a.k.a. endOfPaginationReached
847         remoteMediatorMock.loadCallback = { type, _ ->
848             if (type == APPEND) {
849                 RemoteMediator.MediatorResult.Success(endOfPaginationReached = true)
850             } else {
851                 null
852             }
853         }
854         accessor.requestLoad(APPEND, createMockState())
855         testScope.advanceUntilIdle()
856         assertThat(accessor.state.value)
857             .isEqualTo(
858                 LoadStates(
859                     refresh = LoadState.NotLoading.Incomplete,
860                     prepend = LoadState.Error(exception),
861                     append = LoadState.NotLoading.Complete
862                 )
863             )
864         // clear events
865         remoteMediatorMock.newLoadEvents
866         // another append request should just be ignored
867         accessor.requestLoad(APPEND, createMockState())
868         testScope.advanceUntilIdle()
869         assertThat(remoteMediatorMock.newLoadEvents).isEmpty()
870         assertThat(accessor.state.value)
871             .isEqualTo(
872                 LoadStates(
873                     refresh = LoadState.NotLoading.Incomplete,
874                     prepend = LoadState.Error(exception),
875                     append = LoadState.NotLoading.Complete
876                 )
877             )
878         val refreshState = createMockState()
879         accessor.requestLoad(REFRESH, refreshState)
880         testScope.advanceTimeBy(50)
881         // prepend error state is still present
882         assertThat(accessor.state.value)
883             .isEqualTo(
884                 LoadStates(
885                     refresh = LoadState.Loading,
886                     prepend = LoadState.Error(exception),
887                     append = LoadState.NotLoading.Complete
888                 )
889             )
890         testScope.advanceUntilIdle()
891         // if refresh succeeds, it will clear the error state for refresh
892         assertThat(accessor.state.value).isEqualTo(LoadStates.IDLE)
893         assertThat(remoteMediatorMock.newLoadEvents)
894             .containsExactly(LoadEvent(REFRESH, refreshState))
895     }
896 
897     @Test
898     fun retry_refresh() {
899         val remoteMediatorMock =
900             RemoteMediatorMock(loadDelay = 100).apply { initializeResult = SKIP_INITIAL_REFRESH }
901         val exception = Exception()
902         val accessor = testScope.createAccessor(remoteMediatorMock)
903         remoteMediatorMock.loadCallback = { loadType, _ ->
904             delay(100)
905             if (loadType == REFRESH) {
906                 RemoteMediator.MediatorResult.Error(exception)
907             } else {
908                 null
909             }
910         }
911         val firstRefreshState = createMockState()
912         accessor.requestLoad(REFRESH, firstRefreshState)
913         assertThat(remoteMediatorMock.newLoadEvents)
914             .containsExactly(LoadEvent(REFRESH, firstRefreshState))
915         testScope.advanceUntilIdle()
916         assertThat(accessor.state.value.refresh).isEqualTo(LoadState.Error(exception))
917         val retryState = createMockState()
918         accessor.retryFailed(retryState)
919         testScope.advanceUntilIdle()
920         assertThat(remoteMediatorMock.newLoadEvents).containsExactly(LoadEvent(REFRESH, retryState))
921     }
922 
923     @Test
924     fun failedRefreshShouldNotAllowAppendPrependIfRefreshIsRequired() {
925         val remoteMediatorMock =
926             RemoteMediatorMock(loadDelay = 100).apply {
927                 initializeResult = RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
928             }
929         val exception = Exception()
930         val accessor = testScope.createAccessor(remoteMediatorMock)
931         remoteMediatorMock.loadCallback = { _, _ ->
932             delay(100)
933             RemoteMediator.MediatorResult.Error(exception)
934         }
935         val initialState = createMockState()
936         accessor.requestLoad(REFRESH, initialState)
937         // even though we are sending append prepend, they won't really trigger since refresh will
938         // fail
939 
940         // ensure that we didn't set append/prepend to loading when refresh is required
941         assertThat(accessor.state.value)
942             .isEqualTo(LoadStates.IDLE.modifyState(REFRESH, LoadState.Loading))
943         testScope.advanceUntilIdle()
944         // make sure only refresh has happened
945         assertThat(remoteMediatorMock.newLoadEvents)
946             .containsExactly(LoadEvent(REFRESH, initialState))
947         assertThat(accessor.state.value)
948             .isEqualTo(LoadStates.IDLE.modifyState(REFRESH, LoadState.Error(exception)))
949     }
950 
951     @Test
952     fun failedRefreshShouldAllowAppendPrependIfRefreshIsNotRequired() {
953         val remoteMediatorMock =
954             RemoteMediatorMock(loadDelay = 100).apply { initializeResult = SKIP_INITIAL_REFRESH }
955         val exception = Exception()
956         val accessor = testScope.createAccessor(remoteMediatorMock)
957         remoteMediatorMock.loadCallback = { loadType, _ ->
958             delay(100)
959             if (loadType != APPEND) {
960                 RemoteMediator.MediatorResult.Error(exception)
961             } else {
962                 null // let append succeed
963             }
964         }
965         val initialState = createMockState()
966         accessor.requestLoad(REFRESH, initialState)
967         accessor.requestLoad(PREPEND, initialState)
968         accessor.requestLoad(APPEND, initialState)
969         // make sure we optimistically updated append prepend states
970         assertThat(accessor.state.value)
971             .isEqualTo(
972                 LoadStates(
973                     refresh = LoadState.Loading,
974                     append = LoadState.Loading,
975                     prepend = LoadState.Loading
976                 )
977             )
978         testScope.advanceUntilIdle()
979         // make sure all requests did happen eventually
980         assertThat(remoteMediatorMock.newLoadEvents)
981             .containsExactly(
982                 LoadEvent(REFRESH, initialState),
983                 LoadEvent(PREPEND, initialState),
984                 LoadEvent(APPEND, initialState)
985             )
986         assertThat(accessor.state.value)
987             .isEqualTo(
988                 LoadStates(
989                     refresh = LoadState.Error(exception),
990                     append = LoadState.NotLoading.Incomplete,
991                     prepend = LoadState.Error(exception)
992                 )
993             )
994     }
995 
996     @Test
997     fun retry_retryBothAppendAndPrepend() {
998         val remoteMediatorMock =
999             RemoteMediatorMock(loadDelay = 100).apply { initializeResult = SKIP_INITIAL_REFRESH }
1000         val exception = Exception()
1001         val accessor = testScope.createAccessor(remoteMediatorMock)
1002         remoteMediatorMock.loadCallback = { _, _ ->
1003             delay(100)
1004             RemoteMediator.MediatorResult.Error(exception)
1005         }
1006         val appendState = createMockState()
1007         val prependState = createMockState()
1008         accessor.requestLoad(PREPEND, prependState)
1009         accessor.requestLoad(APPEND, appendState)
1010         testScope.advanceUntilIdle()
1011         assertThat(remoteMediatorMock.newLoadEvents)
1012             .containsExactly(LoadEvent(PREPEND, prependState), LoadEvent(APPEND, appendState))
1013         // now retry, ensure both runs
1014         val retryState = createMockState()
1015         accessor.retryFailed(retryState)
1016         // make sure they both move to loading
1017         assertThat(accessor.state.value)
1018             .isEqualTo(
1019                 LoadStates(
1020                     refresh = LoadState.NotLoading.Incomplete,
1021                     append = LoadState.Loading,
1022                     prepend = LoadState.Loading
1023                 )
1024             )
1025         testScope.advanceUntilIdle()
1026         // ensure they both got called
1027         assertThat(remoteMediatorMock.newLoadEvents)
1028             .containsExactly(LoadEvent(PREPEND, retryState), LoadEvent(APPEND, retryState))
1029         // make sure new loading states are correct
1030         assertThat(accessor.state.value)
1031             .isEqualTo(
1032                 LoadStates(
1033                     refresh = LoadState.NotLoading.Incomplete,
1034                     append = LoadState.Error(exception),
1035                     prepend = LoadState.Error(exception)
1036                 )
1037             )
1038     }
1039 
1040     @Test
1041     fun retry_multipleTriggersOnlyRefresh() {
1042         val remoteMediator =
1043             object : RemoteMediatorMock(100) {
1044                 override suspend fun initialize(): InitializeAction {
1045                     return SKIP_INITIAL_REFRESH
1046                 }
1047             }
1048         val exception = Exception()
1049         remoteMediator.loadCallback = { _, _ ->
1050             // fail all
1051             delay(60)
1052             RemoteMediator.MediatorResult.Error(exception)
1053         }
1054         val accessor = testScope.createAccessor(remoteMediator)
1055         accessor.requestLoad(REFRESH, createMockState())
1056         accessor.requestLoad(APPEND, createMockState())
1057         accessor.requestLoad(PREPEND, createMockState())
1058         assertThat(accessor.state.value)
1059             .isEqualTo(
1060                 LoadStates(
1061                     refresh = LoadState.Loading,
1062                     append = LoadState.Loading,
1063                     prepend = LoadState.Loading
1064                 )
1065             )
1066         // let refresh start but don't let it finish
1067         testScope.advanceUntilIdle()
1068         // get all errors
1069         assertThat(accessor.state.value)
1070             .isEqualTo(
1071                 LoadStates(
1072                     refresh = LoadState.Error(exception),
1073                     append = LoadState.Error(exception),
1074                     prepend = LoadState.Error(exception)
1075                 )
1076             )
1077         // let requests succeed
1078         remoteMediator.loadCallback = null
1079         val retryState = createMockState()
1080         accessor.retryFailed(retryState)
1081         assertThat(accessor.state.value)
1082             .isEqualTo(
1083                 LoadStates(
1084                     refresh = LoadState.Loading,
1085                     append = LoadState.NotLoading.Incomplete,
1086                     prepend = LoadState.NotLoading.Incomplete
1087                 )
1088             )
1089     }
1090 
1091     @Test
1092     fun failingRefreshRetriesAppendPrepend_refreshNotRequired() {
1093         val remoteMediator =
1094             object : RemoteMediatorMock(100) {
1095                 override suspend fun initialize(): InitializeAction {
1096                     return SKIP_INITIAL_REFRESH
1097                 }
1098             }
1099         val exception = Exception()
1100         remoteMediator.loadCallback = { type, _ ->
1101             // only fail for refresh
1102             if (type == REFRESH) {
1103                 delay(60)
1104                 RemoteMediator.MediatorResult.Error(exception)
1105             } else {
1106                 null
1107             }
1108         }
1109         val accessor = testScope.createAccessor(remoteMediator)
1110         accessor.requestLoad(REFRESH, createMockState())
1111         accessor.requestLoad(APPEND, createMockState())
1112         accessor.requestLoad(PREPEND, createMockState())
1113         assertThat(accessor.state.value)
1114             .isEqualTo(
1115                 LoadStates(
1116                     refresh = LoadState.Loading,
1117                     append = LoadState.Loading,
1118                     prepend = LoadState.Loading
1119                 )
1120             )
1121         // let refresh start but don't let it finish
1122         testScope.advanceTimeBy(50)
1123         // make sure refresh does not revert the append / prepend states
1124         assertThat(accessor.state.value)
1125             .isEqualTo(
1126                 LoadStates(
1127                     refresh = LoadState.Loading,
1128                     append = LoadState.Loading,
1129                     prepend = LoadState.Loading
1130                 )
1131             )
1132         // let refresh fail, it should retry append prepend
1133         testScope.advanceTimeBy(20)
1134         assertThat(accessor.state.value)
1135             .isEqualTo(
1136                 LoadStates(
1137                     refresh = LoadState.Error(exception),
1138                     append = LoadState.Loading,
1139                     prepend = LoadState.Loading
1140                 )
1141             )
1142         // let the prepend retry start
1143         testScope.advanceTimeBy(100)
1144         assertThat(accessor.state.value)
1145             .isEqualTo(
1146                 LoadStates(
1147                     refresh = LoadState.Error(exception),
1148                     append = LoadState.NotLoading.Incomplete,
1149                     prepend = LoadState.Loading
1150                 )
1151             )
1152         testScope.advanceUntilIdle()
1153         assertThat(accessor.state.value)
1154             .isEqualTo(
1155                 LoadStates(
1156                     refresh = LoadState.Error(exception),
1157                     append = LoadState.NotLoading.Incomplete,
1158                     prepend = LoadState.NotLoading.Incomplete
1159                 )
1160             )
1161     }
1162 
1163     @Test
1164     fun loadMoreRefreshShouldRetryRefresh() {
1165         // see: b/173438474
1166         val remoteMediator = RemoteMediatorMock(loadDelay = 100)
1167         val exception = Exception()
1168         remoteMediator.loadCallback = { _, _ ->
1169             delay(60)
1170             RemoteMediator.MediatorResult.Error(exception)
1171         }
1172         val accessor = testScope.createAccessor(remoteMediator)
1173         val state1 = createMockState()
1174         accessor.requestLoad(REFRESH, state1)
1175         assertThat(accessor.state.value.refresh).isEqualTo(LoadState.Loading)
1176         // run to get the error
1177         testScope.advanceUntilIdle()
1178         assertThat(accessor.state.value.refresh).isEqualTo(LoadState.Error(exception))
1179         // now send another load type refresh, should trigger another load
1180         remoteMediator.loadCallback = null // let it succeed
1181         val state2 = createMockState()
1182         accessor.requestLoad(REFRESH, state2)
1183         assertThat(accessor.state.value.refresh).isEqualTo(LoadState.Loading)
1184         testScope.advanceUntilIdle()
1185         assertThat(accessor.state.value.refresh).isEqualTo(LoadState.NotLoading.Incomplete)
1186     }
1187 
1188     @Test
1189     fun loadMoreRefreshShouldRetryRefresh_withAppendPrependErrors() {
1190         // see: b/173438474
1191         val remoteMediator = RemoteMediatorMock(loadDelay = 100)
1192         val exception = Exception()
1193         remoteMediator.loadCallback = { _, _ ->
1194             delay(60)
1195             RemoteMediator.MediatorResult.Error(exception)
1196         }
1197         val accessor = testScope.createAccessor(remoteMediator)
1198         val state1 = createMockState()
1199         accessor.requestLoad(REFRESH, state1)
1200         accessor.requestLoad(APPEND, state1)
1201         accessor.requestLoad(PREPEND, state1)
1202         // run to get the error
1203         testScope.advanceUntilIdle()
1204         assertThat(accessor.state.value)
1205             .isEqualTo(
1206                 LoadStates(
1207                     refresh = LoadState.Error(exception),
1208                     prepend = LoadState.Error(exception),
1209                     append = LoadState.Error(exception),
1210                 )
1211             )
1212         // now send another load type refresh, should trigger another load
1213         remoteMediator.loadCallback = null // let it succeed
1214         val state2 = createMockState()
1215         accessor.requestLoad(REFRESH, state2)
1216         assertThat(accessor.state.value)
1217             .isEqualTo(
1218                 LoadStates(
1219                     refresh = LoadState.Loading,
1220                     prepend = LoadState.Error(exception), // keep errors for these for now
1221                     append = LoadState.Error(exception),
1222                 )
1223             )
1224         testScope.advanceUntilIdle()
1225         assertThat(accessor.state.value)
1226             .isEqualTo(
1227                 LoadStates(
1228                     refresh = LoadState.NotLoading.Incomplete,
1229                     prepend = LoadState.NotLoading.Incomplete, // clear errors
1230                     append = LoadState.NotLoading.Incomplete,
1231                 )
1232             )
1233     }
1234 
1235     @Test
1236     fun loadMoreRefreshShouldRetryRefresh_withAppendPrependErrors_secondRefreshFails() {
1237         // see: b/173438474
1238         val remoteMediator = RemoteMediatorMock(loadDelay = 100)
1239         val exception1 = Exception("1")
1240         remoteMediator.loadCallback = { _, _ ->
1241             delay(60)
1242             RemoteMediator.MediatorResult.Error(exception1)
1243         }
1244         val accessor = testScope.createAccessor(remoteMediator)
1245         val state1 = createMockState()
1246         accessor.requestLoad(REFRESH, state1)
1247         accessor.requestLoad(APPEND, state1)
1248         accessor.requestLoad(PREPEND, state1)
1249         // run to get the error
1250         testScope.advanceUntilIdle()
1251         assertThat(accessor.state.value)
1252             .isEqualTo(
1253                 LoadStates(
1254                     refresh = LoadState.Error(exception1),
1255                     prepend = LoadState.Error(exception1),
1256                     append = LoadState.Error(exception1),
1257                 )
1258             )
1259         // now send another load type refresh, should trigger another load
1260         val exception2 = Exception("2")
1261         remoteMediator.loadCallback = { _, _ ->
1262             delay(60)
1263             RemoteMediator.MediatorResult.Error(exception2)
1264         }
1265         val state2 = createMockState()
1266         accessor.requestLoad(REFRESH, state2)
1267         assertThat(accessor.state.value)
1268             .isEqualTo(
1269                 LoadStates(
1270                     refresh = LoadState.Loading,
1271                     prepend = LoadState.Error(exception1), // keep errors for these for now
1272                     append = LoadState.Error(exception1),
1273                 )
1274             )
1275         testScope.advanceUntilIdle()
1276         assertThat(accessor.state.value)
1277             .isEqualTo(
1278                 LoadStates(
1279                     refresh = LoadState.Error(exception2),
1280                     prepend = LoadState.Error(exception1), // these keep their original exceptions
1281                     append = LoadState.Error(exception1),
1282                 )
1283             )
1284     }
1285 
1286     @Test
1287     fun requireRetry_append() {
1288         requireRetry(APPEND)
1289     }
1290 
1291     @Test
1292     fun requireRetry_prepend() {
1293         requireRetry(PREPEND)
1294     }
1295 
1296     private fun requireRetry(loadType: LoadType) {
1297         // ensure that we don't retry a failed request until a retry arrives.
1298         val remoteMediator = RemoteMediatorMock(100)
1299         val exception = Exception()
1300         remoteMediator.loadCallback = { _, _ ->
1301             delay(60)
1302             RemoteMediator.MediatorResult.Error(exception)
1303         }
1304         val accessor = testScope.createAccessor(remoteMediator)
1305         val state1 = createMockState()
1306         accessor.requestLoad(loadType, state1)
1307         assertThat(accessor.state.value.get(loadType)).isEqualTo(LoadState.Loading)
1308         testScope.advanceUntilIdle()
1309         assertThat(accessor.state.value.get(loadType)).isEqualTo(LoadState.Error(exception))
1310         assertThat(remoteMediator.newLoadEvents).containsExactly(LoadEvent(loadType, state1))
1311         // subsequent add calls shouldn't do anything
1312         accessor.requestLoad(loadType, createMockState())
1313         assertThat(accessor.state.value.get(loadType)).isEqualTo(LoadState.Error(exception))
1314         testScope.advanceUntilIdle()
1315         assertThat(accessor.state.value.get(loadType)).isEqualTo(LoadState.Error(exception))
1316         assertThat(remoteMediator.newLoadEvents).isEmpty()
1317 
1318         // if we send a retry, then it will work
1319         remoteMediator.loadCallback = null
1320         val retryState = createMockState()
1321         accessor.retryFailed(retryState)
1322         assertThat(accessor.state.value.get(loadType)).isEqualTo(LoadState.Loading)
1323         testScope.advanceUntilIdle()
1324         assertThat(remoteMediator.newLoadEvents).containsExactly(LoadEvent(loadType, retryState))
1325         assertThat(accessor.state.value.get(loadType)).isEqualTo(LoadState.NotLoading.Incomplete)
1326     }
1327 
1328     private fun TestScope.createAccessor(
1329         mediator: RemoteMediatorMock
1330     ): RemoteMediatorAccessor<Int, Int> {
1331         val accessor = RemoteMediatorAccessor(scope = this, delegate = mediator)
1332         TestScope().launch(coroutineContext) { accessor.initialize() }
1333         return accessor
1334     }
1335 }
1336