Change ViewModel orchestration in account setup
Using more direct dependencies makes it easier to follow what's going on.
This commit is contained in:
parent
a233a63bf4
commit
cb6520efd2
7 changed files with 214 additions and 168 deletions
|
@ -82,20 +82,26 @@ val featureAccountSetupModule: Module = module {
|
|||
viewModel {
|
||||
AccountSetupViewModel(
|
||||
createAccount = get(),
|
||||
autoDiscoveryViewModel = get(),
|
||||
incomingViewModel = get(),
|
||||
incomingValidationViewModel = get(named(NAME_INCOMING_VALIDATION)),
|
||||
outgoingViewModel = get(),
|
||||
outgoingValidationViewModel = get(named(NAME_OUTGOING_VALIDATION)),
|
||||
optionsViewModel = get(),
|
||||
)
|
||||
}
|
||||
viewModel {
|
||||
factory<AccountAutoDiscoveryContract.ViewModel> {
|
||||
AccountAutoDiscoveryViewModel(
|
||||
validator = get(),
|
||||
getAutoDiscovery = get(),
|
||||
)
|
||||
}
|
||||
viewModel {
|
||||
factory<AccountIncomingConfigContract.ViewModel> {
|
||||
AccountIncomingConfigViewModel(
|
||||
validator = get(),
|
||||
)
|
||||
}
|
||||
viewModel(named(NAME_INCOMING_VALIDATION)) {
|
||||
factory<AccountValidationContract.ViewModel>(named(NAME_INCOMING_VALIDATION)) {
|
||||
AccountValidationViewModel(
|
||||
validateServerSettings = get(),
|
||||
initialState = AccountValidationContract.State(
|
||||
|
@ -103,12 +109,12 @@ val featureAccountSetupModule: Module = module {
|
|||
),
|
||||
)
|
||||
}
|
||||
viewModel {
|
||||
factory<AccountOutgoingConfigContract.ViewModel> {
|
||||
AccountOutgoingConfigViewModel(
|
||||
validator = get(),
|
||||
)
|
||||
}
|
||||
viewModel(named(NAME_OUTGOING_VALIDATION)) {
|
||||
factory<AccountValidationContract.ViewModel>(named(NAME_OUTGOING_VALIDATION)) {
|
||||
AccountValidationViewModel(
|
||||
validateServerSettings = get(),
|
||||
initialState = AccountValidationContract.State(
|
||||
|
@ -116,7 +122,7 @@ val featureAccountSetupModule: Module = module {
|
|||
),
|
||||
)
|
||||
}
|
||||
viewModel {
|
||||
factory<AccountOptionsContract.ViewModel> {
|
||||
AccountOptionsViewModel(
|
||||
validator = get(),
|
||||
)
|
||||
|
|
|
@ -5,6 +5,7 @@ import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryCon
|
|||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract
|
||||
import app.k9mail.feature.account.setup.ui.outgoing.AccountOutgoingConfigContract
|
||||
import app.k9mail.feature.account.setup.ui.validation.AccountValidationContract
|
||||
|
||||
interface AccountSetupContract {
|
||||
|
||||
|
@ -17,7 +18,14 @@ interface AccountSetupContract {
|
|||
OPTIONS,
|
||||
}
|
||||
|
||||
interface ViewModel : UnidirectionalViewModel<State, Event, Effect>
|
||||
interface ViewModel : UnidirectionalViewModel<State, Event, Effect> {
|
||||
val autoDiscoveryViewModel: AccountAutoDiscoveryContract.ViewModel
|
||||
val incomingViewModel: AccountIncomingConfigContract.ViewModel
|
||||
val incomingValidationViewModel: AccountValidationContract.ViewModel
|
||||
val outgoingViewModel: AccountOutgoingConfigContract.ViewModel
|
||||
val outgoingValidationViewModel: AccountValidationContract.ViewModel
|
||||
val optionsViewModel: AccountOptionsContract.ViewModel
|
||||
}
|
||||
|
||||
data class State(
|
||||
val setupStep: SetupStep = SetupStep.AUTO_CONFIG,
|
||||
|
@ -32,36 +40,11 @@ interface AccountSetupContract {
|
|||
val isAutomaticConfig: Boolean,
|
||||
) : Event
|
||||
|
||||
data class OnStateCollected(
|
||||
val autoDiscoveryState: AccountAutoDiscoveryContract.State,
|
||||
val incomingState: AccountIncomingConfigContract.State,
|
||||
val outgoingState: AccountOutgoingConfigContract.State,
|
||||
val optionsState: AccountOptionsContract.State,
|
||||
) : Event
|
||||
|
||||
object OnBack : Event
|
||||
}
|
||||
|
||||
sealed interface Effect {
|
||||
|
||||
data class UpdateIncomingConfig(
|
||||
val state: AccountIncomingConfigContract.State,
|
||||
) : Effect
|
||||
|
||||
object UpdateIncomingConfigValidation : Effect
|
||||
|
||||
data class UpdateOutgoingConfig(
|
||||
val state: AccountOutgoingConfigContract.State,
|
||||
) : Effect
|
||||
|
||||
object UpdateOutgoingConfigValidation : Effect
|
||||
|
||||
data class UpdateOptions(
|
||||
val state: AccountOptionsContract.State,
|
||||
) : Effect
|
||||
|
||||
object CollectExternalStates : Effect
|
||||
|
||||
data class NavigateNext(
|
||||
val accountUuid: String,
|
||||
) : Effect
|
||||
|
|
|
@ -3,75 +3,26 @@ package app.k9mail.feature.account.setup.ui
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import app.k9mail.core.ui.compose.common.mvi.observe
|
||||
import app.k9mail.feature.account.setup.NAME_INCOMING_VALIDATION
|
||||
import app.k9mail.feature.account.setup.NAME_OUTGOING_VALIDATION
|
||||
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.autodiscovery.AccountAutoDiscoveryContract
|
||||
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryScreen
|
||||
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryViewModel
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigScreen
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigViewModel
|
||||
import app.k9mail.feature.account.setup.ui.incoming.toValidationState
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsScreen
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsViewModel
|
||||
import app.k9mail.feature.account.setup.ui.outgoing.AccountOutgoingConfigContract
|
||||
import app.k9mail.feature.account.setup.ui.outgoing.AccountOutgoingConfigScreen
|
||||
import app.k9mail.feature.account.setup.ui.outgoing.AccountOutgoingConfigViewModel
|
||||
import app.k9mail.feature.account.setup.ui.outgoing.toValidationState
|
||||
import app.k9mail.feature.account.setup.ui.validation.AccountValidationContract
|
||||
import app.k9mail.feature.account.setup.ui.validation.AccountValidationScreen
|
||||
import app.k9mail.feature.account.setup.ui.validation.AccountValidationViewModel
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import org.koin.core.qualifier.named
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun AccountSetupScreen(
|
||||
onFinish: (String) -> Unit,
|
||||
onBack: () -> Unit,
|
||||
viewModel: ViewModel = koinViewModel<AccountSetupViewModel>(),
|
||||
autoDiscoveryViewModel: AccountAutoDiscoveryContract.ViewModel = koinViewModel<AccountAutoDiscoveryViewModel>(),
|
||||
incomingViewModel: AccountIncomingConfigContract.ViewModel = koinViewModel<AccountIncomingConfigViewModel>(),
|
||||
incomingValidationViewModel: AccountValidationContract.ViewModel = koinViewModel<AccountValidationViewModel>(
|
||||
named(
|
||||
NAME_INCOMING_VALIDATION,
|
||||
),
|
||||
),
|
||||
outgoingViewModel: AccountOutgoingConfigContract.ViewModel = koinViewModel<AccountOutgoingConfigViewModel>(),
|
||||
outgoingValidationViewModel: AccountValidationContract.ViewModel = koinViewModel<AccountValidationViewModel>(
|
||||
named(
|
||||
NAME_OUTGOING_VALIDATION,
|
||||
),
|
||||
),
|
||||
optionsViewModel: AccountOptionsContract.ViewModel = koinViewModel<AccountOptionsViewModel>(),
|
||||
) {
|
||||
val (state, dispatch) = viewModel.observe { effect ->
|
||||
when (effect) {
|
||||
is Effect.UpdateIncomingConfig -> incomingViewModel.initState(effect.state)
|
||||
is Effect.UpdateIncomingConfigValidation -> {
|
||||
incomingValidationViewModel.initState(incomingViewModel.state.value.toValidationState())
|
||||
}
|
||||
|
||||
is Effect.UpdateOutgoingConfig -> outgoingViewModel.initState(effect.state)
|
||||
is Effect.UpdateOutgoingConfigValidation -> {
|
||||
outgoingValidationViewModel.initState(outgoingViewModel.state.value.toValidationState())
|
||||
}
|
||||
|
||||
is Effect.UpdateOptions -> optionsViewModel.initState(effect.state)
|
||||
is Effect.CollectExternalStates -> viewModel.event(
|
||||
Event.OnStateCollected(
|
||||
autoDiscoveryState = autoDiscoveryViewModel.state.value,
|
||||
incomingState = incomingViewModel.state.value,
|
||||
outgoingState = outgoingViewModel.state.value,
|
||||
optionsState = optionsViewModel.state.value,
|
||||
),
|
||||
)
|
||||
|
||||
is Effect.NavigateNext -> onFinish(effect.accountUuid)
|
||||
Effect.NavigateBack -> onBack()
|
||||
}
|
||||
|
@ -89,7 +40,7 @@ fun AccountSetupScreen(
|
|||
)
|
||||
},
|
||||
onBack = { dispatch(Event.OnBack) },
|
||||
viewModel = autoDiscoveryViewModel,
|
||||
viewModel = viewModel.autoDiscoveryViewModel,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -97,7 +48,7 @@ fun AccountSetupScreen(
|
|||
AccountIncomingConfigScreen(
|
||||
onNext = { dispatch(Event.OnNext) },
|
||||
onBack = { dispatch(Event.OnBack) },
|
||||
viewModel = incomingViewModel,
|
||||
viewModel = viewModel.incomingViewModel,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -105,7 +56,7 @@ fun AccountSetupScreen(
|
|||
AccountValidationScreen(
|
||||
onNext = { dispatch(Event.OnNext) },
|
||||
onBack = { dispatch(Event.OnBack) },
|
||||
viewModel = incomingValidationViewModel,
|
||||
viewModel = viewModel.incomingValidationViewModel,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -113,7 +64,7 @@ fun AccountSetupScreen(
|
|||
AccountOutgoingConfigScreen(
|
||||
onNext = { dispatch(Event.OnNext) },
|
||||
onBack = { dispatch(Event.OnBack) },
|
||||
viewModel = outgoingViewModel,
|
||||
viewModel = viewModel.outgoingViewModel,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -121,7 +72,7 @@ fun AccountSetupScreen(
|
|||
AccountValidationScreen(
|
||||
onNext = { dispatch(Event.OnNext) },
|
||||
onBack = { dispatch(Event.OnBack) },
|
||||
viewModel = outgoingValidationViewModel,
|
||||
viewModel = viewModel.outgoingValidationViewModel,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -129,7 +80,7 @@ fun AccountSetupScreen(
|
|||
AccountOptionsScreen(
|
||||
onNext = { dispatch(Event.OnNext) },
|
||||
onBack = { dispatch(Event.OnBack) },
|
||||
viewModel = optionsViewModel,
|
||||
viewModel = viewModel.optionsViewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,23 +7,31 @@ 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
|
||||
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract
|
||||
import app.k9mail.feature.account.setup.ui.common.mapper.toIncomingConfigState
|
||||
import app.k9mail.feature.account.setup.ui.common.mapper.toOptionsState
|
||||
import app.k9mail.feature.account.setup.ui.common.mapper.toOutgoingConfigState
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract
|
||||
import app.k9mail.feature.account.setup.ui.incoming.toServerSettings
|
||||
import app.k9mail.feature.account.setup.ui.incoming.toValidationState
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract
|
||||
import app.k9mail.feature.account.setup.ui.options.toAccountOptions
|
||||
import app.k9mail.feature.account.setup.ui.outgoing.AccountOutgoingConfigContract
|
||||
import app.k9mail.feature.account.setup.ui.outgoing.toServerSettings
|
||||
import app.k9mail.feature.account.setup.ui.outgoing.toValidationState
|
||||
import app.k9mail.feature.account.setup.ui.validation.AccountValidationContract
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class AccountSetupViewModel(
|
||||
private val createAccount: UseCase.CreateAccount,
|
||||
override val autoDiscoveryViewModel: AccountAutoDiscoveryContract.ViewModel,
|
||||
override val incomingViewModel: AccountIncomingConfigContract.ViewModel,
|
||||
override val incomingValidationViewModel: AccountValidationContract.ViewModel,
|
||||
override val outgoingViewModel: AccountOutgoingConfigContract.ViewModel,
|
||||
override val outgoingValidationViewModel: AccountValidationContract.ViewModel,
|
||||
override val optionsViewModel: AccountOptionsContract.ViewModel,
|
||||
initialState: State = State(),
|
||||
) : BaseViewModel<State, Event, Effect>(initialState), ViewModel {
|
||||
) : BaseViewModel<State, Event, Effect>(initialState), AccountSetupContract.ViewModel {
|
||||
|
||||
override fun event(event: Event) {
|
||||
when (event) {
|
||||
|
@ -36,13 +44,6 @@ class AccountSetupViewModel(
|
|||
onAutoDiscoveryFinished(event.state)
|
||||
}
|
||||
|
||||
is Event.OnStateCollected -> onStateCollected(
|
||||
autoDiscoveryState = event.autoDiscoveryState,
|
||||
incomingState = event.incomingState,
|
||||
outgoingState = event.outgoingState,
|
||||
optionsState = event.optionsState,
|
||||
)
|
||||
|
||||
Event.OnBack -> onBack()
|
||||
Event.OnNext -> onNext()
|
||||
}
|
||||
|
@ -51,9 +52,9 @@ class AccountSetupViewModel(
|
|||
private fun onAutoDiscoveryFinished(
|
||||
autoDiscoveryState: AccountAutoDiscoveryContract.State,
|
||||
) {
|
||||
emitEffect(Effect.UpdateIncomingConfig(autoDiscoveryState.toIncomingConfigState()))
|
||||
emitEffect(Effect.UpdateOutgoingConfig(autoDiscoveryState.toOutgoingConfigState()))
|
||||
emitEffect(Effect.UpdateOptions(autoDiscoveryState.toOptionsState()))
|
||||
incomingViewModel.initState(autoDiscoveryState.toIncomingConfigState())
|
||||
outgoingViewModel.initState(autoDiscoveryState.toOutgoingConfigState())
|
||||
optionsViewModel.initState(autoDiscoveryState.toOptionsState())
|
||||
onNext()
|
||||
}
|
||||
|
||||
|
@ -86,8 +87,8 @@ class AccountSetupViewModel(
|
|||
when (state.value.setupStep) {
|
||||
SetupStep.AUTO_CONFIG -> {
|
||||
if (state.value.isAutomaticConfig) {
|
||||
emitEffect(Effect.UpdateIncomingConfigValidation)
|
||||
emitEffect(Effect.UpdateOutgoingConfigValidation)
|
||||
incomingValidationViewModel.initState(incomingViewModel.state.value.toValidationState())
|
||||
outgoingValidationViewModel.initState(outgoingViewModel.state.value.toValidationState())
|
||||
changeToSetupStep(SetupStep.INCOMING_VALIDATION)
|
||||
} else {
|
||||
changeToSetupStep(SetupStep.INCOMING_CONFIG)
|
||||
|
@ -95,7 +96,7 @@ class AccountSetupViewModel(
|
|||
}
|
||||
|
||||
SetupStep.INCOMING_CONFIG -> {
|
||||
emitEffect(Effect.UpdateIncomingConfigValidation)
|
||||
incomingValidationViewModel.initState(incomingViewModel.state.value.toValidationState())
|
||||
changeToSetupStep(SetupStep.INCOMING_VALIDATION)
|
||||
}
|
||||
|
||||
|
@ -108,7 +109,7 @@ class AccountSetupViewModel(
|
|||
}
|
||||
|
||||
SetupStep.OUTGOING_CONFIG -> {
|
||||
emitEffect(Effect.UpdateOutgoingConfigValidation)
|
||||
outgoingValidationViewModel.initState(outgoingViewModel.state.value.toValidationState())
|
||||
changeToSetupStep(SetupStep.OUTGOING_VALIDATION)
|
||||
}
|
||||
|
||||
|
@ -129,15 +130,11 @@ class AccountSetupViewModel(
|
|||
}
|
||||
|
||||
private fun onFinish() {
|
||||
emitEffect(Effect.CollectExternalStates)
|
||||
}
|
||||
val autoDiscoveryState = autoDiscoveryViewModel.state.value
|
||||
val incomingState = incomingViewModel.state.value
|
||||
val outgoingState = outgoingViewModel.state.value
|
||||
val optionsState = optionsViewModel.state.value
|
||||
|
||||
private fun onStateCollected(
|
||||
autoDiscoveryState: AccountAutoDiscoveryContract.State,
|
||||
incomingState: AccountIncomingConfigContract.State,
|
||||
outgoingState: AccountOutgoingConfigContract.State,
|
||||
optionsState: AccountOptionsContract.State,
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
val result = createAccount.execute(
|
||||
emailAddress = autoDiscoveryState.emailAddress.value,
|
||||
|
|
|
@ -7,11 +7,6 @@ import app.k9mail.core.ui.compose.theme.ThunderbirdTheme
|
|||
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 app.k9mail.feature.account.setup.ui.autodiscovery.FakeAccountAutoDiscoveryViewModel
|
||||
import app.k9mail.feature.account.setup.ui.incoming.FakeAccountIncomingConfigViewModel
|
||||
import app.k9mail.feature.account.setup.ui.options.FakeAccountOptionsViewModel
|
||||
import app.k9mail.feature.account.setup.ui.outgoing.FakeAccountOutgoingConfigViewModel
|
||||
import app.k9mail.feature.account.setup.ui.validation.FakeAccountValidationViewModel
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isEqualTo
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
@ -22,12 +17,6 @@ class AccountSetupScreenKtTest : ComposeTest() {
|
|||
@Test
|
||||
fun `should display correct screen for every setup step`() = runTest {
|
||||
val viewModel = FakeAccountSetupViewModel()
|
||||
val autoDiscoveryViewModel = FakeAccountAutoDiscoveryViewModel()
|
||||
val incomingViewModel = FakeAccountIncomingConfigViewModel()
|
||||
val incomingValidationViewModel = FakeAccountValidationViewModel()
|
||||
val outgoingViewModel = FakeAccountOutgoingConfigViewModel()
|
||||
val outgoingValidationViewModel = FakeAccountValidationViewModel()
|
||||
val optionsViewModel = FakeAccountOptionsViewModel()
|
||||
|
||||
setContent {
|
||||
ThunderbirdTheme {
|
||||
|
@ -35,12 +24,6 @@ class AccountSetupScreenKtTest : ComposeTest() {
|
|||
onFinish = { },
|
||||
onBack = { },
|
||||
viewModel = viewModel,
|
||||
autoDiscoveryViewModel = autoDiscoveryViewModel,
|
||||
incomingViewModel = incomingViewModel,
|
||||
incomingValidationViewModel = incomingValidationViewModel,
|
||||
outgoingViewModel = outgoingViewModel,
|
||||
outgoingValidationViewModel = outgoingValidationViewModel,
|
||||
optionsViewModel = optionsViewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -54,13 +37,7 @@ class AccountSetupScreenKtTest : ComposeTest() {
|
|||
@Test
|
||||
fun `should delegate navigation effects`() = runTest {
|
||||
val initialState = State()
|
||||
val viewModel = FakeAccountSetupViewModel(initialState)
|
||||
val autoDiscoveryViewModel = FakeAccountAutoDiscoveryViewModel()
|
||||
val incomingViewModel = FakeAccountIncomingConfigViewModel()
|
||||
val incomingValidationViewModel = FakeAccountValidationViewModel()
|
||||
val outgoingViewModel = FakeAccountOutgoingConfigViewModel()
|
||||
val outgoingValidationViewModel = FakeAccountValidationViewModel()
|
||||
val optionsViewModel = FakeAccountOptionsViewModel()
|
||||
val viewModel = FakeAccountSetupViewModel(initialState = initialState)
|
||||
var onFinishCounter = 0
|
||||
var onBackCounter = 0
|
||||
|
||||
|
@ -70,12 +47,6 @@ class AccountSetupScreenKtTest : ComposeTest() {
|
|||
onFinish = { onFinishCounter++ },
|
||||
onBack = { onBackCounter++ },
|
||||
viewModel = viewModel,
|
||||
autoDiscoveryViewModel = autoDiscoveryViewModel,
|
||||
incomingViewModel = incomingViewModel,
|
||||
incomingValidationViewModel = incomingValidationViewModel,
|
||||
outgoingViewModel = outgoingViewModel,
|
||||
outgoingValidationViewModel = outgoingValidationViewModel,
|
||||
optionsViewModel = optionsViewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,32 +1,81 @@
|
|||
package app.k9mail.feature.account.setup.ui
|
||||
|
||||
import app.cash.turbine.testIn
|
||||
import app.k9mail.autodiscovery.api.AutoDiscoveryResult
|
||||
import app.k9mail.autodiscovery.api.ImapServerSettings
|
||||
import app.k9mail.autodiscovery.api.SmtpServerSettings
|
||||
import app.k9mail.core.common.net.toHostname
|
||||
import app.k9mail.core.common.net.toPort
|
||||
import app.k9mail.core.ui.compose.testing.MainDispatcherRule
|
||||
import app.k9mail.feature.account.setup.domain.entity.AccountOptions
|
||||
import app.k9mail.feature.account.setup.domain.entity.AuthenticationType
|
||||
import app.k9mail.feature.account.setup.domain.entity.ConnectionSecurity
|
||||
import app.k9mail.feature.account.setup.domain.entity.EmailCheckFrequency
|
||||
import app.k9mail.feature.account.setup.domain.entity.EmailDisplayCount
|
||||
import app.k9mail.feature.account.setup.domain.entity.IncomingProtocolType
|
||||
import app.k9mail.feature.account.setup.domain.input.NumberInputField
|
||||
import app.k9mail.feature.account.setup.domain.input.StringInputField
|
||||
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 app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract
|
||||
import app.k9mail.feature.account.setup.ui.autodiscovery.FakeAccountAutoDiscoveryViewModel
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract
|
||||
import app.k9mail.feature.account.setup.ui.incoming.FakeAccountIncomingConfigViewModel
|
||||
import app.k9mail.feature.account.setup.ui.incoming.toServerSettings
|
||||
import app.k9mail.feature.account.setup.ui.incoming.toValidationState
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract
|
||||
import app.k9mail.feature.account.setup.ui.options.FakeAccountOptionsViewModel
|
||||
import app.k9mail.feature.account.setup.ui.outgoing.AccountOutgoingConfigContract
|
||||
import app.k9mail.feature.account.setup.ui.outgoing.FakeAccountOutgoingConfigViewModel
|
||||
import app.k9mail.feature.account.setup.ui.outgoing.toServerSettings
|
||||
import app.k9mail.feature.account.setup.ui.outgoing.toValidationState
|
||||
import app.k9mail.feature.account.setup.ui.validation.FakeAccountValidationViewModel
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.assertThatAndTurbinesConsumed
|
||||
import assertk.assertions.isEqualTo
|
||||
import assertk.assertions.prop
|
||||
import com.fsck.k9.mail.ServerSettings
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import app.k9mail.autodiscovery.api.AuthenticationType as AutoDiscoveryAuthenticationType
|
||||
import app.k9mail.autodiscovery.api.ConnectionSecurity as AutoDiscoveryConnectionSecurity
|
||||
|
||||
@Suppress("LongMethod")
|
||||
class AccountSetupViewModelTest {
|
||||
|
||||
@get:Rule
|
||||
val mainDispatcherRule = MainDispatcherRule()
|
||||
|
||||
@Suppress("LongMethod")
|
||||
private val autoDiscoveryViewModel = FakeAccountAutoDiscoveryViewModel()
|
||||
private val incomingViewModel = FakeAccountIncomingConfigViewModel()
|
||||
private val incomingValidationViewModel = FakeAccountValidationViewModel()
|
||||
private val outgoingViewModel = FakeAccountOutgoingConfigViewModel()
|
||||
private val outgoingValidationViewModel = FakeAccountValidationViewModel()
|
||||
private val optionsViewModel = FakeAccountOptionsViewModel()
|
||||
|
||||
@Test
|
||||
fun `should forward step state on next event`() = runTest {
|
||||
var createAccountEmailAddress: String? = null
|
||||
var createAccountIncomingServerSettings: ServerSettings? = null
|
||||
var createAccountOutgoingServerSettings: ServerSettings? = null
|
||||
var createAccountOptions: AccountOptions? = null
|
||||
val viewModel = AccountSetupViewModel(
|
||||
createAccount = { _, _, _, _ -> "accountUuid" },
|
||||
createAccount = { emailAddress, incomingServerSettings, outgoingServerSettings, options ->
|
||||
createAccountEmailAddress = emailAddress
|
||||
createAccountIncomingServerSettings = incomingServerSettings
|
||||
createAccountOutgoingServerSettings = outgoingServerSettings
|
||||
createAccountOptions = options
|
||||
|
||||
"accountUuid"
|
||||
},
|
||||
autoDiscoveryViewModel = autoDiscoveryViewModel,
|
||||
incomingViewModel = incomingViewModel,
|
||||
incomingValidationViewModel = incomingValidationViewModel,
|
||||
outgoingViewModel = outgoingViewModel,
|
||||
outgoingValidationViewModel = outgoingValidationViewModel,
|
||||
optionsViewModel = optionsViewModel,
|
||||
)
|
||||
val stateTurbine = viewModel.state.testIn(backgroundScope)
|
||||
val effectTurbine = viewModel.effect.testIn(backgroundScope)
|
||||
|
@ -40,21 +89,40 @@ class AccountSetupViewModelTest {
|
|||
prop(State::setupStep).isEqualTo(SetupStep.AUTO_CONFIG)
|
||||
}
|
||||
|
||||
autoDiscoveryViewModel.initState(AUTODISCOVERY_STATE)
|
||||
viewModel.event(
|
||||
AccountSetupContract.Event.OnAutoDiscoveryFinished(
|
||||
state = AccountAutoDiscoveryContract.State(),
|
||||
state = AUTODISCOVERY_STATE,
|
||||
isAutomaticConfig = false,
|
||||
),
|
||||
)
|
||||
|
||||
assertThat(effectTurbine.awaitItem())
|
||||
.isEqualTo(Effect.UpdateIncomingConfig(AccountIncomingConfigContract.State()))
|
||||
val expectedIncomingConfigState = AccountIncomingConfigContract.State(
|
||||
protocolType = IncomingProtocolType.IMAP,
|
||||
server = StringInputField(INCOMING_SERVER_NAME),
|
||||
security = ConnectionSecurity.TLS,
|
||||
port = NumberInputField(INCOMING_SERVER_PORT.toLong()),
|
||||
authenticationType = AuthenticationType.PasswordEncrypted,
|
||||
username = StringInputField(USERNAME),
|
||||
password = StringInputField(PASSWORD),
|
||||
)
|
||||
assertThat(incomingViewModel.state.value).isEqualTo(expectedIncomingConfigState)
|
||||
|
||||
assertThat(effectTurbine.awaitItem())
|
||||
.isEqualTo(Effect.UpdateOutgoingConfig(AccountOutgoingConfigContract.State()))
|
||||
val expectedOutgoingConfigState = AccountOutgoingConfigContract.State(
|
||||
server = StringInputField(OUTGOING_SERVER_NAME),
|
||||
security = ConnectionSecurity.TLS,
|
||||
port = NumberInputField(OUTGOING_SERVER_PORT.toLong()),
|
||||
authenticationType = AuthenticationType.PasswordEncrypted,
|
||||
username = StringInputField(USERNAME),
|
||||
password = StringInputField(PASSWORD),
|
||||
)
|
||||
assertThat(outgoingViewModel.state.value).isEqualTo(expectedOutgoingConfigState)
|
||||
|
||||
assertThat(effectTurbine.awaitItem())
|
||||
.isEqualTo(Effect.UpdateOptions(AccountOptionsContract.State()))
|
||||
assertThat(optionsViewModel.state.value).isEqualTo(
|
||||
AccountOptionsContract.State(
|
||||
accountName = StringInputField(EMAIL_ADDRESS),
|
||||
),
|
||||
)
|
||||
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = stateTurbine.awaitItem(),
|
||||
|
@ -65,7 +133,7 @@ class AccountSetupViewModelTest {
|
|||
|
||||
viewModel.event(AccountSetupContract.Event.OnNext)
|
||||
|
||||
assertThat(effectTurbine.awaitItem()).isEqualTo(Effect.UpdateIncomingConfigValidation)
|
||||
assertThat(incomingValidationViewModel.state.value).isEqualTo(expectedIncomingConfigState.toValidationState())
|
||||
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = stateTurbine.awaitItem(),
|
||||
|
@ -85,7 +153,7 @@ class AccountSetupViewModelTest {
|
|||
|
||||
viewModel.event(AccountSetupContract.Event.OnNext)
|
||||
|
||||
assertThat(effectTurbine.awaitItem()).isEqualTo(Effect.UpdateOutgoingConfigValidation)
|
||||
assertThat(outgoingValidationViewModel.state.value).isEqualTo(expectedOutgoingConfigState.toValidationState())
|
||||
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = stateTurbine.awaitItem(),
|
||||
|
@ -103,30 +171,39 @@ class AccountSetupViewModelTest {
|
|||
prop(State::setupStep).isEqualTo(SetupStep.OPTIONS)
|
||||
}
|
||||
|
||||
viewModel.event(AccountSetupContract.Event.OnNext)
|
||||
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = effectTurbine.awaitItem(),
|
||||
turbines = turbines,
|
||||
) {
|
||||
isEqualTo(Effect.CollectExternalStates)
|
||||
}
|
||||
|
||||
viewModel.event(
|
||||
AccountSetupContract.Event.OnStateCollected(
|
||||
autoDiscoveryState = AccountAutoDiscoveryContract.State(),
|
||||
incomingState = AccountIncomingConfigContract.State(),
|
||||
outgoingState = AccountOutgoingConfigContract.State(),
|
||||
optionsState = AccountOptionsContract.State(),
|
||||
optionsViewModel.initState(
|
||||
optionsViewModel.state.value.copy(
|
||||
accountName = StringInputField("account name"),
|
||||
displayName = StringInputField("display name"),
|
||||
emailSignature = StringInputField("signature"),
|
||||
checkFrequency = EmailCheckFrequency.EVERY_15_MINUTES,
|
||||
messageDisplayCount = EmailDisplayCount.MESSAGES_100,
|
||||
showNotification = true,
|
||||
),
|
||||
)
|
||||
|
||||
viewModel.event(AccountSetupContract.Event.OnNext)
|
||||
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = effectTurbine.awaitItem(),
|
||||
turbines = turbines,
|
||||
) {
|
||||
isEqualTo(Effect.NavigateNext("accountUuid"))
|
||||
}
|
||||
|
||||
assertThat(createAccountEmailAddress).isEqualTo(EMAIL_ADDRESS)
|
||||
assertThat(createAccountIncomingServerSettings).isEqualTo(expectedIncomingConfigState.toServerSettings())
|
||||
assertThat(createAccountOutgoingServerSettings).isEqualTo(expectedOutgoingConfigState.toServerSettings())
|
||||
assertThat(createAccountOptions).isEqualTo(
|
||||
AccountOptions(
|
||||
accountName = "account name",
|
||||
displayName = "display name",
|
||||
emailSignature = "signature",
|
||||
checkFrequencyInMinutes = 15,
|
||||
messageDisplayCount = 100,
|
||||
showNotification = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -134,6 +211,12 @@ class AccountSetupViewModelTest {
|
|||
val initialState = State(setupStep = SetupStep.OPTIONS)
|
||||
val viewModel = AccountSetupViewModel(
|
||||
createAccount = { _, _, _, _ -> "accountUuid" },
|
||||
autoDiscoveryViewModel = autoDiscoveryViewModel,
|
||||
incomingViewModel = FakeAccountIncomingConfigViewModel(),
|
||||
incomingValidationViewModel = FakeAccountValidationViewModel(),
|
||||
outgoingViewModel = FakeAccountOutgoingConfigViewModel(),
|
||||
outgoingValidationViewModel = FakeAccountValidationViewModel(),
|
||||
optionsViewModel = FakeAccountOptionsViewModel(),
|
||||
initialState = initialState,
|
||||
)
|
||||
val stateTurbine = viewModel.state.testIn(backgroundScope)
|
||||
|
@ -202,4 +285,44 @@ class AccountSetupViewModelTest {
|
|||
isEqualTo(Effect.NavigateBack)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val EMAIL_ADDRESS = "test@domain.example"
|
||||
private const val USERNAME = EMAIL_ADDRESS
|
||||
private const val PASSWORD = "password"
|
||||
private const val INCOMING_SERVER_NAME = "imap.domain.example"
|
||||
private const val INCOMING_SERVER_PORT = 993
|
||||
private const val OUTGOING_SERVER_NAME = "smtp.domain.example"
|
||||
private const val OUTGOING_SERVER_PORT = 465
|
||||
|
||||
private val INCOMING_SERVER_SETTINGS = ImapServerSettings(
|
||||
hostname = INCOMING_SERVER_NAME.toHostname(),
|
||||
port = INCOMING_SERVER_PORT.toPort(),
|
||||
connectionSecurity = AutoDiscoveryConnectionSecurity.TLS,
|
||||
authenticationTypes = listOf(AutoDiscoveryAuthenticationType.PasswordEncrypted),
|
||||
username = USERNAME,
|
||||
)
|
||||
|
||||
private val OUTGOING_SERVER_SETTINGS = SmtpServerSettings(
|
||||
hostname = OUTGOING_SERVER_NAME.toHostname(),
|
||||
port = OUTGOING_SERVER_PORT.toPort(),
|
||||
connectionSecurity = AutoDiscoveryConnectionSecurity.TLS,
|
||||
authenticationTypes = listOf(AutoDiscoveryAuthenticationType.PasswordEncrypted),
|
||||
username = USERNAME,
|
||||
)
|
||||
|
||||
private val AUTODISCOVERY_RESULT = AutoDiscoveryResult.Settings(
|
||||
incomingServerSettings = INCOMING_SERVER_SETTINGS,
|
||||
outgoingServerSettings = OUTGOING_SERVER_SETTINGS,
|
||||
isTrusted = true,
|
||||
source = "test",
|
||||
)
|
||||
|
||||
private val AUTODISCOVERY_STATE = AccountAutoDiscoveryContract.State(
|
||||
configStep = AccountAutoDiscoveryContract.ConfigStep.PASSWORD,
|
||||
emailAddress = StringInputField(EMAIL_ADDRESS),
|
||||
password = StringInputField(PASSWORD),
|
||||
autoDiscoverySettings = AUTODISCOVERY_RESULT,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,23 @@ 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.State
|
||||
import app.k9mail.feature.account.setup.ui.autodiscovery.FakeAccountAutoDiscoveryViewModel
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract
|
||||
import app.k9mail.feature.account.setup.ui.incoming.FakeAccountIncomingConfigViewModel
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract
|
||||
import app.k9mail.feature.account.setup.ui.options.FakeAccountOptionsViewModel
|
||||
import app.k9mail.feature.account.setup.ui.outgoing.AccountOutgoingConfigContract
|
||||
import app.k9mail.feature.account.setup.ui.outgoing.FakeAccountOutgoingConfigViewModel
|
||||
import app.k9mail.feature.account.setup.ui.validation.AccountValidationContract
|
||||
import app.k9mail.feature.account.setup.ui.validation.FakeAccountValidationViewModel
|
||||
|
||||
internal class FakeAccountSetupViewModel(
|
||||
override val autoDiscoveryViewModel: FakeAccountAutoDiscoveryViewModel = FakeAccountAutoDiscoveryViewModel(),
|
||||
override val incomingViewModel: AccountIncomingConfigContract.ViewModel = FakeAccountIncomingConfigViewModel(),
|
||||
override val incomingValidationViewModel: AccountValidationContract.ViewModel = FakeAccountValidationViewModel(),
|
||||
override val outgoingViewModel: AccountOutgoingConfigContract.ViewModel = FakeAccountOutgoingConfigViewModel(),
|
||||
override val outgoingValidationViewModel: AccountValidationContract.ViewModel = FakeAccountValidationViewModel(),
|
||||
override val optionsViewModel: AccountOptionsContract.ViewModel = FakeAccountOptionsViewModel(),
|
||||
initialState: State = State(),
|
||||
) : BaseViewModel<State, Event, Effect>(initialState), AccountSetupContract.ViewModel {
|
||||
|
||||
|
|
Loading…
Reference in a new issue