diff --git a/mvi/gradle.properties b/mvi/gradle.properties index 43870c8..b49de00 100644 --- a/mvi/gradle.properties +++ b/mvi/gradle.properties @@ -1,6 +1,6 @@ POM_ARTIFACT_ID=mvi GROUP=com.adidas.mvi VERSION_CODE=1 -VERSION_NAME=1.9.4 +VERSION_NAME=1.9.5 POM_NAME=Adidas MVI -POM_DESCRIPTION=Adidas MVI \ No newline at end of file +POM_DESCRIPTION=Adidas MVI diff --git a/mvi/src/commonMain/kotlin/com/adidas/mvi/reducer/ReducerExtensions.kt b/mvi/src/commonMain/kotlin/com/adidas/mvi/reducer/ReducerExtensions.kt index 16beed5..32b901c 100644 --- a/mvi/src/commonMain/kotlin/com/adidas/mvi/reducer/ReducerExtensions.kt +++ b/mvi/src/commonMain/kotlin/com/adidas/mvi/reducer/ReducerExtensions.kt @@ -10,6 +10,9 @@ import com.adidas.mvi.sideeffects.SideEffects import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map public fun Reducer( coroutineScope: CoroutineScope, @@ -34,3 +37,14 @@ public inline fun Reducer<*, *>.requireView(): TView = throw ClassCastException("Required view of ${TView::class} type, but found $this") } } + +/** + * Wait until reducer's view is [T] and then return that View. Note that this function can potentially suspend + * indefinitely if the view is never reached. + * + * To use, you have to specify two parameters, first one is the target state, the second one can be left blank. Like this: + * `val state = reducer.awaitView()` + */ +public suspend inline fun Reducer<*, out State>.awaitView(): T { + return state.map { it.view }.filterIsInstance().first() +} diff --git a/mvi/src/jvmTest/kotlin/com/adidas/mvi/reducer/ReducerExtensionsTest.kt b/mvi/src/jvmTest/kotlin/com/adidas/mvi/reducer/ReducerExtensionsTest.kt index 352c46c..27af3bf 100644 --- a/mvi/src/jvmTest/kotlin/com/adidas/mvi/reducer/ReducerExtensionsTest.kt +++ b/mvi/src/jvmTest/kotlin/com/adidas/mvi/reducer/ReducerExtensionsTest.kt @@ -2,26 +2,68 @@ package com.adidas.mvi.reducer import com.adidas.mvi.Intent import com.adidas.mvi.State +import com.adidas.mvi.product.FakeProductViewTransform +import com.adidas.mvi.product.ProductSideEffect import com.adidas.mvi.product.ProductState +import com.adidas.mvi.sideeffects.SideEffects import com.adidas.mvi.transform.StateTransform import io.kotest.core.spec.style.ShouldSpec +import io.kotest.engine.coroutines.backgroundScope +import io.kotest.engine.coroutines.testScheduler import io.kotest.matchers.shouldBe +import kotlinx.coroutines.async import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.TestScope -class ReducerExtensionsTest : ShouldSpec({ - context("A reducer instantiated with the extension") { - val reducer = - Reducer( - coroutineScope = TestScope(), - initialInnerState = ProductState.Loading, - intentExecutor = { _: Intent -> - emptyFlow>>() - }, - ) - - should("The initial inner state should be Loading") { - reducer.state.value.view shouldBe ProductState.Loading +class ReducerExtensionsTest : ShouldSpec( + { + coroutineTestScope = true + + context("A reducer instantiated with the extension") { + val reducer = + Reducer( + coroutineScope = TestScope(), + initialInnerState = ProductState.Loading, + intentExecutor = { _: Intent -> + emptyFlow>>() + }, + ) + + should("The initial inner state should be Loading") { + reducer.state.value.view shouldBe ProductState.Loading + } + } + + context("A reducer instantiated ") { + val reducer = + Reducer( + coroutineScope = backgroundScope, + initialInnerState = ProductState.Loading, + intentExecutor = { intent: Intent -> + println("got intent $intent") + if (intent is TestIntent.CainIntent) { + flowOf>>(FakeProductViewTransform(State(ProductState.Loaded, SideEffects()))) + } else { + emptyFlow>>() + } + }, + ) + + val awaitJob = backgroundScope.async { reducer.awaitView() } + testScheduler.runCurrent() + + should("The initial await state should not return yet") { + awaitJob.isCompleted shouldBe false + } + + should("return after emitting Loaded") { + reducer.executeIntent(TestIntent.CainIntent) + testScheduler.runCurrent() + + awaitJob.isCompleted shouldBe true + awaitJob.await() shouldBe ProductState.Loaded + } } - } -}) + }, +)