correctly detect end of pagination in network timeline (#2296)

* correctly detect end of pagination in network timeline

closes #2293

* improve NetworkTimelineRemoteMediatorTest

* remove unused import
This commit is contained in:
Konrad Pozniak 2022-01-20 18:30:21 +01:00 committed by GitHub
parent f29e46ad55
commit a000228165
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 105 additions and 1 deletions

View File

@ -47,7 +47,11 @@ class NetworkTimelineRemoteMediator(
} }
LoadType.APPEND -> { LoadType.APPEND -> {
val maxId = viewModel.nextKey val maxId = viewModel.nextKey
if (maxId != null) {
viewModel.fetchStatusesForKind(maxId, null, limit = state.config.pageSize) viewModel.fetchStatusesForKind(maxId, null, limit = state.config.pageSize)
} else {
return MediatorResult.Success(endOfPaginationReached = true)
}
} }
} }

View File

@ -6,6 +6,7 @@ import androidx.paging.PagingConfig
import androidx.paging.PagingSource import androidx.paging.PagingSource
import androidx.paging.PagingState import androidx.paging.PagingState
import androidx.paging.RemoteMediator import androidx.paging.RemoteMediator
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.keylesspalace.tusky.components.timeline.viewmodel.NetworkTimelineRemoteMediator import com.keylesspalace.tusky.components.timeline.viewmodel.NetworkTimelineRemoteMediator
import com.keylesspalace.tusky.components.timeline.viewmodel.NetworkTimelineViewModel import com.keylesspalace.tusky.components.timeline.viewmodel.NetworkTimelineViewModel
import com.keylesspalace.tusky.db.AccountEntity import com.keylesspalace.tusky.db.AccountEntity
@ -15,15 +16,21 @@ import com.nhaarman.mockitokotlin2.anyOrNull
import com.nhaarman.mockitokotlin2.doReturn import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.doThrow import com.nhaarman.mockitokotlin2.doThrow
import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import okhttp3.Headers
import okhttp3.ResponseBody.Companion.toResponseBody import okhttp3.ResponseBody.Companion.toResponseBody
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
import retrofit2.HttpException import retrofit2.HttpException
import retrofit2.Response import retrofit2.Response
import java.lang.RuntimeException import java.lang.RuntimeException
@Config(sdk = [29])
@RunWith(AndroidJUnit4::class)
class NetworkTimelineRemoteMediatorTest { class NetworkTimelineRemoteMediatorTest {
private val accountManager: AccountManager = mock { private val accountManager: AccountManager = mock {
@ -70,6 +77,52 @@ class NetworkTimelineRemoteMediatorTest {
assertTrue((result as RemoteMediator.MediatorResult.Error).throwable is RuntimeException) assertTrue((result as RemoteMediator.MediatorResult.Error).throwable is RuntimeException)
} }
@Test
@ExperimentalPagingApi
fun `should do initial loading`() {
val statuses: MutableList<StatusViewData> = mutableListOf()
val timelineViewModel: NetworkTimelineViewModel = mock {
on { statusData } doReturn statuses
on { nextKey } doReturn null
onBlocking { fetchStatusesForKind(null, null, 20) } doReturn Response.success(
listOf(
mockStatus("7"),
mockStatus("6"),
mockStatus("5")
),
Headers.headersOf(
"Link", "<https://mastodon.example/api/v1/favourites?limit=20&max_id=4>; rel=\"next\", <https://mastodon.example/api/v1/favourites?limit=20&min_id=8>; rel=\"prev\""
)
)
}
val remoteMediator = NetworkTimelineRemoteMediator(accountManager, timelineViewModel)
val state = state(
listOf(
PagingSource.LoadResult.Page(
data = emptyList(),
prevKey = null,
nextKey = null
)
)
)
val result = runBlocking { remoteMediator.load(LoadType.REFRESH, state) }
val newStatusData = mutableListOf(
mockStatusViewData("7"),
mockStatusViewData("6"),
mockStatusViewData("5"),
)
verify(timelineViewModel).nextKey = "4"
assertTrue(result is RemoteMediator.MediatorResult.Success)
assertEquals(false, (result as RemoteMediator.MediatorResult.Success).endOfPaginationReached)
assertEquals(newStatusData, statuses)
}
@Test @Test
@ExperimentalPagingApi @ExperimentalPagingApi
fun `should not prepend statuses`() { fun `should not prepend statuses`() {
@ -246,6 +299,9 @@ class NetworkTimelineRemoteMediatorTest {
mockStatus("3"), mockStatus("3"),
mockStatus("2"), mockStatus("2"),
mockStatus("1") mockStatus("1")
),
Headers.headersOf(
"Link", "<https://mastodon.example/api/v1/favourites?limit=20&max_id=0>; rel=\"next\", <https://mastodon.example/api/v1/favourites?limit=20&min_id=4>; rel=\"prev\""
) )
) )
} }
@ -277,11 +333,55 @@ class NetworkTimelineRemoteMediatorTest {
mockStatusViewData("1"), mockStatusViewData("1"),
) )
verify(timelineViewModel).nextKey = "0"
assertTrue(result is RemoteMediator.MediatorResult.Success) assertTrue(result is RemoteMediator.MediatorResult.Success)
assertEquals(false, (result as RemoteMediator.MediatorResult.Success).endOfPaginationReached) assertEquals(false, (result as RemoteMediator.MediatorResult.Success).endOfPaginationReached)
assertEquals(newStatusData, statuses) assertEquals(newStatusData, statuses)
} }
@Test
@ExperimentalPagingApi
fun `should not append statuses when pagination end has been reached`() {
val statuses: MutableList<StatusViewData> = mutableListOf(
mockStatusViewData("8"),
mockStatusViewData("7"),
mockStatusViewData("5"),
)
val timelineViewModel: NetworkTimelineViewModel = mock {
on { statusData } doReturn statuses
on { nextKey } doReturn null
}
val remoteMediator = NetworkTimelineRemoteMediator(accountManager, timelineViewModel)
val state = state(
listOf(
PagingSource.LoadResult.Page(
data = listOf(
mockStatusViewData("8"),
mockStatusViewData("7"),
mockStatusViewData("5"),
),
prevKey = null,
nextKey = null
)
)
)
val result = runBlocking { remoteMediator.load(LoadType.APPEND, state) }
val newStatusData = mutableListOf(
mockStatusViewData("8"),
mockStatusViewData("7"),
mockStatusViewData("5")
)
assertTrue(result is RemoteMediator.MediatorResult.Success)
assertTrue((result as RemoteMediator.MediatorResult.Success).endOfPaginationReached)
assertEquals(newStatusData, statuses)
}
private fun state(pages: List<PagingSource.LoadResult.Page<String, StatusViewData>> = emptyList()) = PagingState( private fun state(pages: List<PagingSource.LoadResult.Page<String, StatusViewData>> = emptyList()) = PagingState(
pages = pages, pages = pages,
anchorPosition = null, anchorPosition = null,