Add AccountIncomingConfigContract and ViewModel
This commit is contained in:
parent
2b436e7bc2
commit
e04bc10729
13 changed files with 479 additions and 33 deletions
|
@ -1,6 +1,7 @@
|
|||
package app.k9mail.feature.account.setup
|
||||
|
||||
import app.k9mail.feature.account.setup.ui.AccountSetupViewModel
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigViewModel
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsValidator
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsViewModel
|
||||
|
@ -16,6 +17,7 @@ val featureAccountSetupModule: Module = module {
|
|||
factory<AccountOptionsContract.Validator> { AccountOptionsValidator() }
|
||||
|
||||
viewModel { AccountSetupViewModel() }
|
||||
viewModel { AccountIncomingConfigViewModel() }
|
||||
viewModel {
|
||||
AccountOutgoingConfigViewModel(
|
||||
validator = get(),
|
||||
|
|
|
@ -8,7 +8,9 @@ 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.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.options.AccountOptionsContract
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsScreen
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsViewModel
|
||||
|
@ -22,6 +24,7 @@ fun AccountSetupScreen(
|
|||
onFinish: () -> Unit,
|
||||
onBack: () -> Unit,
|
||||
viewModel: ViewModel = koinViewModel<AccountSetupViewModel>(),
|
||||
incomingViewModel: AccountIncomingConfigContract.ViewModel = koinViewModel<AccountIncomingConfigViewModel>(),
|
||||
outgoingViewModel: AccountOutgoingConfigContract.ViewModel = koinViewModel<AccountOutgoingConfigViewModel>(),
|
||||
optionsViewModel: AccountOptionsContract.ViewModel = koinViewModel<AccountOptionsViewModel>(),
|
||||
) {
|
||||
|
@ -44,6 +47,7 @@ fun AccountSetupScreen(
|
|||
AccountIncomingConfigScreen(
|
||||
onNext = { dispatch(Event.OnNext) },
|
||||
onBack = { dispatch(Event.OnBack) },
|
||||
viewModel = incomingViewModel,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -29,12 +29,16 @@ import app.k9mail.feature.account.setup.R
|
|||
import app.k9mail.feature.account.setup.domain.entity.ConnectionSecurity
|
||||
import app.k9mail.feature.account.setup.domain.entity.IncomingProtocolType
|
||||
import app.k9mail.feature.account.setup.ui.common.defaultItemPadding
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.Event
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.State
|
||||
import app.k9mail.feature.account.setup.ui.toResourceString
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
internal fun AccountIncomingConfigContent(
|
||||
state: State,
|
||||
onEvent: (Event) -> Unit,
|
||||
contentPadding: PaddingValues,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
@ -61,8 +65,8 @@ internal fun AccountIncomingConfigContent(
|
|||
item {
|
||||
SelectInput(
|
||||
options = IncomingProtocolType.all(),
|
||||
selectedOption = IncomingProtocolType.DEFAULT,
|
||||
onOptionChange = { },
|
||||
selectedOption = state.protocolType,
|
||||
onOptionChange = { onEvent(Event.ProtocolTypeChanged(it)) },
|
||||
label = stringResource(id = R.string.account_setup_incoming_config_protocol_type_label),
|
||||
contentPadding = defaultItemPadding(),
|
||||
)
|
||||
|
@ -70,8 +74,8 @@ internal fun AccountIncomingConfigContent(
|
|||
|
||||
item {
|
||||
TextInput(
|
||||
text = "",
|
||||
onTextChange = { },
|
||||
text = state.server.value,
|
||||
onTextChange = { onEvent(Event.ServerChanged(it)) },
|
||||
label = stringResource(id = R.string.account_setup_incoming_config_server_label),
|
||||
contentPadding = defaultItemPadding(),
|
||||
)
|
||||
|
@ -81,8 +85,8 @@ internal fun AccountIncomingConfigContent(
|
|||
SelectInput(
|
||||
options = ConnectionSecurity.all(),
|
||||
optionToStringTransformation = { it.toResourceString(resources) },
|
||||
selectedOption = ConnectionSecurity.DEFAULT,
|
||||
onOptionChange = { },
|
||||
selectedOption = state.security,
|
||||
onOptionChange = { onEvent(Event.SecurityChanged(it)) },
|
||||
label = stringResource(id = R.string.account_setup_incoming_config_security_label),
|
||||
contentPadding = defaultItemPadding(),
|
||||
)
|
||||
|
@ -90,8 +94,8 @@ internal fun AccountIncomingConfigContent(
|
|||
|
||||
item {
|
||||
NumberInput(
|
||||
value = null,
|
||||
onValueChange = { },
|
||||
value = state.port.value,
|
||||
onValueChange = { onEvent(Event.PortChanged(it)) },
|
||||
label = stringResource(id = R.string.account_setup_outgoing_config_port_label),
|
||||
contentPadding = defaultItemPadding(),
|
||||
)
|
||||
|
@ -99,8 +103,8 @@ internal fun AccountIncomingConfigContent(
|
|||
|
||||
item {
|
||||
TextInput(
|
||||
text = "",
|
||||
onTextChange = { },
|
||||
text = state.username.value,
|
||||
onTextChange = { onEvent(Event.UsernameChanged(it)) },
|
||||
label = stringResource(id = R.string.account_setup_outgoing_config_username_label),
|
||||
contentPadding = defaultItemPadding(),
|
||||
)
|
||||
|
@ -108,8 +112,8 @@ internal fun AccountIncomingConfigContent(
|
|||
|
||||
item {
|
||||
PasswordInput(
|
||||
password = "",
|
||||
onPasswordChange = { },
|
||||
password = state.password.value,
|
||||
onPasswordChange = { onEvent(Event.PasswordChanged(it)) },
|
||||
contentPadding = defaultItemPadding(),
|
||||
)
|
||||
}
|
||||
|
@ -126,35 +130,37 @@ internal fun AccountIncomingConfigContent(
|
|||
selectedOption = stringResource(
|
||||
id = R.string.account_setup_client_certificate_none_available,
|
||||
),
|
||||
onOptionChange = { },
|
||||
onOptionChange = { onEvent(Event.ClientCertificateChanged(it)) },
|
||||
label = stringResource(id = R.string.account_setup_outgoing_config_client_certificate_label),
|
||||
contentPadding = defaultItemPadding(),
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
CheckboxInput(
|
||||
text = stringResource(id = R.string.account_setup_incoming_config_imap_namespace_label),
|
||||
checked = true,
|
||||
onCheckedChange = { },
|
||||
contentPadding = defaultItemPadding(),
|
||||
)
|
||||
}
|
||||
if (state.protocolType == IncomingProtocolType.IMAP) {
|
||||
item {
|
||||
CheckboxInput(
|
||||
text = stringResource(id = R.string.account_setup_incoming_config_imap_namespace_label),
|
||||
checked = state.imapAutodetectNamespaceEnabled,
|
||||
onCheckedChange = { onEvent(Event.ImapAutoDetectNamespaceChanged(it)) },
|
||||
contentPadding = defaultItemPadding(),
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
TextInput(
|
||||
text = "",
|
||||
onTextChange = { },
|
||||
label = stringResource(id = R.string.account_setup_incoming_config_imap_prefix_label),
|
||||
contentPadding = defaultItemPadding(),
|
||||
)
|
||||
item {
|
||||
TextInput(
|
||||
text = state.imapPrefix.value,
|
||||
onTextChange = { onEvent(Event.ImapPrefixChanged(it)) },
|
||||
label = stringResource(id = R.string.account_setup_incoming_config_imap_prefix_label),
|
||||
contentPadding = defaultItemPadding(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
CheckboxInput(
|
||||
text = stringResource(id = R.string.account_setup_incoming_config_compression_label),
|
||||
checked = true,
|
||||
onCheckedChange = { },
|
||||
checked = state.useCompression,
|
||||
onCheckedChange = { onEvent(Event.UseCompressionChanged(it)) },
|
||||
contentPadding = defaultItemPadding(),
|
||||
)
|
||||
}
|
||||
|
@ -167,6 +173,8 @@ internal fun AccountIncomingConfigContent(
|
|||
internal fun AccountIncomingConfigContentK9Preview() {
|
||||
K9Theme {
|
||||
AccountIncomingConfigContent(
|
||||
onEvent = { },
|
||||
state = State(),
|
||||
contentPadding = PaddingValues(),
|
||||
)
|
||||
}
|
||||
|
@ -177,6 +185,8 @@ internal fun AccountIncomingConfigContentK9Preview() {
|
|||
internal fun AccountIncomingConfigContentThunderbirdPreview() {
|
||||
ThunderbirdTheme {
|
||||
AccountIncomingConfigContent(
|
||||
onEvent = { },
|
||||
state = State(),
|
||||
contentPadding = PaddingValues(),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
package app.k9mail.feature.account.setup.ui.incoming
|
||||
|
||||
import app.k9mail.core.ui.compose.common.mvi.UnidirectionalViewModel
|
||||
import app.k9mail.feature.account.setup.domain.entity.ConnectionSecurity
|
||||
import app.k9mail.feature.account.setup.domain.entity.IncomingProtocolType
|
||||
import app.k9mail.feature.account.setup.domain.entity.toImapDefaultPort
|
||||
import app.k9mail.feature.account.setup.domain.input.NumberInputField
|
||||
import app.k9mail.feature.account.setup.domain.input.StringInputField
|
||||
|
||||
interface AccountIncomingConfigContract {
|
||||
|
||||
interface ViewModel : UnidirectionalViewModel<State, Event, Effect> {
|
||||
fun initState(state: State)
|
||||
}
|
||||
|
||||
data class State(
|
||||
val protocolType: IncomingProtocolType = IncomingProtocolType.DEFAULT,
|
||||
val server: StringInputField = StringInputField(),
|
||||
val security: ConnectionSecurity = ConnectionSecurity.DEFAULT,
|
||||
val port: NumberInputField = NumberInputField(ConnectionSecurity.DEFAULT.toImapDefaultPort()),
|
||||
val username: StringInputField = StringInputField(),
|
||||
val password: StringInputField = StringInputField(),
|
||||
val clientCertificate: String = "",
|
||||
val imapAutodetectNamespaceEnabled: Boolean = true,
|
||||
val imapPrefix: StringInputField = StringInputField(),
|
||||
val useCompression: Boolean = true,
|
||||
)
|
||||
|
||||
sealed class Event {
|
||||
data class ProtocolTypeChanged(val protocolType: IncomingProtocolType) : Event()
|
||||
data class ServerChanged(val server: String) : Event()
|
||||
data class SecurityChanged(val security: ConnectionSecurity) : Event()
|
||||
data class PortChanged(val port: Long?) : Event()
|
||||
data class UsernameChanged(val username: String) : Event()
|
||||
data class PasswordChanged(val password: String) : Event()
|
||||
data class ClientCertificateChanged(val clientCertificate: String) : Event()
|
||||
data class ImapAutoDetectNamespaceChanged(val enabled: Boolean) : Event()
|
||||
data class ImapPrefixChanged(val imapPrefix: String) : Event()
|
||||
data class UseCompressionChanged(val useCompression: Boolean) : Event()
|
||||
object OnNextClicked : Event()
|
||||
object OnBackClicked : Event()
|
||||
}
|
||||
|
||||
sealed class Effect {
|
||||
object NavigateNext : Effect()
|
||||
object NavigateBack : Effect()
|
||||
}
|
||||
}
|
|
@ -4,19 +4,31 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.k9mail.core.ui.compose.common.DevicePreviews
|
||||
import app.k9mail.core.ui.compose.common.mvi.observe
|
||||
import app.k9mail.core.ui.compose.designsystem.template.Scaffold
|
||||
import app.k9mail.core.ui.compose.theme.K9Theme
|
||||
import app.k9mail.core.ui.compose.theme.ThunderbirdTheme
|
||||
import app.k9mail.feature.account.setup.R
|
||||
import app.k9mail.feature.account.setup.ui.common.AccountSetupBottomBar
|
||||
import app.k9mail.feature.account.setup.ui.common.AccountSetupTopAppBar
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.Effect
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.Event
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.ViewModel
|
||||
|
||||
@Composable
|
||||
fun AccountIncomingConfigScreen(
|
||||
onNext: () -> Unit,
|
||||
onBack: () -> Unit,
|
||||
viewModel: ViewModel,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val (state, dispatch) = viewModel.observe { effect ->
|
||||
when (effect) {
|
||||
is Effect.NavigateNext -> onNext()
|
||||
is Effect.NavigateBack -> onBack()
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AccountSetupTopAppBar(
|
||||
|
@ -27,13 +39,15 @@ fun AccountIncomingConfigScreen(
|
|||
AccountSetupBottomBar(
|
||||
nextButtonText = stringResource(id = R.string.account_setup_button_next),
|
||||
backButtonText = stringResource(id = R.string.account_setup_button_back),
|
||||
onNextClick = onNext,
|
||||
onBackClick = onBack,
|
||||
onNextClick = { dispatch(Event.OnNextClicked) },
|
||||
onBackClick = { dispatch(Event.OnBackClicked) },
|
||||
)
|
||||
},
|
||||
modifier = modifier,
|
||||
) { innerPadding ->
|
||||
AccountIncomingConfigContent(
|
||||
onEvent = { dispatch(it) },
|
||||
state = state.value,
|
||||
contentPadding = innerPadding,
|
||||
)
|
||||
}
|
||||
|
@ -46,6 +60,7 @@ internal fun AccountIncomingConfigScreenK9Preview() {
|
|||
AccountIncomingConfigScreen(
|
||||
onNext = {},
|
||||
onBack = {},
|
||||
viewModel = AccountIncomingConfigViewModel(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -57,6 +72,7 @@ internal fun AccountIncomingConfigScreenThunderbirdPreview() {
|
|||
AccountIncomingConfigScreen(
|
||||
onNext = {},
|
||||
onBack = {},
|
||||
viewModel = AccountIncomingConfigViewModel(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
package app.k9mail.feature.account.setup.ui.incoming
|
||||
|
||||
import app.k9mail.core.ui.compose.common.mvi.BaseViewModel
|
||||
import app.k9mail.feature.account.setup.domain.entity.ConnectionSecurity
|
||||
import app.k9mail.feature.account.setup.domain.entity.toImapDefaultPort
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.Effect
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.Event
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.Event.ClientCertificateChanged
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.Event.ImapAutoDetectNamespaceChanged
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.Event.ImapPrefixChanged
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.Event.OnBackClicked
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.Event.OnNextClicked
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.Event.PasswordChanged
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.Event.PortChanged
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.Event.ProtocolTypeChanged
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.Event.SecurityChanged
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.Event.ServerChanged
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.Event.UseCompressionChanged
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.Event.UsernameChanged
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.State
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.ViewModel
|
||||
|
||||
class AccountIncomingConfigViewModel(
|
||||
initialState: State = State(),
|
||||
) : BaseViewModel<State, Event, Effect>(initialState), ViewModel {
|
||||
|
||||
override fun initState(state: State) {
|
||||
updateState {
|
||||
state.copy()
|
||||
}
|
||||
}
|
||||
|
||||
override fun event(event: Event) {
|
||||
when (event) {
|
||||
is PortChanged -> updateState { it.copy(port = it.port.updateValue(event.port)) }
|
||||
is ProtocolTypeChanged -> updateState { it.copy(protocolType = event.protocolType) }
|
||||
is SecurityChanged -> updateSecurity(event.security)
|
||||
is ServerChanged -> updateState { it.copy(server = it.server.updateValue(event.server)) }
|
||||
is UsernameChanged -> updateState { it.copy(username = it.username.updateValue(event.username)) }
|
||||
is PasswordChanged -> updateState { it.copy(password = it.password.updateValue(event.password)) }
|
||||
is ClientCertificateChanged -> updateState { it.copy(clientCertificate = event.clientCertificate) }
|
||||
is ImapAutoDetectNamespaceChanged -> updateState { it.copy(imapAutodetectNamespaceEnabled = event.enabled) }
|
||||
is ImapPrefixChanged -> updateState { it.copy(imapPrefix = it.imapPrefix.updateValue(event.imapPrefix)) }
|
||||
is UseCompressionChanged -> updateState { it.copy(useCompression = event.useCompression) }
|
||||
OnBackClicked -> navigateBack()
|
||||
OnNextClicked -> submit()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateSecurity(security: ConnectionSecurity) {
|
||||
updateState {
|
||||
it.copy(
|
||||
security = security,
|
||||
port = it.port.updateValue(security.toImapDefaultPort()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun submit() {
|
||||
navigateNext()
|
||||
}
|
||||
|
||||
private fun navigateBack() = emitEffect(Effect.NavigateBack)
|
||||
|
||||
private fun navigateNext() = emitEffect(Effect.NavigateNext)
|
||||
}
|
|
@ -42,8 +42,8 @@ class AccountOutgoingConfigViewModel(
|
|||
is ImapAutoDetectNamespaceChanged -> updateState { it.copy(imapAutodetectNamespaceEnabled = event.enabled) }
|
||||
is UseCompressionChanged -> updateState { it.copy(useCompression = event.useCompression) }
|
||||
|
||||
OnBackClicked -> navigateBack()
|
||||
OnNextClicked -> submit()
|
||||
OnBackClicked -> navigateBack()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ 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.incoming.FakeAccountIncomingConfigViewModel
|
||||
import app.k9mail.feature.account.setup.ui.options.FakeAccountOptionsViewModel
|
||||
import app.k9mail.feature.account.setup.ui.outgoing.FakeAccountOutgoingConfigViewModel
|
||||
import assertk.assertThat
|
||||
|
@ -21,6 +22,7 @@ class AccountSetupScreenKtTest : ComposeTest() {
|
|||
@Test
|
||||
fun `should display correct screen for every setup step`() = runTest {
|
||||
val viewModel = FakeAccountSetupViewModel()
|
||||
val incomingViewModel = FakeAccountIncomingConfigViewModel()
|
||||
val outgoingViewModel = FakeAccountOutgoingConfigViewModel()
|
||||
val optionsViewModel = FakeAccountOptionsViewModel()
|
||||
|
||||
|
@ -30,6 +32,7 @@ class AccountSetupScreenKtTest : ComposeTest() {
|
|||
onFinish = { },
|
||||
onBack = { },
|
||||
viewModel = viewModel,
|
||||
incomingViewModel = incomingViewModel,
|
||||
outgoingViewModel = outgoingViewModel,
|
||||
optionsViewModel = optionsViewModel,
|
||||
)
|
||||
|
@ -46,6 +49,7 @@ class AccountSetupScreenKtTest : ComposeTest() {
|
|||
fun `should delegate navigation effects`() = runTest {
|
||||
val initialState = State()
|
||||
val viewModel = FakeAccountSetupViewModel(initialState)
|
||||
val incomingViewModel = FakeAccountIncomingConfigViewModel()
|
||||
val outgoingViewModel = FakeAccountOutgoingConfigViewModel()
|
||||
val optionsViewModel = FakeAccountOptionsViewModel()
|
||||
var onFinishCounter = 0
|
||||
|
@ -57,6 +61,7 @@ class AccountSetupScreenKtTest : ComposeTest() {
|
|||
onFinish = { onFinishCounter++ },
|
||||
onBack = { onBackCounter++ },
|
||||
viewModel = viewModel,
|
||||
incomingViewModel = incomingViewModel,
|
||||
outgoingViewModel = outgoingViewModel,
|
||||
optionsViewModel = optionsViewModel,
|
||||
)
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
package app.k9mail.feature.account.setup.ui.incoming
|
||||
|
||||
import app.k9mail.core.ui.compose.testing.ComposeTest
|
||||
import app.k9mail.core.ui.compose.testing.setContent
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.Effect
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.State
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isEqualTo
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class AccountIncomingConfigScreenKtTest : ComposeTest() {
|
||||
|
||||
@Test
|
||||
fun `should delegate navigation effects`() = runTest {
|
||||
val initialState = State()
|
||||
val viewModel = FakeAccountIncomingConfigViewModel(initialState)
|
||||
var onNextCounter = 0
|
||||
var onBackCounter = 0
|
||||
|
||||
setContent {
|
||||
AccountIncomingConfigScreen(
|
||||
onNext = { onNextCounter++ },
|
||||
onBack = { onBackCounter++ },
|
||||
viewModel = viewModel,
|
||||
)
|
||||
}
|
||||
|
||||
assertThat(onNextCounter).isEqualTo(0)
|
||||
assertThat(onBackCounter).isEqualTo(0)
|
||||
|
||||
viewModel.effect(Effect.NavigateNext)
|
||||
|
||||
assertThat(onNextCounter).isEqualTo(1)
|
||||
assertThat(onBackCounter).isEqualTo(0)
|
||||
|
||||
viewModel.effect(Effect.NavigateBack)
|
||||
|
||||
assertThat(onNextCounter).isEqualTo(1)
|
||||
assertThat(onBackCounter).isEqualTo(1)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package app.k9mail.feature.account.setup.ui.incoming
|
||||
|
||||
import app.k9mail.feature.account.setup.domain.entity.ConnectionSecurity
|
||||
import app.k9mail.feature.account.setup.domain.entity.IncomingProtocolType
|
||||
import app.k9mail.feature.account.setup.domain.entity.toImapDefaultPort
|
||||
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.incoming.AccountIncomingConfigContract.State
|
||||
import assertk.all
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isEqualTo
|
||||
import assertk.assertions.isTrue
|
||||
import assertk.assertions.prop
|
||||
import org.junit.Test
|
||||
|
||||
class AccountIncomingConfigStateTest {
|
||||
|
||||
@Test
|
||||
fun `should set default values`() {
|
||||
val state = State()
|
||||
|
||||
assertThat(state).all {
|
||||
prop(State::protocolType).isEqualTo(IncomingProtocolType.IMAP)
|
||||
prop(State::server).isEqualTo(StringInputField())
|
||||
prop(State::security).isEqualTo(ConnectionSecurity.DEFAULT)
|
||||
prop(State::port).isEqualTo(NumberInputField(value = ConnectionSecurity.DEFAULT.toImapDefaultPort()))
|
||||
prop(State::username).isEqualTo(StringInputField())
|
||||
prop(State::password).isEqualTo(StringInputField())
|
||||
prop(State::clientCertificate).isEqualTo("")
|
||||
prop(State::imapAutodetectNamespaceEnabled).isTrue()
|
||||
prop(State::useCompression).isTrue()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
package app.k9mail.feature.account.setup.ui.incoming
|
||||
|
||||
import app.cash.turbine.testIn
|
||||
import app.k9mail.core.ui.compose.testing.MainDispatcherRule
|
||||
import app.k9mail.feature.account.setup.domain.entity.ConnectionSecurity
|
||||
import app.k9mail.feature.account.setup.domain.entity.IncomingProtocolType
|
||||
import app.k9mail.feature.account.setup.domain.entity.toImapDefaultPort
|
||||
import app.k9mail.feature.account.setup.domain.input.NumberInputField
|
||||
import app.k9mail.feature.account.setup.domain.input.StringInputField
|
||||
import app.k9mail.feature.account.setup.testing.eventStateTest
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.Effect
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.Event
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.State
|
||||
import assertk.assertions.assertThatAndTurbinesConsumed
|
||||
import assertk.assertions.isEqualTo
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class AccountIncomingConfigViewModelTest {
|
||||
|
||||
@get:Rule
|
||||
val mainDispatcherRule = MainDispatcherRule()
|
||||
|
||||
private val testSubject = AccountIncomingConfigViewModel()
|
||||
|
||||
@Test
|
||||
fun `should change state when ProtocolTypeChanged event is received`() = runTest {
|
||||
eventStateTest(
|
||||
viewModel = testSubject,
|
||||
initialState = State(),
|
||||
event = Event.ProtocolTypeChanged(IncomingProtocolType.POP3),
|
||||
expectedState = State(protocolType = IncomingProtocolType.POP3),
|
||||
coroutineScope = backgroundScope,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should change state when ServerChanged event is received`() = runTest {
|
||||
eventStateTest(
|
||||
viewModel = testSubject,
|
||||
initialState = State(),
|
||||
event = Event.ServerChanged("server"),
|
||||
expectedState = State(server = StringInputField(value = "server")),
|
||||
coroutineScope = backgroundScope,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should change security and port when SecurityChanged event is received`() = runTest {
|
||||
eventStateTest(
|
||||
viewModel = testSubject,
|
||||
initialState = State(),
|
||||
event = Event.SecurityChanged(ConnectionSecurity.StartTLS),
|
||||
expectedState = State(
|
||||
security = ConnectionSecurity.StartTLS,
|
||||
port = NumberInputField(value = ConnectionSecurity.StartTLS.toImapDefaultPort()),
|
||||
),
|
||||
coroutineScope = backgroundScope,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should change state when PortChanged event is received`() = runTest {
|
||||
eventStateTest(
|
||||
viewModel = testSubject,
|
||||
initialState = State(),
|
||||
event = Event.PortChanged(456L),
|
||||
expectedState = State(port = NumberInputField(value = 456L)),
|
||||
coroutineScope = backgroundScope,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should change state when UsernameChanged event is received`() = runTest {
|
||||
eventStateTest(
|
||||
viewModel = testSubject,
|
||||
initialState = State(),
|
||||
event = Event.UsernameChanged("username"),
|
||||
expectedState = State(username = StringInputField(value = "username")),
|
||||
coroutineScope = backgroundScope,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should change state when PasswordChanged event is received`() = runTest {
|
||||
eventStateTest(
|
||||
viewModel = testSubject,
|
||||
initialState = State(),
|
||||
event = Event.PasswordChanged("password"),
|
||||
expectedState = State(password = StringInputField(value = "password")),
|
||||
coroutineScope = backgroundScope,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should change state when ClientCertificateChanged event is received`() = runTest {
|
||||
eventStateTest(
|
||||
viewModel = testSubject,
|
||||
initialState = State(),
|
||||
event = Event.ClientCertificateChanged("clientCertificate"),
|
||||
expectedState = State(clientCertificate = "clientCertificate"),
|
||||
coroutineScope = backgroundScope,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should change state when ImapAutoDetectNamespaceChanged event is received`() = runTest {
|
||||
eventStateTest(
|
||||
viewModel = testSubject,
|
||||
initialState = State(imapAutodetectNamespaceEnabled = true),
|
||||
event = Event.ImapAutoDetectNamespaceChanged(false),
|
||||
expectedState = State(imapAutodetectNamespaceEnabled = false),
|
||||
coroutineScope = backgroundScope,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should change state when ImapPrefixChanged event is received`() = runTest {
|
||||
eventStateTest(
|
||||
viewModel = testSubject,
|
||||
initialState = State(),
|
||||
event = Event.ImapPrefixChanged("imapPrefix"),
|
||||
expectedState = State(imapPrefix = StringInputField(value = "imapPrefix")),
|
||||
coroutineScope = backgroundScope,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should change state when UseCompressionChanged event is received`() = runTest {
|
||||
eventStateTest(
|
||||
viewModel = testSubject,
|
||||
initialState = State(useCompression = true),
|
||||
event = Event.UseCompressionChanged(false),
|
||||
expectedState = State(useCompression = false),
|
||||
coroutineScope = backgroundScope,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should emit NavigateNext effect when OnNextClicked event received`() = runTest {
|
||||
val viewModel = testSubject
|
||||
val stateTurbine = viewModel.state.testIn(backgroundScope)
|
||||
val effectTurbine = viewModel.effect.testIn(backgroundScope)
|
||||
val turbines = listOf(stateTurbine, effectTurbine)
|
||||
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = stateTurbine.awaitItem(),
|
||||
turbines = turbines,
|
||||
) {
|
||||
isEqualTo(State())
|
||||
}
|
||||
|
||||
viewModel.event(Event.OnNextClicked)
|
||||
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = effectTurbine.awaitItem(),
|
||||
turbines = turbines,
|
||||
) {
|
||||
isEqualTo(Effect.NavigateNext)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should emit NavigateBack effect when OnBackClicked event received`() = runTest {
|
||||
val viewModel = testSubject
|
||||
val stateTurbine = viewModel.state.testIn(backgroundScope)
|
||||
val effectTurbine = viewModel.effect.testIn(backgroundScope)
|
||||
val turbines = listOf(stateTurbine, effectTurbine)
|
||||
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = stateTurbine.awaitItem(),
|
||||
turbines = turbines,
|
||||
) {
|
||||
isEqualTo(State())
|
||||
}
|
||||
|
||||
viewModel.event(Event.OnBackClicked)
|
||||
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = effectTurbine.awaitItem(),
|
||||
turbines = turbines,
|
||||
) {
|
||||
isEqualTo(Effect.NavigateBack)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package app.k9mail.feature.account.setup.ui.incoming
|
||||
|
||||
import app.k9mail.core.ui.compose.common.mvi.BaseViewModel
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.Effect
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.Event
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.State
|
||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.ViewModel
|
||||
|
||||
class FakeAccountIncomingConfigViewModel(
|
||||
initialState: State = State(),
|
||||
) : BaseViewModel<State, Event, Effect>(initialState), ViewModel {
|
||||
|
||||
val events = mutableListOf<Event>()
|
||||
|
||||
override fun initState(state: State) {
|
||||
updateState { state }
|
||||
}
|
||||
|
||||
override fun event(event: Event) {
|
||||
events.add(event)
|
||||
}
|
||||
|
||||
fun effect(effect: Effect) {
|
||||
emitEffect(effect)
|
||||
}
|
||||
}
|
|
@ -6,9 +6,11 @@ import app.k9mail.feature.account.setup.ui.outgoing.AccountOutgoingConfigContrac
|
|||
import app.k9mail.feature.account.setup.ui.outgoing.AccountOutgoingConfigContract.State
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isEqualTo
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class AccountOutgoingConfigScreenKtTest : ComposeTest() {
|
||||
|
||||
@Test
|
||||
|
|
Loading…
Reference in a new issue