-
Notifications
You must be signed in to change notification settings - Fork 907
FTUE - Onboarding registration steps unit tests #5408
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 12 commits
c15e908
4225f62
3fa4150
434ee67
b2a1aa1
75cbb72
804513c
390ae43
694016f
fc5c057
fe206fe
3d20d46
d77061b
5df2ae9
d514751
ba76aac
192d1c4
abf62af
7f943d3
ce2c309
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Improved onboarding registration unit test coverage |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 -> currentJob = null | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you think we could add a comment to explain what type of
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as part of the registration flow it's cancelling the wait for email verification step (when we send an email and wait for the user to click the verification link in their email client), however like you mentioned the viewmodel reuses the same for some extra context
private var currentJob: Job? = null
set(value) {
// Cancel any previous Job
field?.cancel()
field = value
}The email validation waiting is tied to the pause/resume lifecycle override fun onResume() {
viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.CheckIfEmailHasBeenValidated(0)))
}
override fun onPause() {
viewModel.handle(OnboardingAction.StopEmailValidationCheck)
}I would prefer to avoid adding a comment if a suitable method could be extracted, how do you feel about... OnboardingAction.StopEmailValidationCheck -> cancelWaitForEmailValidation()
fun cancelWaitForEmailValidation() {
currentJob = null
}
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes okay it is clear with a new private method, no need for comments in that case. |
||
| }.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()) } | ||
| currentJob = viewModelScope.launch { | ||
| try { | ||
| registrationWizard?.addThreePid(action.threePid) | ||
| } catch (failure: Throwable) { | ||
| _viewEvents.post(OnboardingViewEvents.Failure(failure)) | ||
| } | ||
| setState { | ||
| copy( | ||
| asyncRegistration = Uninitialized | ||
| ) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private fun handleSendAgainThreePid() { | ||
| setState { copy(asyncRegistration = Loading()) } | ||
| private fun handleRegisterAction(action: RegisterAction) { | ||
| 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() | ||
| if (action.hasLoadingState()) { | ||
| setState { copy(asyncRegistration = Loading()) } | ||
| } | ||
| kotlin.runCatching { registrationActionHandler.handleRegisterAction(registrationWizard, action) } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we can remove the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good catch, will do 👍 |
||
| .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,15 +651,18 @@ 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 | ||
| _viewEvents.post(OnboardingViewEvents.RegistrationFlowResult(flowResult, isRegistrationStarted)) | ||
| } | ||
| } | ||
|
|
||
| private fun handleRegisterDummy() { | ||
| handleRegisterAction(RegisterAction.RegisterDummy) | ||
| } | ||
|
|
||
| private suspend fun onSessionCreated(session: Session, isAccountCreated: Boolean) { | ||
| val state = awaitState() | ||
| state.useCase?.let { useCase -> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we try to use sealed interface as well for
ResetAction? Like this:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah! makes sense since I'm in the area 👍