mirror of
https://github.com/bitwarden/android.git
synced 2025-03-15 18:58:59 +03:00
BIT-1133: Add account switcher to Landing Screen (#323)
This commit is contained in:
parent
c729d7da1b
commit
a106f0852a
9 changed files with 250 additions and 8 deletions
|
@ -266,7 +266,8 @@ class AuthRepositoryImpl constructor(
|
|||
val previousActiveUserId = currentUserState.activeUserId
|
||||
|
||||
if (userId == previousActiveUserId) {
|
||||
// Nothing to do
|
||||
// No switching to do but clear any special circumstances
|
||||
specialCircumstance = null
|
||||
return SwitchAccountResult.NoChange
|
||||
}
|
||||
|
||||
|
@ -281,6 +282,10 @@ class AuthRepositoryImpl constructor(
|
|||
// Lock and clear data for the previous user
|
||||
vaultRepository.lockVaultIfNecessary(previousActiveUserId)
|
||||
vaultRepository.clearUnlockedData()
|
||||
|
||||
// Clear any special circumstances
|
||||
specialCircumstance = null
|
||||
|
||||
return SwitchAccountResult.AccountSwitched
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
|
@ -46,14 +47,18 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenAccountSwitcher
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenBasicDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenFilledButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenPlaceholderAccountActionItem
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenSelectionDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenSelectionRow
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenSwitch
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextField
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
/**
|
||||
* The top level composable for the Landing screen.
|
||||
|
@ -86,17 +91,34 @@ fun LandingScreen(
|
|||
},
|
||||
)
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
val isAppBarVisible = state.accountSummaries.isNotEmpty()
|
||||
var isAccountMenuVisible by rememberSaveable { mutableStateOf(false) }
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(
|
||||
state = rememberTopAppBarState(),
|
||||
canScroll = { !isAccountMenuVisible },
|
||||
)
|
||||
BitwardenScaffold(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
// Empty
|
||||
if (isAppBarVisible) {
|
||||
BitwardenTopAppBar(
|
||||
title = "",
|
||||
scrollBehavior = scrollBehavior,
|
||||
navigationIcon = null,
|
||||
actions = {
|
||||
BitwardenPlaceholderAccountActionItem(
|
||||
onClick = { isAccountMenuVisible = !isAccountMenuVisible },
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
) { innerPadding ->
|
||||
LandingScreenContent(
|
||||
state = state,
|
||||
isAppBarVisible = isAppBarVisible,
|
||||
onEmailInputChange = remember(viewModel) {
|
||||
{ viewModel.trySendAction(LandingAction.EmailInputChanged(it)) }
|
||||
},
|
||||
|
@ -116,6 +138,23 @@ fun LandingScreen(
|
|||
.padding(innerPadding)
|
||||
.fillMaxSize(),
|
||||
)
|
||||
|
||||
BitwardenAccountSwitcher(
|
||||
isVisible = isAccountMenuVisible,
|
||||
accountSummaries = state.accountSummaries.toImmutableList(),
|
||||
onAccountSummaryClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(LandingAction.SwitchAccountClick(it)) }
|
||||
},
|
||||
onAddAccountClick = {
|
||||
// Not available
|
||||
},
|
||||
onDismissRequest = { isAccountMenuVisible = false },
|
||||
isAddAccountAvailable = false,
|
||||
topAppBarScrollBehavior = scrollBehavior,
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,6 +163,7 @@ fun LandingScreen(
|
|||
@Composable
|
||||
private fun LandingScreenContent(
|
||||
state: LandingState,
|
||||
isAppBarVisible: Boolean,
|
||||
onEmailInputChange: (String) -> Unit,
|
||||
onEnvironmentTypeSelect: (Environment.Type) -> Unit,
|
||||
onRememberMeToggle: (Boolean) -> Unit,
|
||||
|
@ -138,7 +178,8 @@ private fun LandingScreenContent(
|
|||
.imePadding()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(104.dp))
|
||||
val topPadding = if (isAppBarVisible) 40.dp else 104.dp
|
||||
Spacer(modifier = Modifier.height(topPadding))
|
||||
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.logo),
|
||||
|
|
|
@ -11,6 +11,8 @@ import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
|||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.isValidEmail
|
||||
import com.x8bit.bitwarden.ui.platform.components.BasicDialogState
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toAccountSummaries
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
@ -36,6 +38,7 @@ class LandingViewModel @Inject constructor(
|
|||
isRememberMeEnabled = authRepository.rememberedEmailAddress != null,
|
||||
selectedEnvironmentType = environmentRepository.environment.type,
|
||||
errorDialogState = BasicDialogState.Hidden,
|
||||
accountSummaries = authRepository.userStateFlow.value?.toAccountSummaries().orEmpty(),
|
||||
),
|
||||
) {
|
||||
|
||||
|
@ -61,6 +64,7 @@ class LandingViewModel @Inject constructor(
|
|||
|
||||
override fun handleAction(action: LandingAction) {
|
||||
when (action) {
|
||||
is LandingAction.SwitchAccountClick -> handleSwitchAccountClicked(action)
|
||||
is LandingAction.ContinueButtonClick -> handleContinueButtonClicked()
|
||||
LandingAction.CreateAccountClick -> handleCreateAccountClicked()
|
||||
is LandingAction.ErrorDialogDismiss -> handleErrorDialogDismiss()
|
||||
|
@ -73,6 +77,10 @@ class LandingViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleSwitchAccountClicked(action: LandingAction.SwitchAccountClick) {
|
||||
authRepository.switchAccount(userId = action.account.userId)
|
||||
}
|
||||
|
||||
private fun handleEmailInputUpdated(action: LandingAction.EmailInputChanged) {
|
||||
val email = action.input
|
||||
mutableStateFlow.update {
|
||||
|
@ -156,6 +164,7 @@ data class LandingState(
|
|||
val isRememberMeEnabled: Boolean,
|
||||
val selectedEnvironmentType: Environment.Type,
|
||||
val errorDialogState: BasicDialogState,
|
||||
val accountSummaries: List<AccountSummary>,
|
||||
) : Parcelable
|
||||
|
||||
/**
|
||||
|
@ -184,6 +193,13 @@ sealed class LandingEvent {
|
|||
* Models actions for the landing screen.
|
||||
*/
|
||||
sealed class LandingAction {
|
||||
/**
|
||||
* Indicates the user has clicked on the given [account] information in order to switch to it.
|
||||
*/
|
||||
data class SwitchAccountClick(
|
||||
val account: AccountSummary,
|
||||
) : LandingAction()
|
||||
|
||||
/**
|
||||
* Indicates that the continue button has been clicked and the app should navigate to Login.
|
||||
*/
|
||||
|
|
|
@ -66,6 +66,9 @@ private const val MAXIMUM_ACCOUNT_LIMIT = 5
|
|||
* @param onAddAccountClick A callback when the Add Account row is clicked.
|
||||
* @param onDismissRequest A callback when the component requests to be dismissed. This is triggered
|
||||
* whenever the user clicks on the scrim or any of the switcher items.
|
||||
* @param isAddAccountAvailable Whether or not the "Add account" button is available. Note that even
|
||||
* when `true`, this button may be hidden when there are more than [MAXIMUM_ACCOUNT_LIMIT] accounts
|
||||
* present.
|
||||
* @param modifier A [Modifier] for the composable.
|
||||
* @param topAppBarScrollBehavior Used to derive the background color of the content and keep it in
|
||||
* sync with the associated app bar.
|
||||
|
@ -79,6 +82,7 @@ fun BitwardenAccountSwitcher(
|
|||
onAddAccountClick: () -> Unit,
|
||||
onDismissRequest: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
isAddAccountAvailable: Boolean = true,
|
||||
topAppBarScrollBehavior: TopAppBarScrollBehavior,
|
||||
) {
|
||||
Box(modifier = modifier) {
|
||||
|
@ -99,6 +103,7 @@ fun BitwardenAccountSwitcher(
|
|||
onDismissRequest()
|
||||
onAddAccountClick()
|
||||
},
|
||||
isAddAccountAvailable = isAddAccountAvailable,
|
||||
topAppBarScrollBehavior = topAppBarScrollBehavior,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
|
@ -113,6 +118,7 @@ private fun AnimatedAccountSwitcher(
|
|||
accountSummaries: ImmutableList<AccountSummary>,
|
||||
onAccountSummaryClick: (AccountSummary) -> Unit,
|
||||
onAddAccountClick: () -> Unit,
|
||||
isAddAccountAvailable: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
topAppBarScrollBehavior: TopAppBarScrollBehavior,
|
||||
) {
|
||||
|
@ -130,11 +136,16 @@ private fun AnimatedAccountSwitcher(
|
|||
.padding(bottom = 24.dp)
|
||||
// Match the color of the switcher the different states of the app bar.
|
||||
.drawBehind {
|
||||
val progressFraction = if (topAppBarScrollBehavior.isPinned) {
|
||||
topAppBarScrollBehavior.state.overlappedFraction
|
||||
} else {
|
||||
topAppBarScrollBehavior.state.collapsedFraction
|
||||
}
|
||||
val contentBackgroundColor =
|
||||
lerp(
|
||||
start = expandedColor,
|
||||
stop = collapsedColor,
|
||||
fraction = topAppBarScrollBehavior.state.collapsedFraction,
|
||||
fraction = progressFraction,
|
||||
)
|
||||
drawRect(contentBackgroundColor)
|
||||
},
|
||||
|
@ -154,7 +165,7 @@ private fun AnimatedAccountSwitcher(
|
|||
color = MaterialTheme.colorScheme.outlineVariant,
|
||||
)
|
||||
}
|
||||
if (accountSummaries.size < MAXIMUM_ACCOUNT_LIMIT) {
|
||||
if (accountSummaries.size < MAXIMUM_ACCOUNT_LIMIT && isAddAccountAvailable) {
|
||||
item {
|
||||
AddAccountItem(
|
||||
onClick = onAddAccountClick,
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
package com.x8bit.bitwarden.ui.platform.components
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
|
||||
/**
|
||||
* A placeholder item to be used to represent an account.
|
||||
*
|
||||
* @param onClick An action to be invoked when the icon is clicked.
|
||||
*/
|
||||
@Composable
|
||||
fun BitwardenPlaceholderAccountActionItem(
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
IconButton(
|
||||
onClick = onClick,
|
||||
modifier = Modifier
|
||||
.semantics(mergeDescendants = true) {},
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_account_initials_container),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.secondaryContainer,
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_dots),
|
||||
contentDescription = stringResource(id = R.string.account),
|
||||
tint = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun BitwardenPlaceholderAccountActionItem_preview_light() {
|
||||
BitwardenTheme(darkTheme = false) {
|
||||
BitwardenPlaceholderAccountActionItem(
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun BitwardenPlaceholderAccountActionItem_preview_dark() {
|
||||
BitwardenTheme(darkTheme = true) {
|
||||
BitwardenPlaceholderAccountActionItem(
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
}
|
12
app/src/main/res/drawable/ic_dots.xml
Normal file
12
app/src/main/res/drawable/ic_dots.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M13.663,12.05C13.663,11.756 13.753,11.509 13.934,11.309C14.122,11.103 14.391,11 14.741,11C15.091,11 15.356,11.103 15.538,11.309C15.725,11.509 15.819,11.756 15.819,12.05C15.819,12.337 15.725,12.581 15.538,12.781C15.356,12.981 15.091,13.081 14.741,13.081C14.391,13.081 14.122,12.981 13.934,12.781C13.753,12.581 13.663,12.337 13.663,12.05Z"
|
||||
android:fillColor="#151B2C"/>
|
||||
<path
|
||||
android:pathData="M8,12.05C8,11.756 8.091,11.509 8.272,11.309C8.459,11.103 8.728,11 9.078,11C9.428,11 9.694,11.103 9.875,11.309C10.063,11.509 10.156,11.756 10.156,12.05C10.156,12.337 10.063,12.581 9.875,12.781C9.694,12.981 9.428,13.081 9.078,13.081C8.728,13.081 8.459,12.981 8.272,12.781C8.091,12.581 8,12.337 8,12.05Z"
|
||||
android:fillColor="#151B2C"/>
|
||||
</vector>
|
|
@ -1099,7 +1099,7 @@ class AuthRepositoryTest {
|
|||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `switchAccount when the given userId is the same as the current activeUserId should do nothing`() {
|
||||
fun `switchAccount when the given userId is the same as the current activeUserId should only clear any special circumstances`() {
|
||||
val originalUserId = USER_ID_1
|
||||
val originalUserState = SINGLE_USER_STATE_1.toUserState(
|
||||
vaultState = VAULT_STATE,
|
||||
|
@ -1110,6 +1110,7 @@ class AuthRepositoryTest {
|
|||
originalUserState,
|
||||
repository.userStateFlow.value,
|
||||
)
|
||||
repository.specialCircumstance = UserState.SpecialCircumstance.PendingAccountAddition
|
||||
|
||||
assertEquals(
|
||||
SwitchAccountResult.NoChange,
|
||||
|
@ -1120,6 +1121,7 @@ class AuthRepositoryTest {
|
|||
originalUserState,
|
||||
repository.userStateFlow.value,
|
||||
)
|
||||
assertNull(repository.specialCircumstance)
|
||||
verify(exactly = 0) { vaultRepository.lockVaultIfNecessary(originalUserId) }
|
||||
verify(exactly = 0) { vaultRepository.clearUnlockedData() }
|
||||
}
|
||||
|
@ -1154,7 +1156,7 @@ class AuthRepositoryTest {
|
|||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `switchAccount when the userId is valid should update the current UserState, lock the vault of the previous active user, and clear the previously unlocked data`() {
|
||||
fun `switchAccount when the userId is valid should update the current UserState, lock the vault of the previous active user, clear the previously unlocked data, and reset the special circumstance`() {
|
||||
val originalUserId = USER_ID_1
|
||||
val updatedUserId = USER_ID_2
|
||||
val originalUserState = MULTI_USER_STATE.toUserState(
|
||||
|
@ -1166,6 +1168,7 @@ class AuthRepositoryTest {
|
|||
originalUserState,
|
||||
repository.userStateFlow.value,
|
||||
)
|
||||
repository.specialCircumstance = UserState.SpecialCircumstance.PendingAccountAddition
|
||||
|
||||
assertEquals(
|
||||
SwitchAccountResult.AccountSwitched,
|
||||
|
@ -1176,6 +1179,7 @@ class AuthRepositoryTest {
|
|||
originalUserState.copy(activeUserId = updatedUserId),
|
||||
repository.userStateFlow.value,
|
||||
)
|
||||
assertNull(repository.specialCircumstance)
|
||||
verify { vaultRepository.lockVaultIfNecessary(originalUserId) }
|
||||
verify { vaultRepository.clearUnlockedData() }
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import androidx.compose.ui.test.filterToOne
|
|||
import androidx.compose.ui.test.hasAnyAncestor
|
||||
import androidx.compose.ui.test.isDialog
|
||||
import androidx.compose.ui.test.onAllNodesWithText
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performScrollTo
|
||||
|
@ -21,6 +22,7 @@ import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
|||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.components.BasicDialogState
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
|
@ -63,6 +65,58 @@ class LandingScreenTest : BaseComposeTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `account menu icon is present according to the state`() {
|
||||
composeTestRule.onNodeWithContentDescription("Account").assertDoesNotExist()
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(accountSummaries = listOf(ACTIVE_ACCOUNT_SUMMARY))
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithContentDescription("Account").assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `account menu icon click should show the account switcher`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(accountSummaries = listOf(ACTIVE_ACCOUNT_SUMMARY))
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithContentDescription("Account").performClick()
|
||||
|
||||
composeTestRule.onNodeWithText("active@bitwarden.com").assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `account click in the account switcher should send SwitchAccountClick and close switcher`() {
|
||||
// Show the account switcher
|
||||
mutableStateFlow.update {
|
||||
it.copy(accountSummaries = listOf(ACTIVE_ACCOUNT_SUMMARY))
|
||||
}
|
||||
composeTestRule.onNodeWithContentDescription("Account").performClick()
|
||||
composeTestRule.onNodeWithText("active@bitwarden.com").assertIsDisplayed()
|
||||
|
||||
composeTestRule.onNodeWithText("active@bitwarden.com").performClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(LandingAction.SwitchAccountClick(ACTIVE_ACCOUNT_SUMMARY))
|
||||
}
|
||||
composeTestRule.onNodeWithText("active@bitwarden.com").assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `add account button in the account switcher does not exist`() {
|
||||
// Show the account switcher
|
||||
mutableStateFlow.update {
|
||||
it.copy(accountSummaries = listOf(ACTIVE_ACCOUNT_SUMMARY))
|
||||
}
|
||||
composeTestRule.onNodeWithContentDescription("Account").performClick()
|
||||
composeTestRule.onNodeWithText("active@bitwarden.com").assertIsDisplayed()
|
||||
|
||||
composeTestRule.onNodeWithText("Add account").assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `continue button should be enabled or disabled according to the state`() {
|
||||
composeTestRule.onNodeWithText("Continue").assertIsEnabled()
|
||||
|
@ -224,10 +278,19 @@ class LandingScreenTest : BaseComposeTest() {
|
|||
}
|
||||
}
|
||||
|
||||
private val ACTIVE_ACCOUNT_SUMMARY = AccountSummary(
|
||||
userId = "activeUserId",
|
||||
name = "Active User",
|
||||
email = "active@bitwarden.com",
|
||||
avatarColorHex = "#aa00aa",
|
||||
status = AccountSummary.Status.ACTIVE,
|
||||
)
|
||||
|
||||
private val DEFAULT_STATE = LandingState(
|
||||
emailInput = "",
|
||||
isContinueButtonEnabled = true,
|
||||
isRememberMeEnabled = false,
|
||||
selectedEnvironmentType = Environment.Type.US,
|
||||
errorDialogState = BasicDialogState.Hidden,
|
||||
accountSummaries = emptyList(),
|
||||
)
|
||||
|
|
|
@ -3,13 +3,16 @@ package com.x8bit.bitwarden.ui.auth.feature.landing
|
|||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.components.BasicDialogState
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toAccountSummaries
|
||||
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.Test
|
||||
|
@ -42,6 +45,30 @@ class LandingViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `initial state should set the account summaries based on the UserState`() {
|
||||
val userState = UserState(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = listOf(
|
||||
UserState.Account(
|
||||
userId = "activeUserId",
|
||||
name = "name",
|
||||
email = "email",
|
||||
avatarColorHex = "avatarColorHex",
|
||||
isPremium = true,
|
||||
isVaultUnlocked = true,
|
||||
),
|
||||
),
|
||||
)
|
||||
val viewModel = createViewModel(userState = userState)
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
accountSummaries = userState.toAccountSummaries(),
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `initial state should pull from saved state handle when present`() = runTest {
|
||||
val expectedState = DEFAULT_STATE.copy(
|
||||
|
@ -180,10 +207,12 @@ class LandingViewModelTest : BaseViewModelTest() {
|
|||
|
||||
private fun createViewModel(
|
||||
rememberedEmail: String? = null,
|
||||
userState: UserState? = null,
|
||||
savedStateHandle: SavedStateHandle = SavedStateHandle(),
|
||||
): LandingViewModel = LandingViewModel(
|
||||
authRepository = mockk(relaxed = true) {
|
||||
every { rememberedEmailAddress } returns rememberedEmail
|
||||
every { userStateFlow } returns MutableStateFlow(userState)
|
||||
},
|
||||
environmentRepository = fakeEnvironmentRepository,
|
||||
savedStateHandle = savedStateHandle,
|
||||
|
@ -198,6 +227,7 @@ class LandingViewModelTest : BaseViewModelTest() {
|
|||
isRememberMeEnabled = false,
|
||||
selectedEnvironmentType = Environment.Type.US,
|
||||
errorDialogState = BasicDialogState.Hidden,
|
||||
accountSummaries = emptyList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue