diff --git a/changelog.d/5408.misc b/changelog.d/5408.misc new file mode 100644 index 0000000000..3807ee1da8 --- /dev/null +++ b/changelog.d/5408.misc @@ -0,0 +1 @@ +Improved onboarding registration unit test coverage \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt index 4f16231747..7fa75d1544 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt @@ -22,63 +22,49 @@ import im.vector.app.features.login.LoginConfig import im.vector.app.features.login.ServerType import im.vector.app.features.login.SignMode import org.matrix.android.sdk.api.auth.data.Credentials -import org.matrix.android.sdk.api.auth.registration.RegisterThreePid import org.matrix.android.sdk.internal.network.ssl.Fingerprint -sealed class OnboardingAction : VectorViewModelAction { - data class OnGetStarted(val resetLoginConfig: Boolean, val onboardingFlow: OnboardingFlow) : OnboardingAction() - data class OnIAlreadyHaveAnAccount(val resetLoginConfig: Boolean, val onboardingFlow: OnboardingFlow) : OnboardingAction() +sealed interface OnboardingAction : VectorViewModelAction { + data class OnGetStarted(val resetLoginConfig: Boolean, val onboardingFlow: OnboardingFlow) : OnboardingAction + data class OnIAlreadyHaveAnAccount(val resetLoginConfig: Boolean, val onboardingFlow: OnboardingFlow) : OnboardingAction - data class UpdateServerType(val serverType: ServerType) : OnboardingAction() - data class UpdateHomeServer(val homeServerUrl: String) : OnboardingAction() - data class UpdateUseCase(val useCase: FtueUseCase) : OnboardingAction() - object ResetUseCase : OnboardingAction() - data class UpdateSignMode(val signMode: SignMode) : OnboardingAction() - data class LoginWithToken(val loginToken: String) : OnboardingAction() - data class WebLoginSuccess(val credentials: Credentials) : OnboardingAction() - data class InitWith(val loginConfig: LoginConfig?) : OnboardingAction() - data class ResetPassword(val email: String, val newPassword: String) : OnboardingAction() - object ResetPasswordMailConfirmed : OnboardingAction() + data class UpdateServerType(val serverType: ServerType) : OnboardingAction + data class UpdateHomeServer(val homeServerUrl: String) : OnboardingAction + data class UpdateUseCase(val useCase: FtueUseCase) : OnboardingAction + object ResetUseCase : OnboardingAction + data class UpdateSignMode(val signMode: SignMode) : OnboardingAction + data class LoginWithToken(val loginToken: String) : OnboardingAction + data class WebLoginSuccess(val credentials: Credentials) : OnboardingAction + data class InitWith(val loginConfig: LoginConfig?) : OnboardingAction + data class ResetPassword(val email: String, val newPassword: String) : OnboardingAction + object ResetPasswordMailConfirmed : OnboardingAction // Login or Register, depending on the signMode - data class LoginOrRegister(val username: String, val password: String, val initialDeviceName: String) : OnboardingAction() + data class LoginOrRegister(val username: String, val password: String, val initialDeviceName: String) : OnboardingAction + object StopEmailValidationCheck : OnboardingAction - // Register actions - open class RegisterAction : OnboardingAction() - - data class AddThreePid(val threePid: RegisterThreePid) : RegisterAction() - object SendAgainThreePid : RegisterAction() - - // TODO Confirm Email (from link in the email, open in the phone, intercepted by the app) - data class ValidateThreePid(val code: String) : RegisterAction() - - data class CheckIfEmailHasBeenValidated(val delayMillis: Long) : RegisterAction() - object StopEmailValidationCheck : RegisterAction() - - data class CaptchaDone(val captchaResponse: String) : RegisterAction() - object AcceptTerms : RegisterAction() - object RegisterDummy : RegisterAction() + data class PostRegisterAction(val registerAction: RegisterAction) : OnboardingAction // Reset actions - open class ResetAction : OnboardingAction() + sealed interface ResetAction : OnboardingAction - object ResetHomeServerType : ResetAction() - object ResetHomeServerUrl : ResetAction() - object ResetSignMode : ResetAction() - object ResetLogin : ResetAction() - object ResetResetPassword : ResetAction() + object ResetHomeServerType : ResetAction + object ResetHomeServerUrl : ResetAction + object ResetSignMode : ResetAction + object ResetLogin : ResetAction + object ResetResetPassword : ResetAction // Homeserver history - object ClearHomeServerHistory : OnboardingAction() + object ClearHomeServerHistory : OnboardingAction - data class PostViewEvent(val viewEvent: OnboardingViewEvents) : OnboardingAction() + data class PostViewEvent(val viewEvent: OnboardingViewEvents) : OnboardingAction - data class UserAcceptCertificate(val fingerprint: Fingerprint) : OnboardingAction() + data class UserAcceptCertificate(val fingerprint: Fingerprint) : OnboardingAction - object PersonalizeProfile : OnboardingAction() - data class UpdateDisplayName(val displayName: String) : OnboardingAction() - object UpdateDisplayNameSkipped : OnboardingAction() - data class ProfilePictureSelected(val uri: Uri) : OnboardingAction() - object SaveSelectedProfilePicture : OnboardingAction() - object UpdateProfilePictureSkipped : OnboardingAction() + object PersonalizeProfile : OnboardingAction + data class UpdateDisplayName(val displayName: String) : OnboardingAction + object UpdateDisplayNameSkipped : OnboardingAction + data class ProfilePictureSelected(val uri: Uri) : OnboardingAction + object SaveSelectedProfilePicture : OnboardingAction + object UpdateProfilePictureSkipped : OnboardingAction } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index 36020fbe61..6659058b4e 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -83,6 +83,7 @@ class OnboardingViewModel @AssistedInject constructor( private val vectorFeatures: VectorFeatures, private val analyticsTracker: AnalyticsTracker, private val uriFilenameResolver: UriFilenameResolver, + private val registrationActionHandler: RegistrationActionHandler, private val vectorOverrides: VectorOverrides ) : VectorViewModel(initialState) { @@ -116,16 +117,16 @@ class OnboardingViewModel @AssistedInject constructor( private val matrixOrgUrl = stringProvider.getString(R.string.matrix_org_server_url).ensureTrailingSlash() + private val registrationWizard: RegistrationWizard + get() = authenticationService.getRegistrationWizard() + val currentThreePid: String? - get() = registrationWizard?.currentThreePid + get() = registrationWizard.currentThreePid // True when login and password has been sent with success to the homeserver val isRegistrationStarted: Boolean get() = authenticationService.isRegistrationStarted - private val registrationWizard: RegistrationWizard? - get() = authenticationService.getRegistrationWizard() - private val loginWizard: LoginWizard? get() = authenticationService.getLoginWizard() @@ -153,7 +154,7 @@ class OnboardingViewModel @AssistedInject constructor( is OnboardingAction.WebLoginSuccess -> handleWebLoginSuccess(action) is OnboardingAction.ResetPassword -> handleResetPassword(action) is OnboardingAction.ResetPasswordMailConfirmed -> handleResetPasswordMailConfirmed() - is OnboardingAction.RegisterAction -> handleRegisterAction(action) + is OnboardingAction.PostRegisterAction -> handleRegisterAction(action.registerAction) is OnboardingAction.ResetAction -> handleResetAction(action) is OnboardingAction.UserAcceptCertificate -> handleUserAcceptCertificate(action) OnboardingAction.ClearHomeServerHistory -> handleClearHomeServerHistory() @@ -164,6 +165,7 @@ class OnboardingViewModel @AssistedInject constructor( is OnboardingAction.ProfilePictureSelected -> handleProfilePictureSelected(action) OnboardingAction.SaveSelectedProfilePicture -> updateProfilePicture() is OnboardingAction.PostViewEvent -> _viewEvents.post(action.viewEvent) + OnboardingAction.StopEmailValidationCheck -> cancelWaitForEmailValidation() }.exhaustive } @@ -266,131 +268,41 @@ class OnboardingViewModel @AssistedInject constructor( } } - private fun handleRegisterAction(action: OnboardingAction.RegisterAction) { - when (action) { - is OnboardingAction.CaptchaDone -> handleCaptchaDone(action) - is OnboardingAction.AcceptTerms -> handleAcceptTerms() - is OnboardingAction.RegisterDummy -> handleRegisterDummy() - is OnboardingAction.AddThreePid -> handleAddThreePid(action) - is OnboardingAction.SendAgainThreePid -> handleSendAgainThreePid() - is OnboardingAction.ValidateThreePid -> handleValidateThreePid(action) - is OnboardingAction.CheckIfEmailHasBeenValidated -> handleCheckIfEmailHasBeenValidated(action) - is OnboardingAction.StopEmailValidationCheck -> handleStopEmailValidationCheck() - } - } - - private fun handleCheckIfEmailHasBeenValidated(action: OnboardingAction.CheckIfEmailHasBeenValidated) { - // We do not want the common progress bar to be displayed, so we do not change asyncRegistration value in the state - currentJob = executeRegistrationStep(withLoading = false) { - it.checkIfEmailHasBeenValidated(action.delayMillis) - } - } - - private fun handleStopEmailValidationCheck() { - currentJob = null - } - - private fun handleValidateThreePid(action: OnboardingAction.ValidateThreePid) { - currentJob = executeRegistrationStep { - it.handleValidateThreePid(action.code) - } - } - - private fun executeRegistrationStep(withLoading: Boolean = true, - block: suspend (RegistrationWizard) -> RegistrationResult): Job { - if (withLoading) { - setState { copy(asyncRegistration = Loading()) } - } - return viewModelScope.launch { - try { - registrationWizard?.let { block(it) } - /* - // Simulate registration disabled - throw Failure.ServerError(MatrixError( - code = MatrixError.FORBIDDEN, - message = "Registration is disabled" - ), 403)) - */ - } catch (failure: Throwable) { - if (failure !is CancellationException) { - _viewEvents.post(OnboardingViewEvents.Failure(failure)) - } - null - } - ?.let { data -> - when (data) { - is RegistrationResult.Success -> onSessionCreated(data.session, isAccountCreated = true) - is RegistrationResult.FlowResponse -> onFlowResponse(data.flowResult) - } - } - - setState { - copy( - asyncRegistration = Uninitialized - ) - } - } - } - - private fun handleAddThreePid(action: OnboardingAction.AddThreePid) { - setState { copy(asyncRegistration = Loading()) } + private fun handleRegisterAction(action: RegisterAction) { currentJob = viewModelScope.launch { - try { - registrationWizard?.addThreePid(action.threePid) - } catch (failure: Throwable) { - _viewEvents.post(OnboardingViewEvents.Failure(failure)) + if (action.hasLoadingState()) { + setState { copy(asyncRegistration = Loading()) } } - setState { - copy( - asyncRegistration = Uninitialized - ) - } - } - } - - private fun handleSendAgainThreePid() { - setState { copy(asyncRegistration = Loading()) } - currentJob = viewModelScope.launch { - try { - registrationWizard?.sendAgainThreePid() - } catch (failure: Throwable) { - _viewEvents.post(OnboardingViewEvents.Failure(failure)) - } - setState { - copy( - asyncRegistration = Uninitialized - ) - } - } - } - - private fun handleAcceptTerms() { - currentJob = executeRegistrationStep { - it.acceptTerms() - } - } - - private fun handleRegisterDummy() { - currentJob = executeRegistrationStep { - it.dummy() + runCatching { registrationActionHandler.handleRegisterAction(registrationWizard, action) } + .fold( + onSuccess = { + when { + action.ignoresResult() -> { + // do nothing + } + else -> when (it) { + is RegistrationResult.Success -> onSessionCreated(it.session, isAccountCreated = true) + is RegistrationResult.FlowResponse -> onFlowResponse(it.flowResult) + } + } + }, + onFailure = { + if (it !is CancellationException) { + _viewEvents.post(OnboardingViewEvents.Failure(it)) + } + } + ) + setState { copy(asyncRegistration = Uninitialized) } } } private fun handleRegisterWith(action: OnboardingAction.LoginOrRegister) { reAuthHelper.data = action.password - currentJob = executeRegistrationStep { - it.createAccount( - action.username, - action.password, - action.initialDeviceName - ) - } - } - - private fun handleCaptchaDone(action: OnboardingAction.CaptchaDone) { - currentJob = executeRegistrationStep { - it.performReCaptcha(action.captchaResponse) - } + handleRegisterAction(RegisterAction.CreateAccount( + action.username, + action.password, + action.initialDeviceName + )) } private fun handleResetAction(action: OnboardingAction.ResetAction) { @@ -461,7 +373,7 @@ class OnboardingViewModel @AssistedInject constructor( } when (action.signMode) { - SignMode.SignUp -> startRegistrationFlow() + SignMode.SignUp -> handleRegisterAction(RegisterAction.StartRegistration) SignMode.SignIn -> startAuthenticationFlow() SignMode.SignInWithMatrixId -> _viewEvents.post(OnboardingViewEvents.OnSignModeSelected(SignMode.SignInWithMatrixId)) SignMode.Unknown -> Unit @@ -499,7 +411,7 @@ class OnboardingViewModel @AssistedInject constructor( // If there is a pending email validation continue on this step try { - if (registrationWizard?.isRegistrationStarted == true) { + if (registrationWizard.isRegistrationStarted) { currentThreePid?.let { handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnSendEmailSuccess(it))) } @@ -730,12 +642,6 @@ class OnboardingViewModel @AssistedInject constructor( } } - private fun startRegistrationFlow() { - currentJob = executeRegistrationStep { - it.getRegistrationFlow() - } - } - private fun startAuthenticationFlow() { // Ensure Wizard is ready loginWizard @@ -745,8 +651,7 @@ class OnboardingViewModel @AssistedInject constructor( private fun onFlowResponse(flowResult: FlowResult) { // If dummy stage is mandatory, and password is already sent, do the dummy stage now - if (isRegistrationStarted && - flowResult.missingStages.any { it is Stage.Dummy && it.mandatory }) { + if (isRegistrationStarted && flowResult.missingStages.any { it is Stage.Dummy && it.mandatory }) { handleRegisterDummy() } else { // Notify the user @@ -754,6 +659,10 @@ class OnboardingViewModel @AssistedInject constructor( } } + private fun handleRegisterDummy() { + handleRegisterAction(RegisterAction.RegisterDummy) + } + private suspend fun onSessionCreated(session: Session, isAccountCreated: Boolean) { val state = awaitState() state.useCase?.let { useCase -> @@ -1006,6 +915,10 @@ class OnboardingViewModel @AssistedInject constructor( private fun completePersonalization() { _viewEvents.post(OnboardingViewEvents.OnPersonalizationComplete) } + + private fun cancelWaitForEmailValidation() { + currentJob = null + } } private fun LoginMode.supportsSignModeScreen(): Boolean { diff --git a/vector/src/main/java/im/vector/app/features/onboarding/RegistrationActionHandler.kt b/vector/src/main/java/im/vector/app/features/onboarding/RegistrationActionHandler.kt new file mode 100644 index 0000000000..b4998d2ba0 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/onboarding/RegistrationActionHandler.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.onboarding + +import org.matrix.android.sdk.api.auth.registration.RegisterThreePid +import org.matrix.android.sdk.api.auth.registration.RegistrationResult +import org.matrix.android.sdk.api.auth.registration.RegistrationWizard +import javax.inject.Inject + +class RegistrationActionHandler @Inject constructor() { + + suspend fun handleRegisterAction(registrationWizard: RegistrationWizard, action: RegisterAction): RegistrationResult { + return when (action) { + RegisterAction.StartRegistration -> registrationWizard.getRegistrationFlow() + is RegisterAction.CaptchaDone -> registrationWizard.performReCaptcha(action.captchaResponse) + is RegisterAction.AcceptTerms -> registrationWizard.acceptTerms() + is RegisterAction.RegisterDummy -> registrationWizard.dummy() + is RegisterAction.AddThreePid -> registrationWizard.addThreePid(action.threePid) + is RegisterAction.SendAgainThreePid -> registrationWizard.sendAgainThreePid() + is RegisterAction.ValidateThreePid -> registrationWizard.handleValidateThreePid(action.code) + is RegisterAction.CheckIfEmailHasBeenValidated -> registrationWizard.checkIfEmailHasBeenValidated(action.delayMillis) + is RegisterAction.CreateAccount -> registrationWizard.createAccount(action.username, action.password, action.initialDeviceName) + } + } +} + +sealed interface RegisterAction { + object StartRegistration : RegisterAction + data class CreateAccount(val username: String, val password: String, val initialDeviceName: String) : RegisterAction + + data class AddThreePid(val threePid: RegisterThreePid) : RegisterAction + object SendAgainThreePid : RegisterAction + + // TODO Confirm Email (from link in the email, open in the phone, intercepted by the app) + data class ValidateThreePid(val code: String) : RegisterAction + + data class CheckIfEmailHasBeenValidated(val delayMillis: Long) : RegisterAction + + data class CaptchaDone(val captchaResponse: String) : RegisterAction + object AcceptTerms : RegisterAction + object RegisterDummy : RegisterAction +} + +fun RegisterAction.ignoresResult() = when (this) { + is RegisterAction.AddThreePid -> true + is RegisterAction.SendAgainThreePid -> true + else -> false +} + +fun RegisterAction.hasLoadingState() = when (this) { + is RegisterAction.CheckIfEmailHasBeenValidated -> false + else -> true +} diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCaptchaFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCaptchaFragment.kt index e2e390ae2d..4773332138 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCaptchaFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCaptchaFragment.kt @@ -39,6 +39,7 @@ import im.vector.app.databinding.FragmentLoginCaptchaBinding import im.vector.app.features.login.JavascriptResponse import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingViewState +import im.vector.app.features.onboarding.RegisterAction import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.internal.di.MoshiProvider import timber.log.Timber @@ -181,7 +182,7 @@ class FtueAuthCaptchaFragment @Inject constructor( val response = javascriptResponse?.response if (javascriptResponse?.action == "verifyCallback" && response != null) { - viewModel.handle(OnboardingAction.CaptchaDone(response)) + viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.CaptchaDone(response))) } } return true diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthGenericTextInputFormFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthGenericTextInputFormFragment.kt index bd5054f646..2800530152 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthGenericTextInputFormFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthGenericTextInputFormFragment.kt @@ -37,6 +37,7 @@ import im.vector.app.databinding.FragmentLoginGenericTextInputFormBinding import im.vector.app.features.login.TextInputFormFragmentMode import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingViewEvents +import im.vector.app.features.onboarding.RegisterAction import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.parcelize.Parcelize @@ -138,7 +139,7 @@ class FtueAuthGenericTextInputFormFragment @Inject constructor() : AbstractFtueA private fun onOtherButtonClicked() { when (params.mode) { TextInputFormFragmentMode.ConfirmMsisdn -> { - viewModel.handle(OnboardingAction.SendAgainThreePid) + viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.SendAgainThreePid)) } else -> { // Should not happen, button is not displayed @@ -152,19 +153,19 @@ class FtueAuthGenericTextInputFormFragment @Inject constructor() : AbstractFtueA if (text.isEmpty()) { // Perform dummy action - viewModel.handle(OnboardingAction.RegisterDummy) + viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.RegisterDummy)) } else { when (params.mode) { TextInputFormFragmentMode.SetEmail -> { - viewModel.handle(OnboardingAction.AddThreePid(RegisterThreePid.Email(text))) + viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.AddThreePid(RegisterThreePid.Email(text)))) } TextInputFormFragmentMode.SetMsisdn -> { getCountryCodeOrShowError(text)?.let { countryCode -> - viewModel.handle(OnboardingAction.AddThreePid(RegisterThreePid.Msisdn(text, countryCode))) + viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.AddThreePid(RegisterThreePid.Msisdn(text, countryCode)))) } } TextInputFormFragmentMode.ConfirmMsisdn -> { - viewModel.handle(OnboardingAction.ValidateThreePid(text)) + viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.ValidateThreePid(text))) } } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWaitForEmailFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWaitForEmailFragment.kt index 94758c7fad..ec72f52b9e 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWaitForEmailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWaitForEmailFragment.kt @@ -25,6 +25,7 @@ import com.airbnb.mvrx.args import im.vector.app.R import im.vector.app.databinding.FragmentLoginWaitForEmailBinding import im.vector.app.features.onboarding.OnboardingAction +import im.vector.app.features.onboarding.RegisterAction import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.failure.is401 import javax.inject.Inject @@ -54,7 +55,7 @@ class FtueAuthWaitForEmailFragment @Inject constructor() : AbstractFtueAuthFragm override fun onResume() { super.onResume() - viewModel.handle(OnboardingAction.CheckIfEmailHasBeenValidated(0)) + viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.CheckIfEmailHasBeenValidated(0))) } override fun onPause() { @@ -70,7 +71,7 @@ class FtueAuthWaitForEmailFragment @Inject constructor() : AbstractFtueAuthFragm override fun onError(throwable: Throwable) { if (throwable.is401()) { // Try again, with a delay - viewModel.handle(OnboardingAction.CheckIfEmailHasBeenValidated(10_000)) + viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.CheckIfEmailHasBeenValidated(10_000))) } else { super.onError(throwable) } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthTermsFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthTermsFragment.kt index 5ce9a5350d..03598d3a47 100755 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthTermsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthTermsFragment.kt @@ -32,6 +32,7 @@ import im.vector.app.features.login.terms.LoginTermsViewState import im.vector.app.features.login.terms.PolicyController import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingViewState +import im.vector.app.features.onboarding.RegisterAction import im.vector.app.features.onboarding.ftueauth.AbstractFtueAuthFragment import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.internal.auth.registration.LocalizedFlowDataLoginTerms @@ -111,7 +112,7 @@ class FtueAuthTermsFragment @Inject constructor( } private fun submit() { - viewModel.handle(OnboardingAction.AcceptTerms) + viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.AcceptTerms)) } override fun updateWithState(state: OnboardingViewState) { diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt index 085e1a8049..f6c322af40 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt @@ -23,12 +23,14 @@ import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.test.MvRxTestRule import im.vector.app.features.login.ReAuthHelper +import im.vector.app.features.login.SignMode import im.vector.app.test.fakes.FakeActiveSessionHolder import im.vector.app.test.fakes.FakeAnalyticsTracker import im.vector.app.test.fakes.FakeAuthenticationService import im.vector.app.test.fakes.FakeContext import im.vector.app.test.fakes.FakeHomeServerConnectionConfigFactory import im.vector.app.test.fakes.FakeHomeServerHistoryService +import im.vector.app.test.fakes.FakeRegisterActionHandler import im.vector.app.test.fakes.FakeRegistrationWizard import im.vector.app.test.fakes.FakeSession import im.vector.app.test.fakes.FakeStringProvider @@ -36,20 +38,27 @@ import im.vector.app.test.fakes.FakeUri import im.vector.app.test.fakes.FakeUriFilenameResolver import im.vector.app.test.fakes.FakeVectorFeatures import im.vector.app.test.fakes.FakeVectorOverrides +import im.vector.app.test.fixtures.aHomeServerCapabilities import im.vector.app.test.test import kotlinx.coroutines.test.runBlockingTest import org.junit.Before import org.junit.Rule import org.junit.Test +import org.matrix.android.sdk.api.auth.registration.FlowResult +import org.matrix.android.sdk.api.auth.registration.RegisterThreePid +import org.matrix.android.sdk.api.auth.registration.RegistrationResult +import org.matrix.android.sdk.api.auth.registration.Stage import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities private const val A_DISPLAY_NAME = "a display name" private const val A_PICTURE_FILENAME = "a-picture.png" private val AN_ERROR = RuntimeException("an error!") -private val AN_UNSUPPORTED_PERSONALISATION_STATE = PersonalizationState( - supportsChangingDisplayName = false, - supportsChangingProfilePicture = false -) +private val A_LOADABLE_REGISTER_ACTION = RegisterAction.StartRegistration +private val A_NON_LOADABLE_REGISTER_ACTION = RegisterAction.CheckIfEmailHasBeenValidated(delayMillis = -1L) +private val A_RESULT_IGNORED_REGISTER_ACTION = RegisterAction.AddThreePid(RegisterThreePid.Email("an email")) +private val A_HOMESERVER_CAPABILITIES = aHomeServerCapabilities(canChangeDisplayName = true, canChangeAvatar = true) +private val AN_IGNORED_FLOW_RESULT = FlowResult(missingStages = emptyList(), completedStages = emptyList()) +private val ANY_CONTINUING_REGISTRATION_RESULT = RegistrationResult.FlowResponse(AN_IGNORED_FLOW_RESULT) class OnboardingViewModelTest { @@ -63,6 +72,7 @@ class OnboardingViewModelTest { private val fakeUriFilenameResolver = FakeUriFilenameResolver() private val fakeActiveSessionHolder = FakeActiveSessionHolder(fakeSession) private val fakeAuthenticationService = FakeAuthenticationService() + private val fakeRegisterActionHandler = FakeRegisterActionHandler() lateinit var viewModel: OnboardingViewModel @@ -72,7 +82,7 @@ class OnboardingViewModelTest { } @Test - fun `when handling PostViewEvent then emits contents as view event`() = runBlockingTest { + fun `when handling PostViewEvent, then emits contents as view event`() = runBlockingTest { val test = viewModel.test(this) viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnTakeMeHome)) @@ -83,7 +93,7 @@ class OnboardingViewModelTest { } @Test - fun `given supports changing display name when handling PersonalizeProfile then emits contents choose display name`() = runBlockingTest { + fun `given supports changing display name, when handling PersonalizeProfile, then emits contents choose display name`() = runBlockingTest { val initialState = initialState.copy(personalizationState = PersonalizationState(supportsChangingDisplayName = true, supportsChangingProfilePicture = false)) viewModel = createViewModel(initialState) val test = viewModel.test(this) @@ -96,7 +106,7 @@ class OnboardingViewModelTest { } @Test - fun `given only supports changing profile picture when handling PersonalizeProfile then emits contents choose profile picture`() = runBlockingTest { + fun `given only supports changing profile picture, when handling PersonalizeProfile, then emits contents choose profile picture`() = runBlockingTest { val initialState = initialState.copy(personalizationState = PersonalizationState(supportsChangingDisplayName = false, supportsChangingProfilePicture = true)) viewModel = createViewModel(initialState) val test = viewModel.test(this) @@ -109,34 +119,109 @@ class OnboardingViewModelTest { } @Test - fun `given homeserver does not support personalisation when registering account then updates state and emits account created event`() = runBlockingTest { - fakeSession.fakeHomeServerCapabilitiesService.givenCapabilities(HomeServerCapabilities(canChangeDisplayName = false, canChangeAvatar = false)) - givenSuccessfullyCreatesAccount() + fun `when handling SignUp then sets sign mode to sign up and starts registration`() = runBlockingTest { + givenRegistrationResultFor(RegisterAction.StartRegistration, ANY_CONTINUING_REGISTRATION_RESULT) val test = viewModel.test(this) - viewModel.handle(OnboardingAction.RegisterDummy) + viewModel.handle(OnboardingAction.UpdateSignMode(SignMode.SignUp)) test - .assertStates( + .assertStatesChanges( initialState, - initialState.copy(asyncRegistration = Loading()), - initialState.copy( - asyncLoginAction = Success(Unit), - asyncRegistration = Loading(), - personalizationState = AN_UNSUPPORTED_PERSONALISATION_STATE - ), - initialState.copy( - asyncLoginAction = Success(Unit), - asyncRegistration = Uninitialized, - personalizationState = AN_UNSUPPORTED_PERSONALISATION_STATE - ) + { copy(signMode = SignMode.SignUp) }, + { copy(asyncRegistration = Loading()) }, + { copy(asyncRegistration = Uninitialized) } + ) + .assertEvents(OnboardingViewEvents.RegistrationFlowResult(ANY_CONTINUING_REGISTRATION_RESULT.flowResult, isRegistrationStarted = true)) + .finish() + } + + @Test + fun `given register action requires more steps, when handling action, then posts next steps`() = runBlockingTest { + val test = viewModel.test(this) + givenRegistrationResultFor(A_LOADABLE_REGISTER_ACTION, ANY_CONTINUING_REGISTRATION_RESULT) + + viewModel.handle(OnboardingAction.PostRegisterAction(A_LOADABLE_REGISTER_ACTION)) + + test + .assertStatesChanges( + initialState, + { copy(asyncRegistration = Loading()) }, + { copy(asyncRegistration = Uninitialized) } + ) + .assertEvents(OnboardingViewEvents.RegistrationFlowResult(ANY_CONTINUING_REGISTRATION_RESULT.flowResult, isRegistrationStarted = true)) + .finish() + } + + @Test + fun `given register action is non loadable, when handling action, then posts next steps without loading`() = runBlockingTest { + val test = viewModel.test(this) + givenRegistrationResultFor(A_NON_LOADABLE_REGISTER_ACTION, ANY_CONTINUING_REGISTRATION_RESULT) + + viewModel.handle(OnboardingAction.PostRegisterAction(A_NON_LOADABLE_REGISTER_ACTION)) + + test + .assertState(initialState) + .assertEvents(OnboardingViewEvents.RegistrationFlowResult(ANY_CONTINUING_REGISTRATION_RESULT.flowResult, isRegistrationStarted = true)) + .finish() + } + + @Test + fun `given register action ignores result, when handling action, then does nothing on success`() = runBlockingTest { + val test = viewModel.test(this) + givenRegistrationResultFor(A_RESULT_IGNORED_REGISTER_ACTION, RegistrationResult.FlowResponse(AN_IGNORED_FLOW_RESULT)) + + viewModel.handle(OnboardingAction.PostRegisterAction(A_RESULT_IGNORED_REGISTER_ACTION)) + + test + .assertStatesChanges( + initialState, + { copy(asyncRegistration = Loading()) }, + { copy(asyncRegistration = Uninitialized) } + ) + .assertNoEvents() + .finish() + } + + @Test + fun `when registering account, then updates state and emits account created event`() = runBlockingTest { + givenRegistrationResultFor(A_LOADABLE_REGISTER_ACTION, RegistrationResult.Success(fakeSession)) + givenSuccessfullyCreatesAccount(A_HOMESERVER_CAPABILITIES) + val test = viewModel.test(this) + + viewModel.handle(OnboardingAction.PostRegisterAction(A_LOADABLE_REGISTER_ACTION)) + + test + .assertStatesChanges( + initialState, + { copy(asyncRegistration = Loading()) }, + { copy(asyncLoginAction = Success(Unit), personalizationState = A_HOMESERVER_CAPABILITIES.toPersonalisationState()) }, + { copy(asyncLoginAction = Success(Unit), asyncRegistration = Uninitialized) } ) .assertEvents(OnboardingViewEvents.OnAccountCreated) .finish() } @Test - fun `given changing profile picture is supported when updating display name then updates upstream user display name and moves to choose profile picture`() = runBlockingTest { + fun `given registration has started and has dummy step to do, when handling action, then ignores other steps and executes dummy`() = runBlockingTest { + givenSuccessfulRegistrationForStartAndDummySteps(missingStages = listOf(Stage.Dummy(mandatory = true))) + val test = viewModel.test(this) + + viewModel.handle(OnboardingAction.PostRegisterAction(A_LOADABLE_REGISTER_ACTION)) + + test + .assertStatesChanges( + initialState, + { copy(asyncRegistration = Loading()) }, + { copy(asyncLoginAction = Success(Unit), personalizationState = A_HOMESERVER_CAPABILITIES.toPersonalisationState()) }, + { copy(asyncRegistration = Uninitialized) } + ) + .assertEvents(OnboardingViewEvents.OnAccountCreated) + .finish() + } + + @Test + fun `given changing profile picture is supported, when updating display name, then updates upstream user display name and moves to choose profile picture`() = runBlockingTest { val personalisedInitialState = initialState.copy(personalizationState = PersonalizationState(supportsChangingProfilePicture = true)) viewModel = createViewModel(personalisedInitialState) val test = viewModel.test(this) @@ -144,14 +229,14 @@ class OnboardingViewModelTest { viewModel.handle(OnboardingAction.UpdateDisplayName(A_DISPLAY_NAME)) test - .assertStates(expectedSuccessfulDisplayNameUpdateStates(personalisedInitialState)) + .assertStatesChanges(personalisedInitialState, expectedSuccessfulDisplayNameUpdateStates()) .assertEvents(OnboardingViewEvents.OnChooseProfilePicture) .finish() fakeSession.fakeProfileService.verifyUpdatedName(fakeSession.myUserId, A_DISPLAY_NAME) } @Test - fun `given changing profile picture is not supported when updating display name then updates upstream user display name and completes personalization`() = runBlockingTest { + fun `given changing profile picture is not supported, when updating display name, then updates upstream user display name and completes personalization`() = runBlockingTest { val personalisedInitialState = initialState.copy(personalizationState = PersonalizationState(supportsChangingProfilePicture = false)) viewModel = createViewModel(personalisedInitialState) val test = viewModel.test(this) @@ -159,31 +244,31 @@ class OnboardingViewModelTest { viewModel.handle(OnboardingAction.UpdateDisplayName(A_DISPLAY_NAME)) test - .assertStates(expectedSuccessfulDisplayNameUpdateStates(personalisedInitialState)) + .assertStatesChanges(personalisedInitialState, expectedSuccessfulDisplayNameUpdateStates()) .assertEvents(OnboardingViewEvents.OnPersonalizationComplete) .finish() fakeSession.fakeProfileService.verifyUpdatedName(fakeSession.myUserId, A_DISPLAY_NAME) } @Test - fun `given upstream failure when handling display name update then emits failure event`() = runBlockingTest { + fun `given upstream failure, when handling display name update, then emits failure event`() = runBlockingTest { val test = viewModel.test(this) fakeSession.fakeProfileService.givenSetDisplayNameErrors(AN_ERROR) viewModel.handle(OnboardingAction.UpdateDisplayName(A_DISPLAY_NAME)) test - .assertStates( + .assertStatesChanges( initialState, - initialState.copy(asyncDisplayName = Loading()), - initialState.copy(asyncDisplayName = Fail(AN_ERROR)), + { copy(asyncDisplayName = Loading()) }, + { copy(asyncDisplayName = Fail(AN_ERROR)) }, ) .assertEvents(OnboardingViewEvents.Failure(AN_ERROR)) .finish() } @Test - fun `when handling profile picture selected then updates selected picture state`() = runBlockingTest { + fun `when handling profile picture selected, then updates selected picture state`() = runBlockingTest { val test = viewModel.test(this) viewModel.handle(OnboardingAction.ProfilePictureSelected(fakeUri.instance)) @@ -198,7 +283,7 @@ class OnboardingViewModelTest { } @Test - fun `given a selected picture when handling save selected profile picture then updates upstream avatar and completes personalization`() = runBlockingTest { + fun `given a selected picture, when handling save selected profile picture, then updates upstream avatar and completes personalization`() = runBlockingTest { val initialStateWithPicture = givenPictureSelected(fakeUri.instance, A_PICTURE_FILENAME) viewModel = createViewModel(initialStateWithPicture) val test = viewModel.test(this) @@ -213,7 +298,7 @@ class OnboardingViewModelTest { } @Test - fun `given upstream update avatar fails when saving selected profile picture then emits failure event`() = runBlockingTest { + fun `given upstream update avatar fails, when saving selected profile picture, then emits failure event`() = runBlockingTest { fakeSession.fakeProfileService.givenUpdateAvatarErrors(AN_ERROR) val initialStateWithPicture = givenPictureSelected(fakeUri.instance, A_PICTURE_FILENAME) viewModel = createViewModel(initialStateWithPicture) @@ -228,7 +313,7 @@ class OnboardingViewModelTest { } @Test - fun `given no selected picture when saving selected profile picture then emits failure event`() = runBlockingTest { + fun `given no selected picture, when saving selected profile picture, then emits failure event`() = runBlockingTest { val test = viewModel.test(this) viewModel.handle(OnboardingAction.SaveSelectedProfilePicture) @@ -240,7 +325,7 @@ class OnboardingViewModelTest { } @Test - fun `when handling profile picture skipped then completes personalization`() = runBlockingTest { + fun `when handling profile skipped, then completes personalization`() = runBlockingTest { val test = viewModel.test(this) viewModel.handle(OnboardingAction.UpdateProfilePictureSkipped) @@ -264,6 +349,7 @@ class OnboardingViewModelTest { FakeVectorFeatures(), FakeAnalyticsTracker(), fakeUriFilenameResolver.instance, + fakeRegisterActionHandler.instance, FakeVectorOverrides() ) } @@ -286,22 +372,42 @@ class OnboardingViewModelTest { state.copy(asyncProfilePicture = Fail(cause)) ) - private fun givenSuccessfullyCreatesAccount() { + private fun expectedSuccessfulDisplayNameUpdateStates(): List OnboardingViewState> { + return listOf( + { copy(asyncDisplayName = Loading()) }, + { copy(asyncDisplayName = Success(Unit), personalizationState = personalizationState.copy(displayName = A_DISPLAY_NAME)) } + ) + } + + private fun givenSuccessfulRegistrationForStartAndDummySteps(missingStages: List) { + val flowResult = FlowResult(missingStages = missingStages, completedStages = emptyList()) + givenRegistrationResultsFor(listOf( + A_LOADABLE_REGISTER_ACTION to RegistrationResult.FlowResponse(flowResult), + RegisterAction.RegisterDummy to RegistrationResult.Success(fakeSession) + )) + givenSuccessfullyCreatesAccount(A_HOMESERVER_CAPABILITIES) + } + + private fun givenSuccessfullyCreatesAccount(homeServerCapabilities: HomeServerCapabilities) { + fakeSession.fakeHomeServerCapabilitiesService.givenCapabilities(homeServerCapabilities) fakeActiveSessionHolder.expectSetsActiveSession(fakeSession) - val registrationWizard = FakeRegistrationWizard().also { it.givenSuccessfulDummy(fakeSession) } - fakeAuthenticationService.givenRegistrationWizard(registrationWizard) fakeAuthenticationService.expectReset() fakeSession.expectStartsSyncing() } - private fun expectedSuccessfulDisplayNameUpdateStates(personalisedInitialState: OnboardingViewState): List { - return listOf( - personalisedInitialState, - personalisedInitialState.copy(asyncDisplayName = Loading()), - personalisedInitialState.copy( - asyncDisplayName = Success(Unit), - personalizationState = personalisedInitialState.personalizationState.copy(displayName = A_DISPLAY_NAME) - ) - ) + private fun givenRegistrationResultFor(action: RegisterAction, result: RegistrationResult) { + givenRegistrationResultsFor(listOf(action to result)) + } + + private fun givenRegistrationResultsFor(results: List>) { + fakeAuthenticationService.givenRegistrationStarted(true) + val registrationWizard = FakeRegistrationWizard() + fakeAuthenticationService.givenRegistrationWizard(registrationWizard) + fakeRegisterActionHandler.givenResultsFor(registrationWizard, results) } } + +private fun HomeServerCapabilities.toPersonalisationState() = PersonalizationState( + supportsChangingDisplayName = canChangeDisplayName, + supportsChangingProfilePicture = canChangeAvatar +) diff --git a/vector/src/test/java/im/vector/app/features/onboarding/RegistrationActionHandlerTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/RegistrationActionHandlerTest.kt new file mode 100644 index 0000000000..2ca9aaef07 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/onboarding/RegistrationActionHandlerTest.kt @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.onboarding + +import im.vector.app.test.fakes.FakeRegistrationWizard +import im.vector.app.test.fakes.FakeSession +import io.mockk.coVerifyAll +import kotlinx.coroutines.test.runBlockingTest +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test +import org.matrix.android.sdk.api.auth.registration.RegisterThreePid +import org.matrix.android.sdk.api.auth.registration.RegistrationResult +import org.matrix.android.sdk.api.auth.registration.RegistrationWizard + +private val A_SESSION = FakeSession() +private val AN_EXPECTED_RESULT = RegistrationResult.Success(A_SESSION) +private const val A_USERNAME = "a username" +private const val A_PASSWORD = "a password" +private const val AN_INITIAL_DEVICE_NAME = "a device name" +private const val A_CAPTCHA_RESPONSE = "a captcha response" +private const val A_PID_CODE = "a pid code" +private const val EMAIL_VALIDATED_DELAY = 10000L +private val A_PID_TO_REGISTER = RegisterThreePid.Email("an email") + +class RegistrationActionHandlerTest { + + @Test + fun `when handling register action then delegates to wizard`() = runBlockingTest { + val cases = listOf( + case(RegisterAction.StartRegistration) { getRegistrationFlow() }, + case(RegisterAction.CaptchaDone(A_CAPTCHA_RESPONSE)) { performReCaptcha(A_CAPTCHA_RESPONSE) }, + case(RegisterAction.AcceptTerms) { acceptTerms() }, + case(RegisterAction.RegisterDummy) { dummy() }, + case(RegisterAction.AddThreePid(A_PID_TO_REGISTER)) { addThreePid(A_PID_TO_REGISTER) }, + case(RegisterAction.SendAgainThreePid) { sendAgainThreePid() }, + case(RegisterAction.ValidateThreePid(A_PID_CODE)) { handleValidateThreePid(A_PID_CODE) }, + case(RegisterAction.CheckIfEmailHasBeenValidated(EMAIL_VALIDATED_DELAY)) { checkIfEmailHasBeenValidated(EMAIL_VALIDATED_DELAY) }, + case(RegisterAction.CreateAccount(A_USERNAME, A_PASSWORD, AN_INITIAL_DEVICE_NAME)) { + createAccount(A_USERNAME, A_PASSWORD, AN_INITIAL_DEVICE_NAME) + } + ) + + cases.forEach { testSuccessfulActionDelegation(it) } + } + + private suspend fun testSuccessfulActionDelegation(case: Case) { + val registrationActionHandler = RegistrationActionHandler() + val fakeRegistrationWizard = FakeRegistrationWizard() + fakeRegistrationWizard.givenSuccessFor(result = A_SESSION, case.expect) + + val result = registrationActionHandler.handleRegisterAction(fakeRegistrationWizard, case.action) + + coVerifyAll { case.expect(fakeRegistrationWizard) } + result shouldBeEqualTo AN_EXPECTED_RESULT + } +} + +private fun case(action: RegisterAction, expect: suspend RegistrationWizard.() -> RegistrationResult) = Case(action, expect) + +private class Case(val action: RegisterAction, val expect: suspend RegistrationWizard.() -> RegistrationResult) diff --git a/vector/src/test/java/im/vector/app/test/Extensions.kt b/vector/src/test/java/im/vector/app/test/Extensions.kt index 3ff041dc11..67eff7ca11 100644 --- a/vector/src/test/java/im/vector/app/test/Extensions.kt +++ b/vector/src/test/java/im/vector/app/test/Extensions.kt @@ -55,6 +55,25 @@ class ViewModelTest( return this } + fun assertStatesChanges(initial: S, vararg expected: S.() -> S): ViewModelTest { + return assertStatesChanges(initial, expected.toList()) + } + + /** + * Asserts the expected states are in the same order as the actual state emissions + * Each expected lambda is given the previous expected state, starting with the initial + */ + fun assertStatesChanges(initial: S, expected: List S>): ViewModelTest { + val reducedExpectedStates = expected.fold(mutableListOf(initial)) { acc, curr -> + val next = curr.invoke(acc.last()) + acc.add(next) + acc + } + + states.assertValues(reducedExpectedStates) + return this + } + fun assertStates(expected: List): ViewModelTest { states.assertValues(expected) return this diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt index e1a605c7df..10daf5de1e 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt @@ -23,10 +23,15 @@ import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.registration.RegistrationWizard class FakeAuthenticationService : AuthenticationService by mockk() { + fun givenRegistrationWizard(registrationWizard: RegistrationWizard) { every { getRegistrationWizard() } returns registrationWizard } + fun givenRegistrationStarted(started: Boolean) { + every { isRegistrationStarted } returns started + } + fun expectReset() { coJustRun { reset() } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeRegisterActionHandler.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeRegisterActionHandler.kt new file mode 100644 index 0000000000..8d595d91e9 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeRegisterActionHandler.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.test.fakes + +import im.vector.app.features.onboarding.RegisterAction +import im.vector.app.features.onboarding.RegistrationActionHandler +import io.mockk.coEvery +import io.mockk.mockk +import org.matrix.android.sdk.api.auth.registration.RegistrationResult +import org.matrix.android.sdk.api.auth.registration.RegistrationWizard + +class FakeRegisterActionHandler { + + val instance = mockk() + + fun givenResultsFor(wizard: RegistrationWizard, result: List>) { + coEvery { instance.handleRegisterAction(wizard, any()) } answers { call -> + val actionArg = call.invocation.args[1] as RegisterAction + result.first { it.first == actionArg }.second + } + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeRegistrationWizard.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeRegistrationWizard.kt index 6ae394eea1..4e6e511abb 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeRegistrationWizard.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeRegistrationWizard.kt @@ -22,9 +22,9 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationResult import org.matrix.android.sdk.api.auth.registration.RegistrationWizard import org.matrix.android.sdk.api.session.Session -class FakeRegistrationWizard : RegistrationWizard by mockk() { +class FakeRegistrationWizard : RegistrationWizard by mockk(relaxed = false) { - fun givenSuccessfulDummy(session: Session) { - coEvery { dummy() } returns RegistrationResult.Success(session) + fun givenSuccessFor(result: Session, expect: suspend RegistrationWizard.() -> RegistrationResult) { + coEvery { expect(this@FakeRegistrationWizard) } returns RegistrationResult.Success(result) } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt index 265941a531..b6e06bcdda 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorFeatures.kt @@ -23,5 +23,5 @@ class FakeVectorFeatures : VectorFeatures { override fun isOnboardingAlreadyHaveAccountSplashEnabled() = true override fun isOnboardingSplashCarouselEnabled() = true override fun isOnboardingUseCaseEnabled() = true - override fun isOnboardingPersonalizeEnabled() = false + override fun isOnboardingPersonalizeEnabled() = true } diff --git a/vector/src/test/java/im/vector/app/test/fixtures/HomeserverCapabilityFixture.kt b/vector/src/test/java/im/vector/app/test/fixtures/HomeserverCapabilityFixture.kt new file mode 100644 index 0000000000..a4d9869a89 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fixtures/HomeserverCapabilityFixture.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.test.fixtures + +import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities +import org.matrix.android.sdk.api.session.homeserver.RoomVersionCapabilities + +fun aHomeServerCapabilities( + canChangePassword: Boolean = true, + canChangeDisplayName: Boolean = true, + canChangeAvatar: Boolean = true, + canChange3pid: Boolean = true, + maxUploadFileSize: Long = 100L, + lastVersionIdentityServerSupported: Boolean = false, + defaultIdentityServerUrl: String? = null, + roomVersions: RoomVersionCapabilities? = null +) = HomeServerCapabilities( + canChangePassword, + canChangeDisplayName, + canChangeAvatar, + canChange3pid, + maxUploadFileSize, + lastVersionIdentityServerSupported, + defaultIdentityServerUrl, + roomVersions +)