Fix incorrect VM state access & unnecessary eventFlow usages (#1218)

This commit is contained in:
Caleb Derosier 2024-04-03 13:36:57 -06:00 committed by Álison Fernandes
parent ab55b5e535
commit 87254b4436
13 changed files with 381 additions and 430 deletions

View file

@ -191,7 +191,7 @@ class CreateAccountViewModel @Inject constructor(
mutableStateFlow.update { it.copy(dialog = null) }
sendEvent(
CreateAccountEvent.NavigateToLogin(
email = mutableStateFlow.value.emailInput,
email = state.emailInput,
captchaToken = registerAccountResult.captchaToken,
),
)
@ -251,7 +251,7 @@ class CreateAccountViewModel @Inject constructor(
} else {
passwordStrengthJob = viewModelScope.launch {
val result = authRepository.getPasswordStrength(
email = mutableStateFlow.value.emailInput,
email = state.emailInput,
password = action.input,
)
trySendAction(ReceivePasswordStrengthResult(result))
@ -264,7 +264,7 @@ class CreateAccountViewModel @Inject constructor(
}
private fun handleSubmitClick() = when {
mutableStateFlow.value.emailInput.isBlank() -> {
state.emailInput.isBlank() -> {
val dialog = BasicDialogState.Shown(
title = R.string.an_error_has_occurred.asText(),
message = R.string.validation_field_required
@ -273,7 +273,7 @@ class CreateAccountViewModel @Inject constructor(
mutableStateFlow.update { it.copy(dialog = CreateAccountDialog.Error(dialog)) }
}
!mutableStateFlow.value.emailInput.isValidEmail() -> {
!state.emailInput.isValidEmail() -> {
val dialog = BasicDialogState.Shown(
title = R.string.an_error_has_occurred.asText(),
message = R.string.invalid_email.asText(),
@ -281,7 +281,7 @@ class CreateAccountViewModel @Inject constructor(
mutableStateFlow.update { it.copy(dialog = CreateAccountDialog.Error(dialog)) }
}
mutableStateFlow.value.passwordInput.length < MIN_PASSWORD_LENGTH -> {
state.passwordInput.length < MIN_PASSWORD_LENGTH -> {
val dialog = BasicDialogState.Shown(
title = R.string.an_error_has_occurred.asText(),
message = R.string.master_password_length_val_message_x.asText(MIN_PASSWORD_LENGTH),
@ -289,7 +289,7 @@ class CreateAccountViewModel @Inject constructor(
mutableStateFlow.update { it.copy(dialog = CreateAccountDialog.Error(dialog)) }
}
mutableStateFlow.value.passwordInput != mutableStateFlow.value.confirmPasswordInput -> {
state.passwordInput != state.confirmPasswordInput -> {
val dialog = BasicDialogState.Shown(
title = R.string.an_error_has_occurred.asText(),
message = R.string.master_password_confirmation_val_message.asText(),
@ -297,7 +297,7 @@ class CreateAccountViewModel @Inject constructor(
mutableStateFlow.update { it.copy(dialog = CreateAccountDialog.Error(dialog)) }
}
!mutableStateFlow.value.isAcceptPoliciesToggled -> {
!state.isAcceptPoliciesToggled -> {
val dialog = BasicDialogState.Shown(
title = R.string.an_error_has_occurred.asText(),
message = R.string.accept_policies_error.asText(),
@ -307,7 +307,7 @@ class CreateAccountViewModel @Inject constructor(
else -> {
submitRegisterAccountRequest(
shouldCheckForDataBreaches = mutableStateFlow.value.isCheckDataBreachesToggled,
shouldCheckForDataBreaches = state.isCheckDataBreachesToggled,
captchaToken = null,
)
}
@ -327,9 +327,9 @@ class CreateAccountViewModel @Inject constructor(
viewModelScope.launch {
val result = authRepository.register(
shouldCheckDataBreaches = shouldCheckForDataBreaches,
email = mutableStateFlow.value.emailInput,
masterPassword = mutableStateFlow.value.passwordInput,
masterPasswordHint = mutableStateFlow.value.passwordHintInput.ifBlank { null },
email = state.emailInput,
masterPassword = state.passwordInput,
masterPasswordHint = state.passwordHintInput.ifBlank { null },
captchaToken = captchaToken,
)
sendAction(

View file

@ -72,8 +72,6 @@ class EnvironmentViewModel @Inject constructor(
}
private fun handleSaveClickAction() {
val state = mutableStateFlow.value
val urlsAreAllNullOrValid = listOf(
state.serverUrl,
state.webVaultServerUrl,

View file

@ -128,7 +128,7 @@ class LandingViewModel @Inject constructor(
}
private fun handleContinueButtonClicked() {
if (!mutableStateFlow.value.emailInput.isValidEmail()) {
if (!state.emailInput.isValidEmail()) {
mutableStateFlow.update {
it.copy(
dialog = LandingState.DialogState.Error(
@ -150,8 +150,8 @@ class LandingViewModel @Inject constructor(
return
}
val email = mutableStateFlow.value.emailInput
val isRememberMeEnabled = mutableStateFlow.value.isRememberMeEnabled
val email = state.emailInput
val isRememberMeEnabled = state.isRememberMeEnabled
// Update the remembered email address
authRepository.rememberedEmailAddress = email.takeUnless { !isRememberMeEnabled }

View file

@ -232,9 +232,9 @@ class LoginViewModel @Inject constructor(
}
viewModelScope.launch {
val result = authRepository.login(
email = mutableStateFlow.value.emailAddress,
password = mutableStateFlow.value.passwordInput,
captchaToken = mutableStateFlow.value.captchaToken,
email = state.emailAddress,
password = state.passwordInput,
captchaToken = state.captchaToken,
)
sendAction(
LoginAction.Internal.ReceiveLoginResult(
@ -245,7 +245,7 @@ class LoginViewModel @Inject constructor(
}
private fun handleMasterPasswordHintClicked() {
val email = mutableStateFlow.value.emailAddress
val email = state.emailAddress
sendEvent(LoginEvent.NavigateToMasterPasswordHint(email))
}
@ -254,7 +254,7 @@ class LoginViewModel @Inject constructor(
}
private fun handleSingleSignOnClicked() {
val email = mutableStateFlow.value.emailAddress
val email = state.emailAddress
sendEvent(LoginEvent.NavigateToEnterpriseSignOn(email))
}

View file

@ -160,13 +160,13 @@ class VaultUnlockViewModel @Inject constructor(
val vaultUnlockResult = when (state.vaultUnlockType) {
VaultUnlockType.MASTER_PASSWORD -> {
vaultRepo.unlockVaultWithMasterPassword(
mutableStateFlow.value.input,
state.input,
)
}
VaultUnlockType.PIN -> {
vaultRepo.unlockVaultWithPin(
mutableStateFlow.value.input,
state.input,
)
}
}

View file

@ -97,9 +97,9 @@ class LoginApprovalViewModel @Inject constructor(
trySendAction(
LoginApprovalAction.Internal.ApproveRequestResultReceive(
result = authRepository.updateAuthRequest(
requestId = mutableStateFlow.value.requestId,
masterPasswordHash = mutableStateFlow.value.masterPasswordHash,
publicKey = mutableStateFlow.value.publicKey,
requestId = state.requestId,
masterPasswordHash = state.masterPasswordHash,
publicKey = state.publicKey,
isApproved = true,
),
),
@ -116,9 +116,9 @@ class LoginApprovalViewModel @Inject constructor(
trySendAction(
LoginApprovalAction.Internal.DeclineRequestResultReceive(
result = authRepository.updateAuthRequest(
requestId = mutableStateFlow.value.requestId,
masterPasswordHash = mutableStateFlow.value.masterPasswordHash,
publicKey = mutableStateFlow.value.publicKey,
requestId = state.requestId,
masterPasswordHash = state.masterPasswordHash,
publicKey = state.publicKey,
isApproved = false,
),
),

View file

@ -76,7 +76,7 @@ class PendingRequestsViewModel @Inject constructor(
viewState = PendingRequestsState.ViewState.Loading,
)
}
mutableStateFlow.value.authRequests.forEach { request ->
state.authRequests.forEach { request ->
authRepository.updateAuthRequest(
requestId = request.id,
masterPasswordHash = request.masterPasswordHash,

View file

@ -121,7 +121,7 @@ class ExportVaultViewModel @Inject constructor(
@Suppress("ReturnCount")
private fun handleConfirmExportVaultClicked() {
// Display an error alert if the user hasn't entered a password.
if (mutableStateFlow.value.passwordInput.isBlank()) {
if (state.passwordInput.isBlank()) {
updateStateWithError(
message = R.string.validation_field_required.asText(
R.string.master_password.asText(),
@ -149,7 +149,7 @@ class ExportVaultViewModel @Inject constructor(
sendAction(
ExportVaultAction.Internal.ReceiveValidatePasswordResult(
result = authRepository.validatePassword(
password = mutableStateFlow.value.passwordInput,
password = state.passwordInput,
),
),
)

View file

@ -550,7 +550,7 @@ class GeneratorViewModel @Inject constructor(
private fun handleRegenerationClick() {
// Go through the update process with the current state to trigger a
// regeneration of the generated text for the same state.
updateGeneratorMainType(forceRegeneration = true) { mutableStateFlow.value.selectedType }
updateGeneratorMainType(forceRegeneration = true) { state.selectedType }
}
private fun handleCopyClick() {
@ -1294,7 +1294,7 @@ class GeneratorViewModel @Inject constructor(
forceRegeneration: Boolean = false,
crossinline block: (GeneratorState.MainType) -> GeneratorState.MainType?,
) {
val currentSelectedType = mutableStateFlow.value.selectedType
val currentSelectedType = state.selectedType
val updatedMainType = block(currentSelectedType) ?: return
mutableStateFlow.update { it.copy(selectedType = updatedMainType) }

View file

@ -434,7 +434,7 @@ class VaultAddEditViewModel @Inject constructor(
when (action.customFieldAction) {
CustomFieldAction.MOVE_UP -> {
val items =
(mutableStateFlow.value.viewState as VaultAddEditState.ViewState.Content)
(state.viewState as VaultAddEditState.ViewState.Content)
.common
.customFieldData
.toMutableList()
@ -455,7 +455,7 @@ class VaultAddEditViewModel @Inject constructor(
CustomFieldAction.MOVE_DOWN -> {
val items =
(mutableStateFlow.value.viewState as VaultAddEditState.ViewState.Content)
(state.viewState as VaultAddEditState.ViewState.Content)
.common
.customFieldData
.toMutableList()

View file

@ -42,10 +42,8 @@ class ResetPasswordViewModelTest : BaseViewModelTest() {
}
@Test
fun `CurrentPasswordInputChanged should update the current password input in the state`() =
runTest {
fun `CurrentPasswordInputChanged should update the current password input in the state`() {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(ResetPasswordAction.CurrentPasswordInputChanged("Test123"))
assertEquals(
@ -55,12 +53,10 @@ class ResetPasswordViewModelTest : BaseViewModelTest() {
viewModel.stateFlow.value,
)
}
}
@Test
fun `SubmitClicked with blank password shows error alert`() = runTest {
fun `SubmitClicked with blank password shows error alert`() {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(ResetPasswordAction.SubmitClick)
assertEquals(
@ -81,11 +77,9 @@ class ResetPasswordViewModelTest : BaseViewModelTest() {
viewModel.stateFlow.value,
)
}
}
@Test
fun `SubmitClicked with invalid password shows error alert for weak password reason`() =
runTest {
fun `SubmitClicked with invalid password shows error alert for weak password reason`() {
val password = "Test123"
coEvery {
authRepository.validatePasswordAgainstPolicies(password)
@ -93,7 +87,6 @@ class ResetPasswordViewModelTest : BaseViewModelTest() {
val viewModel = createViewModel()
viewModel.trySendAction(ResetPasswordAction.PasswordInputChanged(password))
viewModel.eventFlow.test {
viewModel.trySendAction(ResetPasswordAction.SubmitClick)
assertEquals(
@ -107,10 +100,9 @@ class ResetPasswordViewModelTest : BaseViewModelTest() {
viewModel.stateFlow.value,
)
}
}
@Test
fun `SubmitClicked with invalid password shows error alert for admin reset reason`() = runTest {
fun `SubmitClicked with invalid password shows error alert for admin reset reason`() {
val password = "Test123"
every {
authRepository.passwordResetReason
@ -118,7 +110,6 @@ class ResetPasswordViewModelTest : BaseViewModelTest() {
val viewModel = createViewModel()
viewModel.trySendAction(ResetPasswordAction.PasswordInputChanged(password))
viewModel.eventFlow.test {
viewModel.trySendAction(ResetPasswordAction.SubmitClick)
assertEquals(
@ -134,10 +125,9 @@ class ResetPasswordViewModelTest : BaseViewModelTest() {
viewModel.stateFlow.value,
)
}
}
@Test
fun `SubmitClicked with non-matching retyped password shows error alert`() = runTest {
fun `SubmitClicked with non-matching retyped password shows error alert`() {
val password = "Test123"
coEvery {
authRepository.validatePasswordAgainstPolicies(password)
@ -146,7 +136,6 @@ class ResetPasswordViewModelTest : BaseViewModelTest() {
val viewModel = createViewModel()
viewModel.trySendAction(ResetPasswordAction.PasswordInputChanged(password))
viewModel.eventFlow.test {
viewModel.trySendAction(ResetPasswordAction.SubmitClick)
assertEquals(
@ -160,10 +149,9 @@ class ResetPasswordViewModelTest : BaseViewModelTest() {
viewModel.stateFlow.value,
)
}
}
@Test
fun `SubmitClicked with error for validating current password shows error alert`() = runTest {
fun `SubmitClicked with error for validating current password shows error alert`() {
val currentPassword = "CurrentTest123"
val password = "Test123"
coEvery {
@ -178,7 +166,6 @@ class ResetPasswordViewModelTest : BaseViewModelTest() {
viewModel.trySendAction(ResetPasswordAction.PasswordInputChanged(password))
viewModel.trySendAction(ResetPasswordAction.RetypePasswordInputChanged(password))
viewModel.eventFlow.test {
viewModel.trySendAction(ResetPasswordAction.SubmitClick)
assertEquals(
@ -194,10 +181,9 @@ class ResetPasswordViewModelTest : BaseViewModelTest() {
viewModel.stateFlow.value,
)
}
}
@Test
fun `SubmitClicked with invalid current password shows alert`() = runTest {
fun `SubmitClicked with invalid current password shows alert`() {
val currentPassword = "CurrentTest123"
val password = "Test123"
coEvery {
@ -212,7 +198,6 @@ class ResetPasswordViewModelTest : BaseViewModelTest() {
viewModel.trySendAction(ResetPasswordAction.PasswordInputChanged(password))
viewModel.trySendAction(ResetPasswordAction.RetypePasswordInputChanged(password))
viewModel.eventFlow.test {
viewModel.trySendAction(ResetPasswordAction.SubmitClick)
assertEquals(
@ -228,7 +213,6 @@ class ResetPasswordViewModelTest : BaseViewModelTest() {
viewModel.stateFlow.value,
)
}
}
@Test
fun `SubmitClicked with all valid inputs resets password`() = runTest {
@ -289,9 +273,8 @@ class ResetPasswordViewModelTest : BaseViewModelTest() {
}
@Test
fun `PasswordInputChanged should update the password input in the state`() = runTest {
fun `PasswordInputChanged should update the password input in the state`() {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(ResetPasswordAction.PasswordInputChanged("Test123"))
assertEquals(
@ -301,13 +284,10 @@ class ResetPasswordViewModelTest : BaseViewModelTest() {
viewModel.stateFlow.value,
)
}
}
@Test
fun `RetypePasswordInputChanged should update the retype password input in the state`() =
runTest {
fun `RetypePasswordInputChanged should update the retype password input in the state`() {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(ResetPasswordAction.RetypePasswordInputChanged("Test123"))
assertEquals(
@ -317,12 +297,10 @@ class ResetPasswordViewModelTest : BaseViewModelTest() {
viewModel.stateFlow.value,
)
}
}
@Test
fun `PasswordHintInputChanged should update the password hint input in the state`() = runTest {
fun `PasswordHintInputChanged should update the password hint input in the state`() {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(ResetPasswordAction.PasswordHintInputChanged("Test123"))
assertEquals(
@ -332,7 +310,6 @@ class ResetPasswordViewModelTest : BaseViewModelTest() {
viewModel.stateFlow.value,
)
}
}
private fun createViewModel(): ResetPasswordViewModel =
ResetPasswordViewModel(

View file

@ -162,7 +162,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
runTest {
val input = "123456"
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(TwoFactorLoginAction.CodeInputChanged(input))
assertEquals(
DEFAULT_STATE.copy(
@ -172,13 +171,11 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
viewModel.stateFlow.value,
)
}
}
@Test
fun `CodeInputChanged should update input and disable button if code is blank`() = runTest {
fun `CodeInputChanged should update input and disable button if code is blank`() {
val input = "123456"
val viewModel = createViewModel()
viewModel.eventFlow.test {
// Set it to true.
viewModel.trySendAction(TwoFactorLoginAction.CodeInputChanged(input))
assertEquals(
@ -199,7 +196,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
viewModel.stateFlow.value,
)
}
}
@Test
fun `ContinueButtonClick login returns success should update loadingDialogState`() = runTest {
@ -415,9 +411,8 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
}
@Test
fun `RememberMeToggle should update the state`() = runTest {
fun `RememberMeToggle should update the state`() {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(TwoFactorLoginAction.RememberMeToggle(true))
assertEquals(
DEFAULT_STATE.copy(
@ -426,7 +421,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
viewModel.stateFlow.value,
)
}
}
@Test
fun `ResendEmailClick returns success should emit ShowToast`() = runTest {
@ -534,9 +528,8 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
}
@Test
fun `SelectAuthMethod with other method should update the state`() = runTest {
fun `SelectAuthMethod with other method should update the state`() {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(
TwoFactorLoginAction.SelectAuthMethod(
TwoFactorAuthMethod.AUTHENTICATOR_APP,
@ -549,7 +542,6 @@ class TwoFactorLoginViewModelTest : BaseViewModelTest() {
viewModel.stateFlow.value,
)
}
}
@Test
@Suppress("MaxLineLength")

View file

@ -86,8 +86,7 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
}
@Test
fun `ConfirmExportVaultClicked correct password should call exportVaultDataToString`() =
runTest {
fun `ConfirmExportVaultClicked correct password should call exportVaultDataToString`() {
val password = "password"
coEvery {
authRepository.validatePassword(
@ -96,7 +95,6 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
} returns ValidatePasswordResult.Success(isValid = true)
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(ExportVaultAction.PasswordInputChanged(password))
viewModel.trySendAction(ExportVaultAction.ConfirmExportVaultClicked)
@ -105,12 +103,10 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
vaultRepository.exportVaultDataToString(any())
}
}
}
@Test
fun `ConfirmExportVaultClicked blank password should show an error`() = runTest {
fun `ConfirmExportVaultClicked blank password should show an error`() {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(ExportVaultAction.ConfirmExportVaultClicked)
assertEquals(
DEFAULT_STATE.copy(
@ -130,12 +126,10 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
viewModel.stateFlow.value,
)
}
}
@Suppress("MaxLineLength")
@Test
fun `ConfirmExportVaultClicked blank file password should show an error when export type is JSON_ENCRYPTED`() =
runTest {
fun `ConfirmExportVaultClicked blank file password should show an error when export type is JSON_ENCRYPTED`() {
val password = "password"
val viewModel = createViewModel()
viewModel.trySendAction(
@ -169,8 +163,7 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
@Suppress("MaxLineLength")
@Test
fun `ConfirmExportVaultClicked blank confirm file password should show an error when export type is JSON_ENCRYPTED`() =
runTest {
fun `ConfirmExportVaultClicked blank confirm file password should show an error when export type is JSON_ENCRYPTED`() {
val password = "password"
coEvery {
authRepository.getPasswordStrength(
@ -216,7 +209,7 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
}
@Test
fun `ConfirmExportVaultClicked invalid password should show an error`() = runTest {
fun `ConfirmExportVaultClicked invalid password should show an error`() {
val password = "password"
coEvery {
authRepository.validatePassword(
@ -225,7 +218,6 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
} returns ValidatePasswordResult.Success(isValid = false)
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(ExportVaultAction.PasswordInputChanged(password))
viewModel.trySendAction(ExportVaultAction.ConfirmExportVaultClicked)
@ -239,7 +231,6 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
),
viewModel.stateFlow.value,
)
}
coVerify {
authRepository.validatePassword(
password = password,
@ -248,7 +239,7 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
}
@Test
fun `ConfirmExportVaultClicked error checking password should show an error`() = runTest {
fun `ConfirmExportVaultClicked error checking password should show an error`() {
val password = "password"
coEvery {
authRepository.validatePassword(
@ -257,7 +248,6 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
} returns ValidatePasswordResult.Error
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(ExportVaultAction.PasswordInputChanged(password))
viewModel.trySendAction(ExportVaultAction.ConfirmExportVaultClicked)
@ -272,13 +262,10 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
viewModel.stateFlow.value,
)
}
}
@Test
fun `ExportFormatOptionSelect should update the selected export format in the state`() =
runTest {
fun `ExportFormatOptionSelect should update the selected export format in the state`() {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(
ExportVaultAction.ExportFormatOptionSelect(ExportVaultFormat.CSV),
)
@ -290,7 +277,6 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
viewModel.stateFlow.value,
)
}
}
@Test
fun `ConfirmFilePasswordInputChanged should update the confirm password input in the state`() {
@ -348,8 +334,7 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
}
@Test
fun `ReceiveExportVaultDataToStringResult should update state to error if result is error`() =
runTest {
fun `ReceiveExportVaultDataToStringResult should update state to error if result is error`() {
val viewModel = createViewModel()
viewModel.trySendAction(
@ -493,8 +478,7 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
}
@Test
fun `ExportLocationReceive should update state to error if saving the data fails`() =
runTest {
fun `ExportLocationReceive should update state to error if saving the data fails`() {
val exportData = "TestExportVaultData"
val viewModel = createViewModel(
DEFAULT_STATE.copy(