PM-11310 handle email registration special circumstance after successful login (#3831)

This commit is contained in:
Dave Severns 2024-08-28 13:27:58 -04:00 committed by GitHub
parent e32a9f303d
commit 4c983525d3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 240 additions and 69 deletions

View file

@ -210,7 +210,7 @@ class MainViewModel @Inject constructor(
authRepository.hasPendingAccountAddition = true
}
specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.CompleteRegistration(
SpecialCircumstance.PreLogin.CompleteRegistration(
completeRegistrationData = completeRegistrationData,
timestamp = clock.millis(),
)

View file

@ -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

View file

@ -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,
)
}

View file

@ -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()
}
}

View file

@ -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
}
/**

View file

@ -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()

View file

@ -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)
}

View file

@ -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]

View file

@ -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.",
)

View file

@ -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(),
),

View file

@ -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()
}
}
}

View file

@ -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)

View file

@ -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

View file

@ -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()
}
}
}

View file

@ -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",

View file

@ -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
}

View file

@ -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)

View file

@ -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) }

View file

@ -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> {

View file

@ -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()