From 5c478a7f5a95af53006725507d1eb9fc05df0079 Mon Sep 17 00:00:00 2001 From: HaliksaR Date: Mon, 30 Nov 2020 16:08:14 +0700 Subject: [PATCH] improvement/core-mvi write tests --- core-mvi/build.gradle | 10 ++ .../com/vanced/manager/core/mvi/MviFlow.kt | 2 +- .../vanced/manager/core/mvi/MviFlowStore.kt | 0 .../vanced/manager/core/mvi/MviRenderView.kt | 0 .../com/vanced/manager/core/mvi/Typealias.kt | 4 +- .../example/ExampleManyStoreFragment.kt | 0 .../example/ExampleStoreFragment.kt | 0 .../example/ExampleViewModel.kt | 0 .../example/ExampleWithViewModelFragment.kt | 0 .../src/main/{java => kotlin}/example/Mvi.kt | 0 .../vanced/manager/core/mvi/MviFlowSpec.kt | 169 ++++++++++++++++++ .../manager/core/mvi/subject/MviSubject.kt | 33 ++++ .../manager/core/mvi/subject/SubjectStore.kt | 54 ++++++ .../manager/core/mvi/subject/SubjectView.kt | 43 +++++ 14 files changed, 312 insertions(+), 3 deletions(-) rename core-mvi/src/main/{java => kotlin}/com/vanced/manager/core/mvi/MviFlow.kt (97%) rename core-mvi/src/main/{java => kotlin}/com/vanced/manager/core/mvi/MviFlowStore.kt (100%) rename core-mvi/src/main/{java => kotlin}/com/vanced/manager/core/mvi/MviRenderView.kt (100%) rename core-mvi/src/main/{java => kotlin}/com/vanced/manager/core/mvi/Typealias.kt (87%) rename core-mvi/src/main/{java => kotlin}/example/ExampleManyStoreFragment.kt (100%) rename core-mvi/src/main/{java => kotlin}/example/ExampleStoreFragment.kt (100%) rename core-mvi/src/main/{java => kotlin}/example/ExampleViewModel.kt (100%) rename core-mvi/src/main/{java => kotlin}/example/ExampleWithViewModelFragment.kt (100%) rename core-mvi/src/main/{java => kotlin}/example/Mvi.kt (100%) create mode 100644 core-mvi/src/test/kotlin/com/vanced/manager/core/mvi/MviFlowSpec.kt create mode 100644 core-mvi/src/test/kotlin/com/vanced/manager/core/mvi/subject/MviSubject.kt create mode 100644 core-mvi/src/test/kotlin/com/vanced/manager/core/mvi/subject/SubjectStore.kt create mode 100644 core-mvi/src/test/kotlin/com/vanced/manager/core/mvi/subject/SubjectView.kt diff --git a/core-mvi/build.gradle b/core-mvi/build.gradle index 0aa50cd2..3beb9d8a 100644 --- a/core-mvi/build.gradle +++ b/core-mvi/build.gradle @@ -7,7 +7,17 @@ java { targetCompatibility = JavaVersion.VERSION_1_8 } +test { + useJUnitPlatform() +} + dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1" + + testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.1' + testImplementation 'io.kotest:kotest-runner-junit5:4.3.1' + testImplementation 'io.kotest:kotest-assertions-core:4.3.1' + testImplementation 'io.kotest:kotest-property:4.3.1' + testImplementation "io.mockk:mockk:1.10.2" } \ No newline at end of file diff --git a/core-mvi/src/main/java/com/vanced/manager/core/mvi/MviFlow.kt b/core-mvi/src/main/kotlin/com/vanced/manager/core/mvi/MviFlow.kt similarity index 97% rename from core-mvi/src/main/java/com/vanced/manager/core/mvi/MviFlow.kt rename to core-mvi/src/main/kotlin/com/vanced/manager/core/mvi/MviFlow.kt index 71160bcf..2d583ca0 100644 --- a/core-mvi/src/main/java/com/vanced/manager/core/mvi/MviFlow.kt +++ b/core-mvi/src/main/kotlin/com/vanced/manager/core/mvi/MviFlow.kt @@ -79,7 +79,7 @@ private class MviFlowImpl( also { onEach { modification -> withLock { - state.value = reducer.invoke(state.value, modification) + reducer.invoke(state, state.value, modification) } }.launchIn(this@MviFlowImpl) } diff --git a/core-mvi/src/main/java/com/vanced/manager/core/mvi/MviFlowStore.kt b/core-mvi/src/main/kotlin/com/vanced/manager/core/mvi/MviFlowStore.kt similarity index 100% rename from core-mvi/src/main/java/com/vanced/manager/core/mvi/MviFlowStore.kt rename to core-mvi/src/main/kotlin/com/vanced/manager/core/mvi/MviFlowStore.kt diff --git a/core-mvi/src/main/java/com/vanced/manager/core/mvi/MviRenderView.kt b/core-mvi/src/main/kotlin/com/vanced/manager/core/mvi/MviRenderView.kt similarity index 100% rename from core-mvi/src/main/java/com/vanced/manager/core/mvi/MviRenderView.kt rename to core-mvi/src/main/kotlin/com/vanced/manager/core/mvi/MviRenderView.kt diff --git a/core-mvi/src/main/java/com/vanced/manager/core/mvi/Typealias.kt b/core-mvi/src/main/kotlin/com/vanced/manager/core/mvi/Typealias.kt similarity index 87% rename from core-mvi/src/main/java/com/vanced/manager/core/mvi/Typealias.kt rename to core-mvi/src/main/kotlin/com/vanced/manager/core/mvi/Typealias.kt index a07626db..b108ef2a 100644 --- a/core-mvi/src/main/java/com/vanced/manager/core/mvi/Typealias.kt +++ b/core-mvi/src/main/kotlin/com/vanced/manager/core/mvi/Typealias.kt @@ -10,7 +10,7 @@ typealias Handler = ) -> Unit typealias Reducer = - suspend ( + suspend MutableSharedFlow.( state: State, modification: Modification - ) -> State \ No newline at end of file + ) -> Unit \ No newline at end of file diff --git a/core-mvi/src/main/java/example/ExampleManyStoreFragment.kt b/core-mvi/src/main/kotlin/example/ExampleManyStoreFragment.kt similarity index 100% rename from core-mvi/src/main/java/example/ExampleManyStoreFragment.kt rename to core-mvi/src/main/kotlin/example/ExampleManyStoreFragment.kt diff --git a/core-mvi/src/main/java/example/ExampleStoreFragment.kt b/core-mvi/src/main/kotlin/example/ExampleStoreFragment.kt similarity index 100% rename from core-mvi/src/main/java/example/ExampleStoreFragment.kt rename to core-mvi/src/main/kotlin/example/ExampleStoreFragment.kt diff --git a/core-mvi/src/main/java/example/ExampleViewModel.kt b/core-mvi/src/main/kotlin/example/ExampleViewModel.kt similarity index 100% rename from core-mvi/src/main/java/example/ExampleViewModel.kt rename to core-mvi/src/main/kotlin/example/ExampleViewModel.kt diff --git a/core-mvi/src/main/java/example/ExampleWithViewModelFragment.kt b/core-mvi/src/main/kotlin/example/ExampleWithViewModelFragment.kt similarity index 100% rename from core-mvi/src/main/java/example/ExampleWithViewModelFragment.kt rename to core-mvi/src/main/kotlin/example/ExampleWithViewModelFragment.kt diff --git a/core-mvi/src/main/java/example/Mvi.kt b/core-mvi/src/main/kotlin/example/Mvi.kt similarity index 100% rename from core-mvi/src/main/java/example/Mvi.kt rename to core-mvi/src/main/kotlin/example/Mvi.kt diff --git a/core-mvi/src/test/kotlin/com/vanced/manager/core/mvi/MviFlowSpec.kt b/core-mvi/src/test/kotlin/com/vanced/manager/core/mvi/MviFlowSpec.kt new file mode 100644 index 00000000..854888c2 --- /dev/null +++ b/core-mvi/src/test/kotlin/com/vanced/manager/core/mvi/MviFlowSpec.kt @@ -0,0 +1,169 @@ +package com.vanced.manager.core.mvi + +import com.vanced.manager.core.mvi.subject.* +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.matchers.shouldBe +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.test.TestCoroutineScope + +@ExperimentalCoroutinesApi +class MviFlowSpec : ShouldSpec() { + + data class Test(val msg: String) + + init { + context("Various events") { + should("return Modifications") { + val testData = "testText" + val store = SubjectStore( + scope = TestCoroutineScope(), + defaultState = State.Default, + testData = testData + ) + SubjectView( + scope = TestCoroutineScope(), + store = store, + actions = flow { + emit(Action.Click) + emit(Action.Tap) + emit(Action.Click) + } + ) + delay(600) + store.modifications shouldBe listOf( + Modification.ChangeDescription(testData), + Modification.ChangeTitle(testData), + Modification.ChangeDescription(testData), + ) + } + should("return States") { + val testData = 4545454545 + val store = SubjectStore( + scope = TestCoroutineScope(), + defaultState = State.Default, + testData = testData + ) + val view = SubjectView( + scope = TestCoroutineScope(), + store = store, + actions = flow { + emit(Action.Click) + emit(Action.Tap) + emit(Action.Click) + } + ) + delay(600) + view.states shouldBe listOf( + State.Default, + State.SetDescription(testData), + State.Default, + State.SetTitle(testData), + State.Default, + State.SetDescription(testData), + State.Default, + ) + } + should("return SideEffects") { + val testData = Test("test") + val store = SubjectStore( + scope = TestCoroutineScope(), + defaultState = State.Default, + testData = testData + ) + val view = SubjectView( + scope = TestCoroutineScope(), + store = store, + actions = flow { + emit(Action.Click) + emit(Action.Tap) + emit(Action.Click) + } + ) + delay(600) + view.sideEffects shouldBe listOf( + SideEffect.ShowToast(testData), + SideEffect.ShowToast(testData), + SideEffect.ShowToast(testData), + ) + } + } + + context("The same event") { + should("return Modifications") { + val testData = Test("test") + val store = SubjectStore( + scope = TestCoroutineScope(), + defaultState = State.Default, + testData = testData + ) + SubjectView( + scope = TestCoroutineScope(), + store = store, + actions = flow { + emit(Action.Click) + emit(Action.Click) + emit(Action.Click) + } + ) + delay(600) + store.modifications shouldBe listOf( + Modification.ChangeDescription(testData), + Modification.ChangeDescription(testData), + Modification.ChangeDescription(testData), + ) + } + should("return States") { + val testData = Test("test") + val store = SubjectStore( + scope = TestCoroutineScope(), + defaultState = State.Default, + testData = testData + ) + val view = SubjectView( + scope = TestCoroutineScope(), + store = store, + actions = flow { + emit(Action.Click) + emit(Action.Click) + emit(Action.Click) + } + ) + delay(600) + view.states shouldBe listOf( + State.Default, + State.SetDescription(testData), + State.Default, + State.SetDescription(testData), + State.Default, + State.SetDescription(testData), + State.Default + ) + } + should("return SideEffects") { + val testData = Test("test") + val store = SubjectStore( + scope = TestCoroutineScope(), + defaultState = State.Default, + testData = testData + ) + val view = SubjectView( + scope = TestCoroutineScope(), + store = store, + actions = flow { + emit(Action.Click) + emit(Action.Click) + emit(Action.Click) + } + ) + delay(600) + view.sideEffects shouldBe listOf( + SideEffect.ShowToast(testData), + SideEffect.ShowToast(testData), + SideEffect.ShowToast(testData), + ) + } + } + } +} \ No newline at end of file diff --git a/core-mvi/src/test/kotlin/com/vanced/manager/core/mvi/subject/MviSubject.kt b/core-mvi/src/test/kotlin/com/vanced/manager/core/mvi/subject/MviSubject.kt new file mode 100644 index 00000000..aafab30c --- /dev/null +++ b/core-mvi/src/test/kotlin/com/vanced/manager/core/mvi/subject/MviSubject.kt @@ -0,0 +1,33 @@ +package com.vanced.manager.core.mvi.subject + +// "single events" +sealed class SideEffect { + + data class ShowToast(val message: testData) : SideEffect() +} + +// view state +sealed class State { + + object Default : State() + + data class SetTitle(val text: testData) : State() + + data class SetDescription(val text: testData) : State() +} + +// view actions +sealed class Action { + + object Click : Action() + + object Tap : Action() +} + +// Modification for view +sealed class Modification { + + data class ChangeTitle(val text: testData) : Modification() + + data class ChangeDescription(val text: testData) : Modification() +} \ No newline at end of file diff --git a/core-mvi/src/test/kotlin/com/vanced/manager/core/mvi/subject/SubjectStore.kt b/core-mvi/src/test/kotlin/com/vanced/manager/core/mvi/subject/SubjectStore.kt new file mode 100644 index 00000000..7c0596f6 --- /dev/null +++ b/core-mvi/src/test/kotlin/com/vanced/manager/core/mvi/subject/SubjectStore.kt @@ -0,0 +1,54 @@ +package com.vanced.manager.core.mvi.subject + +import com.vanced.manager.core.mvi.Handler +import com.vanced.manager.core.mvi.MviFlow +import com.vanced.manager.core.mvi.MviFlowStore +import com.vanced.manager.core.mvi.Reducer +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableSharedFlow + +class SubjectStore( + scope: CoroutineScope, + defaultState: State, + private val testData: TD +) : MviFlowStore { + + val modifications = mutableListOf() + + private val handler: Handler = + { state: State, action: Action, sideEffectsFlow: MutableSharedFlow -> + when (action) { + Action.Click -> { + emit(Modification.ChangeDescription(testData)) + sideEffectsFlow.emit(SideEffect.ShowToast(testData)) + } + Action.Tap -> { + emit(Modification.ChangeTitle(testData)) + sideEffectsFlow.emit(SideEffect.ShowToast(testData)) + } + } + } + + private val reducer: Reducer = + { state: State, modification: Modification -> + modifications.add(modification) + when (modification) { + is Modification.ChangeDescription<*> -> { + emit(State.SetDescription(modification.text)) + emit(State.Default) + } + is Modification.ChangeTitle<*> -> { + emit(State.SetTitle(modification.text)) + emit(State.Default) + } + } + } + + override val store: MviFlow = + MviFlow( + scope = scope, + initialState = defaultState, + handler = handler, + reducer = reducer + ) +} \ No newline at end of file diff --git a/core-mvi/src/test/kotlin/com/vanced/manager/core/mvi/subject/SubjectView.kt b/core-mvi/src/test/kotlin/com/vanced/manager/core/mvi/subject/SubjectView.kt new file mode 100644 index 00000000..7a0dc1d7 --- /dev/null +++ b/core-mvi/src/test/kotlin/com/vanced/manager/core/mvi/subject/SubjectView.kt @@ -0,0 +1,43 @@ +package com.vanced.manager.core.mvi.subject + +import com.vanced.manager.core.mvi.MviRenderView +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.launch + +class SubjectView( + scope: CoroutineScope, + private val actions: Flow, + store: SubjectStore +) : MviRenderView { + + val states = mutableListOf() + val sideEffects = mutableListOf() + + init { + scope.launch { + store.store.bindSideEffects( + view = this@SubjectView, + scope = this + ) + } + scope.launch { + store.store.bindView( + view = this@SubjectView, + scope = this + ) + } + } + + override fun render(state: State) { + states.add(state) + } + + @ExperimentalCoroutinesApi + override fun actionsFlow(): Flow = actions + + override fun sideEffects(sideEffect: SideEffect) { + sideEffects.add(sideEffect) + } +} \ No newline at end of file