add tests for paged loading components

This commit is contained in:
2025-03-02 23:06:42 +06:00
parent 3b7dde3cb1
commit e70d34d91a
4 changed files with 169 additions and 28 deletions

View File

@@ -0,0 +1,42 @@
import androidx.compose.runtime.remember
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.runComposeUiTest
import dev.inmo.micro_utils.common.compose.LoadableComponent
import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
import org.jetbrains.annotations.TestOnly
import kotlin.test.Test
import kotlin.test.assertTrue
class LoadableComponentTests {
@OptIn(ExperimentalTestApi::class)
@Test
@TestOnly
fun testSimpleLoad() = runComposeUiTest {
val loadingFlow = SpecialMutableStateFlow<Int>(0)
val loadedFlow = SpecialMutableStateFlow<Int>(0)
setContent {
LoadableComponent<Int>({
loadingFlow.filter { it == 1 }.first()
}) {
assert(dataState.value.data == 1)
remember {
loadedFlow.value = 2
}
}
}
waitForIdle()
assertTrue(loadedFlow.value == 0)
loadingFlow.value = 1
waitForIdle()
assertTrue(loadedFlow.value == 2)
}
}

View File

@@ -19,11 +19,14 @@ class InfinityPagedComponentContext<T> internal constructor(
) { ) {
internal val iterationState: MutableState<Pair<Int, Pagination>> = mutableStateOf(0 to SimplePagination(page, size)) internal val iterationState: MutableState<Pair<Int, Pagination>> = mutableStateOf(0 to SimplePagination(page, size))
internal val dataState: MutableState<List<T>?> = mutableStateOf(null) internal val dataState: MutableState<List<T>?> = mutableStateOf(null)
internal var lastPageLoaded = false
/** /**
* Loads the next page of data. If the current page is the last one, the function returns early. * Loads the next page of data. If the current page is the last one, the function returns early.
*/ */
fun loadNext() { fun loadNext() {
if (lastPageLoaded) return
iterationState.value = iterationState.value.let { iterationState.value = iterationState.value.let {
if ((dataState.value as? PaginationResult<*>) ?.isLastPage == true) return if ((dataState.value as? PaginationResult<*>) ?.isLastPage == true) return
(it.first + 1) to it.second.nextPage() (it.first + 1) to it.second.nextPage()
@@ -35,6 +38,7 @@ class InfinityPagedComponentContext<T> internal constructor(
*/ */
fun reload() { fun reload() {
dataState.value = null dataState.value = null
lastPageLoaded = false
iterationState.value = iterationState.value.let { iterationState.value = iterationState.value.let {
(it.first + 1) to (it.second.firstPage()) (it.first + 1) to (it.second.firstPage())
} }
@@ -59,8 +63,12 @@ internal fun <T> InfinityPagedComponent(
) { ) {
val context = remember { InfinityPagedComponentContext<T>(page, size) } val context = remember { InfinityPagedComponentContext<T>(page, size) }
LaunchedEffect(context.iterationState.value) { LaunchedEffect(context.iterationState.value.first) {
context.dataState.value = (context.dataState.value ?: emptyList()) + loader(context, context.iterationState.value.second).results val paginationResult = loader(context, context.iterationState.value.second)
if (paginationResult.isLastPage) {
context.lastPageLoaded = true
}
context.dataState.value = (context.dataState.value ?: emptyList()) + paginationResult.results
} }
context.dataState.value ?.let { context.dataState.value ?.let {
@@ -79,11 +87,10 @@ internal fun <T> InfinityPagedComponent(
@Composable @Composable
fun <T> InfinityPagedComponent( fun <T> InfinityPagedComponent(
pageInfo: Pagination, pageInfo: Pagination,
loader: suspend PagedComponentContext<T>.(Pagination) -> PaginationResult<T>, loader: suspend InfinityPagedComponentContext<T>.(Pagination) -> PaginationResult<T>,
block: @Composable PagedComponentContext<T>.(PaginationResult<T>) -> Unit block: @Composable InfinityPagedComponentContext<T>.(List<T>?) -> Unit
) { ) {
PagedComponent( InfinityPagedComponent(
null,
pageInfo.page, pageInfo.page,
pageInfo.size, pageInfo.size,
loader, loader,
@@ -91,25 +98,6 @@ fun <T> InfinityPagedComponent(
) )
} }
/**
* Overloaded composable function for an infinitely paged component.
*
* @param T The type of the paginated data.
* @param initialPage Initial page number.
* @param size Number of items per page.
* @param loader Suspended function that loads paginated data.
* @param block Composable function that renders the UI with the loaded data.
*/
@Composable
fun <T> InfinityPagedComponent(
initialPage: Int,
size: Int,
loader: suspend PagedComponentContext<T>.(Pagination) -> PaginationResult<T>,
block: @Composable PagedComponentContext<T>.(PaginationResult<T>) -> Unit
) {
PagedComponent(null, initialPage, size, loader, block)
}
/** /**
* Overloaded composable function for an infinitely paged component. * Overloaded composable function for an infinitely paged component.
* *
@@ -121,8 +109,8 @@ fun <T> InfinityPagedComponent(
@Composable @Composable
fun <T> InfinityPagedComponent( fun <T> InfinityPagedComponent(
size: Int, size: Int,
loader: suspend PagedComponentContext<T>.(Pagination) -> PaginationResult<T>, loader: suspend InfinityPagedComponentContext<T>.(Pagination) -> PaginationResult<T>,
block: @Composable PagedComponentContext<T>.(PaginationResult<T>) -> Unit block: @Composable InfinityPagedComponentContext<T>.(List<T>?) -> Unit
) { ) {
PagedComponent(0, size, loader, block) InfinityPagedComponent(0, size, loader, block)
} }

View File

@@ -0,0 +1,47 @@
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.runComposeUiTest
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.compose.InfinityPagedComponent
import dev.inmo.micro_utils.pagination.compose.PagedComponent
import org.jetbrains.annotations.TestOnly
import kotlin.test.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
class InfinityPagedComponentTests {
@OptIn(ExperimentalTestApi::class)
@Test
@TestOnly
fun testSimpleLoad() = runComposeUiTest {
var expectedList = listOf<Int>()
setContent {
InfinityPagedComponent<Int>(
size = 1,
loader = {
PaginationResult(
page = it.page,
size = it.size,
results = (it.firstIndex .. it.lastIndex).toList(),
objectsNumber = 3
).also {
expectedList += it.results
}
}
) {
assertEquals(expectedList, it)
LaunchedEffect(it ?.size) {
loadNext()
}
}
}
waitForIdle()
assertContentEquals(
listOf(0, 1, 2),
expectedList
)
}
}

View File

@@ -0,0 +1,64 @@
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.runComposeUiTest
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.compose.PagedComponent
import org.jetbrains.annotations.TestOnly
import kotlin.test.Test
import kotlin.test.assertEquals
class PagedComponentTests {
@OptIn(ExperimentalTestApi::class)
@Test
@TestOnly
fun testSimpleLoad() = runComposeUiTest {
var expectedPage = PaginationResult(
page = 0,
size = 1,
results = listOf(0),
objectsNumber = 3
)
var previousPage = expectedPage
setContent {
PagedComponent<Int>(
initialPage = 0,
size = 1,
loader = {
previousPage = expectedPage
expectedPage = PaginationResult(
page = it.page,
size = it.size,
results = (it.firstIndex .. it.lastIndex).toList(),
objectsNumber = 3
)
expectedPage
}
) {
assertEquals(expectedPage, it)
assertEquals(expectedPage.results, it.results)
if (it.isLastPage || it.page < previousPage.page) {
if (it.isFirstPage) {
// do nothing - end of test
} else {
loadPrevious()
}
} else {
loadNext()
}
}
}
waitForIdle()
assertEquals(
PaginationResult(
page = 0,
size = 1,
results = listOf(0),
objectsNumber = 3
),
expectedPage
)
}
}