BIT-1133: Add account switcher to Landing Screen (#323)

This commit is contained in:
Brian Yencho
2023-12-05 15:35:27 -06:00
committed by GitHub
parent d4d79bae01
commit c3cb09b156
9 changed files with 250 additions and 8 deletions

View File

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

View File

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

View File

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