Merge branch 'bitwarden:main' into main

This commit is contained in:
SymphonicDeviation 2024-11-01 16:47:53 -04:00 committed by GitHub
commit e4fa16ac3e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 180 additions and 187 deletions

View file

@ -38,6 +38,7 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.time.Clock
import javax.inject.Singleton
@ -190,7 +191,7 @@ class GeneratorRepositoryImpl(
override suspend fun generateForwardedServiceUsername(
forwardedServiceGeneratorRequest: UsernameGeneratorRequest.Forwarded,
): GeneratedForwardedServiceUsernameResult =
): GeneratedForwardedServiceUsernameResult = withContext(scope.coroutineContext) {
generatorSdkSource.generateForwardedServiceEmail(forwardedServiceGeneratorRequest)
.fold(
onSuccess = { generatedEmail ->
@ -200,6 +201,7 @@ class GeneratorRepositoryImpl(
GeneratedForwardedServiceUsernameResult.InvalidRequest(it.message)
},
)
}
override fun getPasscodeGenerationOptions(): PasscodeGenerationOptions? {
val userId = authDiskSource.userState?.activeUserId

View file

@ -21,7 +21,7 @@ import kotlin.math.floor
* This character takes up no space but can be used to ensure a string is not empty. It can also
* be used to insert "safe" line-break positions in a string.
*
* Note: Is a string only contains this charactor, it is _not_ considered blank.
* Note: Is a string only contains this character, it is _not_ considered blank.
*/
const val ZERO_WIDTH_CHARACTER: String = "\u200B"
@ -126,14 +126,15 @@ fun String.withLineBreaksAtWidth(
): String {
val measurer = rememberTextMeasurer()
return remember(this, widthPx, monospacedTextStyle) {
val characterSizePx = measurer
.measure("*", monospacedTextStyle)
.size
.width
val perLineCharacterLimit = floor(widthPx / characterSizePx).toInt()
if (widthPx > 0) {
if (widthPx > 0 && this.isNotEmpty()) {
val stringLengthPx = measurer
.measure(text = this, softWrap = false, style = monospacedTextStyle)
.size
.width
val linesRequired = stringLengthPx / widthPx
val charsPerLine = floor(this.length / linesRequired).toInt()
this
.chunked(perLineCharacterLimit)
.chunked(size = charsPerLine)
.joinToString(separator = "\n")
} else {
this

View file

@ -84,6 +84,7 @@ fun BitwardenSearchTopAppBar(
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
modifier = Modifier
.tabNavigation()
.testTag("SearchFieldEntry")
.focusRequester(focusRequester)
.fillMaxWidth(),
)

View file

@ -17,7 +17,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.DpOffset
@ -65,6 +64,7 @@ fun BitwardenOverflowActionItem(
containerColor = BitwardenTheme.colorScheme.background.primary,
modifier = Modifier
.semantics { testTagsAsResourceId = true }
.testTag("FloatingOptionsContent")
.widthIn(
min = 112.dp,
max = 280.dp,
@ -72,16 +72,13 @@ fun BitwardenOverflowActionItem(
content = {
menuItemDataList.forEach { dropdownMenuItemData ->
DropdownMenuItem(
modifier = Modifier.semantics {
dropdownMenuItemData.testTag?.let {
testTag = it
}
},
modifier = Modifier.testTag("FloatingOptionsItem"),
colors = bitwardenMenuItemColors(),
text = {
Text(
text = dropdownMenuItemData.text,
style = BitwardenTheme.typography.bodyLarge,
modifier = Modifier.testTag("FloatingOptionsItemName"),
)
},
onClick = {
@ -115,10 +112,8 @@ private fun BitwardenOverflowActionItem_preview() {
*
* @param text The text displayed for the item in the menu.
* @param onClick A callback for when the menu item is clicked.
* @param testTag Optional test tag for the menu item.
*/
data class OverflowMenuItemData(
val text: String,
val onClick: () -> Unit,
val testTag: String? = null,
)

View file

@ -11,6 +11,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.selected
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
@ -35,6 +36,7 @@ fun BitwardenSelectionRow(
Row(
modifier = modifier
.fillMaxWidth()
.testTag("AlertRadioButtonOption")
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = ripple(
@ -56,6 +58,7 @@ fun BitwardenSelectionRow(
text = text(),
color = BitwardenTheme.colorScheme.text.primary,
style = BitwardenTheme.typography.bodyLarge,
modifier = Modifier.testTag("AlertRadioButtonOptionName"),
)
}
}

View file

@ -22,6 +22,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTag
@ -167,7 +168,7 @@ fun BitwardenListItem(
selectionItems = {
selectionDataList.forEach { itemData ->
BitwardenBasicDialogRow(
modifier = Modifier.semantics { itemData.testTag?.let { testTag = it } },
modifier = Modifier.testTag("AlertSelectionOption"),
text = itemData.text,
onClick = {
shouldShowDialog = false

View file

@ -15,7 +15,6 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
@ -90,7 +89,6 @@ fun SearchScreen(
navigationIconContentDescription = stringResource(id = R.string.back),
onNavigationIconClick = searchHandlers.onBackClick,
),
modifier = Modifier.testTag(tag = "SearchFieldEntry"),
)
},
modifier = Modifier

View file

@ -460,7 +460,7 @@ private fun ColumnScope.PasswordTypeContent(
valueTag = "PasswordLengthLabel",
modifier = Modifier
.fillMaxWidth()
.padding(end = 16.dp),
.padding(end = 28.dp),
)
Spacer(modifier = Modifier.height(8.dp))

View file

@ -351,7 +351,7 @@ class GeneratorViewModel @Inject constructor(
GeneratorState.MainType.Passphrase(
numWords = max(options.numWords, minNumWords),
minNumWords = minNumWords,
wordSeparator = options.wordSeparator.toCharArray().first(),
wordSeparator = options.wordSeparator.toCharArray().firstOrNull(),
capitalize = options.allowCapitalize || policy.capitalize == true,
capitalizeEnabled = policy.capitalize != true,
includeNumber = options.allowIncludeNumber || policy.includeNumber == true,
@ -462,7 +462,7 @@ class GeneratorViewModel @Inject constructor(
val newOptions = options.copy(
type = PasscodeGenerationOptions.PasscodeType.PASSPHRASE,
numWords = passphrase.numWords,
wordSeparator = passphrase.wordSeparator.toString(),
wordSeparator = passphrase.wordSeparator?.toString().orEmpty(),
allowCapitalize = passphrase.capitalize,
allowIncludeNumber = passphrase.includeNumber,
)

View file

@ -82,7 +82,6 @@ fun PasswordHistoryScreen(
BitwardenOverflowActionItem(
menuItemDataList = persistentListOf(
OverflowMenuItemData(
testTag = "ClearPasswordList",
text = stringResource(id = R.string.clear),
onClick = remember(viewModel) {
{

View file

@ -78,7 +78,6 @@ fun SendListItem(
SelectionItemData(
text = stringResource(id = R.string.copy_link),
onClick = onCopyClick,
testTag = "Copy",
),
SelectionItemData(
text = stringResource(id = R.string.share_link),

View file

@ -46,7 +46,8 @@ fun LazyListScope.vaultAddEditSshKeyItems(
BitwardenTextField(
label = stringResource(id = R.string.public_key),
value = sshKeyState.publicKey,
onValueChange = sshKeyTypeHandlers.onPublicKeyTextChange,
readOnly = true,
onValueChange = { },
modifier = Modifier
.testTag("PublicKeyEntry")
.fillMaxWidth()
@ -59,7 +60,8 @@ fun LazyListScope.vaultAddEditSshKeyItems(
BitwardenPasswordField(
label = stringResource(id = R.string.private_key),
value = sshKeyState.privateKey,
onValueChange = sshKeyTypeHandlers.onPrivateKeyTextChange,
readOnly = true,
onValueChange = { /* no-op */ },
showPassword = sshKeyState.showPrivateKey,
showPasswordChange = { sshKeyTypeHandlers.onPrivateKeyVisibilityChange(it) },
showPasswordTestTag = "ViewPrivateKeyButton",
@ -75,7 +77,8 @@ fun LazyListScope.vaultAddEditSshKeyItems(
BitwardenTextField(
label = stringResource(id = R.string.fingerprint),
value = sshKeyState.fingerprint,
onValueChange = sshKeyTypeHandlers.onFingerprintTextChange,
readOnly = true,
onValueChange = { /* no-op */ },
modifier = Modifier
.testTag("FingerprintEntry")
.fillMaxWidth()
@ -116,10 +119,7 @@ private fun VaultAddEditSshKeyItems_preview() {
onHiddenFieldVisibilityChange = { },
),
sshKeyTypeHandlers = VaultAddEditSshKeyTypeHandlers(
onPublicKeyTextChange = { },
onPrivateKeyTextChange = { },
onPrivateKeyVisibilityChange = { },
onFingerprintTextChange = { },
),
)
}

View file

@ -1394,48 +1394,18 @@ class VaultAddEditViewModel @Inject constructor(
private fun handleSshKeyTypeActions(action: VaultAddEditAction.ItemType.SshKeyType) {
when (action) {
is VaultAddEditAction.ItemType.SshKeyType.PublicKeyTextChange -> {
handlePublicKeyTextChange(action)
}
is VaultAddEditAction.ItemType.SshKeyType.PrivateKeyTextChange -> {
handlePrivateKeyTextChange(action)
}
is VaultAddEditAction.ItemType.SshKeyType.PrivateKeyVisibilityChange -> {
handlePrivateKeyVisibilityChange(action)
}
is VaultAddEditAction.ItemType.SshKeyType.FingerprintTextChange -> {
handleSshKeyFingerprintTextChange(action)
}
}
}
private fun handlePublicKeyTextChange(
action: VaultAddEditAction.ItemType.SshKeyType.PublicKeyTextChange,
) {
updateSshKeyContent { it.copy(publicKey = action.publicKey) }
}
private fun handlePrivateKeyTextChange(
action: VaultAddEditAction.ItemType.SshKeyType.PrivateKeyTextChange,
) {
updateSshKeyContent { it.copy(privateKey = action.privateKey) }
}
private fun handlePrivateKeyVisibilityChange(
action: VaultAddEditAction.ItemType.SshKeyType.PrivateKeyVisibilityChange,
) {
updateSshKeyContent { it.copy(showPrivateKey = action.isVisible) }
}
private fun handleSshKeyFingerprintTextChange(
action: VaultAddEditAction.ItemType.SshKeyType.FingerprintTextChange,
) {
updateSshKeyContent { it.copy(fingerprint = action.fingerprint) }
}
//endregion SSH Key Type Handlers
//region Internal Type Handlers
@ -3064,25 +3034,11 @@ sealed class VaultAddEditAction {
* Represents actions specific to the SSH Key type.
*/
sealed class SshKeyType : ItemType() {
/**
* Fired when the public key text input is changed.
*/
data class PublicKeyTextChange(val publicKey: String) : SshKeyType()
/**
* Fired when the private key text input is changed.
*/
data class PrivateKeyTextChange(val privateKey: String) : SshKeyType()
/**
* Fired when the private key's visibility has changed.
*/
data class PrivateKeyVisibilityChange(val isVisible: Boolean) : SshKeyType()
/**
* Fired when the fingerprint text input is changed.
*/
data class FingerprintTextChange(val fingerprint: String) : SshKeyType()
}
}

View file

@ -10,16 +10,10 @@ import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditViewModel
* These handlers are used to update the ViewModel with user actions such as text changes and
* visibility changes for different SSH key fields (public key, private key, fingerprint).
*
* @property onPublicKeyTextChange Handler for changes in the public key text field.
* @property onPrivateKeyTextChange Handler for changes in the private key text field.
* @property onPrivateKeyVisibilityChange Handler for toggling the visibility of the private key.
* @property onFingerprintTextChange Handler for changes in the fingerprint text field.
*/
data class VaultAddEditSshKeyTypeHandlers(
val onPublicKeyTextChange: (String) -> Unit,
val onPrivateKeyTextChange: (String) -> Unit,
val onPrivateKeyVisibilityChange: (Boolean) -> Unit,
val onFingerprintTextChange: (String) -> Unit,
) {
@Suppress("UndocumentedPublicClass")
companion object {
@ -31,20 +25,7 @@ data class VaultAddEditSshKeyTypeHandlers(
*/
fun create(viewModel: VaultAddEditViewModel): VaultAddEditSshKeyTypeHandlers =
VaultAddEditSshKeyTypeHandlers(
onPublicKeyTextChange = { newPublicKey ->
viewModel.trySendAction(
VaultAddEditAction.ItemType.SshKeyType.PublicKeyTextChange(
publicKey = newPublicKey,
),
)
},
onPrivateKeyTextChange = { newPrivateKey ->
viewModel.trySendAction(
VaultAddEditAction.ItemType.SshKeyType.PrivateKeyTextChange(
privateKey = newPrivateKey,
),
)
},
onPrivateKeyVisibilityChange = {
viewModel.trySendAction(
VaultAddEditAction.ItemType.SshKeyType.PrivateKeyVisibilityChange(
@ -52,13 +33,6 @@ data class VaultAddEditSshKeyTypeHandlers(
),
)
},
onFingerprintTextChange = { newFingerprint ->
viewModel.trySendAction(
VaultAddEditAction.ItemType.SshKeyType.FingerprintTextChange(
fingerprint = newFingerprint,
),
)
},
)
}
}

View file

@ -12,8 +12,11 @@ import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenTonalIconButton
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenPasswordField
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextFieldWithActions
import com.x8bit.bitwarden.ui.platform.components.header.BitwardenListHeaderText
import com.x8bit.bitwarden.ui.vault.feature.item.handlers.VaultSshKeyItemTypeHandlers
@ -55,16 +58,24 @@ fun VaultItemSshKeyContent(
item {
Spacer(modifier = Modifier.height(8.dp))
BitwardenTextField(
BitwardenTextFieldWithActions(
label = stringResource(id = R.string.public_key),
value = sshKeyItemState.publicKey,
onValueChange = { },
singleLine = false,
readOnly = true,
actions = {
BitwardenTonalIconButton(
vectorIconRes = R.drawable.ic_copy,
contentDescription = stringResource(id = R.string.copy_public_key),
onClick = vaultSshKeyItemTypeHandlers.onCopyPublicKeyClick,
modifier = Modifier.testTag(tag = "SshKeyCopyPublicKeyButton"),
)
},
modifier = Modifier
.testTag("SshKeyItemPublicKeyEntry")
.fillMaxWidth()
.padding(horizontal = 16.dp),
.standardHorizontalMargin(),
)
}
@ -88,16 +99,24 @@ fun VaultItemSshKeyContent(
item {
Spacer(modifier = Modifier.height(8.dp))
BitwardenTextField(
BitwardenTextFieldWithActions(
label = stringResource(id = R.string.fingerprint),
value = sshKeyItemState.fingerprint,
onValueChange = { },
singleLine = false,
readOnly = true,
actions = {
BitwardenTonalIconButton(
vectorIconRes = R.drawable.ic_copy,
contentDescription = stringResource(id = R.string.copy_fingerprint),
onClick = vaultSshKeyItemTypeHandlers.onCopyFingerprintClick,
modifier = Modifier.testTag(tag = "SshKeyCopyFingerprintButton"),
)
},
modifier = Modifier
.testTag("SshKeyItemFingerprintEntry")
.fillMaxWidth()
.padding(horizontal = 16.dp),
.standardHorizontalMargin(),
)
}

View file

@ -758,9 +758,19 @@ class VaultItemViewModel @Inject constructor(
private fun handleSshKeyTypeActions(action: VaultItemAction.ItemType.SshKey) {
when (action) {
VaultItemAction.ItemType.SshKey.CopyPublicKeyClick -> handleCopyPublicKeyClick()
is VaultItemAction.ItemType.SshKey.PrivateKeyVisibilityClicked -> {
handlePrivateKeyVisibilityClicked(action)
}
VaultItemAction.ItemType.SshKey.CopyFingerprintClick -> handleCopyFingerprintClick()
}
}
private fun handleCopyPublicKeyClick() {
onSshKeyContent { _, sshKey ->
clipboardManager.setText(text = sshKey.publicKey)
}
}
@ -778,6 +788,12 @@ class VaultItemViewModel @Inject constructor(
}
}
private fun handleCopyFingerprintClick() {
onSshKeyContent { _, sshKey ->
clipboardManager.setText(text = sshKey.fingerprint)
}
}
//endregion SSH Key Type Handlers
//region Internal Type Handlers
@ -1758,10 +1774,20 @@ sealed class VaultItemAction {
* Represents actions specific to the SshKey type.
*/
sealed class SshKey : ItemType() {
/**
* The user has clicked the copy button for the public key.
*/
data object CopyPublicKeyClick : SshKey()
/**
* The user has clicked to display the private key.
*/
data class PrivateKeyVisibilityClicked(val isVisible: Boolean) : SshKey()
/**
* The user has clicked the copy button for the fingerprint.
*/
data object CopyFingerprintClick : SshKey()
}
}

View file

@ -8,7 +8,9 @@ import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemViewModel
* items in a vault.
*/
data class VaultSshKeyItemTypeHandlers(
val onCopyPublicKeyClick: () -> Unit,
val onShowPrivateKeyClick: (isVisible: Boolean) -> Unit,
val onCopyFingerprintClick: () -> Unit,
) {
@Suppress("UndocumentedPublicClass")
@ -20,6 +22,11 @@ data class VaultSshKeyItemTypeHandlers(
@Suppress("LongMethod")
fun create(viewModel: VaultItemViewModel): VaultSshKeyItemTypeHandlers =
VaultSshKeyItemTypeHandlers(
onCopyPublicKeyClick = {
viewModel.trySendAction(
VaultItemAction.ItemType.SshKey.CopyPublicKeyClick,
)
},
onShowPrivateKeyClick = {
viewModel.trySendAction(
VaultItemAction.ItemType.SshKey.PrivateKeyVisibilityClicked(
@ -27,6 +34,11 @@ data class VaultSshKeyItemTypeHandlers(
),
)
},
onCopyFingerprintClick = {
viewModel.trySendAction(
VaultItemAction.ItemType.SshKey.CopyFingerprintClick,
)
},
)
}
}

View file

@ -57,7 +57,7 @@ fun VaultContent(
showDivider = true,
modifier = Modifier
.fillMaxWidth()
.testTag("FolderFilter")
.testTag("VerificationCodesFilter")
.padding(16.dp),
)
}

View file

@ -1079,4 +1079,6 @@ Do you want to switch to this account?</string>
<string name="public_key">Public key</string>
<string name="private_key">Private key</string>
<string name="ssh_keys">SSH keys</string>
<string name="copy_public_key">Copy public key</string>
<string name="copy_fingerprint">Copy fingerprint</string>
</resources>

View file

@ -18,6 +18,7 @@ import androidx.compose.ui.test.hasContentDescription
import androidx.compose.ui.test.hasSetTextAction
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.isDialog
import androidx.compose.ui.test.isEditable
import androidx.compose.ui.test.isPopup
import androidx.compose.ui.test.onAllNodesWithContentDescription
import androidx.compose.ui.test.onAllNodesWithText
@ -3348,54 +3349,33 @@ class VaultAddEditScreenTest : BaseComposeTest() {
}
@Test
fun `in ItemType_SshKeys changing the public key should trigger PublicKeyTextChange`() {
fun `in ItemType_SshKeys the public key field should be read only`() {
mutableStateFlow.value = DEFAULT_STATE_SSH_KEYS
composeTestRule
.onNodeWithTextAfterScroll("Public key")
.performTextInput("TestPublicKey")
verify {
viewModel.trySendAction(
VaultAddEditAction.ItemType.SshKeyType.PublicKeyTextChange(
publicKey = "TestPublicKey",
),
)
}
.onNodeWithTextAfterScroll(text = "Public key")
.assertExists()
.assert(!isEditable())
}
@Test
fun `in ItemType_SshKeys changing the private key should trigger PrivateKeyTextChange`() {
fun `in ItemType_SshKeys the private key field should be read only`() {
mutableStateFlow.value = DEFAULT_STATE_SSH_KEYS
composeTestRule
.onNodeWithTextAfterScroll("Private key")
.performTextInput("TestPrivateKey")
verify {
viewModel.trySendAction(
VaultAddEditAction.ItemType.SshKeyType.PrivateKeyTextChange(
privateKey = "TestPrivateKey",
),
)
}
.onNodeWithTextAfterScroll(text = "Private key")
.assertExists()
.assert(!isEditable())
}
@Test
fun `in ItemType_SshKeys changing the fingerprint should trigger FingerprintTextChange`() {
fun `in ItemType_SshKeys the fingerprint field should be read only`() {
mutableStateFlow.value = DEFAULT_STATE_SSH_KEYS
composeTestRule
.onNodeWithTextAfterScroll("Fingerprint")
.performTextInput("TestFingerprint")
verify {
viewModel.trySendAction(
VaultAddEditAction.ItemType.SshKeyType.FingerprintTextChange(
fingerprint = "TestFingerprint",
),
)
}
.onNodeWithTextAfterScroll(text = "Fingerprint")
.assertExists()
.assert(!isEditable())
}
@Suppress("MaxLineLength")

View file

@ -2725,36 +2725,6 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
)
}
@Test
fun `PublicKeyTextChange should update public key`() = runTest {
val action = VaultAddEditAction.ItemType.SshKeyType.PublicKeyTextChange(
publicKey = "newPublicKey",
)
val expectedState = createVaultAddItemState(
typeContentViewState = VaultAddEditState.ViewState.Content.ItemType.SshKey(
publicKey = "newPublicKey",
),
)
viewModel.trySendAction(action)
assertEquals(expectedState, viewModel.stateFlow.value)
}
@Test
fun `PrivateKeyTextChange should update private key`() = runTest {
val action = VaultAddEditAction.ItemType.SshKeyType.PrivateKeyTextChange(
privateKey = "newPrivateKey",
)
val expectedState = createVaultAddItemState(
typeContentViewState = VaultAddEditState.ViewState.Content.ItemType.SshKey(
privateKey = "newPrivateKey",
),
)
viewModel.trySendAction(action)
assertEquals(expectedState, viewModel.stateFlow.value)
}
@Test
fun `PrivateKeyVisibilityChange should update private key visibility`() = runTest {
val action = VaultAddEditAction.ItemType.SshKeyType.PrivateKeyVisibilityChange(
@ -2769,21 +2739,6 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
assertEquals(expectedState, viewModel.stateFlow.value)
}
@Test
fun `FingerprintTextChange should update fingerprint`() = runTest {
val action = VaultAddEditAction.ItemType.SshKeyType.FingerprintTextChange(
fingerprint = "newFingerprint",
)
val expectedState = createVaultAddItemState(
typeContentViewState = VaultAddEditState.ViewState.Content.ItemType.SshKey(
fingerprint = "newFingerprint",
),
)
viewModel.trySendAction(action)
assertEquals(expectedState, viewModel.stateFlow.value)
}
}
@Test

View file

@ -2153,6 +2153,18 @@ class VaultItemScreenTest : BaseComposeTest() {
composeTestRule.onNodeWithTextAfterScroll(publicKey).assertIsDisplayed()
}
@Test
fun `in ssh key state, on copy public key click should send CopyPublicKeyClick`() {
mutableStateFlow.update { it.copy(viewState = DEFAULT_SSH_KEY_VIEW_STATE) }
composeTestRule
.onNodeWithContentDescriptionAfterScroll("Copy public key")
.performClick()
verify(exactly = 1) {
viewModel.trySendAction(VaultItemAction.ItemType.SshKey.CopyPublicKeyClick)
}
}
@Test
fun `in ssh key state, private key should be displayed according to state`() {
val privateKey = "the private key"
@ -2195,6 +2207,18 @@ class VaultItemScreenTest : BaseComposeTest() {
composeTestRule.onNodeWithTextAfterScroll(fingerprint).assertIsDisplayed()
}
@Test
fun `in ssh key state, on copy fingerprint click should send CopyFingerprintClick`() {
mutableStateFlow.update { it.copy(viewState = DEFAULT_SSH_KEY_VIEW_STATE) }
composeTestRule
.onNodeWithContentDescription("Copy fingerprint")
.performClick()
verify(exactly = 1) {
viewModel.trySendAction(VaultItemAction.ItemType.SshKey.CopyFingerprintClick)
}
}
//endregion ssh key
}

View file

@ -2349,6 +2349,29 @@ class VaultItemViewModelTest : BaseViewModelTest() {
)
}
@Test
fun `on CopyPublicKeyClick should copy public key to clipboard`() = runTest {
every { clipboardManager.setText("mockPublicKey") } just runs
val mockCipherView = mockk<CipherView> {
every {
toViewState(
previousState = null,
isPremiumUser = true,
hasMasterPassword = true,
totpCodeItemData = null,
)
} returns SSH_KEY_VIEW_STATE
}
mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
mutableAuthCodeItemFlow.value = DataState.Loaded(data = null)
viewModel.trySendAction(VaultItemAction.ItemType.SshKey.CopyPublicKeyClick)
verify(exactly = 1) {
clipboardManager.setText(text = DEFAULT_SSH_KEY_TYPE.publicKey)
}
}
@Suppress("MaxLineLength")
@Test
fun `on PrivateKeyVisibilityClick should show password dialog when re-prompt is required`() =
@ -2388,6 +2411,29 @@ class VaultItemViewModelTest : BaseViewModelTest() {
viewModel.stateFlow.value,
)
}
@Test
fun `on CopyFingerprintClick should copy fingerprint to clipboard`() = runTest {
every { clipboardManager.setText("mockFingerprint") } just runs
val mockCipherView = mockk<CipherView> {
every {
toViewState(
previousState = null,
isPremiumUser = true,
hasMasterPassword = true,
totpCodeItemData = null,
)
} returns SSH_KEY_VIEW_STATE
}
mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
mutableAuthCodeItemFlow.value = DataState.Loaded(data = null)
viewModel.trySendAction(VaultItemAction.ItemType.SshKey.CopyFingerprintClick)
verify(exactly = 1) {
clipboardManager.setText(text = DEFAULT_SSH_KEY_TYPE.fingerprint)
}
}
}
@Nested

View file

@ -7,12 +7,12 @@ targetSdk = "34"
minSdk = "29"
# Dependency Versions
androidGradlePlugin = "8.7.1"
androidGradlePlugin = "8.7.2"
androidxActivity = "1.9.3"
androidXBiometrics = "1.2.0-alpha05"
androidxBrowser = "1.8.0"
androidxCamera = "1.4.0"
androidxComposeBom = "2024.10.00"
androidxComposeBom = "2024.10.01"
androidxCore = "1.13.1"
androidxCredentials = "1.3.0"
androidxHiltNavigationCompose = "1.2.0"