Merge pull request #6954 from thundernest/add_account_setup_incoming_config_validation
Add account setup incoming config validation
This commit is contained in:
commit
8015392d70
16 changed files with 316 additions and 84 deletions
|
@ -1,6 +1,8 @@
|
||||||
package app.k9mail.feature.account.setup
|
package app.k9mail.feature.account.setup
|
||||||
|
|
||||||
import app.k9mail.feature.account.setup.ui.AccountSetupViewModel
|
import app.k9mail.feature.account.setup.ui.AccountSetupViewModel
|
||||||
|
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract
|
||||||
|
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigValidator
|
||||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigViewModel
|
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.AccountOptionsContract
|
||||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsValidator
|
import app.k9mail.feature.account.setup.ui.options.AccountOptionsValidator
|
||||||
|
@ -13,11 +15,16 @@ import org.koin.core.module.Module
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val featureAccountSetupModule: Module = module {
|
val featureAccountSetupModule: Module = module {
|
||||||
|
factory<AccountIncomingConfigContract.Validator> { AccountIncomingConfigValidator() }
|
||||||
factory<AccountOutgoingConfigContract.Validator> { AccountOutgoingConfigValidator() }
|
factory<AccountOutgoingConfigContract.Validator> { AccountOutgoingConfigValidator() }
|
||||||
factory<AccountOptionsContract.Validator> { AccountOptionsValidator() }
|
factory<AccountOptionsContract.Validator> { AccountOptionsValidator() }
|
||||||
|
|
||||||
viewModel { AccountSetupViewModel() }
|
viewModel { AccountSetupViewModel() }
|
||||||
viewModel { AccountIncomingConfigViewModel() }
|
viewModel {
|
||||||
|
AccountIncomingConfigViewModel(
|
||||||
|
validator = get(),
|
||||||
|
)
|
||||||
|
}
|
||||||
viewModel {
|
viewModel {
|
||||||
AccountOutgoingConfigViewModel(
|
AccountOutgoingConfigViewModel(
|
||||||
validator = get(),
|
validator = get(),
|
||||||
|
|
|
@ -68,4 +68,8 @@ class NumberInputField(
|
||||||
result = 31 * result + isValid.hashCode()
|
result = 31 * result + isValid.hashCode()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "NumberInputField(value=$value, error=$error, isValid=$isValid)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,4 +68,8 @@ class StringInputField(
|
||||||
result = 31 * result + isValid.hashCode()
|
result = 31 * result + isValid.hashCode()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "StringInputField(value='$value', error=$error, isValid=$isValid)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
package app.k9mail.feature.account.setup.domain.usecase
|
||||||
|
|
||||||
|
import app.k9mail.core.common.domain.usecase.validation.ValidationError
|
||||||
|
import app.k9mail.core.common.domain.usecase.validation.ValidationResult
|
||||||
|
import app.k9mail.core.common.domain.usecase.validation.ValidationUseCase
|
||||||
|
|
||||||
|
class ValidateImapPrefix : ValidationUseCase<String> {
|
||||||
|
|
||||||
|
override fun execute(input: String): ValidationResult {
|
||||||
|
return when {
|
||||||
|
input.isEmpty() -> ValidationResult.Success
|
||||||
|
input.isBlank() -> ValidationResult.Failure(ValidateImapPrefixError.BlankImapPrefix)
|
||||||
|
|
||||||
|
else -> ValidationResult.Success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface ValidateImapPrefixError : ValidationError {
|
||||||
|
object BlankImapPrefix : ValidateImapPrefixError
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,14 @@
|
||||||
package app.k9mail.feature.account.setup.ui
|
package app.k9mail.feature.account.setup.ui
|
||||||
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
|
import app.k9mail.core.common.domain.usecase.validation.ValidationError
|
||||||
import app.k9mail.feature.account.setup.R
|
import app.k9mail.feature.account.setup.R
|
||||||
import app.k9mail.feature.account.setup.domain.entity.ConnectionSecurity
|
import app.k9mail.feature.account.setup.domain.entity.ConnectionSecurity
|
||||||
|
import app.k9mail.feature.account.setup.domain.usecase.ValidateImapPrefix
|
||||||
|
import app.k9mail.feature.account.setup.domain.usecase.ValidatePassword
|
||||||
|
import app.k9mail.feature.account.setup.domain.usecase.ValidatePort
|
||||||
|
import app.k9mail.feature.account.setup.domain.usecase.ValidateServer
|
||||||
|
import app.k9mail.feature.account.setup.domain.usecase.ValidateUsername
|
||||||
|
|
||||||
internal fun ConnectionSecurity.toResourceString(resources: Resources): String {
|
internal fun ConnectionSecurity.toResourceString(resources: Resources): String {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
|
@ -11,3 +17,58 @@ internal fun ConnectionSecurity.toResourceString(resources: Resources): String {
|
||||||
ConnectionSecurity.TLS -> resources.getString(R.string.account_setup_connection_security_ssl)
|
ConnectionSecurity.TLS -> resources.getString(R.string.account_setup_connection_security_ssl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun ValidationError.toResourceString(resources: Resources): String {
|
||||||
|
return when (this) {
|
||||||
|
is ValidateServer.ValidateServerError -> toServerErrorString(resources)
|
||||||
|
is ValidatePort.ValidatePortError -> toPortErrorString(resources)
|
||||||
|
is ValidateUsername.ValidateUsernameError -> toUsernameErrorString(resources)
|
||||||
|
is ValidatePassword.ValidatePasswordError -> toPasswordErrorString(resources)
|
||||||
|
is ValidateImapPrefix.ValidateImapPrefixError -> toImapPrefixErrorString(resources)
|
||||||
|
else -> throw IllegalArgumentException("Unknown error: $this")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ValidateServer.ValidateServerError.toServerErrorString(resources: Resources): String {
|
||||||
|
return when (this) {
|
||||||
|
is ValidateServer.ValidateServerError.EmptyServer -> resources.getString(
|
||||||
|
R.string.account_setup_validation_error_server_required,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ValidatePort.ValidatePortError.toPortErrorString(resources: Resources): String {
|
||||||
|
return when (this) {
|
||||||
|
is ValidatePort.ValidatePortError.EmptyPort -> resources.getString(
|
||||||
|
R.string.account_setup_validation_error_port_required,
|
||||||
|
)
|
||||||
|
|
||||||
|
is ValidatePort.ValidatePortError.InvalidPort -> resources.getString(
|
||||||
|
R.string.account_setup_validation_error_port_invalid,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ValidateUsername.ValidateUsernameError.toUsernameErrorString(resources: Resources): String {
|
||||||
|
return when (this) {
|
||||||
|
ValidateUsername.ValidateUsernameError.EmptyUsername -> resources.getString(
|
||||||
|
R.string.account_setup_validation_error_username_required,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ValidatePassword.ValidatePasswordError.toPasswordErrorString(resources: Resources): String {
|
||||||
|
return when (this) {
|
||||||
|
ValidatePassword.ValidatePasswordError.EmptyPassword -> resources.getString(
|
||||||
|
R.string.account_setup_validation_error_password_required,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ValidateImapPrefix.ValidateImapPrefixError.toImapPrefixErrorString(resources: Resources): String {
|
||||||
|
return when (this) {
|
||||||
|
ValidateImapPrefix.ValidateImapPrefixError.BlankImapPrefix -> resources.getString(
|
||||||
|
R.string.account_setup_validation_error_imap_prefix_blank,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -75,6 +75,7 @@ internal fun AccountIncomingConfigContent(
|
||||||
item {
|
item {
|
||||||
TextInput(
|
TextInput(
|
||||||
text = state.server.value,
|
text = state.server.value,
|
||||||
|
errorMessage = state.server.error?.toResourceString(resources),
|
||||||
onTextChange = { onEvent(Event.ServerChanged(it)) },
|
onTextChange = { onEvent(Event.ServerChanged(it)) },
|
||||||
label = stringResource(id = R.string.account_setup_incoming_config_server_label),
|
label = stringResource(id = R.string.account_setup_incoming_config_server_label),
|
||||||
contentPadding = defaultItemPadding(),
|
contentPadding = defaultItemPadding(),
|
||||||
|
@ -95,6 +96,7 @@ internal fun AccountIncomingConfigContent(
|
||||||
item {
|
item {
|
||||||
NumberInput(
|
NumberInput(
|
||||||
value = state.port.value,
|
value = state.port.value,
|
||||||
|
errorMessage = state.port.error?.toResourceString(resources),
|
||||||
onValueChange = { onEvent(Event.PortChanged(it)) },
|
onValueChange = { onEvent(Event.PortChanged(it)) },
|
||||||
label = stringResource(id = R.string.account_setup_outgoing_config_port_label),
|
label = stringResource(id = R.string.account_setup_outgoing_config_port_label),
|
||||||
contentPadding = defaultItemPadding(),
|
contentPadding = defaultItemPadding(),
|
||||||
|
@ -104,6 +106,7 @@ internal fun AccountIncomingConfigContent(
|
||||||
item {
|
item {
|
||||||
TextInput(
|
TextInput(
|
||||||
text = state.username.value,
|
text = state.username.value,
|
||||||
|
errorMessage = state.username.error?.toResourceString(resources),
|
||||||
onTextChange = { onEvent(Event.UsernameChanged(it)) },
|
onTextChange = { onEvent(Event.UsernameChanged(it)) },
|
||||||
label = stringResource(id = R.string.account_setup_outgoing_config_username_label),
|
label = stringResource(id = R.string.account_setup_outgoing_config_username_label),
|
||||||
contentPadding = defaultItemPadding(),
|
contentPadding = defaultItemPadding(),
|
||||||
|
@ -113,6 +116,7 @@ internal fun AccountIncomingConfigContent(
|
||||||
item {
|
item {
|
||||||
PasswordInput(
|
PasswordInput(
|
||||||
password = state.password.value,
|
password = state.password.value,
|
||||||
|
errorMessage = state.password.error?.toResourceString(resources),
|
||||||
onPasswordChange = { onEvent(Event.PasswordChanged(it)) },
|
onPasswordChange = { onEvent(Event.PasswordChanged(it)) },
|
||||||
contentPadding = defaultItemPadding(),
|
contentPadding = defaultItemPadding(),
|
||||||
)
|
)
|
||||||
|
@ -149,6 +153,7 @@ internal fun AccountIncomingConfigContent(
|
||||||
item {
|
item {
|
||||||
TextInput(
|
TextInput(
|
||||||
text = state.imapPrefix.value,
|
text = state.imapPrefix.value,
|
||||||
|
errorMessage = state.imapPrefix.error?.toResourceString(resources),
|
||||||
onTextChange = { onEvent(Event.ImapPrefixChanged(it)) },
|
onTextChange = { onEvent(Event.ImapPrefixChanged(it)) },
|
||||||
label = stringResource(id = R.string.account_setup_incoming_config_imap_prefix_label),
|
label = stringResource(id = R.string.account_setup_incoming_config_imap_prefix_label),
|
||||||
contentPadding = defaultItemPadding(),
|
contentPadding = defaultItemPadding(),
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package app.k9mail.feature.account.setup.ui.incoming
|
package app.k9mail.feature.account.setup.ui.incoming
|
||||||
|
|
||||||
|
import app.k9mail.core.common.domain.usecase.validation.ValidationResult
|
||||||
import app.k9mail.core.ui.compose.common.mvi.UnidirectionalViewModel
|
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.ConnectionSecurity
|
||||||
import app.k9mail.feature.account.setup.domain.entity.IncomingProtocolType
|
import app.k9mail.feature.account.setup.domain.entity.IncomingProtocolType
|
||||||
|
@ -48,4 +49,12 @@ interface AccountIncomingConfigContract {
|
||||||
object NavigateNext : Effect()
|
object NavigateNext : Effect()
|
||||||
object NavigateBack : Effect()
|
object NavigateBack : Effect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Validator {
|
||||||
|
fun validateServer(server: String): ValidationResult
|
||||||
|
fun validatePort(port: Long?): ValidationResult
|
||||||
|
fun validateUsername(username: String): ValidationResult
|
||||||
|
fun validatePassword(password: String): ValidationResult
|
||||||
|
fun validateImapPrefix(imapPrefix: String): ValidationResult
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,9 @@ internal fun AccountIncomingConfigScreenK9Preview() {
|
||||||
AccountIncomingConfigScreen(
|
AccountIncomingConfigScreen(
|
||||||
onNext = {},
|
onNext = {},
|
||||||
onBack = {},
|
onBack = {},
|
||||||
viewModel = AccountIncomingConfigViewModel(),
|
viewModel = AccountIncomingConfigViewModel(
|
||||||
|
validator = AccountIncomingConfigValidator(),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,7 +74,9 @@ internal fun AccountIncomingConfigScreenThunderbirdPreview() {
|
||||||
AccountIncomingConfigScreen(
|
AccountIncomingConfigScreen(
|
||||||
onNext = {},
|
onNext = {},
|
||||||
onBack = {},
|
onBack = {},
|
||||||
viewModel = AccountIncomingConfigViewModel(),
|
viewModel = AccountIncomingConfigViewModel(
|
||||||
|
validator = AccountIncomingConfigValidator(),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
package app.k9mail.feature.account.setup.ui.incoming
|
||||||
|
|
||||||
|
import app.k9mail.core.common.domain.usecase.validation.ValidationResult
|
||||||
|
import app.k9mail.feature.account.setup.domain.usecase.ValidateImapPrefix
|
||||||
|
import app.k9mail.feature.account.setup.domain.usecase.ValidatePassword
|
||||||
|
import app.k9mail.feature.account.setup.domain.usecase.ValidatePort
|
||||||
|
import app.k9mail.feature.account.setup.domain.usecase.ValidateServer
|
||||||
|
import app.k9mail.feature.account.setup.domain.usecase.ValidateUsername
|
||||||
|
|
||||||
|
class AccountIncomingConfigValidator(
|
||||||
|
private val serverValidator: ValidateServer = ValidateServer(),
|
||||||
|
private val portValidator: ValidatePort = ValidatePort(),
|
||||||
|
private val usernameValidator: ValidateUsername = ValidateUsername(),
|
||||||
|
private val passwordValidator: ValidatePassword = ValidatePassword(),
|
||||||
|
private val imapPrefixValidator: ValidateImapPrefix = ValidateImapPrefix(),
|
||||||
|
) : AccountIncomingConfigContract.Validator {
|
||||||
|
override fun validateServer(server: String): ValidationResult {
|
||||||
|
return serverValidator.execute(server)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun validatePort(port: Long?): ValidationResult {
|
||||||
|
return portValidator.execute(port)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun validateUsername(username: String): ValidationResult {
|
||||||
|
return usernameValidator.execute(username)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun validatePassword(password: String): ValidationResult {
|
||||||
|
return passwordValidator.execute(password)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun validateImapPrefix(imapPrefix: String): ValidationResult {
|
||||||
|
return imapPrefixValidator.execute(imapPrefix)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package app.k9mail.feature.account.setup.ui.incoming
|
package app.k9mail.feature.account.setup.ui.incoming
|
||||||
|
|
||||||
|
import app.k9mail.core.common.domain.usecase.validation.ValidationResult
|
||||||
import app.k9mail.core.ui.compose.common.mvi.BaseViewModel
|
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.ConnectionSecurity
|
||||||
import app.k9mail.feature.account.setup.domain.entity.IncomingProtocolType
|
import app.k9mail.feature.account.setup.domain.entity.IncomingProtocolType
|
||||||
|
@ -20,10 +21,12 @@ import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContrac
|
||||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.Event.UseCompressionChanged
|
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.Event.UsernameChanged
|
||||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.State
|
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.State
|
||||||
|
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.Validator
|
||||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.ViewModel
|
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.ViewModel
|
||||||
|
|
||||||
class AccountIncomingConfigViewModel(
|
class AccountIncomingConfigViewModel(
|
||||||
initialState: State = State(),
|
initialState: State = State(),
|
||||||
|
private val validator: Validator,
|
||||||
) : BaseViewModel<State, Event, Effect>(initialState), ViewModel {
|
) : BaseViewModel<State, Event, Effect>(initialState), ViewModel {
|
||||||
|
|
||||||
override fun initState(state: State) {
|
override fun initState(state: State) {
|
||||||
|
@ -70,8 +73,29 @@ class AccountIncomingConfigViewModel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun submit() {
|
private fun submit() = with(state.value) {
|
||||||
navigateNext()
|
val serverResult = validator.validateServer(server.value)
|
||||||
|
val portResult = validator.validatePort(port.value)
|
||||||
|
val usernameResult = validator.validateUsername(username.value)
|
||||||
|
val passwordResult = validator.validatePassword(password.value)
|
||||||
|
val imapPrefixResult = validator.validateImapPrefix(imapPrefix.value)
|
||||||
|
|
||||||
|
val hasError = listOf(serverResult, portResult, usernameResult, passwordResult, imapPrefixResult)
|
||||||
|
.any { it is ValidationResult.Failure }
|
||||||
|
|
||||||
|
updateState {
|
||||||
|
it.copy(
|
||||||
|
server = it.server.updateFromValidationResult(serverResult),
|
||||||
|
port = it.port.updateFromValidationResult(portResult),
|
||||||
|
username = it.username.updateFromValidationResult(usernameResult),
|
||||||
|
password = it.password.updateFromValidationResult(passwordResult),
|
||||||
|
imapPrefix = it.imapPrefix.updateFromValidationResult(imapPrefixResult),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasError) {
|
||||||
|
navigateNext()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun navigateBack() = emitEffect(Effect.NavigateBack)
|
private fun navigateBack() = emitEffect(Effect.NavigateBack)
|
||||||
|
|
|
@ -44,7 +44,6 @@ interface AccountOutgoingConfigContract {
|
||||||
|
|
||||||
interface Validator {
|
interface Validator {
|
||||||
fun validateServer(server: String): ValidationResult
|
fun validateServer(server: String): ValidationResult
|
||||||
|
|
||||||
fun validatePort(port: Long?): ValidationResult
|
fun validatePort(port: Long?): ValidationResult
|
||||||
fun validateUsername(username: String): ValidationResult
|
fun validateUsername(username: String): ValidationResult
|
||||||
fun validatePassword(password: String): ValidationResult
|
fun validatePassword(password: String): ValidationResult
|
||||||
|
|
|
@ -1,55 +0,0 @@
|
||||||
package app.k9mail.feature.account.setup.ui.outgoing
|
|
||||||
|
|
||||||
import android.content.res.Resources
|
|
||||||
import app.k9mail.core.common.domain.usecase.validation.ValidationError
|
|
||||||
import app.k9mail.feature.account.setup.R
|
|
||||||
import app.k9mail.feature.account.setup.domain.usecase.ValidatePassword.ValidatePasswordError
|
|
||||||
import app.k9mail.feature.account.setup.domain.usecase.ValidatePort.ValidatePortError
|
|
||||||
import app.k9mail.feature.account.setup.domain.usecase.ValidateServer.ValidateServerError
|
|
||||||
import app.k9mail.feature.account.setup.domain.usecase.ValidateUsername.ValidateUsernameError
|
|
||||||
|
|
||||||
internal fun ValidationError.toResourceString(resources: Resources): String {
|
|
||||||
return when (this) {
|
|
||||||
is ValidateServerError -> toServerErrorString(resources)
|
|
||||||
is ValidatePortError -> toPortErrorString(resources)
|
|
||||||
is ValidateUsernameError -> toUsernameErrorString(resources)
|
|
||||||
is ValidatePasswordError -> toPasswordErrorString(resources)
|
|
||||||
else -> throw IllegalArgumentException("Unknown error: $this")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ValidateServerError.toServerErrorString(resources: Resources): String {
|
|
||||||
return when (this) {
|
|
||||||
is ValidateServerError.EmptyServer -> resources.getString(
|
|
||||||
R.string.account_setup_outgoing_config_server_error_required,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ValidatePortError.toPortErrorString(resources: Resources): String {
|
|
||||||
return when (this) {
|
|
||||||
is ValidatePortError.EmptyPort -> resources.getString(
|
|
||||||
R.string.account_setup_outgoing_config_port_error_required,
|
|
||||||
)
|
|
||||||
|
|
||||||
is ValidatePortError.InvalidPort -> resources.getString(
|
|
||||||
R.string.account_setup_outgoing_config_port_error_invalid,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ValidateUsernameError.toUsernameErrorString(resources: Resources): String {
|
|
||||||
return when (this) {
|
|
||||||
ValidateUsernameError.EmptyUsername -> resources.getString(
|
|
||||||
R.string.account_setup_outgoing_config_username_error_required,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ValidatePasswordError.toPasswordErrorString(resources: Resources): String {
|
|
||||||
return when (this) {
|
|
||||||
ValidatePasswordError.EmptyPassword -> resources.getString(
|
|
||||||
R.string.account_setup_outgoing_config_password_error_required,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,6 +9,13 @@
|
||||||
<string name="account_setup_connection_security_start_tls">StartTLS</string>
|
<string name="account_setup_connection_security_start_tls">StartTLS</string>
|
||||||
<string name="account_setup_client_certificate_none_available">None available</string>
|
<string name="account_setup_client_certificate_none_available">None available</string>
|
||||||
|
|
||||||
|
<string name="account_setup_validation_error_server_required">Server name is required.</string>
|
||||||
|
<string name="account_setup_validation_error_port_required">Port is required.</string>
|
||||||
|
<string name="account_setup_validation_error_port_invalid">Port is invalid (must be 1–65535).</string>
|
||||||
|
<string name="account_setup_validation_error_username_required">Username is required.</string>
|
||||||
|
<string name="account_setup_validation_error_password_required">Password is required.</string>
|
||||||
|
<string name="account_setup_validation_error_imap_prefix_blank">Imap prefix can\'t be blank.</string>
|
||||||
|
|
||||||
<string name="account_setup_auto_config_title">K-9 Mail</string>
|
<string name="account_setup_auto_config_title">K-9 Mail</string>
|
||||||
|
|
||||||
<string name="account_setup_incoming_config_top_bar_title">Incoming server settings</string>
|
<string name="account_setup_incoming_config_top_bar_title">Incoming server settings</string>
|
||||||
|
@ -21,14 +28,9 @@
|
||||||
|
|
||||||
<string name="account_setup_outgoing_config_top_bar_title">Outgoing server settings</string>
|
<string name="account_setup_outgoing_config_top_bar_title">Outgoing server settings</string>
|
||||||
<string name="account_setup_outgoing_config_server_label">Server</string>
|
<string name="account_setup_outgoing_config_server_label">Server</string>
|
||||||
<string name="account_setup_outgoing_config_server_error_required">Server name is required.</string>
|
|
||||||
<string name="account_setup_outgoing_config_security_label">Security</string>
|
<string name="account_setup_outgoing_config_security_label">Security</string>
|
||||||
<string name="account_setup_outgoing_config_port_label">Port</string>
|
<string name="account_setup_outgoing_config_port_label">Port</string>
|
||||||
<string name="account_setup_outgoing_config_port_error_required">Port is required.</string>
|
|
||||||
<string name="account_setup_outgoing_config_port_error_invalid">Port is invalid (must be 1–65535).</string>
|
|
||||||
<string name="account_setup_outgoing_config_username_label">Username</string>
|
<string name="account_setup_outgoing_config_username_label">Username</string>
|
||||||
<string name="account_setup_outgoing_config_username_error_required">Username is required.</string>
|
|
||||||
<string name="account_setup_outgoing_config_password_error_required">Password is required.</string>
|
|
||||||
<string name="account_setup_outgoing_config_client_certificate_label">Client certificate</string>
|
<string name="account_setup_outgoing_config_client_certificate_label">Client certificate</string>
|
||||||
<string name="account_setup_outgoing_config_imap_namespace_label">Auto-detect IMAP namespace</string>
|
<string name="account_setup_outgoing_config_imap_namespace_label">Auto-detect IMAP namespace</string>
|
||||||
<string name="account_setup_outgoing_config_compression_label">Use compression</string>
|
<string name="account_setup_outgoing_config_compression_label">Use compression</string>
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
package app.k9mail.feature.account.setup.domain.usecase
|
||||||
|
|
||||||
|
import app.k9mail.core.common.domain.usecase.validation.ValidationResult
|
||||||
|
import assertk.assertThat
|
||||||
|
import assertk.assertions.isInstanceOf
|
||||||
|
import assertk.assertions.prop
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class ValidateImapPrefixTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should success when imap prefix is set`() {
|
||||||
|
val useCase = ValidateImapPrefix()
|
||||||
|
|
||||||
|
val result = useCase.execute("imap")
|
||||||
|
|
||||||
|
assertThat(result).isInstanceOf(ValidationResult.Success::class)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should succeed when imap prefix is empty`() {
|
||||||
|
val useCase = ValidateImapPrefix()
|
||||||
|
|
||||||
|
val result = useCase.execute("")
|
||||||
|
|
||||||
|
assertThat(result).isInstanceOf(ValidationResult.Success::class)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should fail when imap prefix is blank`() {
|
||||||
|
val useCase = ValidateImapPrefix()
|
||||||
|
|
||||||
|
val result = useCase.execute(" ")
|
||||||
|
|
||||||
|
assertThat(result).isInstanceOf(ValidationResult.Failure::class)
|
||||||
|
.prop(ValidationResult.Failure::error)
|
||||||
|
.isInstanceOf(ValidateImapPrefix.ValidateImapPrefixError.BlankImapPrefix::class)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,8 @@ package app.k9mail.feature.account.setup.ui.incoming
|
||||||
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import app.cash.turbine.testIn
|
import app.cash.turbine.testIn
|
||||||
|
import app.k9mail.core.common.domain.usecase.validation.ValidationError
|
||||||
|
import app.k9mail.core.common.domain.usecase.validation.ValidationResult
|
||||||
import app.k9mail.core.ui.compose.testing.MainDispatcherRule
|
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.ConnectionSecurity
|
||||||
import app.k9mail.feature.account.setup.domain.entity.IncomingProtocolType
|
import app.k9mail.feature.account.setup.domain.entity.IncomingProtocolType
|
||||||
|
@ -13,6 +15,7 @@ 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.Effect
|
||||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract.Event
|
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.State
|
||||||
|
import assertk.assertThat
|
||||||
import assertk.assertions.assertThatAndTurbinesConsumed
|
import assertk.assertions.assertThatAndTurbinesConsumed
|
||||||
import assertk.assertions.isEqualTo
|
import assertk.assertions.isEqualTo
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
@ -26,7 +29,9 @@ class AccountIncomingConfigViewModelTest {
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val mainDispatcherRule = MainDispatcherRule()
|
val mainDispatcherRule = MainDispatcherRule()
|
||||||
|
|
||||||
private val testSubject = AccountIncomingConfigViewModel()
|
private val testSubject = AccountIncomingConfigViewModel(
|
||||||
|
validator = FakeAccountIncomingConfigValidator(),
|
||||||
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `should change protocol, security and port when ProtocolTypeChanged event is received`() = runTest {
|
fun `should change protocol, security and port when ProtocolTypeChanged event is received`() = runTest {
|
||||||
|
@ -152,28 +157,76 @@ class AccountIncomingConfigViewModelTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `should emit NavigateNext effect when OnNextClicked event received`() = runTest {
|
fun `should change state and emit NavigateNext effect when OnNextClicked event is received and input is valid`() =
|
||||||
val viewModel = testSubject
|
runTest {
|
||||||
val stateTurbine = viewModel.state.testIn(backgroundScope)
|
val viewModel = testSubject
|
||||||
val effectTurbine = viewModel.effect.testIn(backgroundScope)
|
val stateTurbine = viewModel.state.testIn(backgroundScope)
|
||||||
val turbines = listOf(stateTurbine, effectTurbine)
|
val effectTurbine = viewModel.effect.testIn(backgroundScope)
|
||||||
|
val turbines = listOf(stateTurbine, effectTurbine)
|
||||||
|
|
||||||
assertThatAndTurbinesConsumed(
|
assertThatAndTurbinesConsumed(
|
||||||
actual = stateTurbine.awaitItem(),
|
actual = stateTurbine.awaitItem(),
|
||||||
turbines = turbines,
|
turbines = turbines,
|
||||||
) {
|
) {
|
||||||
isEqualTo(State())
|
isEqualTo(State())
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.event(Event.OnNextClicked)
|
||||||
|
|
||||||
|
assertThat(stateTurbine.awaitItem()).isEqualTo(
|
||||||
|
State(
|
||||||
|
server = StringInputField(value = "", isValid = true),
|
||||||
|
port = NumberInputField(value = 993L, isValid = true),
|
||||||
|
username = StringInputField(value = "", isValid = true),
|
||||||
|
password = StringInputField(value = "", isValid = true),
|
||||||
|
imapPrefix = StringInputField(value = "", isValid = true),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThatAndTurbinesConsumed(
|
||||||
|
actual = effectTurbine.awaitItem(),
|
||||||
|
turbines = turbines,
|
||||||
|
) {
|
||||||
|
isEqualTo(Effect.NavigateNext)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.event(Event.OnNextClicked)
|
@Test
|
||||||
|
fun `should change state and not emit NavigateNext effect when OnNextClicked event received and input invalid`() =
|
||||||
|
runTest {
|
||||||
|
val viewModel = AccountIncomingConfigViewModel(
|
||||||
|
validator = FakeAccountIncomingConfigValidator(
|
||||||
|
serverAnswer = ValidationResult.Failure(TestError),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val stateTurbine = viewModel.state.testIn(backgroundScope)
|
||||||
|
val effectTurbine = viewModel.effect.testIn(backgroundScope)
|
||||||
|
val turbines = listOf(stateTurbine, effectTurbine)
|
||||||
|
|
||||||
assertThatAndTurbinesConsumed(
|
assertThatAndTurbinesConsumed(
|
||||||
actual = effectTurbine.awaitItem(),
|
actual = stateTurbine.awaitItem(),
|
||||||
turbines = turbines,
|
turbines = turbines,
|
||||||
) {
|
) {
|
||||||
isEqualTo(Effect.NavigateNext)
|
isEqualTo(State())
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.event(Event.OnNextClicked)
|
||||||
|
|
||||||
|
assertThatAndTurbinesConsumed(
|
||||||
|
actual = stateTurbine.awaitItem(),
|
||||||
|
turbines = turbines,
|
||||||
|
) {
|
||||||
|
isEqualTo(
|
||||||
|
State(
|
||||||
|
server = StringInputField(value = "", error = TestError, isValid = false),
|
||||||
|
port = NumberInputField(value = 993L, isValid = true),
|
||||||
|
username = StringInputField(value = "", isValid = true),
|
||||||
|
password = StringInputField(value = "", isValid = true),
|
||||||
|
imapPrefix = StringInputField(value = "", isValid = true),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `should emit NavigateBack effect when OnBackClicked event received`() = runTest {
|
fun `should emit NavigateBack effect when OnBackClicked event received`() = runTest {
|
||||||
|
@ -198,4 +251,6 @@ class AccountIncomingConfigViewModelTest {
|
||||||
isEqualTo(Effect.NavigateBack)
|
isEqualTo(Effect.NavigateBack)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private object TestError : ValidationError
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
package app.k9mail.feature.account.setup.ui.incoming
|
||||||
|
|
||||||
|
import app.k9mail.core.common.domain.usecase.validation.ValidationResult
|
||||||
|
|
||||||
|
class FakeAccountIncomingConfigValidator(
|
||||||
|
private val serverAnswer: ValidationResult = ValidationResult.Success,
|
||||||
|
private val portAnswer: ValidationResult = ValidationResult.Success,
|
||||||
|
private val usernameAnswer: ValidationResult = ValidationResult.Success,
|
||||||
|
private val passwordAnswer: ValidationResult = ValidationResult.Success,
|
||||||
|
private val imapPrefixAnswer: ValidationResult = ValidationResult.Success,
|
||||||
|
) : AccountIncomingConfigContract.Validator {
|
||||||
|
override fun validateServer(server: String): ValidationResult = serverAnswer
|
||||||
|
override fun validatePort(port: Long?): ValidationResult = portAnswer
|
||||||
|
override fun validateUsername(username: String): ValidationResult = usernameAnswer
|
||||||
|
override fun validatePassword(password: String): ValidationResult = passwordAnswer
|
||||||
|
override fun validateImapPrefix(imapPrefix: String): ValidationResult = imapPrefixAnswer
|
||||||
|
}
|
Loading…
Reference in a new issue