From c7112297b8579424e2f6eb7238f15c8af6e8792a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolf-Martell=20Montwe=CC=81?= Date: Thu, 11 May 2023 12:35:29 +0200 Subject: [PATCH] Change AccountSetup to use a contract that follows the unidirectional data flow --- .../preview/navigation/FeatureNavHost.kt | 4 +- feature/account/setup/build.gradle.kts | 2 + .../navigation/AccountSetupNavigation.kt | 8 +- .../account/setup/ui/AccountSetupContract.kt | 28 ++++ .../account/setup/ui/AccountSetupScreen.kt | 60 ++++----- .../account/setup/ui/AccountSetupViewModel.kt | 51 +++++++- .../ui/autoconfig/AccountAutoConfigContent.kt | 5 +- .../AccountManualConfigContent.kt | 5 +- .../setup/ui/options/AccountOptionsContent.kt | 5 +- .../setup/ui/AccountSetupScreenKtTest.kt | 69 ++++++++++ .../account/setup/ui/AccountSetupStateTest.kt | 20 +++ .../setup/ui/AccountSetupViewModelTest.kt | 122 ++++++++++++++++++ .../setup/ui/FakeAccountSetupViewModel.kt | 26 ++++ gradle/libs.versions.toml | 1 + 14 files changed, 362 insertions(+), 44 deletions(-) create mode 100644 feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupContract.kt create mode 100644 feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupScreenKtTest.kt create mode 100644 feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupStateTest.kt create mode 100644 feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupViewModelTest.kt create mode 100644 feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/FakeAccountSetupViewModel.kt diff --git a/app-feature-preview/src/main/java/app/k9mail/feature/preview/navigation/FeatureNavHost.kt b/app-feature-preview/src/main/java/app/k9mail/feature/preview/navigation/FeatureNavHost.kt index b0c7c03709..d725856452 100644 --- a/app-feature-preview/src/main/java/app/k9mail/feature/preview/navigation/FeatureNavHost.kt +++ b/app-feature-preview/src/main/java/app/k9mail/feature/preview/navigation/FeatureNavHost.kt @@ -25,8 +25,8 @@ fun FeatureNavHost( onImportClick = { /* TODO */ }, ) accountSetupScreen( - onBackClick = navController::popBackStack, - onFinishClick = { /* TODO */ }, + onBack = navController::popBackStack, + onFinish = { /* TODO */ }, ) } } diff --git a/feature/account/setup/build.gradle.kts b/feature/account/setup/build.gradle.kts index a0758baa90..5159825fb1 100644 --- a/feature/account/setup/build.gradle.kts +++ b/feature/account/setup/build.gradle.kts @@ -9,4 +9,6 @@ android { dependencies { implementation(projects.core.ui.compose.designsystem) + + testImplementation(projects.core.ui.compose.testing) } diff --git a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/navigation/AccountSetupNavigation.kt b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/navigation/AccountSetupNavigation.kt index 2a9ddfafb9..1a458a59df 100644 --- a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/navigation/AccountSetupNavigation.kt +++ b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/navigation/AccountSetupNavigation.kt @@ -13,13 +13,13 @@ fun NavController.navigateToAccountSetup(navOptions: NavOptions? = null) { } fun NavGraphBuilder.accountSetupScreen( - onBackClick: () -> Unit, - onFinishClick: () -> Unit, + onBack: () -> Unit, + onFinish: () -> Unit, ) { composable(route = NAVIGATION_ROUTE_ACCOUNT_SETUP) { AccountSetupScreen( - onBackClick = onBackClick, - onFinishClick = onFinishClick, + onBack = onBack, + onFinish = onFinish, ) } } diff --git a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupContract.kt b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupContract.kt new file mode 100644 index 0000000000..0f7d042e73 --- /dev/null +++ b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupContract.kt @@ -0,0 +1,28 @@ +package app.k9mail.feature.account.setup.ui + +import app.k9mail.core.ui.compose.common.mvi.UnidirectionalViewModel + +interface AccountSetupContract { + + enum class SetupStep { + AUTO_CONFIG, + MANUAL_CONFIG, + OPTIONS, + } + + interface ViewModel : UnidirectionalViewModel + + data class State( + val setupStep: SetupStep = SetupStep.AUTO_CONFIG, + ) + + sealed interface Event { + object OnNext : Event + object OnBack : Event + } + + sealed interface Effect { + object NavigateNext : Effect + object NavigateBack : Effect + } +} diff --git a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupScreen.kt b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupScreen.kt index 3dcd99b09f..4863959bd6 100644 --- a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupScreen.kt +++ b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupScreen.kt @@ -1,65 +1,59 @@ package app.k9mail.feature.account.setup.ui import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.ui.tooling.preview.Preview +import app.k9mail.core.ui.compose.common.mvi.observe +import app.k9mail.feature.account.setup.ui.AccountSetupContract.Effect +import app.k9mail.feature.account.setup.ui.AccountSetupContract.Event +import app.k9mail.feature.account.setup.ui.AccountSetupContract.SetupStep +import app.k9mail.feature.account.setup.ui.AccountSetupContract.ViewModel import app.k9mail.feature.account.setup.ui.autoconfig.AccountAutoConfigScreen import app.k9mail.feature.account.setup.ui.manualconfig.AccountManualConfigScreen import app.k9mail.feature.account.setup.ui.options.AccountOptionsScreen +import org.koin.androidx.compose.koinViewModel @Composable fun AccountSetupScreen( - onFinishClick: () -> Unit, - onBackClick: () -> Unit, + onFinish: () -> Unit, + onBack: () -> Unit, + viewModel: ViewModel = koinViewModel(), ) { - val accountSetupSteps = remember { mutableStateOf(AccountSetupSteps.AUTO_CONFIG) } + val (state, dispatch) = viewModel.observe { effect -> + when (effect) { + Effect.NavigateBack -> onBack() + Effect.NavigateNext -> onFinish() + } + } - when (accountSetupSteps.value) { - AccountSetupSteps.AUTO_CONFIG -> { + when (state.value.setupStep) { + SetupStep.AUTO_CONFIG -> { AccountAutoConfigScreen( - onNextClick = { - // TODO validate config - accountSetupSteps.value = AccountSetupSteps.MANUAL_CONFIG - }, - onBackClick = onBackClick, + onNextClick = { dispatch(Event.OnNext) }, + onBackClick = { dispatch(Event.OnBack) }, ) } - AccountSetupSteps.MANUAL_CONFIG -> { + SetupStep.MANUAL_CONFIG -> { AccountManualConfigScreen( - onNextClick = { - accountSetupSteps.value = AccountSetupSteps.OPTIONS - }, - onBackClick = { - accountSetupSteps.value = AccountSetupSteps.AUTO_CONFIG - }, + onNextClick = { dispatch(Event.OnNext) }, + onBackClick = { dispatch(Event.OnBack) }, ) } - AccountSetupSteps.OPTIONS -> { + SetupStep.OPTIONS -> { AccountOptionsScreen( - // validate account - onFinishClick = onFinishClick, - onBackClick = { - accountSetupSteps.value = AccountSetupSteps.MANUAL_CONFIG - }, + onFinishClick = { dispatch(Event.OnNext) }, + onBackClick = { dispatch(Event.OnBack) }, ) } } } -enum class AccountSetupSteps { - AUTO_CONFIG, - MANUAL_CONFIG, - OPTIONS, -} - @Preview(showBackground = true) @Composable internal fun AccountSetupScreenPreview() { AccountSetupScreen( - onFinishClick = {}, - onBackClick = {}, + onFinish = {}, + onBack = {}, ) } diff --git a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupViewModel.kt b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupViewModel.kt index 26cf5cd18b..952000cbfb 100644 --- a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupViewModel.kt +++ b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupViewModel.kt @@ -1,5 +1,52 @@ package app.k9mail.feature.account.setup.ui -import androidx.lifecycle.ViewModel +import app.k9mail.core.ui.compose.common.mvi.BaseViewModel +import app.k9mail.feature.account.setup.ui.AccountSetupContract.Effect +import app.k9mail.feature.account.setup.ui.AccountSetupContract.Event +import app.k9mail.feature.account.setup.ui.AccountSetupContract.SetupStep +import app.k9mail.feature.account.setup.ui.AccountSetupContract.State +import app.k9mail.feature.account.setup.ui.AccountSetupContract.ViewModel -class AccountSetupViewModel : ViewModel() +class AccountSetupViewModel( + initialState: State = State(), +) : BaseViewModel(initialState), ViewModel { + + override fun event(event: Event) { + when (event) { + Event.OnBack -> onBack() + Event.OnNext -> onNext() + } + } + + private fun onBack() { + when (state.value.setupStep) { + SetupStep.AUTO_CONFIG -> navigateBack() + SetupStep.MANUAL_CONFIG -> changeToSetupStep(SetupStep.AUTO_CONFIG) + SetupStep.OPTIONS -> changeToSetupStep(SetupStep.MANUAL_CONFIG) + } + } + + private fun onNext() { + when (state.value.setupStep) { + SetupStep.AUTO_CONFIG -> changeToSetupStep(SetupStep.MANUAL_CONFIG) + SetupStep.MANUAL_CONFIG -> changeToSetupStep(SetupStep.OPTIONS) + SetupStep.OPTIONS -> navigateNext() + } + } + + private fun changeToSetupStep(setupStep: SetupStep) { + updateState { + it.copy( + setupStep = setupStep, + ) + } + } + + private fun navigateNext() { + // TODO: validate account + + emitEffect(Effect.NavigateNext) + } + + private fun navigateBack() = emitEffect(Effect.NavigateBack) +} diff --git a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/autoconfig/AccountAutoConfigContent.kt b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/autoconfig/AccountAutoConfigContent.kt index 69c60bfc82..156b04420b 100644 --- a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/autoconfig/AccountAutoConfigContent.kt +++ b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/autoconfig/AccountAutoConfigContent.kt @@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.requiredHeight import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import app.k9mail.core.ui.compose.common.DevicePreviews import app.k9mail.core.ui.compose.designsystem.atom.button.Button @@ -32,7 +33,9 @@ internal fun AccountAutoConfigContent( modifier: Modifier = Modifier, ) { ResponsiveContentWithBackground( - modifier = modifier, + modifier = Modifier + .testTag("AccountAutoConfigContent") + .then(modifier), ) { LazyColumnWithHeaderFooter( modifier = Modifier.fillMaxSize(), diff --git a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/manualconfig/AccountManualConfigContent.kt b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/manualconfig/AccountManualConfigContent.kt index f7b58b6291..6f8b6e2918 100644 --- a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/manualconfig/AccountManualConfigContent.kt +++ b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/manualconfig/AccountManualConfigContent.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import app.k9mail.core.ui.compose.common.DevicePreviews import app.k9mail.core.ui.compose.designsystem.atom.button.Button @@ -27,7 +28,9 @@ internal fun AccountManualConfigContent( modifier: Modifier = Modifier, ) { ResponsiveContentWithBackground( - modifier = modifier, + modifier = Modifier + .testTag("AccountManualConfigContent") + .then(modifier), ) { LazyColumnWithHeaderFooter( modifier = Modifier.fillMaxSize(), diff --git a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/options/AccountOptionsContent.kt b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/options/AccountOptionsContent.kt index 04228c766e..b212e00c66 100644 --- a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/options/AccountOptionsContent.kt +++ b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/options/AccountOptionsContent.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import app.k9mail.core.ui.compose.common.DevicePreviews import app.k9mail.core.ui.compose.designsystem.atom.button.Button @@ -27,7 +28,9 @@ internal fun AccountOptionsContent( modifier: Modifier = Modifier, ) { ResponsiveContentWithBackground( - modifier = modifier, + modifier = Modifier + .testTag("AccountOptionsContent") + .then(modifier), ) { LazyColumnWithHeaderFooter( modifier = Modifier.fillMaxSize(), diff --git a/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupScreenKtTest.kt b/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupScreenKtTest.kt new file mode 100644 index 0000000000..9f95e93195 --- /dev/null +++ b/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupScreenKtTest.kt @@ -0,0 +1,69 @@ +package app.k9mail.feature.account.setup.ui + +import app.k9mail.core.ui.compose.testing.ComposeTest +import app.k9mail.core.ui.compose.testing.onNodeWithTag +import app.k9mail.core.ui.compose.testing.setContent +import app.k9mail.feature.account.setup.ui.AccountSetupContract.Effect +import app.k9mail.feature.account.setup.ui.AccountSetupContract.SetupStep +import app.k9mail.feature.account.setup.ui.AccountSetupContract.State +import assertk.assertThat +import assertk.assertions.isEqualTo +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class AccountSetupScreenKtTest : ComposeTest() { + + @Test + fun `should display correct screen for every setup step`() = runTest { + val viewModel = FakeAccountSetupViewModel() + + setContent { + AccountSetupScreen( + onFinish = { }, + onBack = { }, + viewModel = viewModel, + ) + } + + for (step in SetupStep.values()) { + viewModel.mutableState.update { it.copy(setupStep = step) } + onNodeWithTag(getTagForStep(step)).assertExists() + } + } + + @Test + fun `should delegate navigation effects`() = runTest { + val initialState = State() + val viewModel = FakeAccountSetupViewModel(initialState) + var onFinishCounter = 0 + var onBackCounter = 0 + + setContent { + AccountSetupScreen( + onFinish = { onFinishCounter++ }, + onBack = { onBackCounter++ }, + viewModel = viewModel, + ) + } + + assertThat(onFinishCounter).isEqualTo(0) + assertThat(onBackCounter).isEqualTo(0) + + viewModel.mutableEffect.emit(Effect.NavigateNext) + + assertThat(onFinishCounter).isEqualTo(1) + assertThat(onBackCounter).isEqualTo(0) + + viewModel.mutableEffect.emit(Effect.NavigateBack) + + assertThat(onFinishCounter).isEqualTo(1) + assertThat(onBackCounter).isEqualTo(1) + } + + private fun getTagForStep(step: SetupStep): String = when (step) { + SetupStep.AUTO_CONFIG -> "AccountAutoConfigContent" + SetupStep.MANUAL_CONFIG -> "AccountManualConfigContent" + SetupStep.OPTIONS -> "AccountOptionsContent" + } +} diff --git a/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupStateTest.kt b/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupStateTest.kt new file mode 100644 index 0000000000..c1763c34c6 --- /dev/null +++ b/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupStateTest.kt @@ -0,0 +1,20 @@ +package app.k9mail.feature.account.setup.ui + +import app.k9mail.feature.account.setup.ui.AccountSetupContract.State +import assertk.all +import assertk.assertThat +import assertk.assertions.isEqualTo +import assertk.assertions.prop +import org.junit.Test + +class AccountSetupStateTest { + + @Test + fun `should set default values`() { + val state = State() + + assertThat(state).all { + prop(State::setupStep).isEqualTo(AccountSetupContract.SetupStep.AUTO_CONFIG) + } + } +} diff --git a/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupViewModelTest.kt b/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupViewModelTest.kt new file mode 100644 index 0000000000..2cf824dd4f --- /dev/null +++ b/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupViewModelTest.kt @@ -0,0 +1,122 @@ +package app.k9mail.feature.account.setup.ui + +import app.cash.turbine.ReceiveTurbine +import app.cash.turbine.testIn +import app.k9mail.core.ui.compose.testing.MainDispatcherRule +import app.k9mail.feature.account.setup.ui.AccountSetupContract.Effect.NavigateBack +import app.k9mail.feature.account.setup.ui.AccountSetupContract.Effect.NavigateNext +import app.k9mail.feature.account.setup.ui.AccountSetupContract.SetupStep +import app.k9mail.feature.account.setup.ui.AccountSetupContract.State +import assertk.Assert +import assertk.all +import assertk.assertThat +import assertk.assertions.isEqualTo +import assertk.assertions.prop +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test + +class AccountSetupViewModelTest { + + @get:Rule + val mainDispatcherRule = MainDispatcherRule() + + @Test + fun `should forward step state on next event`() = runTest { + val viewModel = AccountSetupViewModel() + val stateTurbine = viewModel.state.testIn(backgroundScope) + val effectTurbine = viewModel.effect.testIn(backgroundScope) + val turbines = listOf(stateTurbine, effectTurbine) + + // Initial state + assertThatAndAllEventsConsumed( + actual = stateTurbine.awaitItem(), + turbines = turbines, + ) { + prop(State::setupStep).isEqualTo(SetupStep.AUTO_CONFIG) + } + + viewModel.event(AccountSetupContract.Event.OnNext) + + assertThatAndAllEventsConsumed( + actual = stateTurbine.awaitItem(), + turbines = turbines, + ) { + prop(State::setupStep).isEqualTo(SetupStep.MANUAL_CONFIG) + } + + viewModel.event(AccountSetupContract.Event.OnNext) + + assertThatAndAllEventsConsumed( + actual = stateTurbine.awaitItem(), + turbines = turbines, + ) { + prop(State::setupStep).isEqualTo(SetupStep.OPTIONS) + } + + viewModel.event(AccountSetupContract.Event.OnNext) + + assertThatAndAllEventsConsumed( + actual = effectTurbine.awaitItem(), + turbines = turbines, + ) { + isEqualTo(NavigateNext) + } + } + + @Test + fun `should rewind step state on back event`() = runTest { + val initialState = State(setupStep = SetupStep.OPTIONS) + val viewModel = AccountSetupViewModel(initialState) + val stateTurbine = viewModel.state.testIn(backgroundScope) + val effectTurbine = viewModel.effect.testIn(backgroundScope) + val turbines = listOf(stateTurbine, effectTurbine) + + // Initial state + assertThatAndAllEventsConsumed( + actual = stateTurbine.awaitItem(), + turbines = turbines, + ) { + prop(State::setupStep).isEqualTo(SetupStep.OPTIONS) + } + + viewModel.event(AccountSetupContract.Event.OnBack) + + assertThatAndAllEventsConsumed( + actual = stateTurbine.awaitItem(), + turbines = turbines, + ) { + prop(State::setupStep).isEqualTo(SetupStep.MANUAL_CONFIG) + } + + viewModel.event(AccountSetupContract.Event.OnBack) + + assertThatAndAllEventsConsumed( + actual = stateTurbine.awaitItem(), + turbines = turbines, + ) { + prop(State::setupStep).isEqualTo(SetupStep.AUTO_CONFIG) + } + + viewModel.event(AccountSetupContract.Event.OnBack) + + assertThatAndAllEventsConsumed( + actual = effectTurbine.awaitItem(), + turbines = turbines, + ) { + isEqualTo(NavigateBack) + } + } + + private fun assertThatAndAllEventsConsumed( + actual: T, + turbines: List>, + assertion: Assert.() -> Unit, + ) { + assertThat(actual).all { + assertion() + } + + turbines.forEach { it.ensureAllEventsConsumed() } + } +} diff --git a/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/FakeAccountSetupViewModel.kt b/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/FakeAccountSetupViewModel.kt new file mode 100644 index 0000000000..0cb98302ed --- /dev/null +++ b/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/FakeAccountSetupViewModel.kt @@ -0,0 +1,26 @@ +package app.k9mail.feature.account.setup.ui + +import app.k9mail.feature.account.setup.ui.AccountSetupContract.Effect +import app.k9mail.feature.account.setup.ui.AccountSetupContract.Event +import app.k9mail.feature.account.setup.ui.AccountSetupContract.State +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow + +internal class FakeAccountSetupViewModel( + initialState: State = State(), +) : AccountSetupContract.ViewModel { + + val mutableState = MutableStateFlow(initialState) + val mutableEffect = MutableSharedFlow() + val events = mutableListOf() + + override val state: StateFlow = mutableState.asStateFlow() + override val effect: SharedFlow = mutableEffect.asSharedFlow() + override fun event(event: Event) { + events.add(event) + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0e0973b7ac..9fe7438df4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -188,6 +188,7 @@ shared-jvm-test = [ "mockito-kotlin", "koin-test", "koin-test-junit4", + "turbine", ] shared-jvm-test-compose = [ "robolectric",