From 00e9540d51fdc29f93f033e6736445e96f0f0874 Mon Sep 17 00:00:00 2001 From: David Perez Date: Thu, 7 Dec 2023 09:17:22 -0600 Subject: [PATCH] Migrate VaultAddItemScreen to use a LazyColumn (#345) --- .../feature/additem/AddEditLoginItems.kt | 282 ++++++++++++ .../additem/AddEditSecureNotesItems.kt | 170 ++++++++ .../feature/additem/VaultAddItemNavigation.kt | 2 +- .../feature/additem/VaultAddItemScreen.kt | 407 ++---------------- .../bitwarden/ui/util/ComposeTestHelpers.kt | 41 +- .../feature/additem/VaultAddItemScreenTest.kt | 162 ++++--- 6 files changed, 596 insertions(+), 468 deletions(-) create mode 100644 app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditLoginItems.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditSecureNotesItems.kt diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditLoginItems.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditLoginItems.kt new file mode 100644 index 0000000000..f60bb3063d --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditLoginItems.kt @@ -0,0 +1,282 @@ +package com.x8bit.bitwarden.ui.vault.feature.additem + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.x8bit.bitwarden.R +import com.x8bit.bitwarden.ui.platform.components.BitwardenFilledTonalButton +import com.x8bit.bitwarden.ui.platform.components.BitwardenFilledTonalButtonWithIcon +import com.x8bit.bitwarden.ui.platform.components.BitwardenIconButtonWithResource +import com.x8bit.bitwarden.ui.platform.components.BitwardenListHeaderText +import com.x8bit.bitwarden.ui.platform.components.BitwardenMultiSelectButton +import com.x8bit.bitwarden.ui.platform.components.BitwardenPasswordFieldWithActions +import com.x8bit.bitwarden.ui.platform.components.BitwardenSwitch +import com.x8bit.bitwarden.ui.platform.components.BitwardenSwitchWithActions +import com.x8bit.bitwarden.ui.platform.components.BitwardenTextField +import com.x8bit.bitwarden.ui.platform.components.BitwardenTextFieldWithActions +import com.x8bit.bitwarden.ui.platform.components.model.IconResource +import kotlinx.collections.immutable.toImmutableList + +/** + * The UI for adding and editing a login cipher. + */ +@Suppress("LongMethod") +fun LazyListScope.addEditLoginItems( + state: VaultAddItemState.ItemType.Login, + loginItemTypeHandlers: VaultAddLoginItemTypeHandlers, +) { + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenTextField( + label = stringResource(id = R.string.name), + value = state.name, + onValueChange = loginItemTypeHandlers.onNameTextChange, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenTextFieldWithActions( + label = stringResource(id = R.string.username), + value = state.username, + onValueChange = loginItemTypeHandlers.onUsernameTextChange, + actions = { + BitwardenIconButtonWithResource( + iconRes = IconResource( + iconPainter = painterResource(id = R.drawable.ic_generator), + contentDescription = stringResource(id = R.string.generate_username), + ), + onClick = loginItemTypeHandlers.onOpenUsernameGeneratorClick, + ) + }, + modifier = Modifier.padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenPasswordFieldWithActions( + label = stringResource(id = R.string.password), + value = state.password, + onValueChange = loginItemTypeHandlers.onPasswordTextChange, + modifier = Modifier + .padding(horizontal = 16.dp), + ) { + BitwardenIconButtonWithResource( + iconRes = IconResource( + iconPainter = painterResource(id = R.drawable.ic_check_mark), + contentDescription = stringResource(id = R.string.check_password), + ), + onClick = loginItemTypeHandlers.onPasswordCheckerClick, + ) + BitwardenIconButtonWithResource( + iconRes = IconResource( + iconPainter = painterResource(id = R.drawable.ic_generator), + contentDescription = stringResource(id = R.string.generate_password), + ), + onClick = loginItemTypeHandlers.onOpenPasswordGeneratorClick, + ) + } + } + + item { + Spacer(modifier = Modifier.height(24.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.authenticator_key), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(16.dp)) + BitwardenFilledTonalButtonWithIcon( + label = stringResource(id = R.string.setup_totp), + icon = painterResource(id = R.drawable.ic_light_bulb), + onClick = loginItemTypeHandlers.onSetupTotpClick, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(24.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.ur_is), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenTextFieldWithActions( + label = stringResource(id = R.string.uri), + value = state.uri, + onValueChange = loginItemTypeHandlers.onUriTextChange, + actions = { + BitwardenIconButtonWithResource( + iconRes = IconResource( + iconPainter = painterResource(id = R.drawable.ic_settings), + contentDescription = stringResource(id = R.string.options), + ), + onClick = loginItemTypeHandlers.onUriSettingsClick, + ) + }, + modifier = Modifier.padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(16.dp)) + BitwardenFilledTonalButton( + label = stringResource(id = R.string.new_uri), + onClick = loginItemTypeHandlers.onAddNewUriClick, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(24.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.miscellaneous), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenMultiSelectButton( + label = stringResource(id = R.string.folder), + options = state.availableFolders.toImmutableList(), + selectedOption = state.folder, + onOptionSelected = loginItemTypeHandlers.onFolderTextChange, + modifier = Modifier.padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(16.dp)) + BitwardenSwitch( + label = stringResource( + id = R.string.favorite, + ), + isChecked = state.favorite, + onCheckedChange = loginItemTypeHandlers.onToggleFavorite, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(16.dp)) + BitwardenSwitchWithActions( + label = stringResource(id = R.string.password_prompt), + isChecked = state.masterPasswordReprompt, + onCheckedChange = loginItemTypeHandlers.onToggleMasterPasswordReprompt, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + actions = { + IconButton(onClick = loginItemTypeHandlers.onTooltipClick) { + Icon( + painter = painterResource(id = R.drawable.ic_tooltip), + tint = MaterialTheme.colorScheme.onSurface, + contentDescription = stringResource( + id = R.string.master_password_re_prompt_help, + ), + ) + } + }, + ) + } + + item { + Spacer(modifier = Modifier.height(24.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.notes), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenTextField( + singleLine = false, + label = stringResource(id = R.string.notes), + value = state.notes, + onValueChange = loginItemTypeHandlers.onNotesTextChange, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(24.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.custom_fields), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(16.dp)) + BitwardenFilledTonalButton( + label = stringResource(id = R.string.new_custom_field), + onClick = loginItemTypeHandlers.onAddNewCustomFieldClick, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(24.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.ownership), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenMultiSelectButton( + label = stringResource(id = R.string.who_owns_this_item), + options = state.availableOwners.toImmutableList(), + selectedOption = state.ownership, + onOptionSelected = loginItemTypeHandlers.onOwnershipTextChange, + modifier = Modifier.padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(24.dp)) + } +} diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditSecureNotesItems.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditSecureNotesItems.kt new file mode 100644 index 0000000000..74099a207f --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditSecureNotesItems.kt @@ -0,0 +1,170 @@ +package com.x8bit.bitwarden.ui.vault.feature.additem + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.x8bit.bitwarden.R +import com.x8bit.bitwarden.ui.platform.components.BitwardenFilledTonalButton +import com.x8bit.bitwarden.ui.platform.components.BitwardenListHeaderText +import com.x8bit.bitwarden.ui.platform.components.BitwardenMultiSelectButton +import com.x8bit.bitwarden.ui.platform.components.BitwardenSwitch +import com.x8bit.bitwarden.ui.platform.components.BitwardenSwitchWithActions +import com.x8bit.bitwarden.ui.platform.components.BitwardenTextField +import kotlinx.collections.immutable.toImmutableList + +/** + * The UI for adding and editing a secure notes cipher. + */ +@Suppress("LongMethod") +fun LazyListScope.addEditSecureNotesItems( + state: VaultAddItemState.ItemType.SecureNotes, + secureNotesTypeHandlers: VaultAddSecureNotesItemTypeHandlers, +) { + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenTextField( + label = stringResource(id = R.string.name), + value = state.name, + onValueChange = secureNotesTypeHandlers.onNameTextChange, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(24.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.miscellaneous), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenMultiSelectButton( + label = stringResource(id = R.string.folder), + options = state.availableFolders.map { it.invoke() }.toImmutableList(), + selectedOption = state.folderName.invoke(), + onOptionSelected = secureNotesTypeHandlers.onFolderTextChange, + modifier = Modifier + .padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(16.dp)) + BitwardenSwitch( + label = stringResource(id = R.string.favorite), + isChecked = state.favorite, + onCheckedChange = secureNotesTypeHandlers.onToggleFavorite, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(16.dp)) + BitwardenSwitchWithActions( + label = stringResource(id = R.string.password_prompt), + isChecked = state.masterPasswordReprompt, + onCheckedChange = secureNotesTypeHandlers.onToggleMasterPasswordReprompt, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + actions = { + IconButton(onClick = secureNotesTypeHandlers.onTooltipClick) { + Icon( + painter = painterResource(id = R.drawable.ic_tooltip), + tint = MaterialTheme.colorScheme.onSurface, + contentDescription = stringResource( + id = R.string.master_password_re_prompt_help, + ), + ) + } + }, + ) + } + + item { + Spacer(modifier = Modifier.height(24.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.notes), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenTextField( + singleLine = false, + label = stringResource(id = R.string.notes), + value = state.notes, + onValueChange = secureNotesTypeHandlers.onNotesTextChange, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(24.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.custom_fields), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(16.dp)) + BitwardenFilledTonalButton( + label = stringResource(id = R.string.new_custom_field), + onClick = secureNotesTypeHandlers.onAddNewCustomFieldClick, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(24.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.ownership), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenMultiSelectButton( + label = stringResource(id = R.string.who_owns_this_item), + options = state.availableOwners.toImmutableList(), + selectedOption = state.ownership, + onOptionSelected = secureNotesTypeHandlers.onOwnershipTextChange, + modifier = Modifier + .padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(24.dp)) + } +} diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemNavigation.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemNavigation.kt index 08580f8158..22ba260066 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemNavigation.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemNavigation.kt @@ -15,7 +15,7 @@ fun NavGraphBuilder.vaultAddItemDestination( onNavigateBack: () -> Unit, ) { composable( - ADD_ITEM_ROUTE, + route = ADD_ITEM_ROUTE, enterTransition = TransitionProviders.Enter.slideUp, exitTransition = TransitionProviders.Exit.slideDown, popEnterTransition = TransitionProviders.Enter.slideUp, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemScreen.kt index 3f09dff863..fe4c8c0e7a 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemScreen.kt @@ -1,8 +1,6 @@ package com.x8bit.bitwarden.ui.vault.feature.additem import android.widget.Toast -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -10,12 +8,8 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable @@ -31,20 +25,11 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.x8bit.bitwarden.R import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect -import com.x8bit.bitwarden.ui.platform.components.BitwardenFilledTonalButton -import com.x8bit.bitwarden.ui.platform.components.BitwardenFilledTonalButtonWithIcon -import com.x8bit.bitwarden.ui.platform.components.BitwardenIconButtonWithResource import com.x8bit.bitwarden.ui.platform.components.BitwardenListHeaderText import com.x8bit.bitwarden.ui.platform.components.BitwardenMultiSelectButton -import com.x8bit.bitwarden.ui.platform.components.BitwardenPasswordFieldWithActions import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold -import com.x8bit.bitwarden.ui.platform.components.BitwardenSwitch -import com.x8bit.bitwarden.ui.platform.components.BitwardenSwitchWithActions import com.x8bit.bitwarden.ui.platform.components.BitwardenTextButton -import com.x8bit.bitwarden.ui.platform.components.BitwardenTextField -import com.x8bit.bitwarden.ui.platform.components.BitwardenTextFieldWithActions import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar -import com.x8bit.bitwarden.ui.platform.components.model.IconResource import kotlinx.collections.immutable.toImmutableList /** @@ -58,7 +43,6 @@ fun VaultAddItemScreen( viewModel: VaultAddItemViewModel = hiltViewModel(), ) { val state by viewModel.stateFlow.collectAsStateWithLifecycle() - val scrollState = rememberScrollState() val context = LocalContext.current EventsEffect(viewModel = viewModel) { event -> @@ -80,10 +64,8 @@ fun VaultAddItemScreen( } val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) - BitwardenScaffold( modifier = Modifier - .imePadding() .fillMaxSize() .nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { @@ -106,32 +88,36 @@ fun VaultAddItemScreen( ) }, ) { innerPadding -> - Column( + LazyColumn( modifier = Modifier + .imePadding() .padding(innerPadding) - .fillMaxSize() - .verticalScroll(scrollState), + .fillMaxSize(), ) { - BitwardenListHeaderText( - label = stringResource(id = R.string.item_information), - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - Spacer(modifier = Modifier.height(8.dp)) - TypeOptionsItem( - selectedType = state.selectedType, - onTypeOptionClicked = remember(viewModel) { - { typeOption: VaultAddItemState.ItemTypeOption -> - viewModel.trySendAction(VaultAddItemAction.TypeOptionSelect(typeOption)) - } - }, - modifier = Modifier.padding(horizontal = 16.dp), - ) + item { + BitwardenListHeaderText( + label = stringResource(id = R.string.item_information), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + item { + Spacer(modifier = Modifier.height(8.dp)) + TypeOptionsItem( + selectedType = state.selectedType, + onTypeOptionClicked = remember(viewModel) { + { typeOption: VaultAddItemState.ItemTypeOption -> + viewModel.trySendAction(VaultAddItemAction.TypeOptionSelect(typeOption)) + } + }, + modifier = Modifier.padding(horizontal = 16.dp), + ) + } when (val selectedType = state.selectedType) { is VaultAddItemState.ItemType.Login -> { - AddLoginTypeItemContent( + addEditLoginItems( state = selectedType, loginItemTypeHandlers = loginItemTypeHandlers, ) @@ -146,12 +132,16 @@ fun VaultAddItemScreen( } is VaultAddItemState.ItemType.SecureNotes -> { - AddSecureNotesTypeItemContent( + addEditSecureNotesItems( state = selectedType, secureNotesTypeHandlers = secureNotesTypeHandlers, ) } } + + item { + Spacer(modifier = Modifier.navigationBarsPadding()) + } } } } @@ -179,340 +169,3 @@ private fun TypeOptionsItem( modifier = modifier, ) } - -@Suppress("LongMethod") -@Composable -private fun ColumnScope.AddLoginTypeItemContent( - state: VaultAddItemState.ItemType.Login, - loginItemTypeHandlers: VaultAddLoginItemTypeHandlers, -) { - Spacer(modifier = Modifier.height(8.dp)) - BitwardenTextField( - label = stringResource(id = R.string.name), - value = state.name, - onValueChange = loginItemTypeHandlers.onNameTextChange, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - - Spacer(modifier = Modifier.height(8.dp)) - BitwardenTextFieldWithActions( - label = stringResource(id = R.string.username), - value = state.username, - onValueChange = loginItemTypeHandlers.onUsernameTextChange, - actions = { - BitwardenIconButtonWithResource( - iconRes = IconResource( - iconPainter = painterResource(id = R.drawable.ic_generator), - contentDescription = stringResource(id = R.string.generate_username), - ), - onClick = loginItemTypeHandlers.onOpenUsernameGeneratorClick, - ) - }, - modifier = Modifier.padding(horizontal = 16.dp), - ) - - Spacer(modifier = Modifier.height(8.dp)) - BitwardenPasswordFieldWithActions( - label = stringResource(id = R.string.password), - value = state.password, - onValueChange = loginItemTypeHandlers.onPasswordTextChange, - modifier = Modifier - .padding(horizontal = 16.dp), - ) { - BitwardenIconButtonWithResource( - iconRes = IconResource( - iconPainter = painterResource(id = R.drawable.ic_check_mark), - contentDescription = stringResource(id = R.string.check_password), - ), - onClick = loginItemTypeHandlers.onPasswordCheckerClick, - ) - BitwardenIconButtonWithResource( - iconRes = IconResource( - iconPainter = painterResource(id = R.drawable.ic_generator), - contentDescription = stringResource(id = R.string.generate_password), - ), - onClick = loginItemTypeHandlers.onOpenPasswordGeneratorClick, - ) - } - - Spacer(modifier = Modifier.height(24.dp)) - BitwardenListHeaderText( - label = stringResource(id = R.string.authenticator_key), - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - - Spacer(modifier = Modifier.height(16.dp)) - BitwardenFilledTonalButtonWithIcon( - label = stringResource(id = R.string.setup_totp), - icon = painterResource(id = R.drawable.ic_light_bulb), - onClick = loginItemTypeHandlers.onSetupTotpClick, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - - Spacer(modifier = Modifier.height(24.dp)) - BitwardenListHeaderText( - label = stringResource(id = R.string.ur_is), - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - - Spacer(modifier = Modifier.height(8.dp)) - BitwardenTextFieldWithActions( - label = stringResource(id = R.string.uri), - value = state.uri, - onValueChange = loginItemTypeHandlers.onUriTextChange, - actions = { - BitwardenIconButtonWithResource( - iconRes = IconResource( - iconPainter = painterResource(id = R.drawable.ic_settings), - contentDescription = stringResource(id = R.string.options), - ), - onClick = loginItemTypeHandlers.onUriSettingsClick, - ) - }, - modifier = Modifier.padding(horizontal = 16.dp), - ) - - Spacer(modifier = Modifier.height(16.dp)) - BitwardenFilledTonalButton( - label = stringResource(id = R.string.new_uri), - onClick = loginItemTypeHandlers.onAddNewUriClick, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - - Spacer(modifier = Modifier.height(24.dp)) - BitwardenListHeaderText( - label = stringResource(id = R.string.miscellaneous), - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - - Spacer(modifier = Modifier.height(8.dp)) - BitwardenMultiSelectButton( - label = stringResource(id = R.string.folder), - options = state.availableFolders.toImmutableList(), - selectedOption = state.folder, - onOptionSelected = loginItemTypeHandlers.onFolderTextChange, - modifier = Modifier.padding(horizontal = 16.dp), - ) - - Spacer(modifier = Modifier.height(16.dp)) - BitwardenSwitch( - label = stringResource( - id = R.string.favorite, - ), - isChecked = state.favorite, - onCheckedChange = loginItemTypeHandlers.onToggleFavorite, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - - Spacer(modifier = Modifier.height(16.dp)) - BitwardenSwitchWithActions( - label = stringResource(id = R.string.password_prompt), - isChecked = state.masterPasswordReprompt, - onCheckedChange = loginItemTypeHandlers.onToggleMasterPasswordReprompt, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - actions = { - IconButton(onClick = loginItemTypeHandlers.onTooltipClick) { - Icon( - painter = painterResource(id = R.drawable.ic_tooltip), - tint = MaterialTheme.colorScheme.onSurface, - contentDescription = stringResource( - id = R.string.master_password_re_prompt_help, - ), - ) - } - }, - ) - - Spacer(modifier = Modifier.height(24.dp)) - BitwardenListHeaderText( - label = stringResource(id = R.string.notes), - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - - Spacer(modifier = Modifier.height(8.dp)) - BitwardenTextField( - singleLine = false, - label = stringResource(id = R.string.notes), - value = state.notes, - onValueChange = loginItemTypeHandlers.onNotesTextChange, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - - Spacer(modifier = Modifier.height(24.dp)) - BitwardenListHeaderText( - label = stringResource(id = R.string.custom_fields), - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - - Spacer(modifier = Modifier.height(16.dp)) - BitwardenFilledTonalButton( - label = stringResource(id = R.string.new_custom_field), - onClick = loginItemTypeHandlers.onAddNewCustomFieldClick, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - - Spacer(modifier = Modifier.height(24.dp)) - BitwardenListHeaderText( - label = stringResource(id = R.string.ownership), - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - - Spacer(modifier = Modifier.height(8.dp)) - BitwardenMultiSelectButton( - label = stringResource(id = R.string.who_owns_this_item), - options = state.availableOwners.toImmutableList(), - selectedOption = state.ownership, - onOptionSelected = loginItemTypeHandlers.onOwnershipTextChange, - modifier = Modifier.padding(horizontal = 16.dp), - ) - Spacer(modifier = Modifier.height(24.dp)) - Spacer(modifier = Modifier.navigationBarsPadding()) -} - -@Suppress("LongMethod") -@Composable -private fun ColumnScope.AddSecureNotesTypeItemContent( - state: VaultAddItemState.ItemType.SecureNotes, - secureNotesTypeHandlers: VaultAddSecureNotesItemTypeHandlers, -) { - Spacer(modifier = Modifier.height(8.dp)) - BitwardenTextField( - label = stringResource(id = R.string.name), - value = state.name, - onValueChange = secureNotesTypeHandlers.onNameTextChange, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - - Spacer(modifier = Modifier.height(24.dp)) - BitwardenListHeaderText( - label = stringResource(id = R.string.miscellaneous), - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - - Spacer(modifier = Modifier.height(8.dp)) - BitwardenMultiSelectButton( - label = stringResource(id = R.string.folder), - options = state.availableFolders.map { it.invoke() }.toImmutableList(), - selectedOption = state.folderName.invoke(), - onOptionSelected = secureNotesTypeHandlers.onFolderTextChange, - modifier = Modifier - .padding(horizontal = 16.dp), - ) - - Spacer(modifier = Modifier.height(16.dp)) - BitwardenSwitch( - label = stringResource(id = R.string.favorite), - isChecked = state.favorite, - onCheckedChange = secureNotesTypeHandlers.onToggleFavorite, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - - Spacer(modifier = Modifier.height(16.dp)) - BitwardenSwitchWithActions( - label = stringResource(id = R.string.password_prompt), - isChecked = state.masterPasswordReprompt, - onCheckedChange = secureNotesTypeHandlers.onToggleMasterPasswordReprompt, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - actions = { - IconButton(onClick = secureNotesTypeHandlers.onTooltipClick) { - Icon( - painter = painterResource(id = R.drawable.ic_tooltip), - tint = MaterialTheme.colorScheme.onSurface, - contentDescription = stringResource( - id = R.string.master_password_re_prompt_help, - ), - ) - } - }, - ) - - Spacer(modifier = Modifier.height(24.dp)) - BitwardenListHeaderText( - label = stringResource(id = R.string.notes), - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - - Spacer(modifier = Modifier.height(8.dp)) - BitwardenTextField( - singleLine = false, - label = stringResource(id = R.string.notes), - value = state.notes, - onValueChange = secureNotesTypeHandlers.onNotesTextChange, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - - Spacer(modifier = Modifier.height(24.dp)) - BitwardenListHeaderText( - label = stringResource(id = R.string.custom_fields), - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - - Spacer(modifier = Modifier.height(16.dp)) - BitwardenFilledTonalButton( - label = stringResource(id = R.string.new_custom_field), - onClick = secureNotesTypeHandlers.onAddNewCustomFieldClick, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - - Spacer(modifier = Modifier.height(24.dp)) - BitwardenListHeaderText( - label = stringResource(id = R.string.ownership), - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - - Spacer(modifier = Modifier.height(8.dp)) - BitwardenMultiSelectButton( - label = stringResource(id = R.string.who_owns_this_item), - options = state.availableOwners.toImmutableList(), - selectedOption = state.ownership, - onOptionSelected = secureNotesTypeHandlers.onOwnershipTextChange, - modifier = Modifier - .padding(horizontal = 16.dp), - ) - Spacer(modifier = Modifier.height(24.dp)) - Spacer(modifier = Modifier.navigationBarsPadding()) -} diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/util/ComposeTestHelpers.kt b/app/src/test/java/com/x8bit/bitwarden/ui/util/ComposeTestHelpers.kt index 489e6ae8b0..8137565d39 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/util/ComposeTestHelpers.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/util/ComposeTestHelpers.kt @@ -4,11 +4,15 @@ import androidx.compose.ui.semantics.SemanticsProperties import androidx.compose.ui.semantics.getOrNull import androidx.compose.ui.test.SemanticsMatcher import androidx.compose.ui.test.SemanticsNodeInteraction +import androidx.compose.ui.test.SemanticsNodeInteractionCollection +import androidx.compose.ui.test.hasContentDescription import androidx.compose.ui.test.hasScrollToNodeAction import androidx.compose.ui.test.hasText import androidx.compose.ui.test.junit4.ComposeContentTestRule +import androidx.compose.ui.test.onAllNodesWithContentDescription import androidx.compose.ui.test.onAllNodesWithText import androidx.compose.ui.test.onFirst +import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performScrollToNode import org.junit.jupiter.api.assertThrows @@ -44,11 +48,44 @@ fun ComposeContentTestRule.onNodeWithTextAfterScroll(text: String): SemanticsNod return onNodeWithText(text) } +/** + * A helper used to scroll to and get the matching node in a scrollable list. This is intended to + * be used with lazy lists that would otherwise fail when calling [performScrollToNode]. + */ +fun ComposeContentTestRule.onNodeWithContentDescriptionAfterScroll( + label: String, +): SemanticsNodeInteraction { + onNode(hasScrollToNodeAction()).performScrollToNode(hasContentDescription(label)) + return onNodeWithContentDescription(label) +} + /** * A helper used to scroll to and get a thr first matching node in a scrollable list. This is * intended to be used with lazy lists that would otherwise fail when calling [performScrollToNode]. */ -fun ComposeContentTestRule.onFirstNodeWithTextAfterScroll(text: String): SemanticsNodeInteraction { +fun ComposeContentTestRule.onAllNodesWithTextAfterScroll( + text: String, +): SemanticsNodeInteractionCollection { onNode(hasScrollToNodeAction()).performScrollToNode(hasText(text)) - return onAllNodesWithText(text).onFirst() + return onAllNodesWithText(text) } + +/** + * A helper used to scroll to and get a thr first matching node in a scrollable list. This is + * intended to be used with lazy lists that would otherwise fail when calling [performScrollToNode]. + */ +fun ComposeContentTestRule.onAllNodesWithContentDescriptionAfterScroll( + label: String, +): SemanticsNodeInteractionCollection { + onNode(hasScrollToNodeAction()).performScrollToNode(hasContentDescription(label)) + return onAllNodesWithContentDescription(label) +} + +/** + * A helper used to scroll to and get all matching nodes in a scrollable list. This is intended + * to be used with lazy lists that would otherwise fail when calling [performScrollToNode]. + */ +fun ComposeContentTestRule.onFirstNodeWithTextAfterScroll( + text: String, +): SemanticsNodeInteraction = + onAllNodesWithTextAfterScroll(text).onFirst() diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemScreenTest.kt index 213b4f72a5..8cc62974e9 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemScreenTest.kt @@ -9,7 +9,6 @@ import androidx.compose.ui.test.click import androidx.compose.ui.test.filterToOne import androidx.compose.ui.test.hasContentDescription import androidx.compose.ui.test.hasSetTextAction -import androidx.compose.ui.test.hasText import androidx.compose.ui.test.onAllNodesWithText import androidx.compose.ui.test.onFirst import androidx.compose.ui.test.onLast @@ -22,6 +21,9 @@ import androidx.compose.ui.test.performTextInput import androidx.compose.ui.test.performTouchInput import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest import com.x8bit.bitwarden.ui.platform.base.util.asText +import com.x8bit.bitwarden.ui.util.onAllNodesWithTextAfterScroll +import com.x8bit.bitwarden.ui.util.onNodeWithContentDescriptionAfterScroll +import com.x8bit.bitwarden.ui.util.onNodeWithTextAfterScroll import io.mockk.every import io.mockk.mockk import io.mockk.verify @@ -85,7 +87,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { // Opens the menu composeTestRule - .onNodeWithContentDescription(label = "Type, Login") + .onNodeWithContentDescriptionAfterScroll(label = "Type, Login") .performClick() // Choose the option from the menu @@ -109,13 +111,13 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithContentDescription(label = "Type, Login") + .onNodeWithContentDescriptionAfterScroll(label = "Type, Login") .assertIsDisplayed() mutableStateFlow.update { it.copy(selectedType = VaultAddItemState.ItemType.Card) } composeTestRule - .onNodeWithContentDescription(label = "Type, Card") + .onNodeWithContentDescriptionAfterScroll(label = "Type, Card") .assertIsDisplayed() } @@ -126,7 +128,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText(text = "Name") + .onNodeWithTextAfterScroll(text = "Name") .performTextInput(text = "TestName") verify { @@ -143,7 +145,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText(text = "Name") + .onNodeWithTextAfterScroll(text = "Name") .assertTextContains("") mutableStateFlow.update { currentState -> @@ -151,7 +153,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText(text = "Name") + .onNodeWithTextAfterScroll(text = "Name") .assertTextContains("NewName") } @@ -162,7 +164,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText(text = "Username") + .onNodeWithTextAfterScroll(text = "Username") .performTextInput(text = "TestUsername") verify { @@ -179,7 +181,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText(text = "Username") + .onNodeWithTextAfterScroll(text = "Username") .assertTextContains("") mutableStateFlow.update { currentState -> @@ -187,7 +189,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText(text = "Username") + .onNodeWithTextAfterScroll(text = "Username") .assertTextContains("NewUsername") } @@ -199,7 +201,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithContentDescription(label = "Generate username") + .onNodeWithContentDescriptionAfterScroll(label = "Generate username") .performClick() verify { @@ -217,10 +219,9 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText(text = "Password") + .onNodeWithTextAfterScroll(text = "Password") .onSiblings() .onFirst() - .performScrollTo() .performClick() verify { @@ -236,10 +237,9 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText(text = "Password") + .onNodeWithTextAfterScroll(text = "Password") .onSiblings() .onLast() - .performScrollTo() .performClick() verify { @@ -256,7 +256,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText(text = "Password") + .onNodeWithTextAfterScroll(text = "Password") .performTextInput(text = "TestPassword") verify { @@ -273,7 +273,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText(text = "Password") + .onNodeWithTextAfterScroll(text = "Password") .assertTextContains("") mutableStateFlow.update { currentState -> @@ -281,7 +281,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText(text = "Password") + .onNodeWithTextAfterScroll(text = "Password") .assertTextContains("•••••••••••") } @@ -292,8 +292,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText(text = "Set up TOTP") - .performScrollTo() + .onNodeWithTextAfterScroll(text = "Set up TOTP") .performClick() verify { @@ -310,8 +309,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText("URI") - .performScrollTo() + .onNodeWithTextAfterScroll("URI") .performTextInput("TestURI") verify { @@ -328,7 +326,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText(text = "URI") + .onNodeWithTextAfterScroll("URI") .assertTextContains("") mutableStateFlow.update { currentState -> @@ -336,7 +334,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText(text = "URI") + .onNodeWithTextAfterScroll(text = "URI") .assertTextContains("NewURI") } @@ -348,10 +346,9 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText(text = "URI") + .onNodeWithTextAfterScroll(text = "URI") .onSiblings() .filterToOne(hasContentDescription(value = "Options")) - .performScrollTo() .performClick() verify { @@ -368,8 +365,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText(text = "New URI") - .performScrollTo() + .onNodeWithTextAfterScroll(text = "New URI") .performClick() verify { @@ -387,8 +383,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { // Opens the menu composeTestRule - .onNodeWithContentDescription(label = "Folder, No Folder") - .performScrollTo() + .onNodeWithContentDescriptionAfterScroll(label = "Folder, No Folder") .performClick() // Choose the option from the menu @@ -412,8 +407,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithContentDescription(label = "Folder, No Folder") - .performScrollTo() + .onNodeWithContentDescriptionAfterScroll(label = "Folder, No Folder") .assertIsDisplayed() mutableStateFlow.update { currentState -> @@ -421,8 +415,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithContentDescription(label = "Folder, Folder 2") - .performScrollTo() + .onNodeWithContentDescriptionAfterScroll(label = "Folder, Folder 2") .assertIsDisplayed() } @@ -433,8 +426,8 @@ class VaultAddItemScreenTest : BaseComposeTest() { VaultAddItemScreen(viewModel = viewModel, onNavigateBack = {}) } - composeTestRule.onNodeWithText("Favorite") - .performScrollTo() + composeTestRule + .onNodeWithTextAfterScroll("Favorite") .performClick() verify { @@ -453,7 +446,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText(text = "Favorite") + .onNodeWithTextAfterScroll("Favorite") .assertIsOff() mutableStateFlow.update { currentState -> @@ -461,7 +454,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText(text = "Favorite") + .onNodeWithTextAfterScroll("Favorite") .assertIsOn() } @@ -473,8 +466,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText("Master password re-prompt") - .performScrollTo() + .onNodeWithTextAfterScroll("Master password re-prompt") .performTouchInput { click(position = Offset(x = 1f, y = center.y)) } @@ -496,7 +488,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText("Master password re-prompt") + .onNodeWithTextAfterScroll("Master password re-prompt") .assertIsOff() mutableStateFlow.update { currentState -> @@ -504,7 +496,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText("Master password re-prompt") + .onNodeWithTextAfterScroll("Master password re-prompt") .assertIsOn() } @@ -515,8 +507,8 @@ class VaultAddItemScreenTest : BaseComposeTest() { VaultAddItemScreen(viewModel = viewModel, onNavigateBack = {}) } - composeTestRule.onNodeWithContentDescription(label = "Master password re-prompt help") - .performScrollTo() + composeTestRule + .onNodeWithContentDescriptionAfterScroll(label = "Master password re-prompt help") .performClick() verify { @@ -533,8 +525,8 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNode(hasSetTextAction() and hasText("Notes")) - .performScrollTo() + .onAllNodesWithTextAfterScroll("Notes") + .filterToOne(hasSetTextAction()) .performTextInput("TestNotes") verify { @@ -551,15 +543,17 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNode(hasSetTextAction() and hasText("Notes")) - .assertTextContains("") + .onAllNodesWithTextAfterScroll("Notes") + .filterToOne(hasSetTextAction()) + .performTextInput("") mutableStateFlow.update { currentState -> updateLoginType(currentState) { copy(notes = "NewNote") } } composeTestRule - .onNode(hasSetTextAction() and hasText("Notes")) + .onAllNodesWithTextAfterScroll("Notes") + .filterToOne(hasSetTextAction()) .assertTextContains("NewNote") } @@ -571,8 +565,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText(text = "New custom field") - .performScrollTo() + .onNodeWithTextAfterScroll(text = "New custom field") .performClick() verify { @@ -588,8 +581,9 @@ class VaultAddItemScreenTest : BaseComposeTest() { // Opens the menu composeTestRule - .onNodeWithContentDescription(label = "Who owns this item?, placeholder@email.com") - .performScrollTo() + .onNodeWithContentDescriptionAfterScroll( + label = "Who owns this item?, placeholder@email.com", + ) .performClick() // Choose the option from the menu @@ -613,8 +607,9 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithContentDescription(label = "Who owns this item?, placeholder@email.com") - .performScrollTo() + .onNodeWithContentDescriptionAfterScroll( + label = "Who owns this item?, placeholder@email.com", + ) .assertIsDisplayed() mutableStateFlow.update { currentState -> @@ -622,8 +617,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithContentDescription(label = "Who owns this item?, Owner 2") - .performScrollTo() + .onNodeWithContentDescriptionAfterScroll(label = "Who owns this item?, Owner 2") .assertIsDisplayed() } @@ -637,7 +631,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText(text = "Name") + .onNodeWithTextAfterScroll(text = "Name") .performTextInput(text = "TestName") verify { @@ -657,7 +651,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText(text = "Name") + .onNodeWithTextAfterScroll(text = "Name") .assertTextContains("") mutableStateFlow.update { currentState -> @@ -665,7 +659,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText(text = "Name") + .onNodeWithTextAfterScroll(text = "Name") .assertTextContains("NewName") } @@ -680,8 +674,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { // Opens the menu composeTestRule - .onNodeWithContentDescription(label = "Folder, No Folder") - .performScrollTo() + .onNodeWithContentDescriptionAfterScroll(label = "Folder, No Folder") .performClick() // Choose the option from the menu @@ -709,8 +702,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithContentDescription(label = "Folder, No Folder") - .performScrollTo() + .onNodeWithContentDescriptionAfterScroll(label = "Folder, No Folder") .assertIsDisplayed() mutableStateFlow.update { currentState -> @@ -718,8 +710,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithContentDescription(label = "Folder, Folder 2") - .performScrollTo() + .onNodeWithContentDescriptionAfterScroll(label = "Folder, Folder 2") .assertIsDisplayed() } @@ -734,8 +725,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText("Favorite") - .performScrollTo() + .onNodeWithTextAfterScroll("Favorite") .performClick() verify { @@ -758,7 +748,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText("Favorite") + .onNodeWithTextAfterScroll("Favorite") .assertIsOff() mutableStateFlow.update { currentState -> @@ -766,7 +756,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText("Favorite") + .onNodeWithTextAfterScroll("Favorite") .assertIsOn() } @@ -781,8 +771,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText("Master password re-prompt") - .performScrollTo() + .onNodeWithTextAfterScroll("Master password re-prompt") .performTouchInput { click(position = Offset(x = 1f, y = center.y)) } @@ -807,7 +796,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText("Master password re-prompt") + .onNodeWithTextAfterScroll("Master password re-prompt") .assertIsOff() mutableStateFlow.update { currentState -> @@ -815,7 +804,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText("Master password re-prompt") + .onNodeWithTextAfterScroll("Master password re-prompt") .assertIsOn() } @@ -830,8 +819,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithContentDescription(label = "Master password re-prompt help") - .performScrollTo() + .onNodeWithContentDescriptionAfterScroll(label = "Master password re-prompt help") .performClick() verify { @@ -851,8 +839,8 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNode(hasSetTextAction() and hasText("Notes")) - .performScrollTo() + .onAllNodesWithTextAfterScroll("Notes") + .filterToOne(hasSetTextAction()) .performTextInput("TestNotes") verify { @@ -873,7 +861,8 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNode(hasSetTextAction() and hasText("Notes")) + .onAllNodesWithTextAfterScroll("Notes") + .filterToOne(hasSetTextAction()) .assertTextContains("") mutableStateFlow.update { currentState -> @@ -881,7 +870,8 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNode(hasSetTextAction() and hasText("Notes")) + .onAllNodesWithTextAfterScroll("Notes") + .filterToOne(hasSetTextAction()) .assertTextContains("NewNote") } @@ -896,8 +886,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithText(text = "New custom field") - .performScrollTo() + .onNodeWithTextAfterScroll(text = "New custom field") .performClick() verify { @@ -919,8 +908,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { // Opens the menu composeTestRule - .onNodeWithContentDescription(label = "Who owns this item?, placeholder@email.com") - .performScrollTo() + .onNodeWithContentDescriptionAfterScroll(label = "Who owns this item?, placeholder@email.com") .performClick() // Choose the option from the menu @@ -948,8 +936,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithContentDescription(label = "Who owns this item?, placeholder@email.com") - .performScrollTo() + .onNodeWithContentDescriptionAfterScroll(label = "Who owns this item?, placeholder@email.com") .assertIsDisplayed() mutableStateFlow.update { currentState -> @@ -957,8 +944,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } composeTestRule - .onNodeWithContentDescription(label = "Who owns this item?, Owner 2") - .performScrollTo() + .onNodeWithContentDescriptionAfterScroll(label = "Who owns this item?, Owner 2") .assertIsDisplayed() }