mirror of
https://github.com/element-hq/element-android
synced 2024-11-23 18:05:36 +03:00
Merge pull request #5408 from vector-im/feature/adm/onboarding-tests
FTUE - Onboarding registration steps unit tests
This commit is contained in:
commit
ea9c9ae490
16 changed files with 490 additions and 239 deletions
1
changelog.d/5408.misc
Normal file
1
changelog.d/5408.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Improved onboarding registration unit test coverage
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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<OnboardingViewState, OnboardingAction, OnboardingViewEvents>(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 {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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.() -> OnboardingViewState> {
|
||||
return listOf(
|
||||
{ copy(asyncDisplayName = Loading()) },
|
||||
{ copy(asyncDisplayName = Success(Unit), personalizationState = personalizationState.copy(displayName = A_DISPLAY_NAME)) }
|
||||
)
|
||||
}
|
||||
|
||||
private fun givenSuccessfulRegistrationForStartAndDummySteps(missingStages: List<Stage>) {
|
||||
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<OnboardingViewState> {
|
||||
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<Pair<RegisterAction, RegistrationResult>>) {
|
||||
fakeAuthenticationService.givenRegistrationStarted(true)
|
||||
val registrationWizard = FakeRegistrationWizard()
|
||||
fakeAuthenticationService.givenRegistrationWizard(registrationWizard)
|
||||
fakeRegisterActionHandler.givenResultsFor(registrationWizard, results)
|
||||
}
|
||||
}
|
||||
|
||||
private fun HomeServerCapabilities.toPersonalisationState() = PersonalizationState(
|
||||
supportsChangingDisplayName = canChangeDisplayName,
|
||||
supportsChangingProfilePicture = canChangeAvatar
|
||||
)
|
||||
|
|
|
@ -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)
|
|
@ -55,6 +55,25 @@ class ViewModelTest<S, VE>(
|
|||
return this
|
||||
}
|
||||
|
||||
fun assertStatesChanges(initial: S, vararg expected: S.() -> S): ViewModelTest<S, VE> {
|
||||
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.() -> S>): ViewModelTest<S, VE> {
|
||||
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<S>): ViewModelTest<S, VE> {
|
||||
states.assertValues(expected)
|
||||
return this
|
||||
|
|
|
@ -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() }
|
||||
}
|
||||
|
|
|
@ -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<RegistrationActionHandler>()
|
||||
|
||||
fun givenResultsFor(wizard: RegistrationWizard, result: List<Pair<RegisterAction, RegistrationResult>>) {
|
||||
coEvery { instance.handleRegisterAction(wizard, any()) } answers { call ->
|
||||
val actionArg = call.invocation.args[1] as RegisterAction
|
||||
result.first { it.first == actionArg }.second
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
40
vector/src/test/java/im/vector/app/test/fixtures/HomeserverCapabilityFixture.kt
vendored
Normal file
40
vector/src/test/java/im/vector/app/test/fixtures/HomeserverCapabilityFixture.kt
vendored
Normal file
|
@ -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
|
||||
)
|
Loading…
Reference in a new issue