mirror of
https://github.com/bitwarden/android.git
synced 2024-11-21 08:55:48 +03:00
PM-13019: Add special circumstance to navigate to the vault listing UI for TOTP code (#4033)
This commit is contained in:
parent
8d578a9b57
commit
c4467f0cba
10 changed files with 201 additions and 5 deletions
|
@ -32,6 +32,7 @@ import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppThem
|
|||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import com.x8bit.bitwarden.ui.platform.util.isMyVaultShortcut
|
||||
import com.x8bit.bitwarden.ui.platform.util.isPasswordGeneratorShortcut
|
||||
import com.x8bit.bitwarden.ui.vault.util.getTotpDataOrNull
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
|
@ -232,6 +233,7 @@ class MainViewModel @Inject constructor(
|
|||
val autofillSaveItem = intent.getAutofillSaveItemOrNull()
|
||||
val autofillSelectionData = intent.getAutofillSelectionDataOrNull()
|
||||
val shareData = intentManager.getShareDataFromIntent(intent)
|
||||
val totpData = intent.getTotpDataOrNull()
|
||||
val hasGeneratorShortcut = intent.isPasswordGeneratorShortcut
|
||||
val hasVaultShortcut = intent.isMyVaultShortcut
|
||||
val fido2CredentialRequestData = intent.getFido2CredentialRequestOrNull()
|
||||
|
@ -270,6 +272,11 @@ class MainViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
totpData != null -> {
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.AddTotpLoginItem(data = totpData)
|
||||
}
|
||||
|
||||
shareData != null -> {
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.ShareNewSend(
|
||||
|
|
|
@ -7,6 +7,7 @@ import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2GetCredentialsRequest
|
|||
import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import com.x8bit.bitwarden.ui.vault.model.TotpData
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
|
@ -14,6 +15,14 @@ import kotlinx.parcelize.Parcelize
|
|||
* of navigation that is counter to what otherwise may happen based on the state of the app.
|
||||
*/
|
||||
sealed class SpecialCircumstance : Parcelable {
|
||||
/**
|
||||
* The app was launched in order to add a new TOTP to a cipher.
|
||||
*/
|
||||
@Parcelize
|
||||
data class AddTotpLoginItem(
|
||||
val data: TotpData,
|
||||
) : SpecialCircumstance()
|
||||
|
||||
/**
|
||||
* The app was launched in order to create/share a new Send using the given [data].
|
||||
*/
|
||||
|
|
|
@ -6,6 +6,7 @@ import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2GetCredentialsRequest
|
|||
import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
import com.x8bit.bitwarden.ui.vault.model.TotpData
|
||||
|
||||
/**
|
||||
* Returns [AutofillSaveItem] when contained in the given [SpecialCircumstance].
|
||||
|
@ -51,3 +52,12 @@ fun SpecialCircumstance.toFido2GetCredentialsRequestOrNull(): Fido2GetCredential
|
|||
is SpecialCircumstance.Fido2GetCredentials -> this.fido2GetCredentialsRequest
|
||||
else -> null
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the [TotpData] when contained in the given [SpecialCircumstance].
|
||||
*/
|
||||
fun SpecialCircumstance.toTotpDataOrNull(): TotpData? =
|
||||
when (this) {
|
||||
is SpecialCircumstance.AddTotpLoginItem -> this.data
|
||||
else -> null
|
||||
}
|
||||
|
|
|
@ -121,6 +121,7 @@ fun RootNavScreen(
|
|||
is RootNavState.VaultUnlockedForAutofillSave,
|
||||
is RootNavState.VaultUnlockedForAutofillSelection,
|
||||
is RootNavState.VaultUnlockedForNewSend,
|
||||
is RootNavState.VaultUnlockedForNewTotp,
|
||||
is RootNavState.VaultUnlockedForAuthRequest,
|
||||
is RootNavState.VaultUnlockedForFido2Save,
|
||||
is RootNavState.VaultUnlockedForFido2Assertion,
|
||||
|
@ -197,6 +198,14 @@ fun RootNavScreen(
|
|||
)
|
||||
}
|
||||
|
||||
is RootNavState.VaultUnlockedForNewTotp -> {
|
||||
navController.navigateToVaultUnlock(rootNavOptions)
|
||||
navController.navigateToVaultItemListingAsRoot(
|
||||
vaultItemListingType = VaultItemListingType.Login,
|
||||
navOptions = rootNavOptions,
|
||||
)
|
||||
}
|
||||
|
||||
is RootNavState.VaultUnlockedForAutofillSave -> {
|
||||
navController.navigateToVaultUnlockedGraph(rootNavOptions)
|
||||
navController.navigateToVaultAddEdit(
|
||||
|
|
|
@ -117,6 +117,12 @@ class RootNavViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
is SpecialCircumstance.AddTotpLoginItem -> {
|
||||
RootNavState.VaultUnlockedForNewTotp(
|
||||
activeUserId = userState.activeAccount.userId,
|
||||
)
|
||||
}
|
||||
|
||||
is SpecialCircumstance.ShareNewSend -> RootNavState.VaultUnlockedForNewSend
|
||||
|
||||
is SpecialCircumstance.PasswordlessRequest -> {
|
||||
|
@ -305,6 +311,14 @@ sealed class RootNavState : Parcelable {
|
|||
val fido2GetCredentialsRequest: Fido2GetCredentialsRequest,
|
||||
) : RootNavState()
|
||||
|
||||
/**
|
||||
* App should show the new verification codes listing screen for an unlocked user.
|
||||
*/
|
||||
@Parcelize
|
||||
data class VaultUnlockedForNewTotp(
|
||||
val activeUserId: String,
|
||||
) : RootNavState()
|
||||
|
||||
/**
|
||||
* App should show the new send screen for an unlocked user.
|
||||
*/
|
||||
|
|
|
@ -50,6 +50,8 @@ import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppThem
|
|||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import com.x8bit.bitwarden.ui.platform.util.isMyVaultShortcut
|
||||
import com.x8bit.bitwarden.ui.platform.util.isPasswordGeneratorShortcut
|
||||
import com.x8bit.bitwarden.ui.vault.model.TotpData
|
||||
import com.x8bit.bitwarden.ui.vault.util.getTotpDataOrNull
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
|
@ -119,6 +121,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
@BeforeEach
|
||||
fun setup() {
|
||||
mockkStatic(
|
||||
Intent::getTotpDataOrNull,
|
||||
Intent::getPasswordlessRequestDataIntentOrNull,
|
||||
Intent::getAutofillSaveItemOrNull,
|
||||
Intent::getAutofillSelectionDataOrNull,
|
||||
|
@ -134,6 +137,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
@AfterEach
|
||||
fun tearDown() {
|
||||
unmockkStatic(
|
||||
Intent::getTotpDataOrNull,
|
||||
Intent::getPasswordlessRequestDataIntentOrNull,
|
||||
Intent::getAutofillSaveItemOrNull,
|
||||
Intent::getAutofillSelectionDataOrNull,
|
||||
|
@ -294,12 +298,35 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on ReceiveFirstIntent with TOTP data should set the special circumstance to AddTotpLoginItem`() {
|
||||
val viewModel = createViewModel()
|
||||
val mockIntent = mockk<Intent>()
|
||||
val totpData = mockk<TotpData>()
|
||||
every { mockIntent.getTotpDataOrNull() } returns totpData
|
||||
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
|
||||
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
||||
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
||||
every { mockIntent.getCompleteRegistrationDataIntentOrNull() } returns null
|
||||
every { intentManager.getShareDataFromIntent(mockIntent) } returns null
|
||||
every { mockIntent.isMyVaultShortcut } returns false
|
||||
every { mockIntent.isPasswordGeneratorShortcut } returns false
|
||||
|
||||
viewModel.trySendAction(MainAction.ReceiveFirstIntent(intent = mockIntent))
|
||||
assertEquals(
|
||||
SpecialCircumstance.AddTotpLoginItem(data = totpData),
|
||||
specialCircumstanceManager.specialCircumstance,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on ReceiveFirstIntent with share data should set the special circumstance to ShareNewSend`() {
|
||||
val viewModel = createViewModel()
|
||||
val mockIntent = mockk<Intent>()
|
||||
val shareData = mockk<IntentManager.ShareData>()
|
||||
every { mockIntent.getTotpDataOrNull() } returns null
|
||||
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
|
||||
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
||||
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
||||
|
@ -328,6 +355,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
val viewModel = createViewModel()
|
||||
val mockIntent = mockk<Intent>()
|
||||
val autofillSelectionData = mockk<AutofillSelectionData>()
|
||||
every { mockIntent.getTotpDataOrNull() } returns null
|
||||
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
|
||||
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
||||
every { mockIntent.getCompleteRegistrationDataIntentOrNull() } returns null
|
||||
|
@ -359,6 +387,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
every { verificationToken } returns "token"
|
||||
}
|
||||
val mockIntent = mockk<Intent> {
|
||||
every { getTotpDataOrNull() } returns null
|
||||
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
||||
every { getAutofillSaveItemOrNull() } returns null
|
||||
every { getCompleteRegistrationDataIntentOrNull() } returns completeRegistrationData
|
||||
|
@ -394,6 +423,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
every { verificationToken } returns "token"
|
||||
}
|
||||
val mockIntent = mockk<Intent> {
|
||||
every { getTotpDataOrNull() } returns null
|
||||
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
||||
every { getAutofillSaveItemOrNull() } returns null
|
||||
every { getCompleteRegistrationDataIntentOrNull() } returns completeRegistrationData
|
||||
|
@ -431,6 +461,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
every { verificationToken } returns token
|
||||
}
|
||||
val mockIntent = mockk<Intent> {
|
||||
every { getTotpDataOrNull() } returns null
|
||||
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
||||
every { getAutofillSaveItemOrNull() } returns null
|
||||
every { getCompleteRegistrationDataIntentOrNull() } returns completeRegistrationData
|
||||
|
@ -470,6 +501,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
every { verificationToken } returns token
|
||||
}
|
||||
val mockIntent = mockk<Intent> {
|
||||
every { getTotpDataOrNull() } returns null
|
||||
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
||||
every { getAutofillSaveItemOrNull() } returns null
|
||||
every { getCompleteRegistrationDataIntentOrNull() } returns completeRegistrationData
|
||||
|
@ -511,6 +543,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
every { verificationToken } returns token
|
||||
}
|
||||
val mockIntent = mockk<Intent> {
|
||||
every { getTotpDataOrNull() } returns null
|
||||
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
||||
every { getAutofillSaveItemOrNull() } returns null
|
||||
every { getCompleteRegistrationDataIntentOrNull() } returns completeRegistrationData
|
||||
|
@ -548,6 +581,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
val viewModel = createViewModel()
|
||||
val mockIntent = mockk<Intent>()
|
||||
val autofillSaveItem = mockk<AutofillSaveItem>()
|
||||
every { mockIntent.getTotpDataOrNull() } returns null
|
||||
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
|
||||
every { mockIntent.getAutofillSaveItemOrNull() } returns autofillSaveItem
|
||||
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
||||
|
@ -578,6 +612,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
every {
|
||||
mockIntent.getPasswordlessRequestDataIntentOrNull()
|
||||
} returns passwordlessRequestData
|
||||
every { mockIntent.getTotpDataOrNull() } returns null
|
||||
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
||||
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
||||
every { mockIntent.getCompleteRegistrationDataIntentOrNull() } returns null
|
||||
|
@ -663,6 +698,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
origin = "mockOrigin",
|
||||
)
|
||||
val mockIntent = mockk<Intent> {
|
||||
every { getTotpDataOrNull() } returns null
|
||||
every { getFido2CredentialRequestOrNull() } returns fido2CredentialRequest
|
||||
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
||||
every { getAutofillSelectionDataOrNull() } returns null
|
||||
|
@ -701,6 +737,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
val mockIntent = mockk<Intent> {
|
||||
every { getFido2CredentialRequestOrNull() } returns fido2CredentialRequest
|
||||
every { getTotpDataOrNull() } returns null
|
||||
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
||||
every { getAutofillSelectionDataOrNull() } returns null
|
||||
every { getAutofillSaveItemOrNull() } returns null
|
||||
|
@ -773,6 +810,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
val viewModel = createViewModel()
|
||||
val mockIntent = mockk<Intent>()
|
||||
val shareData = mockk<IntentManager.ShareData>()
|
||||
every { mockIntent.getTotpDataOrNull() } returns null
|
||||
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
|
||||
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
||||
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
||||
|
@ -795,12 +833,35 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on ReceiveNewIntent with TOTP data should set the special circumstance to AddTotpLoginItem`() {
|
||||
val viewModel = createViewModel()
|
||||
val mockIntent = mockk<Intent>()
|
||||
val totpData = mockk<TotpData>()
|
||||
every { mockIntent.getTotpDataOrNull() } returns totpData
|
||||
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
|
||||
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
||||
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
||||
every { mockIntent.getCompleteRegistrationDataIntentOrNull() } returns null
|
||||
every { intentManager.getShareDataFromIntent(mockIntent) } returns null
|
||||
every { mockIntent.isMyVaultShortcut } returns false
|
||||
every { mockIntent.isPasswordGeneratorShortcut } returns false
|
||||
|
||||
viewModel.trySendAction(MainAction.ReceiveNewIntent(intent = mockIntent))
|
||||
assertEquals(
|
||||
SpecialCircumstance.AddTotpLoginItem(data = totpData),
|
||||
specialCircumstanceManager.specialCircumstance,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on ReceiveNewIntent with autofill data should set the special circumstance to AutofillSelection`() {
|
||||
val viewModel = createViewModel()
|
||||
val mockIntent = mockk<Intent>()
|
||||
val autofillSelectionData = mockk<AutofillSelectionData>()
|
||||
every { mockIntent.getTotpDataOrNull() } returns null
|
||||
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
|
||||
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
||||
every { mockIntent.getCompleteRegistrationDataIntentOrNull() } returns null
|
||||
|
@ -829,6 +890,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
val viewModel = createViewModel()
|
||||
val mockIntent = mockk<Intent>()
|
||||
val autofillSaveItem = mockk<AutofillSaveItem>()
|
||||
every { mockIntent.getTotpDataOrNull() } returns null
|
||||
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
|
||||
every { mockIntent.getAutofillSaveItemOrNull() } returns autofillSaveItem
|
||||
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
||||
|
@ -859,6 +921,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
every {
|
||||
mockIntent.getPasswordlessRequestDataIntentOrNull()
|
||||
} returns passwordlessRequestData
|
||||
every { mockIntent.getTotpDataOrNull() } returns null
|
||||
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
||||
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
||||
every { mockIntent.getCompleteRegistrationDataIntentOrNull() } returns null
|
||||
|
@ -885,6 +948,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
fun `on ReceiveNewIntent with a Vault deeplink data should set the special circumstance to VaultShortcut`() {
|
||||
val viewModel = createViewModel()
|
||||
val mockIntent = mockk<Intent> {
|
||||
every { getTotpDataOrNull() } returns null
|
||||
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
||||
every { getAutofillSaveItemOrNull() } returns null
|
||||
every { getAutofillSelectionDataOrNull() } returns null
|
||||
|
@ -910,6 +974,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
fun `on ReceiveNewIntent with a password generator deeplink data should set the special circumstance to GeneratorShortcut`() {
|
||||
val viewModel = createViewModel()
|
||||
val mockIntent = mockk<Intent> {
|
||||
every { getTotpDataOrNull() } returns null
|
||||
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
||||
every { getAutofillSaveItemOrNull() } returns null
|
||||
every { getAutofillSelectionDataOrNull() } returns null
|
||||
|
@ -1043,6 +1108,7 @@ private fun createMockFido2RegistrationIntent(
|
|||
fido2CredentialRequest: Fido2CredentialRequest = createMockFido2CredentialRequest(number = 1),
|
||||
): Intent = mockk<Intent> {
|
||||
every { getFido2CredentialRequestOrNull() } returns fido2CredentialRequest
|
||||
every { getTotpDataOrNull() } returns null
|
||||
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
||||
every { getAutofillSelectionDataOrNull() } returns null
|
||||
every { getAutofillSaveItemOrNull() } returns null
|
||||
|
@ -1056,6 +1122,7 @@ private fun createMockFido2AssertionIntent(
|
|||
createMockFido2CredentialAssertionRequest(number = 1),
|
||||
): Intent = mockk<Intent> {
|
||||
every { getFido2AssertionRequestOrNull() } returns fido2CredentialAssertionRequest
|
||||
every { getTotpDataOrNull() } returns null
|
||||
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
||||
every { getAutofillSelectionDataOrNull() } returns null
|
||||
every { getAutofillSaveItemOrNull() } returns null
|
||||
|
@ -1070,6 +1137,7 @@ private fun createMockFido2GetCredentialsIntent(
|
|||
),
|
||||
): Intent = mockk<Intent> {
|
||||
every { getFido2GetCredentialsRequestOrNull() } returns fido2GetCredentialsRequest
|
||||
every { getTotpDataOrNull() } returns null
|
||||
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
||||
every { getAutofillSelectionDataOrNull() } returns null
|
||||
every { getAutofillSaveItemOrNull() } returns null
|
||||
|
|
|
@ -7,6 +7,7 @@ import com.x8bit.bitwarden.data.autofill.fido2.model.createMockFido2GetCredentia
|
|||
import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
import com.x8bit.bitwarden.ui.vault.model.TotpData
|
||||
import io.mockk.mockk
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
|
@ -38,6 +39,7 @@ class SpecialCircumstanceExtensionsTest {
|
|||
data = mockk(),
|
||||
shouldFinishWhenComplete = true,
|
||||
),
|
||||
mockk<SpecialCircumstance.AddTotpLoginItem>(),
|
||||
SpecialCircumstance.PasswordlessRequest(
|
||||
passwordlessRequestData = mockk(),
|
||||
shouldFinishWhenComplete = true,
|
||||
|
@ -87,6 +89,7 @@ class SpecialCircumstanceExtensionsTest {
|
|||
data = mockk(),
|
||||
shouldFinishWhenComplete = true,
|
||||
),
|
||||
mockk<SpecialCircumstance.AddTotpLoginItem>(),
|
||||
SpecialCircumstance.PasswordlessRequest(
|
||||
passwordlessRequestData = mockk(),
|
||||
shouldFinishWhenComplete = true,
|
||||
|
@ -118,6 +121,7 @@ class SpecialCircumstanceExtensionsTest {
|
|||
SpecialCircumstance.AutofillSave(
|
||||
autofillSaveItem = mockk(),
|
||||
),
|
||||
mockk<SpecialCircumstance.AddTotpLoginItem>(),
|
||||
SpecialCircumstance.ShareNewSend(
|
||||
data = mockk(),
|
||||
shouldFinishWhenComplete = true,
|
||||
|
@ -188,6 +192,7 @@ class SpecialCircumstanceExtensionsTest {
|
|||
data = mockk(),
|
||||
shouldFinishWhenComplete = true,
|
||||
),
|
||||
mockk<SpecialCircumstance.AddTotpLoginItem>(),
|
||||
SpecialCircumstance.PasswordlessRequest(
|
||||
passwordlessRequestData = mockk(),
|
||||
shouldFinishWhenComplete = true,
|
||||
|
@ -234,6 +239,7 @@ class SpecialCircumstanceExtensionsTest {
|
|||
data = mockk(),
|
||||
shouldFinishWhenComplete = true,
|
||||
),
|
||||
mockk<SpecialCircumstance.AddTotpLoginItem>(),
|
||||
SpecialCircumstance.PasswordlessRequest(
|
||||
passwordlessRequestData = mockk(),
|
||||
shouldFinishWhenComplete = true,
|
||||
|
@ -251,4 +257,31 @@ class SpecialCircumstanceExtensionsTest {
|
|||
assertNull(specialCircumstance.toFido2GetCredentialsRequestOrNull())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toTotpDataOrNull should return a non-null value for AddTotpLoginItem`() {
|
||||
val totpData = mockk<TotpData>()
|
||||
assertEquals(
|
||||
totpData,
|
||||
SpecialCircumstance.AddTotpLoginItem(data = totpData).toTotpDataOrNull(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toTotpDataOrNull should return a null value for other types`() {
|
||||
listOf(
|
||||
mockk<SpecialCircumstance.AutofillSelection>(),
|
||||
mockk<SpecialCircumstance.AutofillSave>(),
|
||||
mockk<SpecialCircumstance.ShareNewSend>(),
|
||||
mockk<SpecialCircumstance.PasswordlessRequest>(),
|
||||
mockk<SpecialCircumstance.Fido2Save>(),
|
||||
mockk<SpecialCircumstance.Fido2Assertion>(),
|
||||
mockk<SpecialCircumstance.RegistrationEvent>(),
|
||||
SpecialCircumstance.GeneratorShortcut,
|
||||
SpecialCircumstance.VaultShortcut,
|
||||
)
|
||||
.forEach { specialCircumstance ->
|
||||
assertNull(specialCircumstance.toTotpDataOrNull())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -117,7 +117,7 @@ class FakeNavHostController : NavHostController(context = mockk()) {
|
|||
* Asserts the [currentRoute] matches the given [route].
|
||||
*/
|
||||
fun assertCurrentRoute(route: String) {
|
||||
assertEquals(currentRoute, route)
|
||||
assertEquals(route, currentRoute)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -128,16 +128,16 @@ class FakeNavHostController : NavHostController(context = mockk()) {
|
|||
navOptions: NavOptions? = null,
|
||||
navigatorExtras: Navigator.Extras? = null,
|
||||
) {
|
||||
assertEquals(currentRoute, route)
|
||||
assertEquals(lastNavigation?.navOptions, navOptions)
|
||||
assertEquals(lastNavigation?.navigatorExtras, navigatorExtras)
|
||||
assertEquals(route, currentRoute)
|
||||
assertEquals(navOptions, lastNavigation?.navOptions)
|
||||
assertEquals(navigatorExtras, lastNavigation?.navigatorExtras)
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the [lastNavigation] includes the given [navOptions].
|
||||
*/
|
||||
fun assertLastNavOptions(navOptions: NavOptions?) {
|
||||
assertEquals(lastNavigation?.navOptions, navOptions)
|
||||
assertEquals(navOptions, lastNavigation?.navOptions)
|
||||
}
|
||||
|
||||
data class Navigation(
|
||||
|
|
|
@ -151,6 +151,15 @@ class RootNavScreenTest : BaseComposeTest() {
|
|||
)
|
||||
}
|
||||
|
||||
// Make sure navigating to vault unlocked for new totp works as expected:
|
||||
rootNavStateFlow.value = RootNavState.VaultUnlockedForNewTotp(activeUserId = "userId")
|
||||
composeTestRule.runOnIdle {
|
||||
fakeNavHostController.assertLastNavigation(
|
||||
route = "vault_item_listing_as_root/login",
|
||||
navOptions = expectedNavOptions,
|
||||
)
|
||||
}
|
||||
|
||||
// Make sure navigating to vault unlocked for new sends works as expected:
|
||||
rootNavStateFlow.value = RootNavState.VaultUnlockedForNewSend
|
||||
composeTestRule.runOnIdle {
|
||||
|
|
|
@ -414,6 +414,43 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `when the active user has an unlocked vault but there is an AddTotpLoginItem special circumstance the nav state should be VaultUnlockedForNewTotp`() {
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.AddTotpLoginItem(data = mockk())
|
||||
mutableUserStateFlow.tryEmit(
|
||||
UserState(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = listOf(
|
||||
UserState.Account(
|
||||
userId = "activeUserId",
|
||||
name = "name",
|
||||
email = "email",
|
||||
avatarColorHex = "avatarColorHex",
|
||||
environment = Environment.Us,
|
||||
isPremium = true,
|
||||
isLoggedIn = true,
|
||||
isVaultUnlocked = true,
|
||||
needsPasswordReset = false,
|
||||
isBiometricsEnabled = false,
|
||||
organizations = emptyList(),
|
||||
needsMasterPassword = false,
|
||||
trustedDevice = null,
|
||||
hasMasterPassword = true,
|
||||
isUsingKeyConnector = false,
|
||||
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
assertEquals(
|
||||
RootNavState.VaultUnlockedForNewTotp(activeUserId = "activeUserId"),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `when the active user has an unlocked vault but the is a ShareNewSend special circumstance the nav state should be VaultUnlockedForNewSend`() {
|
||||
|
|
Loading…
Reference in a new issue