mirror of
https://github.com/bitwarden/android.git
synced 2024-11-24 18:36:32 +03:00
PM-11310 handle email registration special circumstance after successful login (#3831)
This commit is contained in:
parent
e32a9f303d
commit
4c983525d3
20 changed files with 240 additions and 69 deletions
|
@ -210,7 +210,7 @@ class MainViewModel @Inject constructor(
|
|||
authRepository.hasPendingAccountAddition = true
|
||||
}
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.CompleteRegistration(
|
||||
SpecialCircumstance.PreLogin.CompleteRegistration(
|
||||
completeRegistrationData = completeRegistrationData,
|
||||
timestamp = clock.millis(),
|
||||
)
|
||||
|
|
|
@ -1,14 +1,37 @@
|
|||
package com.x8bit.bitwarden.data.platform.manager
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
/**
|
||||
* Primary implementation of [SpecialCircumstanceManager].
|
||||
*/
|
||||
class SpecialCircumstanceManagerImpl : SpecialCircumstanceManager {
|
||||
class SpecialCircumstanceManagerImpl(
|
||||
authRepository: AuthRepository,
|
||||
dispatcherManager: DispatcherManager,
|
||||
) : SpecialCircumstanceManager {
|
||||
private val mutableSpecialCircumstanceFlow = MutableStateFlow<SpecialCircumstance?>(null)
|
||||
private val unconfinedScope = CoroutineScope(dispatcherManager.unconfined)
|
||||
init {
|
||||
authRepository
|
||||
.userStateFlow
|
||||
.filter {
|
||||
it?.activeAccount?.isLoggedIn == true
|
||||
}
|
||||
.onEach { _ ->
|
||||
if (specialCircumstance is SpecialCircumstance.PreLogin) {
|
||||
specialCircumstance = null
|
||||
}
|
||||
}
|
||||
.launchIn(unconfinedScope)
|
||||
}
|
||||
|
||||
override var specialCircumstance: SpecialCircumstance?
|
||||
get() = mutableSpecialCircumstanceFlow.value
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package com.x8bit.bitwarden.data.platform.manager.di
|
||||
|
||||
import com.x8bit.bitwarden.MainActivity
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManagerImpl
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
@ -20,6 +22,12 @@ class ActivityPlatformManagerModule {
|
|||
|
||||
@Provides
|
||||
@ActivityRetainedScoped
|
||||
fun provideActivityScopedSpecialCircumstanceRepository(): SpecialCircumstanceManager =
|
||||
SpecialCircumstanceManagerImpl()
|
||||
fun provideActivityScopedSpecialCircumstanceRepository(
|
||||
authRepository: AuthRepository,
|
||||
dispatcher: DispatcherManager,
|
||||
): SpecialCircumstanceManager =
|
||||
SpecialCircumstanceManagerImpl(
|
||||
authRepository = authRepository,
|
||||
dispatcherManager = dispatcher,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ 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 kotlinx.parcelize.Parcelize
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||
|
||||
/**
|
||||
* Represents a special circumstance the app may be in. These circumstances could require some kind
|
||||
|
@ -50,15 +51,6 @@ sealed class SpecialCircumstance : Parcelable {
|
|||
val shouldFinishWhenComplete: Boolean,
|
||||
) : SpecialCircumstance()
|
||||
|
||||
/**
|
||||
* The app was launched via AppLink in order to allow the user complete an ongoing registration.
|
||||
*/
|
||||
@Parcelize
|
||||
data class CompleteRegistration(
|
||||
val completeRegistrationData: CompleteRegistrationData,
|
||||
val timestamp: Long,
|
||||
) : SpecialCircumstance()
|
||||
|
||||
/**
|
||||
* The app was launched via the credential manager framework in order to allow the user to
|
||||
* manually save a passkey to their vault.
|
||||
|
@ -97,4 +89,23 @@ sealed class SpecialCircumstance : Parcelable {
|
|||
*/
|
||||
@Parcelize
|
||||
data object VaultShortcut : SpecialCircumstance()
|
||||
|
||||
/**
|
||||
* A subset of [SpecialCircumstance] that are only relevant in a pre-login state and should be
|
||||
* cleared after a successful login.
|
||||
*
|
||||
* @see [SpecialCircumstanceManager.clearSpecialCircumstanceAfterLogin]
|
||||
*/
|
||||
@Parcelize
|
||||
sealed class PreLogin : SpecialCircumstance() {
|
||||
/**
|
||||
* The app was launched via AppLink in order to allow the user complete an ongoing
|
||||
* registration.
|
||||
*/
|
||||
@Parcelize
|
||||
data class CompleteRegistration(
|
||||
val completeRegistrationData: CompleteRegistrationData,
|
||||
val timestamp: Long,
|
||||
) : PreLogin()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,9 +19,9 @@ fun SpecialCircumstance.toAutofillSaveItemOrNull(): AutofillSaveItem? =
|
|||
SpecialCircumstance.GeneratorShortcut -> null
|
||||
SpecialCircumstance.VaultShortcut -> null
|
||||
is SpecialCircumstance.Fido2Save -> null
|
||||
is SpecialCircumstance.CompleteRegistration -> null
|
||||
is SpecialCircumstance.Fido2Assertion -> null
|
||||
is SpecialCircumstance.Fido2GetCredentials -> null
|
||||
is SpecialCircumstance.PreLogin.CompleteRegistration -> null
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -36,9 +36,9 @@ fun SpecialCircumstance.toAutofillSelectionDataOrNull(): AutofillSelectionData?
|
|||
SpecialCircumstance.GeneratorShortcut -> null
|
||||
SpecialCircumstance.VaultShortcut -> null
|
||||
is SpecialCircumstance.Fido2Save -> null
|
||||
is SpecialCircumstance.CompleteRegistration -> null
|
||||
is SpecialCircumstance.Fido2Assertion -> null
|
||||
is SpecialCircumstance.Fido2GetCredentials -> null
|
||||
is SpecialCircumstance.PreLogin.CompleteRegistration -> null
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.ui.auth.feature.completeregistration
|
|||
|
||||
import android.content.res.Configuration
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
|
@ -78,6 +79,10 @@ fun CompleteRegistrationScreen(
|
|||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
val handler = rememberCompleteRegistrationHandler(viewModel = viewModel)
|
||||
val context = LocalContext.current
|
||||
|
||||
// route OS back actions through the VM to clear the special circumstance
|
||||
BackHandler(onBack = handler.onBackClick)
|
||||
|
||||
EventsEffect(viewModel) { event ->
|
||||
when (event) {
|
||||
is CompleteRegistrationEvent.NavigateBack -> onNavigateBack.invoke()
|
||||
|
|
|
@ -37,7 +37,6 @@ import kotlinx.coroutines.flow.onEach
|
|||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.jetbrains.annotations.VisibleForTesting
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val KEY_STATE = "state"
|
||||
|
@ -105,13 +104,6 @@ class CompleteRegistrationViewModel @Inject constructor(
|
|||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public override fun onCleared() {
|
||||
// clean the specialCircumstance after being handled
|
||||
specialCircumstanceManager.specialCircumstance = null
|
||||
super.onCleared()
|
||||
}
|
||||
|
||||
override fun handleAction(action: CompleteRegistrationAction) {
|
||||
when (action) {
|
||||
is ConfirmPasswordInputChange -> handleConfirmPasswordInputChanged(action)
|
||||
|
@ -280,6 +272,8 @@ class CompleteRegistrationViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun handleBackClicked() {
|
||||
// clear the special circumstance manager as user has elected not to proceed.
|
||||
specialCircumstanceManager.specialCircumstance = null
|
||||
sendEvent(CompleteRegistrationEvent.NavigateBack)
|
||||
}
|
||||
|
||||
|
|
|
@ -35,10 +35,10 @@ private const val KEY_STATE = "state"
|
|||
*/
|
||||
@HiltViewModel
|
||||
class LoginViewModel @Inject constructor(
|
||||
private val authRepository: AuthRepository,
|
||||
environmentRepository: EnvironmentRepository,
|
||||
private val vaultRepository: VaultRepository,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
environmentRepository: EnvironmentRepository,
|
||||
private val authRepository: AuthRepository,
|
||||
private val vaultRepository: VaultRepository,
|
||||
) : BaseViewModel<LoginState, LoginEvent, LoginAction>(
|
||||
// We load the state from the savedStateHandle for testing purposes.
|
||||
initialState = savedStateHandle[KEY_STATE]
|
||||
|
|
|
@ -70,7 +70,7 @@ class RootNavViewModel @Inject constructor(
|
|||
|
||||
userState?.activeAccount?.needsPasswordReset == true -> RootNavState.ResetPassword
|
||||
|
||||
specialCircumstance is SpecialCircumstance.CompleteRegistration -> {
|
||||
specialCircumstance is SpecialCircumstance.PreLogin.CompleteRegistration -> {
|
||||
RootNavState.CompleteOngoingRegistration(
|
||||
email = specialCircumstance.completeRegistrationData.email,
|
||||
verificationToken = specialCircumstance.completeRegistrationData.verificationToken,
|
||||
|
@ -141,7 +141,7 @@ class RootNavViewModel @Inject constructor(
|
|||
null,
|
||||
-> RootNavState.VaultUnlocked(activeUserId = userState.activeAccount.userId)
|
||||
|
||||
is SpecialCircumstance.CompleteRegistration -> {
|
||||
is SpecialCircumstance.PreLogin.CompleteRegistration -> {
|
||||
throw IllegalStateException(
|
||||
"Special circumstance should have been already handled.",
|
||||
)
|
||||
|
|
|
@ -27,6 +27,8 @@ import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
|
|||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.data.autofill.util.getAutofillSaveItemOrNull
|
||||
import com.x8bit.bitwarden.data.autofill.util.getAutofillSelectionDataOrNull
|
||||
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManagerImpl
|
||||
import com.x8bit.bitwarden.data.platform.manager.garbage.GarbageCollectionManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.CompleteRegistrationData
|
||||
|
@ -86,7 +88,12 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
private val garbageCollectionManager = mockk<GarbageCollectionManager> {
|
||||
every { tryCollect() } just runs
|
||||
}
|
||||
private val specialCircumstanceManager = SpecialCircumstanceManagerImpl()
|
||||
private val mockAuthRepository = mockk<AuthRepository>(relaxed = true)
|
||||
private val specialCircumstanceManager: SpecialCircumstanceManager =
|
||||
SpecialCircumstanceManagerImpl(
|
||||
authRepository = mockAuthRepository,
|
||||
dispatcherManager = FakeDispatcherManager(),
|
||||
)
|
||||
private val intentManager: IntentManager = mockk {
|
||||
every { getShareDataFromIntent(any()) } returns null
|
||||
}
|
||||
|
@ -338,7 +345,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
),
|
||||
)
|
||||
assertEquals(
|
||||
SpecialCircumstance.CompleteRegistration(
|
||||
SpecialCircumstance.PreLogin.CompleteRegistration(
|
||||
completeRegistrationData = completeRegistrationData,
|
||||
timestamp = FIXED_CLOCK.millis(),
|
||||
),
|
||||
|
|
|
@ -1,16 +1,29 @@
|
|||
package com.x8bit.bitwarden.data.platform.manager
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class SpecialCircumstanceManagerTest {
|
||||
private val mutableUserStateFlow = MutableStateFlow<UserState?>(null)
|
||||
private val mockAuthRepository = mockk<AuthRepository>(relaxed = true) {
|
||||
every { userStateFlow } returns mutableUserStateFlow
|
||||
}
|
||||
|
||||
private val specialCircumstanceManager: SpecialCircumstanceManager =
|
||||
SpecialCircumstanceManagerImpl()
|
||||
SpecialCircumstanceManagerImpl(
|
||||
authRepository = mockAuthRepository,
|
||||
dispatcherManager = FakeDispatcherManager(),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `specialCircumstanceStateFlow should emit whenever the SpecialCircumstance is updated`() =
|
||||
|
@ -29,4 +42,48 @@ class SpecialCircumstanceManagerTest {
|
|||
assertEquals(specialCircumstance2, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `clearSpecialCircumstanceAfterLogin should clear the SpecialCircumstance if it is a PreLogin`() =
|
||||
runTest {
|
||||
specialCircumstanceManager.specialCircumstanceStateFlow.test {
|
||||
assertNull(awaitItem())
|
||||
|
||||
val preLoginSpecialCircumstance =
|
||||
mockk<SpecialCircumstance.PreLogin.CompleteRegistration>()
|
||||
|
||||
specialCircumstanceManager.specialCircumstance = preLoginSpecialCircumstance
|
||||
assertEquals(preLoginSpecialCircumstance, awaitItem())
|
||||
val mockUserAccount = mockk<UserState.Account>() {
|
||||
every { isLoggedIn } returns true
|
||||
}
|
||||
val mockUserState = mockk<UserState> {
|
||||
every { activeAccount } returns mockUserAccount
|
||||
}
|
||||
mutableUserStateFlow.value = mockUserState
|
||||
|
||||
assertNull(awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `clearSpecialCircumstanceAfterLogin should not clear the SpecialCircumstance if it is not a PreLogin`() =
|
||||
runTest {
|
||||
specialCircumstanceManager.specialCircumstanceStateFlow.test {
|
||||
assertNull(awaitItem())
|
||||
|
||||
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.VaultShortcut
|
||||
assertEquals(SpecialCircumstance.VaultShortcut, awaitItem())
|
||||
val mockUserAccount = mockk<UserState.Account>() {
|
||||
every { isLoggedIn } returns true
|
||||
}
|
||||
val mockUserState = mockk<UserState> {
|
||||
every { activeAccount } returns mockUserAccount
|
||||
}
|
||||
mutableUserStateFlow.value = mockUserState
|
||||
expectNoEvents()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ class CompleteRegistrationScreenTest : BaseComposeTest() {
|
|||
|
||||
@Before
|
||||
fun setup() {
|
||||
composeTestRule.setContent {
|
||||
setContentWithBackDispatcher {
|
||||
CompleteRegistrationScreen(
|
||||
onNavigateBack = { onNavigateBackCalled = true },
|
||||
onNavigateToPasswordGuidance = { onNavigateToPasswordGuidanceCalled = true },
|
||||
|
@ -120,6 +120,12 @@ class CompleteRegistrationScreenTest : BaseComposeTest() {
|
|||
assertTrue(onNavigateBackCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `system back event should send BackClick action`() {
|
||||
backDispatcher?.onBackPressed()
|
||||
verify { viewModel.trySendAction(BackClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `password input change should send PasswordInputChange action`() {
|
||||
composeTestRule.onNodeWithText("Master password").performTextInput(TEST_INPUT)
|
||||
|
|
|
@ -12,13 +12,14 @@ import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL
|
|||
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.RegisterResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
|
||||
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManagerImpl
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.CompleteRegistrationData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance.PreLogin
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
import com.x8bit.bitwarden.data.tools.generator.repository.GeneratorRepository
|
||||
|
@ -42,6 +43,7 @@ import kotlinx.coroutines.test.runTest
|
|||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertFalse
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
|
@ -53,24 +55,32 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
|
|||
* Saved state handle that has valid inputs. Useful for tests that want to test things
|
||||
* after the user has entered all valid inputs.
|
||||
*/
|
||||
private val mockAuthRepository = mockk<AuthRepository>()
|
||||
private val mutableUserStateFlow = MutableStateFlow<UserState?>(null)
|
||||
private val mockAuthRepository = mockk<AuthRepository>() {
|
||||
every { userStateFlow } returns mutableUserStateFlow
|
||||
}
|
||||
|
||||
private val fakeEnvironmentRepository = FakeEnvironmentRepository()
|
||||
|
||||
private val specialCircumstanceManager: SpecialCircumstanceManager =
|
||||
SpecialCircumstanceManagerImpl()
|
||||
SpecialCircumstanceManagerImpl(
|
||||
authRepository = mockAuthRepository,
|
||||
dispatcherManager = FakeDispatcherManager(),
|
||||
)
|
||||
private val mutableFeatureFlagFlow = MutableStateFlow(false)
|
||||
private val featureFlagManager = mockk<FeatureFlagManager>(relaxed = true) {
|
||||
every { getFeatureFlag(FlagKey.OnboardingFlow) } returns false
|
||||
every { getFeatureFlagFlow(FlagKey.OnboardingFlow) } returns mutableFeatureFlagFlow
|
||||
}
|
||||
private val mutableGeneratorResultFlow = bufferedMutableSharedFlow<GeneratorResult>()
|
||||
private val mockCompleteRegistrationCircumstance = mockk<PreLogin.CompleteRegistration>()
|
||||
private val generatorRepository = mockk<GeneratorRepository>(relaxed = true) {
|
||||
every { generatorResultFlow } returns mutableGeneratorResultFlow
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
specialCircumstanceManager.specialCircumstance = mockCompleteRegistrationCircumstance
|
||||
mockkStatic(::generateUriForCaptcha)
|
||||
}
|
||||
|
||||
|
@ -85,29 +95,6 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
|
|||
assertEquals(DEFAULT_STATE, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `onCleared should erase specialCircumstance`() = runTest {
|
||||
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.CompleteRegistration(
|
||||
completeRegistrationData = CompleteRegistrationData(
|
||||
email = EMAIL,
|
||||
verificationToken = TOKEN,
|
||||
fromEmail = true,
|
||||
),
|
||||
System.currentTimeMillis(),
|
||||
)
|
||||
|
||||
val viewModel = CompleteRegistrationViewModel(
|
||||
savedStateHandle = SavedStateHandle(mapOf("state" to DEFAULT_STATE)),
|
||||
authRepository = mockAuthRepository,
|
||||
environmentRepository = fakeEnvironmentRepository,
|
||||
specialCircumstanceManager = specialCircumstanceManager,
|
||||
featureFlagManager = featureFlagManager,
|
||||
generatorRepository = generatorRepository,
|
||||
)
|
||||
viewModel.onCleared()
|
||||
assertTrue(specialCircumstanceManager.specialCircumstance == null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Password below 12 chars should have non-valid state`() = runTest {
|
||||
val input = "abcdefghikl"
|
||||
|
@ -371,12 +358,18 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `CloseClick should emit NavigateBack`() = runTest {
|
||||
fun `CloseClick should emit NavigateBack and clear special circumstances`() = runTest {
|
||||
assertEquals(
|
||||
mockCompleteRegistrationCircumstance,
|
||||
specialCircumstanceManager.specialCircumstance,
|
||||
)
|
||||
val viewModel = createCompleteRegistrationViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(BackClick)
|
||||
assertEquals(CompleteRegistrationEvent.NavigateBack, awaitItem())
|
||||
}
|
||||
|
||||
assertNull(specialCircumstanceManager.specialCircumstance)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package com.x8bit.bitwarden.ui.platform.base
|
||||
|
||||
import androidx.activity.OnBackPressedDispatcher
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
|
||||
|
@ -14,6 +17,14 @@ abstract class BaseComposeTest : BaseRobolectricTest() {
|
|||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
|
||||
/**
|
||||
* instance of [OnBackPressedDispatcher] made available if testing using
|
||||
*
|
||||
* [setContentWithBackDispatcher] or [runTestWithTheme]
|
||||
*/
|
||||
var backDispatcher: OnBackPressedDispatcher? = null
|
||||
private set
|
||||
|
||||
/**
|
||||
* Helper for testing a basic Composable function that only requires a Composable environment
|
||||
* with the [BitwardenTheme].
|
||||
|
@ -26,8 +37,22 @@ abstract class BaseComposeTest : BaseRobolectricTest() {
|
|||
BitwardenTheme(
|
||||
theme = theme,
|
||||
) {
|
||||
backDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
|
||||
test()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for testing a basic Composable function that provides access to a
|
||||
* [OnBackPressedDispatcher].
|
||||
*
|
||||
* Use if the [Composable] function being tested uses a [BackHandler]
|
||||
*/
|
||||
protected fun setContentWithBackDispatcher(test: @Composable () -> Unit) {
|
||||
composeTestRule.setContent {
|
||||
backDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
|
||||
test()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@ import com.x8bit.bitwarden.data.autofill.fido2.model.createMockFido2CredentialAs
|
|||
import com.x8bit.bitwarden.data.autofill.fido2.model.createMockFido2GetCredentialsRequest
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManagerImpl
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.CompleteRegistrationData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
|
@ -40,7 +42,13 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
every { authStateFlow } returns mutableAuthStateFlow
|
||||
every { showWelcomeCarousel } returns false
|
||||
}
|
||||
private val specialCircumstanceManager = SpecialCircumstanceManagerImpl()
|
||||
|
||||
private val mockAuthRepository = mockk<AuthRepository>(relaxed = true)
|
||||
private val specialCircumstanceManager: SpecialCircumstanceManager =
|
||||
SpecialCircumstanceManagerImpl(
|
||||
authRepository = mockAuthRepository,
|
||||
dispatcherManager = FakeDispatcherManager(),
|
||||
)
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
|
@ -655,7 +663,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
every { authRepository.hasPendingAccountAddition } returns false
|
||||
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.CompleteRegistration(
|
||||
SpecialCircumstance.PreLogin.CompleteRegistration(
|
||||
CompleteRegistrationData(
|
||||
email = "example@email.com",
|
||||
verificationToken = "token",
|
||||
|
@ -682,7 +690,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
every { authRepository.hasPendingAccountAddition } returns true
|
||||
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.CompleteRegistration(
|
||||
SpecialCircumstance.PreLogin.CompleteRegistration(
|
||||
CompleteRegistrationData(
|
||||
email = "example@email.com",
|
||||
verificationToken = "token",
|
||||
|
@ -732,7 +740,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
every { authRepository.hasPendingAccountAddition } returns true
|
||||
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.CompleteRegistration(
|
||||
SpecialCircumstance.PreLogin.CompleteRegistration(
|
||||
CompleteRegistrationData(
|
||||
email = "example@email.com",
|
||||
verificationToken = "token",
|
||||
|
|
|
@ -13,6 +13,7 @@ import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
|||
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManagerImpl
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManagerImpl
|
||||
|
@ -107,8 +108,13 @@ class SearchViewModelTest : BaseViewModelTest() {
|
|||
every { isIconLoadingDisabled } returns false
|
||||
every { isIconLoadingDisabledFlow } returns mutableIsIconLoadingDisabledFlow
|
||||
}
|
||||
|
||||
private val mockAuthRepository = mockk<AuthRepository>(relaxed = true)
|
||||
private val specialCircumstanceManager: SpecialCircumstanceManager =
|
||||
SpecialCircumstanceManagerImpl()
|
||||
SpecialCircumstanceManagerImpl(
|
||||
authRepository = mockAuthRepository,
|
||||
dispatcherManager = FakeDispatcherManager(),
|
||||
)
|
||||
private val organizationEventManager = mockk<OrganizationEventManager> {
|
||||
every { trackEvent(event = any()) } just runs
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ class LoginApprovalScreenTest : BaseComposeTest() {
|
|||
|
||||
@Before
|
||||
fun setUp() {
|
||||
composeTestRule.setContent {
|
||||
setContentWithBackDispatcher {
|
||||
LoginApprovalScreen(
|
||||
onNavigateBack = { onNavigateBackCalled = true },
|
||||
viewModel = viewModel,
|
||||
|
@ -52,6 +52,14 @@ class LoginApprovalScreenTest : BaseComposeTest() {
|
|||
assertTrue(onNavigateBackCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `system back should send CloseClick`() {
|
||||
backDispatcher?.onBackPressed()
|
||||
verify {
|
||||
viewModel.trySendAction(LoginApprovalAction.CloseClick)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on ExitApp should call exit appliction`() {
|
||||
mutableEventFlow.tryEmit(LoginApprovalEvent.ExitApp)
|
||||
|
|
|
@ -62,7 +62,7 @@ class AddSendScreenTest : BaseComposeTest() {
|
|||
|
||||
@Before
|
||||
fun setUp() {
|
||||
composeTestRule.setContent {
|
||||
setContentWithBackDispatcher {
|
||||
AddSendScreen(
|
||||
viewModel = viewModel,
|
||||
exitManager = exitManager,
|
||||
|
@ -104,6 +104,12 @@ class AddSendScreenTest : BaseComposeTest() {
|
|||
verify { viewModel.trySendAction(AddSendAction.CloseClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on system back should send CloseClick`() {
|
||||
backDispatcher?.onBackPressed()
|
||||
verify { viewModel.trySendAction(AddSendAction.CloseClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `display navigation icon according to state`() {
|
||||
mutableStateFlow.update { it.copy(isShared = false) }
|
||||
|
|
|
@ -24,6 +24,7 @@ import com.x8bit.bitwarden.data.autofill.fido2.model.UserVerificationRequirement
|
|||
import com.x8bit.bitwarden.data.autofill.fido2.model.createMockFido2CredentialRequest
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManagerImpl
|
||||
|
@ -137,8 +138,13 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
every { vaultDataStateFlow } returns mutableVaultDataFlow
|
||||
every { totpCodeFlow } returns totpTestCodeFlow
|
||||
}
|
||||
|
||||
private val mockAuthRepository = mockk<AuthRepository>(relaxed = true)
|
||||
private val specialCircumstanceManager: SpecialCircumstanceManager =
|
||||
SpecialCircumstanceManagerImpl()
|
||||
SpecialCircumstanceManagerImpl(
|
||||
authRepository = mockAuthRepository,
|
||||
dispatcherManager = FakeDispatcherManager(),
|
||||
)
|
||||
|
||||
private val generatorRepository: GeneratorRepository = FakeGeneratorRepository()
|
||||
private val organizationEventManager = mockk<OrganizationEventManager> {
|
||||
|
|
|
@ -26,7 +26,9 @@ import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager
|
|||
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManagerImpl
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManagerImpl
|
||||
import com.x8bit.bitwarden.data.platform.manager.ciphermatching.CipherMatchingManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
|
||||
|
@ -143,7 +145,13 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
every { getPullToRefreshEnabledFlow() } returns mutablePullToRefreshEnabledFlow
|
||||
every { isUnlockWithPinEnabled } returns false
|
||||
}
|
||||
private val specialCircumstanceManager = SpecialCircumstanceManagerImpl()
|
||||
|
||||
private val mockAuthRepository = mockk<AuthRepository>(relaxed = true)
|
||||
private val specialCircumstanceManager: SpecialCircumstanceManager =
|
||||
SpecialCircumstanceManagerImpl(
|
||||
authRepository = mockAuthRepository,
|
||||
dispatcherManager = FakeDispatcherManager(),
|
||||
)
|
||||
private val policyManager: PolicyManager = mockk {
|
||||
every { getActivePolicies(type = PolicyTypeJson.DISABLE_SEND) } returns emptyList()
|
||||
every { getActivePoliciesFlow(type = PolicyTypeJson.DISABLE_SEND) } returns emptyFlow()
|
||||
|
|
Loading…
Reference in a new issue