From e70d34d91a3968824e6fdfaa6ac4831979eac0b0 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 2 Mar 2025 23:06:42 +0600 Subject: [PATCH] add tests for paged loading components --- .../jvmTest/kotlin/LoadableComponentTests.kt | 42 ++++++++++++ .../kotlin/InfinityPagedComponent.kt | 44 +++++-------- .../kotlin/InfinityPagedComponentTests.kt | 47 ++++++++++++++ .../src/jvmTest/kotlin/PagedComponentTests.kt | 64 +++++++++++++++++++ 4 files changed, 169 insertions(+), 28 deletions(-) create mode 100644 common/compose/src/jvmTest/kotlin/LoadableComponentTests.kt create mode 100644 pagination/compose/src/jvmTest/kotlin/InfinityPagedComponentTests.kt create mode 100644 pagination/compose/src/jvmTest/kotlin/PagedComponentTests.kt diff --git a/common/compose/src/jvmTest/kotlin/LoadableComponentTests.kt b/common/compose/src/jvmTest/kotlin/LoadableComponentTests.kt new file mode 100644 index 00000000000..dc33811f442 --- /dev/null +++ b/common/compose/src/jvmTest/kotlin/LoadableComponentTests.kt @@ -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(0) + val loadedFlow = SpecialMutableStateFlow(0) + setContent { + LoadableComponent({ + 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) + } +} \ No newline at end of file diff --git a/pagination/compose/src/commonMain/kotlin/InfinityPagedComponent.kt b/pagination/compose/src/commonMain/kotlin/InfinityPagedComponent.kt index 9a44bb7a52c..1af1fe28ab6 100644 --- a/pagination/compose/src/commonMain/kotlin/InfinityPagedComponent.kt +++ b/pagination/compose/src/commonMain/kotlin/InfinityPagedComponent.kt @@ -19,11 +19,14 @@ class InfinityPagedComponentContext internal constructor( ) { internal val iterationState: MutableState> = mutableStateOf(0 to SimplePagination(page, size)) internal val dataState: MutableState?> = mutableStateOf(null) + internal var lastPageLoaded = false /** * Loads the next page of data. If the current page is the last one, the function returns early. */ fun loadNext() { + if (lastPageLoaded) return + iterationState.value = iterationState.value.let { if ((dataState.value as? PaginationResult<*>) ?.isLastPage == true) return (it.first + 1) to it.second.nextPage() @@ -35,6 +38,7 @@ class InfinityPagedComponentContext internal constructor( */ fun reload() { dataState.value = null + lastPageLoaded = false iterationState.value = iterationState.value.let { (it.first + 1) to (it.second.firstPage()) } @@ -59,8 +63,12 @@ internal fun InfinityPagedComponent( ) { val context = remember { InfinityPagedComponentContext(page, size) } - LaunchedEffect(context.iterationState.value) { - context.dataState.value = (context.dataState.value ?: emptyList()) + loader(context, context.iterationState.value.second).results + LaunchedEffect(context.iterationState.value.first) { + 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 { @@ -79,11 +87,10 @@ internal fun InfinityPagedComponent( @Composable fun InfinityPagedComponent( pageInfo: Pagination, - loader: suspend PagedComponentContext.(Pagination) -> PaginationResult, - block: @Composable PagedComponentContext.(PaginationResult) -> Unit + loader: suspend InfinityPagedComponentContext.(Pagination) -> PaginationResult, + block: @Composable InfinityPagedComponentContext.(List?) -> Unit ) { - PagedComponent( - null, + InfinityPagedComponent( pageInfo.page, pageInfo.size, loader, @@ -91,25 +98,6 @@ fun 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 InfinityPagedComponent( - initialPage: Int, - size: Int, - loader: suspend PagedComponentContext.(Pagination) -> PaginationResult, - block: @Composable PagedComponentContext.(PaginationResult) -> Unit -) { - PagedComponent(null, initialPage, size, loader, block) -} - /** * Overloaded composable function for an infinitely paged component. * @@ -121,8 +109,8 @@ fun InfinityPagedComponent( @Composable fun InfinityPagedComponent( size: Int, - loader: suspend PagedComponentContext.(Pagination) -> PaginationResult, - block: @Composable PagedComponentContext.(PaginationResult) -> Unit + loader: suspend InfinityPagedComponentContext.(Pagination) -> PaginationResult, + block: @Composable InfinityPagedComponentContext.(List?) -> Unit ) { - PagedComponent(0, size, loader, block) + InfinityPagedComponent(0, size, loader, block) } diff --git a/pagination/compose/src/jvmTest/kotlin/InfinityPagedComponentTests.kt b/pagination/compose/src/jvmTest/kotlin/InfinityPagedComponentTests.kt new file mode 100644 index 00000000000..9844e0d400b --- /dev/null +++ b/pagination/compose/src/jvmTest/kotlin/InfinityPagedComponentTests.kt @@ -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() + setContent { + InfinityPagedComponent( + 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 + ) + } +} \ No newline at end of file diff --git a/pagination/compose/src/jvmTest/kotlin/PagedComponentTests.kt b/pagination/compose/src/jvmTest/kotlin/PagedComponentTests.kt new file mode 100644 index 00000000000..375db11cdef --- /dev/null +++ b/pagination/compose/src/jvmTest/kotlin/PagedComponentTests.kt @@ -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( + 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 + ) + } +} \ No newline at end of file