diff --git a/docs/README.md b/docs/README.md index d6efc6c..f07fe7d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,7 +2,7 @@ adidas mvi Logo -[![Kotlin](https://img.shields.io/badge/Kotlin-1.8.20-blue.svg?style=flat&logo=kotlin)](https://kotlinlang.org) +[![Kotlin](https://img.shields.io/badge/Kotlin-1.9.22-blue.svg?style=flat&logo=kotlin)](https://kotlinlang.org) ![Test workflow](https://github.com/adidas/mvi/actions/workflows/deploy_docs.yml/badge.svg) [![adidas official](https://img.shields.io/badge/adidas-official-000000)](https://github.com/adidas) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fcfbe27..e71134d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,11 +1,14 @@ [versions] -kotlin = "1.8.21" -coroutines = "1.7.1" -kotest = "5.6.2" -ktlint = "0.45.2" -mvi = "1.5.0" -activity = "1.7.1" -lifecycle = "2.6.1" +kotlin = "1.9.22" +coroutines = "1.8.0" +kotest = "5.8.0" +ktlint-lib = "1.2.1" +mvi = "1.6.0" +activity = "1.8.2" +lifecycle = "2.7.0" +ktlint-gradle = "12.1.0" +android-library = "8.3.0" +maven-publish = "0.25.2" [libraries] coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } @@ -18,7 +21,7 @@ lifecycleRuntimeCompose = { module = "androidx.lifecycle:lifecycle-runtime-compo [plugins] kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } -ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "10.3.0" } -mavenPublish = { id = "com.vanniktech.maven.publish", version = "0.25.2" } -android-library = { id = "com.android.library", version = "8.0.1" } +ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint-gradle" } +mavenPublish = { id = "com.vanniktech.maven.publish", version.ref = "maven-publish" } +android-library = { id = "com.android.library", version.ref = "android-library" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index da1db5f..17655d0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/mvi-compose/build.gradle.kts b/mvi-compose/build.gradle.kts index ae01fb2..13a2d4c 100644 --- a/mvi-compose/build.gradle.kts +++ b/mvi-compose/build.gradle.kts @@ -1,6 +1,3 @@ -import com.vanniktech.maven.publish.SonatypeHost - -@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlin.android) @@ -13,7 +10,7 @@ kotlin { android { namespace = "com.adidas.mvi.compose" - compileSdk = 33 + compileSdk = 34 defaultConfig { minSdk = 24 @@ -41,7 +38,7 @@ android { } composeOptions { - kotlinCompilerExtensionVersion = "1.4.7" + kotlinCompilerExtensionVersion = "1.5.10" } } diff --git a/mvi-compose/src/main/kotlin/com/adidas/mvi/compose/MviContainer.kt b/mvi-compose/src/main/kotlin/com/adidas/mvi/compose/MviContainer.kt index 168e45f..d1ac516 100644 --- a/mvi-compose/src/main/kotlin/com/adidas/mvi/compose/MviContainer.kt +++ b/mvi-compose/src/main/kotlin/com/adidas/mvi/compose/MviContainer.kt @@ -9,7 +9,7 @@ import kotlinx.coroutines.flow.StateFlow /** * The [MviContainer] is a composable function for implementing the MVI in Jetpack Compose, providing a convenient way to observe the state of your ViewModel. - * The MVIContainer should be used in non-screen composable with a [com.adidas.confirmed.mvi.MviViewModel] attached, such as a ViewPager page that will be + * The MVIContainer should be used in non-screen composable with a ViewModel attached, such as a ViewPager page that will be * included inside of a parent screen. * @param state The current state of the MVI ViewModel. This parameter must be of type [StateFlow]<[State]>, where TViewState is the type * of the view state and TSideEffect is the type of the side effect. diff --git a/mvi-compose/src/main/kotlin/com/adidas/mvi/compose/MviScreen.kt b/mvi-compose/src/main/kotlin/com/adidas/mvi/compose/MviScreen.kt index 7c921b8..783a828 100644 --- a/mvi-compose/src/main/kotlin/com/adidas/mvi/compose/MviScreen.kt +++ b/mvi-compose/src/main/kotlin/com/adidas/mvi/compose/MviScreen.kt @@ -8,7 +8,7 @@ import kotlinx.coroutines.flow.StateFlow /** * The [MviScreen] is a composable function for implementing the MVI Jetpack Compose, providing a convenient way to observe the state of your ViewModel * inside a screen composable. - * @param state The current state of the [MviViewModel]. This parameter must be of type [StateFlow]<[State]>, where TViewState is the type + * @param state The current state of the ViewModel or similar component. This parameter must be of type [StateFlow]<[State]>, where TViewState is the type * of the view state and TSideEffect is the type of the side effect. * @param onSideEffect A lambda function that is called when a side effect is emitted by the MVI architecture. The TSideEffect parameter of the function * is the emitted side effect. diff --git a/mvi/build.gradle.kts b/mvi/build.gradle.kts index e4373cb..0c68413 100644 --- a/mvi/build.gradle.kts +++ b/mvi/build.gradle.kts @@ -1,7 +1,5 @@ import org.jlleitschuh.gradle.ktlint.KtlintExtension -@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed - plugins { kotlin("jvm") version libs.versions.kotlin.get() alias(libs.plugins.ktlint) @@ -18,7 +16,7 @@ compileTestKotlin.kotlinOptions { } configure { - version.set(libs.versions.ktlint.get()) + version.set(libs.versions.ktlint.lib.get()) } tasks.getByName("test") { diff --git a/mvi/src/main/kotlin/com/adidas/mvi/Logger.kt b/mvi/src/main/kotlin/com/adidas/mvi/Logger.kt index b0faeb6..0b17eed 100644 --- a/mvi/src/main/kotlin/com/adidas/mvi/Logger.kt +++ b/mvi/src/main/kotlin/com/adidas/mvi/Logger.kt @@ -2,7 +2,21 @@ package com.adidas.mvi public interface Logger { public fun logIntent(intent: Loggable) - public fun logFailedIntent(intent: Loggable, throwable: Throwable) - public fun logTransformedNewState(transform: Loggable, previousState: Loggable, newState: Loggable) - public fun logFailedTransformNewState(transform: Loggable, state: Loggable, throwable: Throwable) + + public fun logFailedIntent( + intent: Loggable, + throwable: Throwable, + ) + + public fun logTransformedNewState( + transform: Loggable, + previousState: Loggable, + newState: Loggable, + ) + + public fun logFailedTransformNewState( + transform: Loggable, + state: Loggable, + throwable: Throwable, + ) } diff --git a/mvi/src/main/kotlin/com/adidas/mvi/Multimap.kt b/mvi/src/main/kotlin/com/adidas/mvi/Multimap.kt index 1e2e07d..aec0b1b 100644 --- a/mvi/src/main/kotlin/com/adidas/mvi/Multimap.kt +++ b/mvi/src/main/kotlin/com/adidas/mvi/Multimap.kt @@ -9,7 +9,10 @@ internal class Multimap { val keys: Collection get() = Collections.unmodifiableSet(innerMap.keys) - fun put(key: TKey, value: TValue): MultimapEntry { + fun put( + key: TKey, + value: TValue, + ): MultimapEntry { val entry = MultimapEntry(key, value) innerMap.getOrPut(key) { @@ -19,15 +22,19 @@ internal class Multimap { return entry } - private fun putAllReplacing(key: TKey, values: List>) { + private fun putAllReplacing( + key: TKey, + values: List>, + ) { innerMap[key] = MutableList(values.size) { values[it] } } operator fun get(key: TKey): List> = innerMap[key]?.let(Collections::unmodifiableList) ?: listOf() - operator fun get(keyClass: KClass): List> = keys - .filter { keyClass.isInstance(it) } - .flatMap { get(it) } + operator fun get(keyClass: KClass): List> = + keys + .filter { keyClass.isInstance(it) } + .flatMap { get(it) } fun remove(multimapEntry: MultimapEntry) { val key = multimapEntry.key diff --git a/mvi/src/main/kotlin/com/adidas/mvi/MviHost.kt b/mvi/src/main/kotlin/com/adidas/mvi/MviHost.kt index c717518..4c1e204 100644 --- a/mvi/src/main/kotlin/com/adidas/mvi/MviHost.kt +++ b/mvi/src/main/kotlin/com/adidas/mvi/MviHost.kt @@ -8,7 +8,6 @@ import kotlinx.coroutines.flow.Flow * */ public interface MviHost { - public val state: Flow public fun execute(intent: TIntent) diff --git a/mvi/src/main/kotlin/com/adidas/mvi/Reducer.kt b/mvi/src/main/kotlin/com/adidas/mvi/Reducer.kt index f4fccb0..afd2efd 100644 --- a/mvi/src/main/kotlin/com/adidas/mvi/Reducer.kt +++ b/mvi/src/main/kotlin/com/adidas/mvi/Reducer.kt @@ -21,33 +21,34 @@ public class Reducer( initialState: TState, private val logger: Logger? = null, private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default, - private val intentExecutor: IntentExecutor + private val intentExecutor: IntentExecutor, ) where TIntent : Intent, - TState : LoggableState { - - private val _transforms = MutableSharedFlow>() + TState : LoggableState { + private val transforms = MutableSharedFlow>() private val multimap = Multimap() - public val state: StateFlow = _transforms - .scan(initialState, this::reduce) - .stateIn(coroutineScope, SharingStarted.Eagerly, initialState) + public val state: StateFlow = + transforms + .scan(initialState, this::reduce) + .stateIn(coroutineScope, SharingStarted.Eagerly, initialState) public fun executeIntent(intent: TIntent) { logger?.logIntent(intent) - val job = coroutineScope.launch { - if (intent is UniqueIntent) { - cleanIntentJobsOfType(intent::class) - } + val job = + coroutineScope.launch { + if (intent is UniqueIntent) { + cleanIntentJobsOfType(intent::class) + } - try { - _transforms.emitAll(intentExecutor.executeIntent(intent)) - } catch (throwable: Throwable) { - if (coroutineScope.isActive && throwable !is TerminatedIntentException) { - logger?.logFailedIntent(intent, throwable) + try { + transforms.emitAll(intentExecutor.executeIntent(intent)) + } catch (throwable: Throwable) { + if (coroutineScope.isActive && throwable !is TerminatedIntentException) { + logger?.logFailedIntent(intent, throwable) + } } } - } val entry = multimap.put(intent, job) @@ -65,8 +66,10 @@ public class Reducer( public inline fun requireState(): T = state.value as T - @Suppress("FunctionName") - private suspend fun reduce(previousState: TState, transform: StateTransform): TState { + private suspend fun reduce( + previousState: TState, + transform: StateTransform, + ): TState { return try { transform.reduce(previousState, defaultDispatcher).also { newState -> logger?.logTransformedNewState(transform, previousState, newState) diff --git a/mvi/src/main/kotlin/com/adidas/mvi/StateRequiredNotFulfilledException.kt b/mvi/src/main/kotlin/com/adidas/mvi/StateRequiredNotFulfilledException.kt index c0b33ac..0cfab35 100644 --- a/mvi/src/main/kotlin/com/adidas/mvi/StateRequiredNotFulfilledException.kt +++ b/mvi/src/main/kotlin/com/adidas/mvi/StateRequiredNotFulfilledException.kt @@ -4,8 +4,7 @@ import kotlin.reflect.KClass public class StateRequiredNotFulfilledException( expectedState: KClass<*>, - realState: KClass<*> + realState: KClass<*>, ) : Exception() { - override val message: String = "Unexpected state! State was $realState, but the expected was $expectedState" } diff --git a/mvi/src/main/kotlin/com/adidas/mvi/reducer/ReducerExtensions.kt b/mvi/src/main/kotlin/com/adidas/mvi/reducer/ReducerExtensions.kt index c2869a9..adfea9c 100644 --- a/mvi/src/main/kotlin/com/adidas/mvi/reducer/ReducerExtensions.kt +++ b/mvi/src/main/kotlin/com/adidas/mvi/reducer/ReducerExtensions.kt @@ -16,14 +16,14 @@ public fun Reducer( initialInnerState: TInnerState, intentExecutor: IntentExecutor>, logger: Logger? = null, - defaultDispatcher: CoroutineDispatcher = Dispatchers.Default + defaultDispatcher: CoroutineDispatcher = Dispatchers.Default, ): Reducer> { return Reducer( coroutineScope = coroutineScope, initialState = State(initialInnerState, SideEffects()), intentExecutor = intentExecutor, logger = logger, - defaultDispatcher = defaultDispatcher + defaultDispatcher = defaultDispatcher, ) } diff --git a/mvi/src/main/kotlin/com/adidas/mvi/requirements/DoubleReduceRequirement.kt b/mvi/src/main/kotlin/com/adidas/mvi/requirements/DoubleReduceRequirement.kt index 7f234c6..c7b5437 100644 --- a/mvi/src/main/kotlin/com/adidas/mvi/requirements/DoubleReduceRequirement.kt +++ b/mvi/src/main/kotlin/com/adidas/mvi/requirements/DoubleReduceRequirement.kt @@ -2,9 +2,8 @@ package com.adidas.mvi.requirements internal class DoubleReduceRequirement( private val leftReduceRequirement: ReduceRequirement, - private val rightReduceRequirement: ReduceRequirement + private val rightReduceRequirement: ReduceRequirement, ) : ReduceRequirement { - override fun reduce(state: TState): TState { return try { leftReduceRequirement.reduce(state) diff --git a/mvi/src/main/kotlin/com/adidas/mvi/requirements/ReduceExtensions.kt b/mvi/src/main/kotlin/com/adidas/mvi/requirements/ReduceExtensions.kt index 51b3f0b..b2e1505 100644 --- a/mvi/src/main/kotlin/com/adidas/mvi/requirements/ReduceExtensions.kt +++ b/mvi/src/main/kotlin/com/adidas/mvi/requirements/ReduceExtensions.kt @@ -4,13 +4,13 @@ import com.adidas.mvi.LoggableState public inline fun requireAndReduceState( state: TState, - noinline reduce: (TRequiredState) -> TState + noinline reduce: (TRequiredState) -> TState, ): TState { return StateReduceRequirement(TRequiredState::class, reduce).reduce(state) } public inline fun requireState( - noinline reduce: (TRequiredState) -> TState + noinline reduce: (TRequiredState) -> TState, ): ReduceRequirement { return StateReduceRequirement(TRequiredState::class, reduce) } diff --git a/mvi/src/main/kotlin/com/adidas/mvi/requirements/StateReduceRequirement.kt b/mvi/src/main/kotlin/com/adidas/mvi/requirements/StateReduceRequirement.kt index b6a9e91..151e373 100644 --- a/mvi/src/main/kotlin/com/adidas/mvi/requirements/StateReduceRequirement.kt +++ b/mvi/src/main/kotlin/com/adidas/mvi/requirements/StateReduceRequirement.kt @@ -7,9 +7,8 @@ import kotlin.reflect.cast public class StateReduceRequirement( private val expectedState: KClass, - private val reduceFunction: (TRequiredState) -> TState + private val reduceFunction: (TRequiredState) -> TState, ) : ReduceRequirement where TState : LoggableState, TRequiredState : TState { - public override fun reduce(state: TState): TState { if (expectedState.isInstance(state)) { return reduceFunction(expectedState.cast(state)) diff --git a/mvi/src/main/kotlin/com/adidas/mvi/sideeffects/SideEffects.kt b/mvi/src/main/kotlin/com/adidas/mvi/sideeffects/SideEffects.kt index 2da7fe5..3b36754 100644 --- a/mvi/src/main/kotlin/com/adidas/mvi/sideeffects/SideEffects.kt +++ b/mvi/src/main/kotlin/com/adidas/mvi/sideeffects/SideEffects.kt @@ -9,7 +9,6 @@ import java.util.Queue * It locks itself, so you can't add and read at the same time, also it's not possible to read it at the same time from different threads, being completely thread-safe. */ public class SideEffects() : Iterable { - private val sideEffects: Queue = LinkedList() private constructor(sideEffects: Iterable) : this() { @@ -24,10 +23,11 @@ public class SideEffects() : Iterable { return SideEffects() } - override fun iterator(): Iterator = iterator { - do { - val nextSideEffect: T? = sideEffects.poll() - nextSideEffect?.let { yield(it) } - } while (nextSideEffect != null) - } + override fun iterator(): Iterator = + iterator { + do { + val nextSideEffect: T? = sideEffects.poll() + nextSideEffect?.let { yield(it) } + } while (nextSideEffect != null) + } } diff --git a/mvi/src/main/kotlin/com/adidas/mvi/transform/Operators.kt b/mvi/src/main/kotlin/com/adidas/mvi/transform/Operators.kt index d950754..fa7a6b0 100644 --- a/mvi/src/main/kotlin/com/adidas/mvi/transform/Operators.kt +++ b/mvi/src/main/kotlin/com/adidas/mvi/transform/Operators.kt @@ -6,12 +6,18 @@ public fun notOperator(function: (TOwner) -> Boolean): (TOwner) -> Bool } } -public fun equalsOperator(retriever: (TOwner) -> TValue, valueToCompare: TValue): (TOwner) -> Boolean { +public fun equalsOperator( + retriever: (TOwner) -> TValue, + valueToCompare: TValue, +): (TOwner) -> Boolean { return { retriever(it) == valueToCompare } } -public fun notEqualsOperator(retriever: (TOwner) -> TValue, valueToCompare: TValue): (TOwner) -> Boolean { +public fun notEqualsOperator( + retriever: (TOwner) -> TValue, + valueToCompare: TValue, +): (TOwner) -> Boolean { return notOperator(equalsOperator(retriever, valueToCompare)) } diff --git a/mvi/src/main/kotlin/com/adidas/mvi/transform/Replace.kt b/mvi/src/main/kotlin/com/adidas/mvi/transform/Replace.kt index 3adb240..ed1401a 100644 --- a/mvi/src/main/kotlin/com/adidas/mvi/transform/Replace.kt +++ b/mvi/src/main/kotlin/com/adidas/mvi/transform/Replace.kt @@ -1,6 +1,9 @@ package com.adidas.mvi.transform -public fun List.replaceIf(evaluation: (T) -> Boolean, producer: (T) -> T): List { +public fun List.replaceIf( + evaluation: (T) -> Boolean, + producer: (T) -> T, +): List { return map { if (evaluation(it)) { return@map producer(it) @@ -12,7 +15,7 @@ public fun List.replaceIf(evaluation: (T) -> Boolean, producer: (T) -> T) public inline fun List.replaceIfIs( evaluation: (TNew) -> Boolean = { true }, - producer: (TNew) -> TOld + producer: (TNew) -> TOld, ): List { return map { if (it is TNew && evaluation(it)) { diff --git a/mvi/src/main/kotlin/com/adidas/mvi/transform/SideEffectTransform.kt b/mvi/src/main/kotlin/com/adidas/mvi/transform/SideEffectTransform.kt index ae44f63..725a305 100644 --- a/mvi/src/main/kotlin/com/adidas/mvi/transform/SideEffectTransform.kt +++ b/mvi/src/main/kotlin/com/adidas/mvi/transform/SideEffectTransform.kt @@ -5,7 +5,6 @@ import com.adidas.mvi.sideeffects.SideEffects public abstract class SideEffectTransform : StateTransform> { - protected abstract fun mutate(sideEffects: SideEffects): SideEffects final override fun reduce(currentState: State): State { diff --git a/mvi/src/main/kotlin/com/adidas/mvi/transform/StateTransform.kt b/mvi/src/main/kotlin/com/adidas/mvi/transform/StateTransform.kt index 007d050..87f1113 100644 --- a/mvi/src/main/kotlin/com/adidas/mvi/transform/StateTransform.kt +++ b/mvi/src/main/kotlin/com/adidas/mvi/transform/StateTransform.kt @@ -6,7 +6,7 @@ import kotlinx.coroutines.CoroutineDispatcher public interface StateTransform : Loggable { public suspend fun reduce( currentState: TState, - defaultDispatcher: CoroutineDispatcher + defaultDispatcher: CoroutineDispatcher, ): TState { return this.reduce(currentState) } diff --git a/mvi/src/main/kotlin/com/adidas/mvi/transform/ViewTransform.kt b/mvi/src/main/kotlin/com/adidas/mvi/transform/ViewTransform.kt index d7a7c39..a954887 100644 --- a/mvi/src/main/kotlin/com/adidas/mvi/transform/ViewTransform.kt +++ b/mvi/src/main/kotlin/com/adidas/mvi/transform/ViewTransform.kt @@ -3,7 +3,6 @@ package com.adidas.mvi.transform import com.adidas.mvi.State public abstract class ViewTransform : StateTransform> { - protected abstract fun mutate(currentState: TView): TView final override fun reduce(currentState: State): State { diff --git a/mvi/src/test/kotlin/com/adidas/mvi/CoroutineListener.kt b/mvi/src/test/kotlin/com/adidas/mvi/CoroutineListener.kt index 98ed690..44db1e2 100644 --- a/mvi/src/test/kotlin/com/adidas/mvi/CoroutineListener.kt +++ b/mvi/src/test/kotlin/com/adidas/mvi/CoroutineListener.kt @@ -9,16 +9,18 @@ import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.setMain internal class CoroutineListener( - internal val testCoroutineDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher() + internal val testCoroutineDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher(), ) : TestListener { - internal val dispatchersContainer: DispatchersContainer = FixedDispatchersContainer(testCoroutineDispatcher) override suspend fun beforeContainer(testCase: TestCase) { Dispatchers.setMain(testCoroutineDispatcher) } - override suspend fun afterContainer(testCase: TestCase, result: TestResult) { + override suspend fun afterContainer( + testCase: TestCase, + result: TestResult, + ) { Dispatchers.resetMain() testCoroutineDispatcher.cleanupTestCoroutines() } diff --git a/mvi/src/test/kotlin/com/adidas/mvi/DispatchersContainer.kt b/mvi/src/test/kotlin/com/adidas/mvi/DispatchersContainer.kt index cf522fc..a589857 100644 --- a/mvi/src/test/kotlin/com/adidas/mvi/DispatchersContainer.kt +++ b/mvi/src/test/kotlin/com/adidas/mvi/DispatchersContainer.kt @@ -1,3 +1,5 @@ +@file:Suppress("PropertyName") + package com.adidas.mvi import kotlinx.coroutines.CoroutineDispatcher diff --git a/mvi/src/test/kotlin/com/adidas/mvi/FixedDispatchersContainer.kt b/mvi/src/test/kotlin/com/adidas/mvi/FixedDispatchersContainer.kt index ee8b8dd..bf5f20f 100644 --- a/mvi/src/test/kotlin/com/adidas/mvi/FixedDispatchersContainer.kt +++ b/mvi/src/test/kotlin/com/adidas/mvi/FixedDispatchersContainer.kt @@ -1,9 +1,11 @@ +@file:Suppress("PropertyName") + package com.adidas.mvi import kotlinx.coroutines.CoroutineDispatcher internal class FixedDispatchersContainer( - coroutineDispatcher: CoroutineDispatcher + coroutineDispatcher: CoroutineDispatcher, ) : DispatchersContainer { override val Default: CoroutineDispatcher = coroutineDispatcher override val Main: CoroutineDispatcher = coroutineDispatcher diff --git a/mvi/src/test/kotlin/com/adidas/mvi/product/FakeProductSideEffectTransform.kt b/mvi/src/test/kotlin/com/adidas/mvi/product/FakeProductSideEffectTransform.kt index cd096ef..24819be 100644 --- a/mvi/src/test/kotlin/com/adidas/mvi/product/FakeProductSideEffectTransform.kt +++ b/mvi/src/test/kotlin/com/adidas/mvi/product/FakeProductSideEffectTransform.kt @@ -5,7 +5,6 @@ import com.adidas.mvi.transform.SideEffectTransform class FakeProductSideEffectTransform(private val sideEffect: ProductSideEffect) : SideEffectTransform() { - override fun mutate(sideEffects: SideEffects): SideEffects { return sideEffects.add(sideEffect) } diff --git a/mvi/src/test/kotlin/com/adidas/mvi/product/FakeProductViewTransform.kt b/mvi/src/test/kotlin/com/adidas/mvi/product/FakeProductViewTransform.kt index 5926991..42526ca 100644 --- a/mvi/src/test/kotlin/com/adidas/mvi/product/FakeProductViewTransform.kt +++ b/mvi/src/test/kotlin/com/adidas/mvi/product/FakeProductViewTransform.kt @@ -5,7 +5,6 @@ import com.adidas.mvi.transform.ViewTransform class FakeProductViewTransform(private val state: State) : ViewTransform() { - override fun mutate(currentState: ProductState): ProductState { return state.view } diff --git a/mvi/src/test/kotlin/com/adidas/mvi/product/ProductSideEffect.kt b/mvi/src/test/kotlin/com/adidas/mvi/product/ProductSideEffect.kt index 255f7a6..533b7e4 100644 --- a/mvi/src/test/kotlin/com/adidas/mvi/product/ProductSideEffect.kt +++ b/mvi/src/test/kotlin/com/adidas/mvi/product/ProductSideEffect.kt @@ -1,5 +1,5 @@ package com.adidas.mvi.product sealed class ProductSideEffect { - object NavigateToProductDetailsSideEffect : ProductSideEffect() + data object NavigateToProductDetailsSideEffect : ProductSideEffect() } diff --git a/mvi/src/test/kotlin/com/adidas/mvi/product/ProductState.kt b/mvi/src/test/kotlin/com/adidas/mvi/product/ProductState.kt index e49b071..186cc8d 100644 --- a/mvi/src/test/kotlin/com/adidas/mvi/product/ProductState.kt +++ b/mvi/src/test/kotlin/com/adidas/mvi/product/ProductState.kt @@ -3,6 +3,7 @@ package com.adidas.mvi.product import com.adidas.mvi.LoggableState sealed class ProductState : LoggableState { - object Loading : ProductState() - object Loaded : ProductState() + data object Loading : ProductState() + + data object Loaded : ProductState() } diff --git a/mvi/src/test/kotlin/com/adidas/mvi/reducer/ReducerExtensionsTest.kt b/mvi/src/test/kotlin/com/adidas/mvi/reducer/ReducerExtensionsTest.kt index e75ae6f..352c46c 100644 --- a/mvi/src/test/kotlin/com/adidas/mvi/reducer/ReducerExtensionsTest.kt +++ b/mvi/src/test/kotlin/com/adidas/mvi/reducer/ReducerExtensionsTest.kt @@ -11,13 +11,14 @@ 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>>() - } - ) + 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 diff --git a/mvi/src/test/kotlin/com/adidas/mvi/reducer/ReducerTests.kt b/mvi/src/test/kotlin/com/adidas/mvi/reducer/ReducerTests.kt index 01a3ecd..24961bd 100644 --- a/mvi/src/test/kotlin/com/adidas/mvi/reducer/ReducerTests.kt +++ b/mvi/src/test/kotlin/com/adidas/mvi/reducer/ReducerTests.kt @@ -34,15 +34,16 @@ internal class ReducerTests : BehaviorSpec({ listeners(coroutineListener) fun createReducer( - executor: (intent: TestIntent) -> Flow>> = createIntentExecutorContainer( - transform = TestTransform.Transform1 - ) + executor: (intent: TestIntent) -> Flow>> = + createIntentExecutorContainer( + transform = TestTransform.Transform1, + ), ) = Reducer( coroutineScope = TestScope(coroutineListener.testCoroutineDispatcher), initialState = State(TestState.InitialState, SideEffects()), defaultDispatcher = coroutineListener.testCoroutineDispatcher, logger = logger, - intentExecutor = executor + intentExecutor = executor, ) Given("A reducer with a initial state") { @@ -118,12 +119,13 @@ internal class ReducerTests : BehaviorSpec({ } Given("A reducer which produces Transform1 partial state when Transform1Producer intent is sent") { - val reducer = createReducer( - createIntentExecutorContainer( - intent = TestIntent.Transform1Producer, - transform = TestTransform.Transform1 + val reducer = + createReducer( + createIntentExecutorContainer( + intent = TestIntent.Transform1Producer, + transform = TestTransform.Transform1, + ), ) - ) When("I execute Transform1Producer intent") { reducer.executeIntent(TestIntent.Transform1Producer) @@ -159,10 +161,11 @@ internal class ReducerTests : BehaviorSpec({ Given("A class that contains the reducer and it's intent executor has an execution that should run solo") { val testFlow = MutableStateFlow(0) - val reducerWrapper = TestCancellationReducerWrapper( - someTestFlow = testFlow, - coroutineListener = coroutineListener - ) + val reducerWrapper = + TestCancellationReducerWrapper( + someTestFlow = testFlow, + coroutineListener = coroutineListener, + ) When("I execute an intent which kills another intent job") { reducerWrapper.execute(TestIntent.AbelIntent) @@ -186,13 +189,17 @@ internal class ReducerTests : BehaviorSpec({ } }) -private fun createIntentExecutorContainer(executedIntents: MutableList = mutableListOf()): (TestIntent) -> Flow>> = +private fun createIntentExecutorContainer( + executedIntents: MutableList = mutableListOf(), +): (TestIntent) -> Flow>> = { executedIntents.add(it) flowOf(TestTransform.Transform1) } -private fun createIntentExecutorContainer(exception: java.lang.Exception): (TestIntent) -> Flow>> = +private fun createIntentExecutorContainer( + exception: java.lang.Exception, +): (TestIntent) -> Flow>> = { if (it is TestIntent.SimpleIntent) throw exception emptyFlow() @@ -200,7 +207,7 @@ private fun createIntentExecutorContainer(exception: java.lang.Exception): (Test private fun createIntentExecutorContainer( intent: TestIntent = TestIntent.FailedTransformProducer, - transform: TestTransform + transform: TestTransform, ): (TestIntent) -> Flow>> = { when (it) { diff --git a/mvi/src/test/kotlin/com/adidas/mvi/reducer/TestCancellationReducerWrapper.kt b/mvi/src/test/kotlin/com/adidas/mvi/reducer/TestCancellationReducerWrapper.kt index d29fceb..4804df7 100644 --- a/mvi/src/test/kotlin/com/adidas/mvi/reducer/TestCancellationReducerWrapper.kt +++ b/mvi/src/test/kotlin/com/adidas/mvi/reducer/TestCancellationReducerWrapper.kt @@ -14,32 +14,31 @@ import kotlinx.coroutines.test.TestScope internal class TestCancellationReducerWrapper( private val someTestFlow: Flow, - coroutineListener: CoroutineListener + coroutineListener: CoroutineListener, ) { - - private val reducer = Reducer( - coroutineScope = TestScope(coroutineListener.testCoroutineDispatcher), - initialState = State(TestState.InitialState, SideEffects()), - defaultDispatcher = coroutineListener.testCoroutineDispatcher, - logger = SpyLogger(), - intentExecutor = this::executeIntent - ) + private val reducer = + Reducer( + coroutineScope = TestScope(coroutineListener.testCoroutineDispatcher), + initialState = State(TestState.InitialState, SideEffects()), + defaultDispatcher = coroutineListener.testCoroutineDispatcher, + logger = SpyLogger(), + intentExecutor = this::executeIntent, + ) val state = reducer.state fun execute(intent: TestIntent) { reducer.executeIntent(intent) } - private fun executeIntent( - intent: TestIntent - ): Flow { + private fun executeIntent(intent: TestIntent): Flow { return when (intent) { TestIntent.AbelIntent -> someTestFlow.map { TestTransform.AbelTransform } - TestIntent.CainIntent -> flowOf(TestTransform.CainTransform).onStart { - reducer.cleanIntentJobsOfType( - TestIntent.AbelIntent::class - ) - } + TestIntent.CainIntent -> + flowOf(TestTransform.CainTransform).onStart { + reducer.cleanIntentJobsOfType( + TestIntent.AbelIntent::class, + ) + } is TestIntent.UniqueTransformIntent -> someTestFlow.map { TestTransform.UniqueTransform(intent.id) } else -> emptyFlow() diff --git a/mvi/src/test/kotlin/com/adidas/mvi/reducer/TestIntent.kt b/mvi/src/test/kotlin/com/adidas/mvi/reducer/TestIntent.kt index 17b91d5..d71a8a9 100644 --- a/mvi/src/test/kotlin/com/adidas/mvi/reducer/TestIntent.kt +++ b/mvi/src/test/kotlin/com/adidas/mvi/reducer/TestIntent.kt @@ -4,14 +4,15 @@ import com.adidas.mvi.Intent import com.adidas.mvi.UniqueIntent internal sealed class TestIntent : Intent { - object SimpleIntent : TestIntent() + data object SimpleIntent : TestIntent() - object Transform1Producer : TestIntent() + data object Transform1Producer : TestIntent() - object FailedTransformProducer : TestIntent() + data object FailedTransformProducer : TestIntent() - object AbelIntent : TestIntent() - object CainIntent : TestIntent() + data object AbelIntent : TestIntent() + + data object CainIntent : TestIntent() data class UniqueTransformIntent(val id: Int) : TestIntent(), UniqueIntent } diff --git a/mvi/src/test/kotlin/com/adidas/mvi/reducer/TestState.kt b/mvi/src/test/kotlin/com/adidas/mvi/reducer/TestState.kt index a083847..dfed1c2 100644 --- a/mvi/src/test/kotlin/com/adidas/mvi/reducer/TestState.kt +++ b/mvi/src/test/kotlin/com/adidas/mvi/reducer/TestState.kt @@ -3,13 +3,15 @@ package com.adidas.mvi.reducer import com.adidas.mvi.LoggableState internal sealed class TestState : LoggableState { - object InitialState : TestState() + data object InitialState : TestState() - object StateFromTransform1 : TestState() + data object StateFromTransform1 : TestState() - object AbelState : TestState() - object CainState : TestState() + data object AbelState : TestState() - object ImpossibleState : TestState() - object UniqueTransformState : TestState() + data object CainState : TestState() + + data object ImpossibleState : TestState() + + data object UniqueTransformState : TestState() } diff --git a/mvi/src/test/kotlin/com/adidas/mvi/reducer/TestTransform.kt b/mvi/src/test/kotlin/com/adidas/mvi/reducer/TestTransform.kt index 771594e..d23119d 100644 --- a/mvi/src/test/kotlin/com/adidas/mvi/reducer/TestTransform.kt +++ b/mvi/src/test/kotlin/com/adidas/mvi/reducer/TestTransform.kt @@ -6,25 +6,25 @@ internal const val IMPOSSIBLE_INTENT_ID = 1 internal const val UNIQUE_INTENT_ID = 2 internal sealed class TestTransform : ViewTransform() { - object Transform1 : TestTransform() { + data object Transform1 : TestTransform() { override fun mutate(currentState: TestState): TestState { return TestState.StateFromTransform1 } } - object FailedTransform : TestTransform() { + data object FailedTransform : TestTransform() { override fun mutate(currentState: TestState): TestState { throw Exception() } } - object AbelTransform : TestTransform() { + data object AbelTransform : TestTransform() { override fun mutate(currentState: TestState): TestState { return TestState.AbelState } } - object CainTransform : TestTransform() { + data object CainTransform : TestTransform() { override fun mutate(currentState: TestState): TestState { return TestState.CainState } diff --git a/mvi/src/test/kotlin/com/adidas/mvi/reducer/logger/SpyLogger.kt b/mvi/src/test/kotlin/com/adidas/mvi/reducer/logger/SpyLogger.kt index 3f029c2..b1439be 100644 --- a/mvi/src/test/kotlin/com/adidas/mvi/reducer/logger/SpyLogger.kt +++ b/mvi/src/test/kotlin/com/adidas/mvi/reducer/logger/SpyLogger.kt @@ -11,7 +11,6 @@ internal const val SUCCESSFUL_TRANSFORM = "SuccessfulTransform:" internal const val FAILED_TRANSFORM = "FailedTransform:" class SpyLogger : Logger { - var history = mutableListOf() override fun logIntent(intent: Loggable) { @@ -20,22 +19,29 @@ class SpyLogger : Logger { StringBuilder().apply { append(SUCCESSFUL_INTENT) append(intent.toString()) - }.toString() + }.toString(), ) } - override fun logFailedIntent(intent: Loggable, throwable: Throwable) { + override fun logFailedIntent( + intent: Loggable, + throwable: Throwable, + ) { log( StringBuilder().apply { append(FAILED_INTENT) append(intent.toString()) append(SPACE) append(throwable) - }.toString() + }.toString(), ) } - override fun logTransformedNewState(transform: Loggable, previousState: Loggable, newState: Loggable) { + override fun logTransformedNewState( + transform: Loggable, + previousState: Loggable, + newState: Loggable, + ) { log( StringBuilder().apply { append(SUCCESSFUL_TRANSFORM) @@ -44,11 +50,15 @@ class SpyLogger : Logger { append(previousState.toString()) append(SPACE) append(newState.toString()) - }.toString() + }.toString(), ) } - override fun logFailedTransformNewState(transform: Loggable, state: Loggable, throwable: Throwable) { + override fun logFailedTransformNewState( + transform: Loggable, + state: Loggable, + throwable: Throwable, + ) { log( StringBuilder().apply { append(FAILED_TRANSFORM) @@ -57,7 +67,7 @@ class SpyLogger : Logger { append(state.toString()) append(SPACE) append(throwable) - }.toString() + }.toString(), ) } diff --git a/mvi/src/test/kotlin/com/adidas/mvi/requirements/ReduceRequirementTests.kt b/mvi/src/test/kotlin/com/adidas/mvi/requirements/ReduceRequirementTests.kt index c64ce74..ad1205d 100644 --- a/mvi/src/test/kotlin/com/adidas/mvi/requirements/ReduceRequirementTests.kt +++ b/mvi/src/test/kotlin/com/adidas/mvi/requirements/ReduceRequirementTests.kt @@ -9,9 +9,11 @@ import io.kotest.matchers.shouldBe import io.kotest.matchers.types.shouldBeInstanceOf private sealed class TestState : LoggableState { - object State1 : TestState() - object State2 : TestState() - object State3 : TestState() + data object State1 : TestState() + + data object State2 : TestState() + + data object State3 : TestState() } internal class ReduceRequirementTests : BehaviorSpec({ @@ -20,9 +22,10 @@ internal class ReduceRequirementTests : BehaviorSpec({ given("A state requirement") { - val stateReduceRequirement = StateReduceRequirement(TestState.State1::class) { - TestState.State2 - } + val stateReduceRequirement = + StateReduceRequirement(TestState.State1::class) { + TestState.State2 + } `when`("I try to convert using the required state") { val newState = stateReduceRequirement.reduce(TestState.State1) @@ -88,7 +91,10 @@ internal class ReduceRequirementTests : BehaviorSpec({ } given("The or extension and two requirements produced by the requireState extension") { - val reduceRequirement = requireState { TestState.State2 } or requireState { TestState.State3 } + val reduceRequirement = + requireState { + TestState.State2 + } or requireState { TestState.State3 } `when`("I try to use with a state required by the right requirement") { val newState = reduceRequirement.reduce(TestState.State2) @@ -100,13 +106,16 @@ internal class ReduceRequirementTests : BehaviorSpec({ } given("The or extension and three requirements produced by the requireState extension") { - val reduceRequirement = requireState { - TestState.State3 - } or requireState { - TestState.State1 - } or requireState { - TestState.State2 - } + val reduceRequirement = + requireState { + TestState.State3 + } or + requireState { + TestState.State1 + } or + requireState { + TestState.State2 + } `when`("I try to use with a state required by the last, third requirement") { val newState = reduceRequirement.reduce(TestState.State3) @@ -119,9 +128,10 @@ internal class ReduceRequirementTests : BehaviorSpec({ given("The requireAndReduceState") { `when`("I try to use it with a correct state") { - val newState = requireAndReduceState(TestState.State1 as TestState) { - TestState.State2 - } + val newState = + requireAndReduceState(TestState.State1 as TestState) { + TestState.State2 + } then("The state should be transformed") { newState shouldBe TestState.State2 diff --git a/mvi/src/test/kotlin/com/adidas/mvi/sideeffects/SideEffectsTest.kt b/mvi/src/test/kotlin/com/adidas/mvi/sideeffects/SideEffectsTest.kt index 71f8e35..0013a78 100644 --- a/mvi/src/test/kotlin/com/adidas/mvi/sideeffects/SideEffectsTest.kt +++ b/mvi/src/test/kotlin/com/adidas/mvi/sideeffects/SideEffectsTest.kt @@ -11,10 +11,8 @@ import kotlin.time.DurationUnit import kotlin.time.ExperimentalTime import kotlin.time.toDuration -private class TestSideEffect - @ExperimentalTime -internal class SideEffectsTests : BehaviorSpec({ +internal class SideEffectsTest : BehaviorSpec({ given("An empty SideEffects") { val sideEffects = SideEffects() @@ -70,22 +68,27 @@ internal class SideEffectsTests : BehaviorSpec({ val semaphore = Semaphore(0) - val readThread = thread { - returnedSideEffects.forEach { _ -> - semaphore.acquire() + val readThread = + thread { + returnedSideEffects.forEach { _ -> + semaphore.acquire() + } } - } - val addThread = thread { - returnedSideEffects = returnedSideEffects.add(secondSideEffectToBeAddedLater) - } + val addThread = + thread { + returnedSideEffects = returnedSideEffects.add(secondSideEffectToBeAddedLater) + } semaphore.release() - then("It should be released only by the semaphore").config(timeout = 5.toDuration(DurationUnit.SECONDS)) { - @Suppress("BlockingMethodInNonBlockingContext") + then("It should be released only by the semaphore").config( + timeout = + 5.toDuration( + DurationUnit.SECONDS, + ), + ) { readThread.join() - @Suppress("BlockingMethodInNonBlockingContext") addThread.join() readThread.isAlive.shouldBeFalse() diff --git a/mvi/src/test/kotlin/com/adidas/mvi/sideeffects/TestSideEffect.kt b/mvi/src/test/kotlin/com/adidas/mvi/sideeffects/TestSideEffect.kt new file mode 100644 index 0000000..adfc392 --- /dev/null +++ b/mvi/src/test/kotlin/com/adidas/mvi/sideeffects/TestSideEffect.kt @@ -0,0 +1,3 @@ +package com.adidas.mvi.sideeffects + +internal class TestSideEffect diff --git a/mvi/src/test/kotlin/com/adidas/mvi/transform/ReplaceTests.kt b/mvi/src/test/kotlin/com/adidas/mvi/transform/ReplaceTests.kt index 69ae09e..55125a0 100644 --- a/mvi/src/test/kotlin/com/adidas/mvi/transform/ReplaceTests.kt +++ b/mvi/src/test/kotlin/com/adidas/mvi/transform/ReplaceTests.kt @@ -9,9 +9,11 @@ import io.kotest.matchers.shouldNotBe private data class ReplaceTestNumberContainerObject(val number: Int) private sealed class ReplaceTestComplexObject { - object Child1 : ReplaceTestComplexObject() - object Child2 : ReplaceTestComplexObject() - object Child3 : ReplaceTestComplexObject() + data object Child1 : ReplaceTestComplexObject() + + data object Child2 : ReplaceTestComplexObject() + + data object Child3 : ReplaceTestComplexObject() } internal class ReplaceTests : BehaviorSpec({ @@ -22,15 +24,16 @@ internal class ReplaceTests : BehaviorSpec({ val numberContainers = (1..3).map { ReplaceTestNumberContainerObject(it) } `when`("I try to replace containers which have 1 with 0") { - val replaced = numberContainers.replaceIf(equalsOperator(ReplaceTestNumberContainerObject::number, 1)) { - it.copy(number = 0) - } + val replaced = + numberContainers.replaceIf(equalsOperator(ReplaceTestNumberContainerObject::number, 1)) { + it.copy(number = 0) + } then("The first item should have a number 0") { replaced.shouldContainInOrder( ReplaceTestNumberContainerObject(0), ReplaceTestNumberContainerObject(2), - ReplaceTestNumberContainerObject(3) + ReplaceTestNumberContainerObject(3), ) } @@ -44,14 +47,15 @@ internal class ReplaceTests : BehaviorSpec({ val complexObjects = listOf(ReplaceTestComplexObject.Child1, ReplaceTestComplexObject.Child2) `when`("I try to replace Child1 with Child3") { - val replaced = complexObjects.replaceIfIs { _: ReplaceTestComplexObject.Child1 -> - ReplaceTestComplexObject.Child3 - } + val replaced = + complexObjects.replaceIfIs { _: ReplaceTestComplexObject.Child1 -> + ReplaceTestComplexObject.Child3 + } then("The first item should be Child 3") { replaced.shouldContainInOrder( ReplaceTestComplexObject.Child3, - ReplaceTestComplexObject.Child2 + ReplaceTestComplexObject.Child2, ) } @@ -61,9 +65,10 @@ internal class ReplaceTests : BehaviorSpec({ } `when`("I try to replace Child1 with Child3 but using a filter which will fail for all items") { - val replaced = complexObjects.replaceIfIs({ false }) { _: ReplaceTestComplexObject.Child1 -> - ReplaceTestComplexObject.Child3 - } + val replaced = + complexObjects.replaceIfIs({ false }) { _: ReplaceTestComplexObject.Child1 -> + ReplaceTestComplexObject.Child3 + } then("No item should be replaced") { replaced shouldBe complexObjects