PM-17660: Add additional context for the sync feature (#5243)

This commit is contained in:
David Perez
2025-05-22 17:55:50 +00:00
committed by GitHub
parent c9d4d35f07
commit 000a7d141e
11 changed files with 362 additions and 78 deletions
@@ -8,16 +8,21 @@ import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FabPosition
import androidx.compose.material3.HorizontalDivider
@@ -56,7 +61,9 @@ import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.model.Ve
import com.bitwarden.authenticator.ui.platform.components.appbar.BitwardenMediumTopAppBar
import com.bitwarden.authenticator.ui.platform.components.appbar.BitwardenTopAppBar
import com.bitwarden.authenticator.ui.platform.components.appbar.action.BitwardenSearchActionItem
import com.bitwarden.authenticator.ui.platform.components.button.BitwardenFilledButton
import com.bitwarden.authenticator.ui.platform.components.button.BitwardenFilledTonalButton
import com.bitwarden.authenticator.ui.platform.components.button.BitwardenTextButton
import com.bitwarden.authenticator.ui.platform.components.card.BitwardenActionCard
import com.bitwarden.authenticator.ui.platform.components.dialog.BasicDialogState
import com.bitwarden.authenticator.ui.platform.components.dialog.BitwardenBasicDialog
@@ -139,6 +146,10 @@ fun ItemListingScreen(
)
}
ItemListingEvent.NavigateToSyncInformation -> {
intentManager.launchUri("https://bitwarden.com/help/totp-sync".toUri())
}
ItemListingEvent.NavigateToBitwardenSettings -> {
intentManager.startMainBitwardenAppAccountSettings()
}
@@ -230,6 +241,9 @@ fun ItemListingScreen(
viewModel.trySendAction(ItemListingAction.SyncWithBitwardenDismiss)
}
},
onSyncLearnMoreClick = remember(viewModel) {
{ viewModel.trySendAction(ItemListingAction.SyncLearnMoreClick) }
},
)
}
@@ -270,6 +284,9 @@ fun ItemListingScreen(
viewModel.trySendAction(ItemListingAction.SyncWithBitwardenClick)
}
},
onSyncLearnMoreClick = remember(viewModel) {
{ viewModel.trySendAction(ItemListingAction.SyncLearnMoreClick) }
},
onDismissSyncWithBitwardenClick = remember(viewModel) {
{
viewModel.trySendAction(ItemListingAction.SyncWithBitwardenDismiss)
@@ -339,6 +356,7 @@ private fun ItemListingContent(
onDismissDownloadBitwardenClick: () -> Unit,
onSyncWithBitwardenClick: () -> Unit,
onDismissSyncWithBitwardenClick: () -> Unit,
onSyncLearnMoreClick: () -> Unit,
) {
BitwardenScaffold(
modifier = Modifier
@@ -402,23 +420,15 @@ private fun ItemListingContent(
) {
LazyColumn {
item {
when (state.actionCard) {
ItemListingState.ActionCardState.DownloadBitwardenApp ->
DownloadBitwardenActionCard(
modifier = Modifier.padding(horizontal = 16.dp),
onDownloadBitwardenClick = onDownloadBitwardenClick,
onDismissClick = onDismissDownloadBitwardenClick,
)
ItemListingState.ActionCardState.SyncWithBitwarden ->
SyncWithBitwardenActionCard(
modifier = Modifier.padding(16.dp),
onSyncWithBitwardenClick = onSyncWithBitwardenClick,
onDismissClick = onDismissSyncWithBitwardenClick,
)
ItemListingState.ActionCardState.None -> Unit
}
ActionCard(
actionCardState = state.actionCard,
onDownloadBitwardenClick = onDownloadBitwardenClick,
onDownloadBitwardenDismissClick = onDismissDownloadBitwardenClick,
onSyncWithBitwardenClick = onSyncWithBitwardenClick,
onSyncWithBitwardenDismissClick = onDismissSyncWithBitwardenClick,
onSyncLearnMoreClick = onSyncLearnMoreClick,
modifier = Modifier.padding(all = 16.dp),
)
}
if (state.favoriteItems.isNotEmpty()) {
item {
@@ -572,6 +582,7 @@ fun EmptyItemListingContent(
onDownloadBitwardenClick: () -> Unit,
onDismissDownloadBitwardenClick: () -> Unit,
onSyncWithBitwardenClick: () -> Unit,
onSyncLearnMoreClick: () -> Unit,
onDismissSyncWithBitwardenClick: () -> Unit,
) {
BitwardenScaffold(
@@ -635,23 +646,14 @@ fun EmptyItemListingContent(
ItemListingState.ActionCardState.SyncWithBitwarden -> Arrangement.Top
},
) {
when (actionCardState) {
ItemListingState.ActionCardState.DownloadBitwardenApp ->
DownloadBitwardenActionCard(
modifier = Modifier.padding(16.dp),
onDismissClick = onDismissDownloadBitwardenClick,
onDownloadBitwardenClick = onDownloadBitwardenClick,
)
ItemListingState.ActionCardState.SyncWithBitwarden ->
SyncWithBitwardenActionCard(
modifier = Modifier.padding(16.dp),
onDismissClick = onDismissSyncWithBitwardenClick,
onSyncWithBitwardenClick = onSyncWithBitwardenClick,
)
ItemListingState.ActionCardState.None -> Unit
}
ActionCard(
actionCardState = actionCardState,
onDownloadBitwardenClick = onDownloadBitwardenClick,
onDownloadBitwardenDismissClick = onDismissDownloadBitwardenClick,
onSyncWithBitwardenClick = onSyncWithBitwardenClick,
onSyncWithBitwardenDismissClick = onDismissSyncWithBitwardenClick,
onSyncLearnMoreClick = onSyncLearnMoreClick,
)
// Add a spacer if an action card is showing:
when (actionCardState) {
@@ -735,32 +737,114 @@ private fun DownloadBitwardenActionCard(
},
)
@Suppress("LongMethod")
@Composable
private fun SyncWithBitwardenActionCard(
modifier: Modifier = Modifier,
onDismissClick: () -> Unit,
onAppSettingsClick: () -> Unit,
onLearnMoreClick: () -> Unit,
) {
Card(
modifier = modifier,
shape = RoundedCornerShape(size = 16.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainer,
disabledContainerColor = MaterialTheme.colorScheme.surfaceContainer,
),
elevation = CardDefaults.elevatedCardElevation(),
) {
Spacer(Modifier.height(height = 4.dp))
Row(modifier = Modifier.fillMaxWidth()) {
Spacer(Modifier.width(width = 16.dp))
Row(
modifier = Modifier.padding(top = 12.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
painter = rememberVectorPainter(id = R.drawable.ic_bitwarden),
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(size = 20.dp),
)
Spacer(Modifier.width(width = 16.dp))
Text(
text = stringResource(id = R.string.sync_with_the_bitwarden_app),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface,
)
}
Spacer(Modifier.weight(weight = 1f))
Spacer(Modifier.width(width = 16.dp))
IconButton(onClick = onDismissClick) {
Icon(
painter = painterResource(id = R.drawable.ic_close),
contentDescription = stringResource(id = R.string.close),
tint = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.size(size = 24.dp),
)
}
Spacer(Modifier.width(width = 4.dp))
}
Text(
text = stringResource(id = R.string.sync_with_bitwarden_action_card_message),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier
.padding(horizontal = 16.dp)
.padding(start = 36.dp, end = 48.dp)
.fillMaxWidth(),
)
Spacer(Modifier.height(height = 16.dp))
BitwardenFilledButton(
label = stringResource(id = R.string.take_me_to_app_settings),
onClick = onAppSettingsClick,
modifier = Modifier
.padding(horizontal = 16.dp)
.fillMaxWidth(),
)
BitwardenTextButton(
label = stringResource(id = R.string.learn_more),
onClick = onLearnMoreClick,
modifier = Modifier
.padding(horizontal = 16.dp)
.fillMaxWidth(),
)
Spacer(Modifier.height(height = 4.dp))
}
}
@Composable
private fun ActionCard(
actionCardState: ItemListingState.ActionCardState,
onDownloadBitwardenClick: () -> Unit,
onDownloadBitwardenDismissClick: () -> Unit,
onSyncWithBitwardenClick: () -> Unit,
) = BitwardenActionCard(
modifier = modifier,
actionIcon = rememberVectorPainter(R.drawable.ic_refresh),
actionText = stringResource(R.string.sync_with_bitwarden_action_card_message),
callToActionText = stringResource(R.string.go_to_settings),
titleText = stringResource(R.string.sync_with_the_bitwarden_app),
onCardClicked = onSyncWithBitwardenClick,
trailingContent = {
IconButton(
onClick = onDismissClick,
) {
Icon(
painter = painterResource(id = R.drawable.ic_close),
contentDescription = stringResource(id = R.string.close),
tint = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier
.size(24.dp),
onSyncWithBitwardenDismissClick: () -> Unit,
onSyncLearnMoreClick: () -> Unit,
modifier: Modifier = Modifier,
) {
when (actionCardState) {
ItemListingState.ActionCardState.DownloadBitwardenApp -> {
DownloadBitwardenActionCard(
modifier = modifier,
onDownloadBitwardenClick = onDownloadBitwardenClick,
onDismissClick = onDownloadBitwardenDismissClick,
)
}
},
)
ItemListingState.ActionCardState.SyncWithBitwarden -> {
SyncWithBitwardenActionCard(
modifier = modifier,
onAppSettingsClick = onSyncWithBitwardenClick,
onDismissClick = onSyncWithBitwardenDismissClick,
onLearnMoreClick = onSyncLearnMoreClick,
)
}
ItemListingState.ActionCardState.None -> Unit
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -776,6 +860,7 @@ private fun EmptyListingContentPreview() {
onDownloadBitwardenClick = { },
onDismissDownloadBitwardenClick = { },
onSyncWithBitwardenClick = { },
onSyncLearnMoreClick = { },
onDismissSyncWithBitwardenClick = { },
)
}
@@ -153,6 +153,10 @@ class ItemListingViewModel @Inject constructor(
ItemListingAction.SyncWithBitwardenDismiss -> {
handleSyncWithBitwardenDismiss()
}
ItemListingAction.SyncLearnMoreClick -> {
handleSyncLearnMoreClick()
}
}
}
@@ -564,6 +568,10 @@ class ItemListingViewModel @Inject constructor(
}
}
private fun handleSyncLearnMoreClick() {
sendEvent(ItemListingEvent.NavigateToSyncInformation)
}
/**
* Converts a [SharedVerificationCodesState] into an action card for display.
*/
@@ -794,6 +802,11 @@ sealed class ItemListingEvent {
*/
data object NavigateToAppSettings : ItemListingEvent()
/**
* Navigate to the sync information web page.
*/
data object NavigateToSyncInformation : ItemListingEvent()
/**
* Navigate to Bitwarden play store listing.
*/
@@ -872,6 +885,11 @@ sealed class ItemListingAction {
*/
data object SyncWithBitwardenClick : ItemListingAction()
/**
* The user tapped the learn more button on the sync action card.
*/
data object SyncLearnMoreClick : ItemListingAction()
/**
* The user dismissed sync Bitwarden action card.
*/
@@ -886,7 +904,7 @@ sealed class ItemListingAction {
* Represents an action triggered when the user clicks an item in the dropdown menu.
*
* @param menuAction The action selected from the dropdown menu.
* @param id The identifier of the item on which the action is being performed.
* @param item The item on which the action is being performed.
*/
data class DropdownMenuClick(
val menuAction: VaultDropdownMenuAction,
@@ -44,8 +44,8 @@ fun BitwardenActionCard(
onClick = onCardClicked,
shape = RoundedCornerShape(size = 16.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainerHighest,
disabledContainerColor = MaterialTheme.colorScheme.surfaceContainerHighest,
containerColor = MaterialTheme.colorScheme.surfaceContainer,
disabledContainerColor = MaterialTheme.colorScheme.surfaceContainer,
),
modifier = modifier,
elevation = CardDefaults.elevatedCardElevation(),
@@ -18,6 +18,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.unit.dp
/**
@@ -37,7 +38,7 @@ fun BitwardenTextRow(
text: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
description: String? = null,
description: AnnotatedString? = null,
withDivider: Boolean = false,
content: (@Composable () -> Unit)? = null,
) {
@@ -65,6 +65,8 @@ import com.bitwarden.authenticator.ui.platform.theme.AuthenticatorTheme
import com.bitwarden.authenticator.ui.platform.util.displayLabel
import com.bitwarden.ui.platform.base.util.EventsEffect
import com.bitwarden.ui.platform.base.util.mirrorIfRtl
import com.bitwarden.ui.platform.base.util.spanStyleOf
import com.bitwarden.ui.platform.base.util.toAnnotatedString
import com.bitwarden.ui.platform.components.util.rememberVectorPainter
import com.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
import com.bitwarden.ui.util.Text
@@ -107,6 +109,10 @@ fun SettingsScreen(
intentManager.launchUri("https://bitwarden.com/privacy".toUri())
}
SettingsEvent.NavigateToSyncInformation -> {
intentManager.launchUri("https://bitwarden.com/help/totp-sync".toUri())
}
SettingsEvent.NavigateToBitwardenApp -> {
intentManager.startActivity(
@@ -177,6 +183,9 @@ fun SettingsScreen(
viewModel.trySendAction(SettingsAction.DataClick.SyncWithBitwardenClick)
}
},
onSyncLearnMoreClick = remember(viewModel) {
{ viewModel.trySendAction(SettingsAction.DataClick.SyncLearnMoreClick) }
},
onDefaultSaveOptionUpdated = remember(viewModel) {
{
viewModel.trySendAction(
@@ -280,6 +289,7 @@ private fun VaultSettings(
onImportClick: () -> Unit,
onBackupClick: () -> Unit,
onSyncWithBitwardenClick: () -> Unit,
onSyncLearnMoreClick: () -> Unit,
onDefaultSaveOptionUpdated: (DefaultSaveOption) -> Unit,
shouldShowSyncWithBitwardenApp: Boolean,
shouldShowDefaultSaveOptions: Boolean,
@@ -339,6 +349,22 @@ private fun VaultSettings(
Spacer(modifier = Modifier.height(8.dp))
BitwardenTextRow(
text = stringResource(id = R.string.sync_with_bitwarden_app),
description = R.string
.this_feature_is_not_not_yet_available_for_self_hosted_users
.toAnnotatedString(
style = spanStyleOf(
color = MaterialTheme.colorScheme.onSurfaceVariant,
textStyle = MaterialTheme.typography.bodyMedium,
),
linkHighlightStyle = spanStyleOf(
color = MaterialTheme.colorScheme.primary,
textStyle = MaterialTheme.typography.labelLarge,
),
) {
when (it) {
"learnMore" -> onSyncLearnMoreClick()
}
},
onClick = onSyncWithBitwardenClick,
modifier = modifier,
withDivider = true,
@@ -184,6 +184,7 @@ class SettingsViewModel @Inject constructor(
SettingsAction.DataClick.ImportClick -> handleImportClick()
SettingsAction.DataClick.BackupClick -> handleBackupClick()
SettingsAction.DataClick.SyncWithBitwardenClick -> handleSyncWithBitwardenClick()
SettingsAction.DataClick.SyncLearnMoreClick -> handleSyncLearnMoreClick()
is SettingsAction.DataClick.DefaultSaveOptionUpdated ->
handleDefaultSaveOptionChosen(action)
}
@@ -215,6 +216,10 @@ class SettingsViewModel @Inject constructor(
}
}
private fun handleSyncLearnMoreClick() {
sendEvent(SettingsEvent.NavigateToSyncInformation)
}
private fun handleExportClick() {
sendEvent(SettingsEvent.NavigateToExport)
}
@@ -424,6 +429,11 @@ sealed class SettingsEvent {
*/
data object NavigateToPrivacyPolicy : SettingsEvent()
/**
* Navigate to the sync learn more web page.
*/
data object NavigateToSyncInformation : SettingsEvent()
/**
* Navigate to the Bitwarden account settings.
*/
@@ -491,7 +501,12 @@ sealed class SettingsAction(
data object SyncWithBitwardenClick : DataClick()
/**
* User confirmed a new [DeafultSaveOption].
* Indicates the user clicked sync learn more button.
*/
data object SyncLearnMoreClick : DataClick()
/**
* User confirmed a new [DefaultSaveOption].
*/
data class DefaultSaveOptionUpdated(val option: DefaultSaveOption) : DataClick()
}
@@ -125,11 +125,13 @@
<string name="download_bitwarden_card_message">Store all of your logins and sync verification codes directly with the Authenticator app.</string>
<string name="download_now">Download now</string>
<string name="sync_with_bitwarden_app">Sync with Bitwarden app</string>
<string name="this_feature_is_not_not_yet_available_for_self_hosted_users">This feature is not yet available for self-hosted users. <annotation link="learnMore">Learn more</annotation></string>
<string name="shared_codes_error">Unable to sync codes from the Bitwarden app. Make sure both apps are up-to-date. You can still access your existing codes in the Bitwarden app.</string>
<string name="shared_accounts_header">%1$s | %2$s</string>
<string name="sync_with_the_bitwarden_app">Sync with the Bitwarden app</string>
<string name="go_to_settings">Go to settings</string>
<string name="sync_with_bitwarden_action_card_message">Allow Authenticator app syncing in settings to view all of your verification codes here.</string>
<string name="sync_with_bitwarden_action_card_message">In order to view all of your verification codes, youll need to allow for syncing on all of your accounts.</string>
<string name="take_me_to_app_settings">Take me to the app settings</string>
<string name="something_went_wrong">Something went wrong</string>
<string name="please_try_again">Please try again</string>
<string name="move_to_bitwarden">Move to Bitwarden</string>
@@ -8,6 +8,7 @@ import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo
import androidx.compose.ui.test.performTouchInput
import androidx.core.net.toUri
import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.model.SharedCodesDisplayState
import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.model.VaultDropdownMenuAction
import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.model.VerificationCodeDisplayItem
@@ -17,6 +18,8 @@ import com.bitwarden.authenticator.ui.platform.manager.permissions.FakePermissio
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
import com.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
import com.bitwarden.ui.util.asText
import com.bitwarden.ui.util.onNodeWithContentDescriptionAfterScroll
import com.bitwarden.ui.util.onNodeWithTextAfterScroll
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
@@ -44,7 +47,9 @@ class ItemListingScreenTest : AuthenticatorComposeTest() {
every { trySendAction(any()) } just runs
}
private val intentManager: IntentManager = mockk()
private val intentManager: IntentManager = mockk {
every { launchUri(uri = any()) } just runs
}
private val permissionsManager = FakePermissionManager()
@Before
@@ -64,6 +69,14 @@ class ItemListingScreenTest : AuthenticatorComposeTest() {
}
}
@Test
fun `on NavigateToSyncInformation should launch sync uri`() {
mutableEventFlow.tryEmit(ItemListingEvent.NavigateToSyncInformation)
verify(exactly = 1) {
intentManager.launchUri(uri = "https://bitwarden.com/help/totp-sync".toUri())
}
}
@Test
@Suppress("MaxLineLength")
fun `when denying camera permissions and attempting to add a code we should be shown the manual entry screen`() {
@@ -175,35 +188,74 @@ class ItemListingScreenTest : AuthenticatorComposeTest() {
@Test
@Suppress("MaxLineLength")
fun `on sync with bitwarden action card click in empty state should send SyncWithBitwardenClick`() {
mutableStateFlow.value = DEFAULT_STATE.copy(
viewState = ItemListingState.ViewState.NoItems(
actionCard = ItemListingState.ActionCardState.SyncWithBitwarden,
),
)
fun `on sync with bitwarden app settings click in empty state should send SyncWithBitwardenClick`() {
mutableStateFlow.update {
it.copy(
viewState = ItemListingState.ViewState.NoItems(
actionCard = ItemListingState.ActionCardState.SyncWithBitwarden,
),
)
}
composeTestRule
.onNodeWithText("Sync with the Bitwarden app")
.onNodeWithText(text = "Take me to the app settings")
.performClick()
verify { viewModel.trySendAction(ItemListingAction.SyncWithBitwardenClick) }
}
@Test
@Suppress("MaxLineLength")
fun `on sync with bitwarden action card click in full state should send SyncWithBitwardenClick`() {
mutableStateFlow.value = DEFAULT_STATE.copy(
viewState = ItemListingState.ViewState.Content(
favoriteItems = emptyList(),
itemList = emptyList(),
sharedItems = SharedCodesDisplayState.Codes(emptyList()),
actionCard = ItemListingState.ActionCardState.SyncWithBitwarden,
),
)
fun `on sync with bitwarden learn more click in empty state should send SyncLearnMoreClick`() {
mutableStateFlow.update {
it.copy(
viewState = ItemListingState.ViewState.NoItems(
actionCard = ItemListingState.ActionCardState.SyncWithBitwarden,
),
)
}
composeTestRule
.onNodeWithText("Sync with the Bitwarden app")
.onNodeWithText(text = "Learn more")
.performClick()
verify { viewModel.trySendAction(ItemListingAction.SyncLearnMoreClick) }
}
@Test
@Suppress("MaxLineLength")
fun `on sync with bitwarden app settings click in full state should send SyncWithBitwardenClick`() {
mutableStateFlow.update {
it.copy(
viewState = ItemListingState.ViewState.Content(
favoriteItems = emptyList(),
itemList = emptyList(),
sharedItems = SharedCodesDisplayState.Codes(emptyList()),
actionCard = ItemListingState.ActionCardState.SyncWithBitwarden,
),
)
}
composeTestRule
.onNodeWithTextAfterScroll(text = "Take me to the app settings")
.performClick()
verify { viewModel.trySendAction(ItemListingAction.SyncWithBitwardenClick) }
}
@Test
fun `on sync with bitwarden learn more click in full state should send SyncLearnMoreClick`() {
mutableStateFlow.update {
it.copy(
viewState = ItemListingState.ViewState.Content(
favoriteItems = emptyList(),
itemList = emptyList(),
sharedItems = SharedCodesDisplayState.Codes(emptyList()),
actionCard = ItemListingState.ActionCardState.SyncWithBitwarden,
),
)
}
composeTestRule
.onNodeWithTextAfterScroll(text = "Learn more")
.performClick()
verify { viewModel.trySendAction(ItemListingAction.SyncLearnMoreClick) }
}
@Test
@Suppress("MaxLineLength")
fun `on sync with bitwarden action card dismiss in empty state should send SyncWithBitwardenDismiss`() {
@@ -235,6 +287,64 @@ class ItemListingScreenTest : AuthenticatorComposeTest() {
verify { viewModel.trySendAction(ItemListingAction.SyncWithBitwardenDismiss) }
}
@Test
fun `on download bitwarden click in empty state should send DownloadBitwardenClick`() {
mutableStateFlow.value = DEFAULT_STATE.copy(
viewState = ItemListingState.ViewState.NoItems(
actionCard = ItemListingState.ActionCardState.DownloadBitwardenApp,
),
)
composeTestRule
.onNodeWithText(text = "Download now")
.performClick()
verify { viewModel.trySendAction(ItemListingAction.DownloadBitwardenClick) }
}
@Test
fun `on download bitwarden click in full state should send DownloadBitwardenClick`() {
mutableStateFlow.value = DEFAULT_STATE.copy(
viewState = ItemListingState.ViewState.Content(
favoriteItems = emptyList(),
itemList = emptyList(),
sharedItems = SharedCodesDisplayState.Codes(emptyList()),
actionCard = ItemListingState.ActionCardState.DownloadBitwardenApp,
),
)
composeTestRule
.onNodeWithTextAfterScroll(text = "Download now")
.performClick()
verify { viewModel.trySendAction(ItemListingAction.DownloadBitwardenClick) }
}
@Test
fun `on download bitwarden dismiss in empty state should send DownloadBitwardenDismiss`() {
mutableStateFlow.value = DEFAULT_STATE.copy(
viewState = ItemListingState.ViewState.NoItems(
actionCard = ItemListingState.ActionCardState.DownloadBitwardenApp,
),
)
composeTestRule
.onNodeWithContentDescription(label = "Close")
.performClick()
verify { viewModel.trySendAction(ItemListingAction.DownloadBitwardenDismiss) }
}
@Test
fun `on download bitwarden dismiss in full state should send DownloadBitwardenDismiss`() {
mutableStateFlow.value = DEFAULT_STATE.copy(
viewState = ItemListingState.ViewState.Content(
favoriteItems = emptyList(),
itemList = emptyList(),
sharedItems = SharedCodesDisplayState.Codes(emptyList()),
actionCard = ItemListingState.ActionCardState.DownloadBitwardenApp,
),
)
composeTestRule
.onNodeWithContentDescriptionAfterScroll(label = "Close")
.performClick()
verify { viewModel.trySendAction(ItemListingAction.DownloadBitwardenDismiss) }
}
@Test
fun `clicking Move to Bitwarden should send MoveToBitwardenClick`() {
mutableStateFlow.value = DEFAULT_STATE.copy(
@@ -368,6 +368,15 @@ class ItemListingViewModelTest : BaseViewModelTest() {
assertEquals(expectedState, viewModel.stateFlow.value)
}
@Test
fun `on SyncLearnMoreClick should send NavigateToSyncInformation`() = runTest {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(ItemListingAction.SyncLearnMoreClick)
assertEquals(ItemListingEvent.NavigateToSyncInformation, awaitItem())
}
}
@Test
fun `on MoveToBitwardenClick receive should call startAddTotpLoginItemFlow`() {
val expectedUriString = "expectedUriString"
@@ -119,6 +119,15 @@ class SettingsScreenTest : AuthenticatorComposeTest() {
}
}
@Test
fun `on NavigateToSyncInformation receive launch sync totp uri`() {
every { intentManager.launchUri(uri = any()) } just runs
mutableEventFlow.tryEmit(SettingsEvent.NavigateToSyncInformation)
verify(exactly = 1) {
intentManager.launchUri("https://bitwarden.com/help/totp-sync".toUri())
}
}
@Test
fun `Default Save Option row should be hidden when showDefaultSaveOptionRow is false`() {
mutableStateFlow.value = DEFAULT_STATE
@@ -157,6 +157,15 @@ class SettingsViewModelTest : BaseViewModelTest() {
}
}
@Test
fun `on SyncLearnMoreClick should emit NavigateToSyncInformation`() = runTest {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(SettingsAction.DataClick.SyncLearnMoreClick)
assertEquals(SettingsEvent.NavigateToSyncInformation, awaitItem())
}
}
@Test
@Suppress("MaxLineLength")
fun `Default save option row should only show when shared codes state shows syncing as enabled`() =