mirror of
https://github.com/YTVanced/VancedManager
synced 2024-11-24 20:25:13 +00:00
improvement/core-mvi write tests
This commit is contained in:
parent
17c5e04143
commit
5c478a7f5a
14 changed files with 312 additions and 3 deletions
|
@ -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"
|
||||
}
|
|
@ -79,7 +79,7 @@ private class MviFlowImpl<State, Action, Modification, SideEffect>(
|
|||
also {
|
||||
onEach { modification ->
|
||||
withLock {
|
||||
state.value = reducer.invoke(state.value, modification)
|
||||
reducer.invoke(state, state.value, modification)
|
||||
}
|
||||
}.launchIn(this@MviFlowImpl)
|
||||
}
|
|
@ -10,7 +10,7 @@ typealias Handler<State, Action, Modification, SideEffect> =
|
|||
) -> Unit
|
||||
|
||||
typealias Reducer<State, Modification> =
|
||||
suspend (
|
||||
suspend MutableSharedFlow<State>.(
|
||||
state: State,
|
||||
modification: Modification
|
||||
) -> State
|
||||
) -> Unit
|
|
@ -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<String>(
|
||||
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<Long>(
|
||||
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<Test>(
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package com.vanced.manager.core.mvi.subject
|
||||
|
||||
// "single events"
|
||||
sealed class SideEffect {
|
||||
|
||||
data class ShowToast<testData>(val message: testData) : SideEffect()
|
||||
}
|
||||
|
||||
// view state
|
||||
sealed class State {
|
||||
|
||||
object Default : State()
|
||||
|
||||
data class SetTitle<testData>(val text: testData) : State()
|
||||
|
||||
data class SetDescription<testData>(val text: testData) : State()
|
||||
}
|
||||
|
||||
// view actions
|
||||
sealed class Action {
|
||||
|
||||
object Click : Action()
|
||||
|
||||
object Tap : Action()
|
||||
}
|
||||
|
||||
// Modification for view
|
||||
sealed class Modification {
|
||||
|
||||
data class ChangeTitle<testData>(val text: testData) : Modification()
|
||||
|
||||
data class ChangeDescription<testData>(val text: testData) : Modification()
|
||||
}
|
|
@ -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<TD>(
|
||||
scope: CoroutineScope,
|
||||
defaultState: State,
|
||||
private val testData: TD
|
||||
) : MviFlowStore<State, Action, Modification, SideEffect> {
|
||||
|
||||
val modifications = mutableListOf<Modification>()
|
||||
|
||||
private val handler: Handler<State, Action, Modification, SideEffect> =
|
||||
{ state: State, action: Action, sideEffectsFlow: MutableSharedFlow<SideEffect> ->
|
||||
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, Modification> =
|
||||
{ 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<State, Action, Modification, SideEffect> =
|
||||
MviFlow(
|
||||
scope = scope,
|
||||
initialState = defaultState,
|
||||
handler = handler,
|
||||
reducer = reducer
|
||||
)
|
||||
}
|
|
@ -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<TD>(
|
||||
scope: CoroutineScope,
|
||||
private val actions: Flow<Action>,
|
||||
store: SubjectStore<TD>
|
||||
) : MviRenderView<State, Action, SideEffect> {
|
||||
|
||||
val states = mutableListOf<State>()
|
||||
val sideEffects = mutableListOf<SideEffect>()
|
||||
|
||||
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<Action> = actions
|
||||
|
||||
override fun sideEffects(sideEffect: SideEffect) {
|
||||
sideEffects.add(sideEffect)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue