From 11b12f9c5eb8833bcaa324169f920e069c5336b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolf-Martell=20Montw=C3=A9?= Date: Wed, 13 Sep 2023 18:10:51 +0200 Subject: [PATCH 1/3] Change named injection to typed injection for server validation to avoid issues with dependency resolution --- .../com/fsck/k9/DependencyInjectionTest.kt | 11 +-- .../validation/ServerValidationModule.kt | 17 ++--- ...el.kt => BaseServerValidationViewModel.kt} | 2 +- .../ui/IncomingServerValidationViewModel.kt | 25 +++++++ .../ui/OutgoingServerValidationViewModel.kt | 25 +++++++ .../validation/ui/ServerValidationContract.kt | 3 + .../ui/ServerValidationMainScreen.kt | 41 ++-------- .../FakeIncomingServerValidationViewModel.kt | 27 +++++++ .../FakeOutgoingServerValidationViewModel.kt | 27 +++++++ ...t => BaseServerValidationViewModelTest.kt} | 71 ++++++++++-------- .../IncomingServerValidationViewModelTest.kt | 75 +++++++++++++++++++ .../OutgoingServerValidationViewModelTest.kt | 75 +++++++++++++++++++ .../account/setup/ui/AccountSetupScreen.kt | 16 ++-- 13 files changed, 319 insertions(+), 96 deletions(-) rename feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/{ServerValidationViewModel.kt => BaseServerValidationViewModel.kt} (99%) create mode 100644 feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/IncomingServerValidationViewModel.kt create mode 100644 feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/OutgoingServerValidationViewModel.kt create mode 100644 feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/fake/FakeIncomingServerValidationViewModel.kt create mode 100644 feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/fake/FakeOutgoingServerValidationViewModel.kt rename feature/account/server/validation/src/test/kotlin/app/k9mail/feature/account/server/validation/ui/{ServerValidationViewModelTest.kt => BaseServerValidationViewModelTest.kt} (81%) create mode 100644 feature/account/server/validation/src/test/kotlin/app/k9mail/feature/account/server/validation/ui/IncomingServerValidationViewModelTest.kt create mode 100644 feature/account/server/validation/src/test/kotlin/app/k9mail/feature/account/server/validation/ui/OutgoingServerValidationViewModelTest.kt diff --git a/app/k9mail/src/test/java/com/fsck/k9/DependencyInjectionTest.kt b/app/k9mail/src/test/java/com/fsck/k9/DependencyInjectionTest.kt index 3593d9fbae7..005a826becc 100644 --- a/app/k9mail/src/test/java/com/fsck/k9/DependencyInjectionTest.kt +++ b/app/k9mail/src/test/java/com/fsck/k9/DependencyInjectionTest.kt @@ -3,8 +3,6 @@ package com.fsck.k9 import android.view.ContextThemeWrapper import androidx.lifecycle.LifecycleOwner import androidx.work.WorkerParameters -import app.k9mail.feature.account.server.validation.KOIN_NAME_INCOMING_SERVER_VALIDATION -import app.k9mail.feature.account.server.validation.KOIN_NAME_OUTGOING_SERVER_VALIDATION import app.k9mail.feature.account.server.validation.domain.ServerValidationDomainContract import app.k9mail.feature.account.server.validation.ui.ServerValidationContract import com.fsck.k9.account.AccountRemoverWorker @@ -23,7 +21,6 @@ import org.junit.runner.RunWith import org.koin.core.annotation.KoinInternalApi import org.koin.core.logger.PrintLogger import org.koin.core.parameter.parametersOf -import org.koin.core.qualifier.named import org.koin.java.KoinJavaComponent import org.koin.test.AutoCloseKoinTest import org.koin.test.check.checkModules @@ -61,12 +58,8 @@ class DependencyInjectionTest : AutoCloseKoinTest() { withParameters(clazz = Class.forName("com.fsck.k9.view.K9WebViewClient").kotlin) { parametersOf(null, null) } - withParameter( - named(KOIN_NAME_INCOMING_SERVER_VALIDATION), - ) { authStateStorage } - withParameter( - named(KOIN_NAME_OUTGOING_SERVER_VALIDATION), - ) { authStateStorage } + withParameter { authStateStorage } + withParameter { authStateStorage } withParameter { authStateStorage } withParameter { mock() } } diff --git a/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ServerValidationModule.kt b/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ServerValidationModule.kt index 6c9bc29e70f..35f61cda13b 100644 --- a/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ServerValidationModule.kt +++ b/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ServerValidationModule.kt @@ -6,12 +6,12 @@ import app.k9mail.feature.account.oauth.featureAccountOAuthModule import app.k9mail.feature.account.server.certificate.featureAccountServerCertificateModule import app.k9mail.feature.account.server.validation.domain.ServerValidationDomainContract import app.k9mail.feature.account.server.validation.domain.usecase.ValidateServerSettings -import app.k9mail.feature.account.server.validation.ui.ServerValidationViewModel +import app.k9mail.feature.account.server.validation.ui.IncomingServerValidationViewModel +import app.k9mail.feature.account.server.validation.ui.OutgoingServerValidationViewModel import com.fsck.k9.mail.store.imap.ImapServerSettingsValidator import com.fsck.k9.mail.store.pop3.Pop3ServerSettingsValidator import com.fsck.k9.mail.transport.smtp.SmtpServerSettingsValidator import org.koin.androidx.viewmodel.dsl.viewModel -import org.koin.core.qualifier.named import org.koin.dsl.module val featureAccountServerValidationModule = module { @@ -40,28 +40,23 @@ val featureAccountServerValidationModule = module { ) } - viewModel(named(KOIN_NAME_INCOMING_SERVER_VALIDATION)) { - ServerValidationViewModel( + viewModel { + IncomingServerValidationViewModel( validateServerSettings = get(), accountStateRepository = get(), authorizationStateRepository = get(), certificateErrorRepository = get(), oAuthViewModel = get(), - isIncomingValidation = true, ) } - viewModel(named(KOIN_NAME_OUTGOING_SERVER_VALIDATION)) { - ServerValidationViewModel( + viewModel { + OutgoingServerValidationViewModel( validateServerSettings = get(), accountStateRepository = get(), authorizationStateRepository = get(), certificateErrorRepository = get(), oAuthViewModel = get(), - isIncomingValidation = false, ) } } - -const val KOIN_NAME_INCOMING_SERVER_VALIDATION = "incoming_server_validation" -const val KOIN_NAME_OUTGOING_SERVER_VALIDATION = "outgoing_server_validation" diff --git a/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/ServerValidationViewModel.kt b/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/BaseServerValidationViewModel.kt similarity index 99% rename from feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/ServerValidationViewModel.kt rename to feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/BaseServerValidationViewModel.kt index bdd457c3ef5..acb629f66fa 100644 --- a/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/ServerValidationViewModel.kt +++ b/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/BaseServerValidationViewModel.kt @@ -22,7 +22,7 @@ import kotlinx.coroutines.launch private const val CONTINUE_NEXT_DELAY = 2000L @Suppress("TooManyFunctions") -class ServerValidationViewModel( +abstract class BaseServerValidationViewModel( private val accountStateRepository: AccountDomainContract.AccountStateRepository, private val validateServerSettings: ServerValidationDomainContract.UseCase.ValidateServerSettings, private val authorizationStateRepository: AccountOAuthDomainContract.AuthorizationStateRepository, diff --git a/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/IncomingServerValidationViewModel.kt b/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/IncomingServerValidationViewModel.kt new file mode 100644 index 00000000000..dcef4fc7489 --- /dev/null +++ b/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/IncomingServerValidationViewModel.kt @@ -0,0 +1,25 @@ +package app.k9mail.feature.account.server.validation.ui + +import app.k9mail.feature.account.common.domain.AccountDomainContract +import app.k9mail.feature.account.oauth.domain.AccountOAuthDomainContract +import app.k9mail.feature.account.oauth.ui.AccountOAuthContract +import app.k9mail.feature.account.server.certificate.domain.ServerCertificateDomainContract +import app.k9mail.feature.account.server.validation.domain.ServerValidationDomainContract.UseCase + +class IncomingServerValidationViewModel( + accountStateRepository: AccountDomainContract.AccountStateRepository, + validateServerSettings: UseCase.ValidateServerSettings, + authorizationStateRepository: AccountOAuthDomainContract.AuthorizationStateRepository, + certificateErrorRepository: ServerCertificateDomainContract.ServerCertificateErrorRepository, + oAuthViewModel: AccountOAuthContract.ViewModel, + initialState: ServerValidationContract.State? = null, +) : BaseServerValidationViewModel( + accountStateRepository = accountStateRepository, + validateServerSettings = validateServerSettings, + authorizationStateRepository = authorizationStateRepository, + certificateErrorRepository = certificateErrorRepository, + oAuthViewModel = oAuthViewModel, + initialState = initialState, + isIncomingValidation = true, +), + ServerValidationContract.IncomingViewModel diff --git a/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/OutgoingServerValidationViewModel.kt b/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/OutgoingServerValidationViewModel.kt new file mode 100644 index 00000000000..da1d13db883 --- /dev/null +++ b/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/OutgoingServerValidationViewModel.kt @@ -0,0 +1,25 @@ +package app.k9mail.feature.account.server.validation.ui + +import app.k9mail.feature.account.common.domain.AccountDomainContract +import app.k9mail.feature.account.oauth.domain.AccountOAuthDomainContract +import app.k9mail.feature.account.oauth.ui.AccountOAuthContract +import app.k9mail.feature.account.server.certificate.domain.ServerCertificateDomainContract +import app.k9mail.feature.account.server.validation.domain.ServerValidationDomainContract.UseCase + +class OutgoingServerValidationViewModel( + accountStateRepository: AccountDomainContract.AccountStateRepository, + validateServerSettings: UseCase.ValidateServerSettings, + authorizationStateRepository: AccountOAuthDomainContract.AuthorizationStateRepository, + certificateErrorRepository: ServerCertificateDomainContract.ServerCertificateErrorRepository, + oAuthViewModel: AccountOAuthContract.ViewModel, + initialState: ServerValidationContract.State? = null, +) : BaseServerValidationViewModel( + accountStateRepository = accountStateRepository, + validateServerSettings = validateServerSettings, + authorizationStateRepository = authorizationStateRepository, + certificateErrorRepository = certificateErrorRepository, + oAuthViewModel = oAuthViewModel, + initialState = initialState, + isIncomingValidation = false, +), + ServerValidationContract.OutgoingViewModel diff --git a/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/ServerValidationContract.kt b/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/ServerValidationContract.kt index 083f4a606e6..a4555c6f35c 100644 --- a/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/ServerValidationContract.kt +++ b/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/ServerValidationContract.kt @@ -14,6 +14,9 @@ interface ServerValidationContract { val oAuthViewModel: AccountOAuthContract.ViewModel } + interface OutgoingViewModel : ViewModel + interface IncomingViewModel : ViewModel + data class State( val emailAddress: String? = null, val serverSettings: ServerSettings? = null, diff --git a/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/ServerValidationMainScreen.kt b/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/ServerValidationMainScreen.kt index 0d1db380f80..8b138e02557 100644 --- a/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/ServerValidationMainScreen.kt +++ b/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/ServerValidationMainScreen.kt @@ -10,12 +10,11 @@ import app.k9mail.core.ui.compose.theme.ThunderbirdTheme import app.k9mail.feature.account.common.ui.AppTitleTopHeader import app.k9mail.feature.account.common.ui.WizardNavigationBar import app.k9mail.feature.account.common.ui.WizardNavigationBarState -import app.k9mail.feature.account.common.ui.preview.PreviewAccountStateRepository import app.k9mail.feature.account.oauth.ui.preview.PreviewAccountOAuthViewModel -import app.k9mail.feature.account.server.certificate.data.InMemoryServerCertificateErrorRepository import app.k9mail.feature.account.server.validation.ui.ServerValidationContract.Event import app.k9mail.feature.account.server.validation.ui.ServerValidationContract.ViewModel -import com.fsck.k9.mail.server.ServerSettingsValidationResult +import app.k9mail.feature.account.server.validation.ui.fake.FakeIncomingServerValidationViewModel +import app.k9mail.feature.account.server.validation.ui.fake.FakeOutgoingServerValidationViewModel @Composable internal fun ServerValidationMainScreen( @@ -55,15 +54,8 @@ internal fun ServerValidationMainScreen( internal fun IncomingServerValidationScreenK9Preview() { K9Theme { ServerValidationMainScreen( - viewModel = ServerValidationViewModel( - validateServerSettings = { - ServerSettingsValidationResult.Success - }, - accountStateRepository = PreviewAccountStateRepository(), - authorizationStateRepository = { true }, - certificateErrorRepository = InMemoryServerCertificateErrorRepository(), + viewModel = FakeIncomingServerValidationViewModel( oAuthViewModel = PreviewAccountOAuthViewModel(), - isIncomingValidation = true, ), ) } @@ -74,15 +66,8 @@ internal fun IncomingServerValidationScreenK9Preview() { internal fun IncomingServerValidationScreenThunderbirdPreview() { ThunderbirdTheme { ServerValidationMainScreen( - viewModel = ServerValidationViewModel( - validateServerSettings = { - ServerSettingsValidationResult.Success - }, - accountStateRepository = PreviewAccountStateRepository(), - authorizationStateRepository = { true }, - certificateErrorRepository = InMemoryServerCertificateErrorRepository(), + viewModel = FakeIncomingServerValidationViewModel( oAuthViewModel = PreviewAccountOAuthViewModel(), - isIncomingValidation = true, ), ) } @@ -93,15 +78,8 @@ internal fun IncomingServerValidationScreenThunderbirdPreview() { internal fun AccountOutgoingValidationScreenK9Preview() { K9Theme { ServerValidationMainScreen( - viewModel = ServerValidationViewModel( - validateServerSettings = { - ServerSettingsValidationResult.Success - }, - accountStateRepository = PreviewAccountStateRepository(), - authorizationStateRepository = { true }, - certificateErrorRepository = InMemoryServerCertificateErrorRepository(), + viewModel = FakeOutgoingServerValidationViewModel( oAuthViewModel = PreviewAccountOAuthViewModel(), - isIncomingValidation = false, ), ) } @@ -112,15 +90,8 @@ internal fun AccountOutgoingValidationScreenK9Preview() { internal fun AccountOutgoingValidationScreenThunderbirdPreview() { ThunderbirdTheme { ServerValidationMainScreen( - viewModel = ServerValidationViewModel( - validateServerSettings = { - ServerSettingsValidationResult.Success - }, - accountStateRepository = PreviewAccountStateRepository(), - authorizationStateRepository = { true }, - certificateErrorRepository = InMemoryServerCertificateErrorRepository(), + viewModel = FakeOutgoingServerValidationViewModel( oAuthViewModel = PreviewAccountOAuthViewModel(), - isIncomingValidation = false, ), ) } diff --git a/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/fake/FakeIncomingServerValidationViewModel.kt b/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/fake/FakeIncomingServerValidationViewModel.kt new file mode 100644 index 00000000000..38b7fbefe84 --- /dev/null +++ b/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/fake/FakeIncomingServerValidationViewModel.kt @@ -0,0 +1,27 @@ +package app.k9mail.feature.account.server.validation.ui.fake + +import app.k9mail.core.ui.compose.common.mvi.BaseViewModel +import app.k9mail.feature.account.oauth.ui.AccountOAuthContract +import app.k9mail.feature.account.oauth.ui.fake.FakeAccountOAuthViewModel +import app.k9mail.feature.account.server.validation.ui.ServerValidationContract +import app.k9mail.feature.account.server.validation.ui.ServerValidationContract.Effect +import app.k9mail.feature.account.server.validation.ui.ServerValidationContract.Event +import app.k9mail.feature.account.server.validation.ui.ServerValidationContract.State +import app.k9mail.feature.account.server.validation.ui.ServerValidationContract.ViewModel + +class FakeIncomingServerValidationViewModel( + override val oAuthViewModel: AccountOAuthContract.ViewModel = FakeAccountOAuthViewModel(), + override val isIncomingValidation: Boolean = true, + initialState: State = State(), +) : BaseViewModel(initialState), ServerValidationContract.IncomingViewModel { + + val events = mutableListOf() + + override fun event(event: Event) { + events.add(event) + } + + fun effect(effect: Effect) { + emitEffect(effect) + } +} diff --git a/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/fake/FakeOutgoingServerValidationViewModel.kt b/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/fake/FakeOutgoingServerValidationViewModel.kt new file mode 100644 index 00000000000..3b82517c18e --- /dev/null +++ b/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/fake/FakeOutgoingServerValidationViewModel.kt @@ -0,0 +1,27 @@ +package app.k9mail.feature.account.server.validation.ui.fake + +import app.k9mail.core.ui.compose.common.mvi.BaseViewModel +import app.k9mail.feature.account.oauth.ui.AccountOAuthContract +import app.k9mail.feature.account.oauth.ui.fake.FakeAccountOAuthViewModel +import app.k9mail.feature.account.server.validation.ui.ServerValidationContract +import app.k9mail.feature.account.server.validation.ui.ServerValidationContract.Effect +import app.k9mail.feature.account.server.validation.ui.ServerValidationContract.Event +import app.k9mail.feature.account.server.validation.ui.ServerValidationContract.State +import app.k9mail.feature.account.server.validation.ui.ServerValidationContract.ViewModel + +class FakeOutgoingServerValidationViewModel( + override val oAuthViewModel: AccountOAuthContract.ViewModel = FakeAccountOAuthViewModel(), + override val isIncomingValidation: Boolean = true, + initialState: State = State(), +) : BaseViewModel(initialState), ServerValidationContract.OutgoingViewModel { + + val events = mutableListOf() + + override fun event(event: Event) { + events.add(event) + } + + fun effect(effect: Effect) { + emitEffect(effect) + } +} diff --git a/feature/account/server/validation/src/test/kotlin/app/k9mail/feature/account/server/validation/ui/ServerValidationViewModelTest.kt b/feature/account/server/validation/src/test/kotlin/app/k9mail/feature/account/server/validation/ui/BaseServerValidationViewModelTest.kt similarity index 81% rename from feature/account/server/validation/src/test/kotlin/app/k9mail/feature/account/server/validation/ui/ServerValidationViewModelTest.kt rename to feature/account/server/validation/src/test/kotlin/app/k9mail/feature/account/server/validation/ui/BaseServerValidationViewModelTest.kt index a482fdf65d6..af0f8b03d12 100644 --- a/feature/account/server/validation/src/test/kotlin/app/k9mail/feature/account/server/validation/ui/ServerValidationViewModelTest.kt +++ b/feature/account/server/validation/src/test/kotlin/app/k9mail/feature/account/server/validation/ui/BaseServerValidationViewModelTest.kt @@ -4,9 +4,14 @@ import app.k9mail.core.ui.compose.testing.MainDispatcherRule import app.k9mail.core.ui.compose.testing.mvi.assertThatAndMviTurbinesConsumed import app.k9mail.core.ui.compose.testing.mvi.turbinesWithInitialStateCheck import app.k9mail.feature.account.common.data.InMemoryAccountStateRepository +import app.k9mail.feature.account.common.domain.AccountDomainContract import app.k9mail.feature.account.common.domain.entity.AccountState +import app.k9mail.feature.account.oauth.domain.AccountOAuthDomainContract +import app.k9mail.feature.account.oauth.ui.AccountOAuthContract import app.k9mail.feature.account.oauth.ui.fake.FakeAccountOAuthViewModel import app.k9mail.feature.account.server.certificate.data.InMemoryServerCertificateErrorRepository +import app.k9mail.feature.account.server.certificate.domain.ServerCertificateDomainContract +import app.k9mail.feature.account.server.validation.domain.ServerValidationDomainContract import app.k9mail.feature.account.server.validation.ui.ServerValidationContract.Effect import app.k9mail.feature.account.server.validation.ui.ServerValidationContract.Error import app.k9mail.feature.account.server.validation.ui.ServerValidationContract.Event @@ -23,16 +28,22 @@ import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test -class ServerValidationViewModelTest { +abstract class BaseServerValidationViewModelTest { @get:Rule val mainDispatcherRule = MainDispatcherRule() @Test fun `should update state when LoadAccountStateAndValidate event received and validate`() = runTest { - val accountState = AccountState( - incomingServerSettings = IMAP_SERVER_SETTINGS, - ) + val accountState = if (isIncomingValidation) { + AccountState( + incomingServerSettings = SERVER_SETTINGS, + ) + } else { + AccountState( + outgoingServerSettings = SERVER_SETTINGS, + ) + } val initialState = State( serverSettings = null, isLoading = true, @@ -47,7 +58,7 @@ class ServerValidationViewModelTest { val turbines = turbinesWithInitialStateCheck(testSubject, initialState) val expectedState = State( - serverSettings = IMAP_SERVER_SETTINGS, + serverSettings = SERVER_SETTINGS, isLoading = false, error = null, isSuccess = false, @@ -93,7 +104,7 @@ class ServerValidationViewModelTest { @Test fun `should validate server settings when ValidateServerSettings event received`() = runTest { val initialState = State( - serverSettings = IMAP_SERVER_SETTINGS, + serverSettings = SERVER_SETTINGS, ) val testSubject = createTestSubject( serverSettingsValidationResult = ServerSettingsValidationResult.Success, @@ -123,7 +134,7 @@ class ServerValidationViewModelTest { @Test fun `should set error state when ValidateServerSettings received and check settings failed`() = runTest { val initialState = State( - serverSettings = IMAP_SERVER_SETTINGS, + serverSettings = SERVER_SETTINGS, ) val testSubject = createTestSubject( serverSettingsValidationResult = ServerSettingsValidationResult.ServerError("server error"), @@ -151,7 +162,7 @@ class ServerValidationViewModelTest { @Test fun `should emit effect NavigateNext when ValidateConfig is successful`() = runTest { val initialState = State( - serverSettings = IMAP_SERVER_SETTINGS, + serverSettings = SERVER_SETTINGS, isSuccess = true, ) val testSubject = createTestSubject( @@ -187,11 +198,12 @@ class ServerValidationViewModelTest { @Test fun `should clear error and trigger check settings when OnRetryClicked event received`() = runTest { val initialState = State( - serverSettings = IMAP_SERVER_SETTINGS, + serverSettings = SERVER_SETTINGS, error = Error.ServerError("server error"), ) var checkSettingsCalled = false - val testSubject = ServerValidationViewModel( + + val testSubject = createTestSubject( validateServerSettings = { delay(50) checkSettingsCalled = true @@ -225,26 +237,25 @@ class ServerValidationViewModelTest { } } - private companion object { - fun createTestSubject( - serverSettingsValidationResult: ServerSettingsValidationResult = ServerSettingsValidationResult.Success, - accountState: AccountState = AccountState(), - initialState: State = State(), - ): ServerValidationViewModel { - return ServerValidationViewModel( - validateServerSettings = { - delay(50) - serverSettingsValidationResult - }, - accountStateRepository = InMemoryAccountStateRepository(accountState), - authorizationStateRepository = { true }, - certificateErrorRepository = InMemoryServerCertificateErrorRepository(), - oAuthViewModel = FakeAccountOAuthViewModel(), - initialState = initialState, - ) - } - - val IMAP_SERVER_SETTINGS = ServerSettings( + abstract fun createTestSubject( + serverSettingsValidationResult: ServerSettingsValidationResult = ServerSettingsValidationResult.Success, + accountState: AccountState = AccountState(), + initialState: State = State(), + ): T + + abstract fun createTestSubject( + accountStateRepository: AccountDomainContract.AccountStateRepository, + validateServerSettings: ServerValidationDomainContract.UseCase.ValidateServerSettings, + authorizationStateRepository: AccountOAuthDomainContract.AuthorizationStateRepository, + certificateErrorRepository: ServerCertificateDomainContract.ServerCertificateErrorRepository, + oAuthViewModel: AccountOAuthContract.ViewModel, + initialState: State, + ): T + + abstract val isIncomingValidation: Boolean + + protected companion object { + val SERVER_SETTINGS = ServerSettings( type = "imap", host = "imap.example.com", port = 465, diff --git a/feature/account/server/validation/src/test/kotlin/app/k9mail/feature/account/server/validation/ui/IncomingServerValidationViewModelTest.kt b/feature/account/server/validation/src/test/kotlin/app/k9mail/feature/account/server/validation/ui/IncomingServerValidationViewModelTest.kt new file mode 100644 index 00000000000..51b150e295d --- /dev/null +++ b/feature/account/server/validation/src/test/kotlin/app/k9mail/feature/account/server/validation/ui/IncomingServerValidationViewModelTest.kt @@ -0,0 +1,75 @@ +package app.k9mail.feature.account.server.validation.ui + +import app.k9mail.feature.account.common.data.InMemoryAccountStateRepository +import app.k9mail.feature.account.common.domain.AccountDomainContract +import app.k9mail.feature.account.common.domain.entity.AccountState +import app.k9mail.feature.account.oauth.domain.AccountOAuthDomainContract +import app.k9mail.feature.account.oauth.ui.AccountOAuthContract +import app.k9mail.feature.account.oauth.ui.fake.FakeAccountOAuthViewModel +import app.k9mail.feature.account.server.certificate.data.InMemoryServerCertificateErrorRepository +import app.k9mail.feature.account.server.certificate.domain.ServerCertificateDomainContract +import app.k9mail.feature.account.server.validation.domain.ServerValidationDomainContract +import app.k9mail.feature.account.server.validation.ui.ServerValidationContract.Error +import app.k9mail.feature.account.server.validation.ui.ServerValidationContract.State +import assertk.assertThat +import assertk.assertions.isTrue +import com.fsck.k9.mail.server.ServerSettingsValidationResult +import kotlinx.coroutines.delay +import org.junit.Test + +class IncomingServerValidationViewModelTest : BaseServerValidationViewModelTest() { + + @Test + fun `should set isIncoming to true`() { + val testSubject = createTestSubject( + serverSettingsValidationResult = ServerSettingsValidationResult.Success, + accountState = AccountState( + incomingServerSettings = SERVER_SETTINGS, + ), + initialState = State( + serverSettings = null, + isLoading = true, + error = Error.ServerError("server error"), + isSuccess = true, + ), + ) + + assertThat(testSubject.isIncomingValidation).isTrue() + } + + override fun createTestSubject( + serverSettingsValidationResult: ServerSettingsValidationResult, + accountState: AccountState, + initialState: State, + ): IncomingServerValidationViewModel { + return IncomingServerValidationViewModel( + validateServerSettings = { + delay(50) + serverSettingsValidationResult + }, + accountStateRepository = InMemoryAccountStateRepository(accountState), + authorizationStateRepository = { true }, + certificateErrorRepository = InMemoryServerCertificateErrorRepository(), + oAuthViewModel = FakeAccountOAuthViewModel(), + initialState = initialState, + ) + } + + override fun createTestSubject( + accountStateRepository: AccountDomainContract.AccountStateRepository, + validateServerSettings: ServerValidationDomainContract.UseCase.ValidateServerSettings, + authorizationStateRepository: AccountOAuthDomainContract.AuthorizationStateRepository, + certificateErrorRepository: ServerCertificateDomainContract.ServerCertificateErrorRepository, + oAuthViewModel: AccountOAuthContract.ViewModel, + initialState: State, + ) = IncomingServerValidationViewModel( + accountStateRepository = accountStateRepository, + validateServerSettings = validateServerSettings, + authorizationStateRepository = authorizationStateRepository, + certificateErrorRepository = certificateErrorRepository, + oAuthViewModel = oAuthViewModel, + initialState = initialState, + ) + + override val isIncomingValidation: Boolean = true +} diff --git a/feature/account/server/validation/src/test/kotlin/app/k9mail/feature/account/server/validation/ui/OutgoingServerValidationViewModelTest.kt b/feature/account/server/validation/src/test/kotlin/app/k9mail/feature/account/server/validation/ui/OutgoingServerValidationViewModelTest.kt new file mode 100644 index 00000000000..068fbf43f35 --- /dev/null +++ b/feature/account/server/validation/src/test/kotlin/app/k9mail/feature/account/server/validation/ui/OutgoingServerValidationViewModelTest.kt @@ -0,0 +1,75 @@ +package app.k9mail.feature.account.server.validation.ui + +import app.k9mail.feature.account.common.data.InMemoryAccountStateRepository +import app.k9mail.feature.account.common.domain.AccountDomainContract +import app.k9mail.feature.account.common.domain.entity.AccountState +import app.k9mail.feature.account.oauth.domain.AccountOAuthDomainContract +import app.k9mail.feature.account.oauth.ui.AccountOAuthContract +import app.k9mail.feature.account.oauth.ui.fake.FakeAccountOAuthViewModel +import app.k9mail.feature.account.server.certificate.data.InMemoryServerCertificateErrorRepository +import app.k9mail.feature.account.server.certificate.domain.ServerCertificateDomainContract +import app.k9mail.feature.account.server.validation.domain.ServerValidationDomainContract +import app.k9mail.feature.account.server.validation.ui.ServerValidationContract.Error +import app.k9mail.feature.account.server.validation.ui.ServerValidationContract.State +import assertk.assertThat +import assertk.assertions.isFalse +import com.fsck.k9.mail.server.ServerSettingsValidationResult +import kotlinx.coroutines.delay +import org.junit.Test + +class OutgoingServerValidationViewModelTest : BaseServerValidationViewModelTest() { + + @Test + fun `should set isIncoming to false`() { + val testSubject = createTestSubject( + serverSettingsValidationResult = ServerSettingsValidationResult.Success, + accountState = AccountState( + outgoingServerSettings = SERVER_SETTINGS, + ), + initialState = State( + serverSettings = null, + isLoading = true, + error = Error.ServerError("server error"), + isSuccess = true, + ), + ) + + assertThat(testSubject.isIncomingValidation).isFalse() + } + + override fun createTestSubject( + serverSettingsValidationResult: ServerSettingsValidationResult, + accountState: AccountState, + initialState: State, + ): OutgoingServerValidationViewModel { + return OutgoingServerValidationViewModel( + validateServerSettings = { + delay(50) + serverSettingsValidationResult + }, + accountStateRepository = InMemoryAccountStateRepository(accountState), + authorizationStateRepository = { true }, + certificateErrorRepository = InMemoryServerCertificateErrorRepository(), + oAuthViewModel = FakeAccountOAuthViewModel(), + initialState = initialState, + ) + } + + override fun createTestSubject( + accountStateRepository: AccountDomainContract.AccountStateRepository, + validateServerSettings: ServerValidationDomainContract.UseCase.ValidateServerSettings, + authorizationStateRepository: AccountOAuthDomainContract.AuthorizationStateRepository, + certificateErrorRepository: ServerCertificateDomainContract.ServerCertificateErrorRepository, + oAuthViewModel: AccountOAuthContract.ViewModel, + initialState: State, + ) = OutgoingServerValidationViewModel( + accountStateRepository = accountStateRepository, + validateServerSettings = validateServerSettings, + authorizationStateRepository = authorizationStateRepository, + certificateErrorRepository = certificateErrorRepository, + oAuthViewModel = oAuthViewModel, + initialState = initialState, + ) + + override val isIncomingValidation: Boolean = false +} diff --git a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupScreen.kt b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupScreen.kt index 3bc1b51268a..98b96ce9ac8 100644 --- a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupScreen.kt +++ b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupScreen.kt @@ -9,11 +9,10 @@ import app.k9mail.feature.account.server.settings.ui.incoming.IncomingServerSett import app.k9mail.feature.account.server.settings.ui.outgoing.OutgoingServerSettingsContract import app.k9mail.feature.account.server.settings.ui.outgoing.OutgoingServerSettingsScreen import app.k9mail.feature.account.server.settings.ui.outgoing.OutgoingServerSettingsViewModel -import app.k9mail.feature.account.server.validation.KOIN_NAME_INCOMING_SERVER_VALIDATION -import app.k9mail.feature.account.server.validation.KOIN_NAME_OUTGOING_SERVER_VALIDATION +import app.k9mail.feature.account.server.validation.ui.IncomingServerValidationViewModel +import app.k9mail.feature.account.server.validation.ui.OutgoingServerValidationViewModel import app.k9mail.feature.account.server.validation.ui.ServerValidationContract import app.k9mail.feature.account.server.validation.ui.ServerValidationScreen -import app.k9mail.feature.account.server.validation.ui.ServerValidationViewModel 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 @@ -25,7 +24,6 @@ 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 org.koin.androidx.compose.koinViewModel -import org.koin.core.qualifier.named @Suppress("LongMethod") @Composable @@ -35,13 +33,11 @@ fun AccountSetupScreen( viewModel: ViewModel = koinViewModel(), autoDiscoveryViewModel: AccountAutoDiscoveryContract.ViewModel = koinViewModel(), incomingViewModel: IncomingServerSettingsContract.ViewModel = koinViewModel(), - incomingValidationViewModel: ServerValidationContract.ViewModel = koinViewModel( - named(KOIN_NAME_INCOMING_SERVER_VALIDATION), - ), + incomingValidationViewModel: ServerValidationContract.ViewModel = + koinViewModel(), outgoingViewModel: OutgoingServerSettingsContract.ViewModel = koinViewModel(), - outgoingValidationViewModel: ServerValidationContract.ViewModel = koinViewModel( - named(KOIN_NAME_OUTGOING_SERVER_VALIDATION), - ), + outgoingValidationViewModel: ServerValidationContract.ViewModel = + koinViewModel(), optionsViewModel: AccountOptionsContract.ViewModel = koinViewModel(), ) { val (state, dispatch) = viewModel.observe { effect -> From 25accaf905225db924d1f346bb2f7a180dcde7a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolf-Martell=20Montw=C3=A9?= Date: Wed, 13 Sep 2023 17:46:07 +0200 Subject: [PATCH 2/3] Add observeWithoutEffect method --- .../common/mvi/UnidirectionalViewModel.kt | 42 +++++++++++++++++++ .../ui/ServerValidationMainScreen.kt | 5 +-- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/core/ui/compose/common/src/main/kotlin/app/k9mail/core/ui/compose/common/mvi/UnidirectionalViewModel.kt b/core/ui/compose/common/src/main/kotlin/app/k9mail/core/ui/compose/common/mvi/UnidirectionalViewModel.kt index f79840deff5..de46e8f2914 100644 --- a/core/ui/compose/common/src/main/kotlin/app/k9mail/core/ui/compose/common/mvi/UnidirectionalViewModel.kt +++ b/core/ui/compose/common/src/main/kotlin/app/k9mail/core/ui/compose/common/mvi/UnidirectionalViewModel.kt @@ -102,3 +102,45 @@ inline fun UnidirectionalViewModel, + * onNavigateNext: () -> Unit, + * onNavigateBack: () -> Unit, + * ) { + * val (state, dispatch) = viewModel.observeWithoutEffect() + * + * MyContent( + * onNextClick = { + * dispatch(MyEvent.OnNext) + * }, + * onBackClick = { + * dispatch(MyEvent.OnBack) + * }, + * state = state.value, + * ) + * } + * ``` + * + * @param STATE The type that represents the state of the ViewModel. + * @param EVENT The type that represents user actions that can occur and should be handled by the ViewModel. + * + * @return A [StateDispatch] containing the state and a dispatch function. + */ +@Composable +inline fun UnidirectionalViewModel + .observeWithoutEffect(): StateDispatch { + val collectedState = state.collectAsStateWithLifecycle() + val dispatch: (EVENT) -> Unit = { event(it) } + + return StateDispatch( + state = collectedState, + dispatch = dispatch, + ) +} diff --git a/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/ServerValidationMainScreen.kt b/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/ServerValidationMainScreen.kt index 8b138e02557..419e24a7ea7 100644 --- a/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/ServerValidationMainScreen.kt +++ b/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/ServerValidationMainScreen.kt @@ -2,8 +2,8 @@ package app.k9mail.feature.account.server.validation.ui import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.lifecycle.compose.collectAsStateWithLifecycle import app.k9mail.core.ui.compose.common.DevicePreviews +import app.k9mail.core.ui.compose.common.mvi.observeWithoutEffect 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 @@ -21,8 +21,7 @@ internal fun ServerValidationMainScreen( viewModel: ViewModel, modifier: Modifier = Modifier, ) { - val state = viewModel.state.collectAsStateWithLifecycle() - val dispatch = { event: Event -> viewModel.event(event) } + val (state, dispatch) = viewModel.observeWithoutEffect() Scaffold( topBar = { From 72de4c6b67b0cdeb639445e18b0df672da2abe59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolf-Martell=20Montw=C3=A9?= Date: Thu, 14 Sep 2023 14:57:03 +0200 Subject: [PATCH 3/3] Suppress error about line length --- .../core/ui/compose/common/mvi/UnidirectionalViewModel.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/ui/compose/common/src/main/kotlin/app/k9mail/core/ui/compose/common/mvi/UnidirectionalViewModel.kt b/core/ui/compose/common/src/main/kotlin/app/k9mail/core/ui/compose/common/mvi/UnidirectionalViewModel.kt index de46e8f2914..fb8f9685adf 100644 --- a/core/ui/compose/common/src/main/kotlin/app/k9mail/core/ui/compose/common/mvi/UnidirectionalViewModel.kt +++ b/core/ui/compose/common/src/main/kotlin/app/k9mail/core/ui/compose/common/mvi/UnidirectionalViewModel.kt @@ -133,9 +133,9 @@ inline fun UnidirectionalViewModel UnidirectionalViewModel - .observeWithoutEffect(): StateDispatch { +inline fun UnidirectionalViewModel.observeWithoutEffect(): StateDispatch { val collectedState = state.collectAsStateWithLifecycle() val dispatch: (EVENT) -> Unit = { event(it) }