PM-15036 Show visual feedback for the send code on export vault. (#4346)

This commit is contained in:
Dave Severns
2024-11-21 15:25:44 +00:00
committed by GitHub
parent cbfd7ad1b1
commit dbeb00ba1c
2 changed files with 89 additions and 11 deletions
@@ -8,6 +8,7 @@ import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.model.PasswordStrengthResult
import com.x8bit.bitwarden.data.auth.repository.model.RequestOtpResult
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
import com.x8bit.bitwarden.data.auth.repository.model.VerifyOtpResult
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
@@ -116,9 +117,24 @@ class ExportVaultViewModel @Inject constructor(
is ExportVaultAction.Internal.ReceiveVerifyOneTimePasscodeResult -> {
handleReceiveVerifyOneTimePasscodeResult(action)
}
is ExportVaultAction.Internal.OtpCodeResult -> handleOtpCodeResult(action)
}
}
private fun handleOtpCodeResult(action: ExportVaultAction.Internal.OtpCodeResult) {
mutableStateFlow.update {
it.copy(dialogState = null)
}
val toastMessage = when (val result = action.result) {
is RequestOtpResult.Error -> {
result.message?.asText() ?: R.string.generic_error_message.asText()
}
RequestOtpResult.Success -> R.string.code_sent.asText()
}
sendEvent(ExportVaultEvent.ShowToast(message = toastMessage))
}
/**
* Dismiss the view.
*/
@@ -267,8 +283,19 @@ class ExportVaultViewModel @Inject constructor(
}
private fun handleSendCodeClick() {
mutableStateFlow.update {
it.copy(
dialogState = ExportVaultState.DialogState.Loading(
message = R.string.sending.asText(),
),
)
}
viewModelScope.launch {
authRepository.requestOneTimePasscode()
sendAction(
ExportVaultAction.Internal.OtpCodeResult(
result = authRepository.requestOneTimePasscode(),
),
)
}
}
@@ -572,5 +599,10 @@ sealed class ExportVaultAction {
data class ReceiveVerifyOneTimePasscodeResult(
val result: VerifyOtpResult,
) : Internal()
/**
* Indicates that a result for requesting the one-time passcode has been received.
*/
data class OtpCodeResult(val result: RequestOtpResult) : Internal()
}
}
@@ -38,6 +38,7 @@ import java.time.Clock
import java.time.Instant
import java.time.ZoneOffset
@Suppress("LargeClass")
class ExportVaultViewModelTest : BaseViewModelTest() {
private val mutableUserStateFlow = MutableStateFlow<UserState?>(DEFAULT_USER_STATE)
private val authRepository: AuthRepository = mockk {
@@ -479,18 +480,63 @@ class ExportVaultViewModelTest : BaseViewModelTest() {
)
}
@Suppress("MaxLineLength")
@Test
fun `SendCodeClick should call requestOneTimePasscode`() {
val viewModel = createViewModel()
coEvery { authRepository.requestOneTimePasscode() } returns RequestOtpResult.Success
viewModel.trySendAction(ExportVaultAction.SendCodeClick)
fun `SendCodeClick should call requestOneTimePasscode and update dialog state to sending then back to null when request completes and send correct event on success`() =
runTest {
val viewModel = createViewModel()
coEvery { authRepository.requestOneTimePasscode() } returns RequestOtpResult.Success
viewModel.stateEventFlow(backgroundScope) { stateTurbine, eventTurbine ->
assertEquals(DEFAULT_STATE, stateTurbine.awaitItem())
viewModel.trySendAction(ExportVaultAction.SendCodeClick)
assertEquals(
DEFAULT_STATE.copy(
dialogState = ExportVaultState.DialogState.Loading(
message = R.string.sending.asText(),
),
),
stateTurbine.awaitItem(),
)
assertEquals(DEFAULT_STATE, stateTurbine.awaitItem())
assertEquals(
ExportVaultEvent.ShowToast(
message = R.string.code_sent.asText(),
),
eventTurbine.awaitItem(),
)
}
coVerify { authRepository.requestOneTimePasscode() }
}
assertEquals(
DEFAULT_STATE,
viewModel.stateFlow.value,
)
coVerify { authRepository.requestOneTimePasscode() }
}
@Suppress("MaxLineLength")
@Test
fun `SendCodeClick should call requestOneTimePasscode and update dialog state to sending then back to null when request completes and send correct event on error`() =
runTest {
val viewModel = createViewModel()
coEvery {
authRepository.requestOneTimePasscode()
} returns RequestOtpResult.Error(message = null)
viewModel.stateEventFlow(backgroundScope) { stateTurbine, eventTurbine ->
assertEquals(DEFAULT_STATE, stateTurbine.awaitItem())
viewModel.trySendAction(ExportVaultAction.SendCodeClick)
assertEquals(
DEFAULT_STATE.copy(
dialogState = ExportVaultState.DialogState.Loading(
message = R.string.sending.asText(),
),
),
stateTurbine.awaitItem(),
)
assertEquals(DEFAULT_STATE, stateTurbine.awaitItem())
assertEquals(
ExportVaultEvent.ShowToast(
message = R.string.generic_error_message.asText(),
),
eventTurbine.awaitItem(),
)
}
coVerify { authRepository.requestOneTimePasscode() }
}
@Test
fun `ReceiveExportVaultDataToStringResult should update state to error if result is error`() {