diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlocked/VaultUnlockedNavigation.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlocked/VaultUnlockedNavigation.kt index f0cefd6396..94dd0ab0d4 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlocked/VaultUnlockedNavigation.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlocked/VaultUnlockedNavigation.kt @@ -92,8 +92,15 @@ fun NavGraphBuilder.vaultUnlockedGraph( ) vaultItemDestination( onNavigateBack = { navController.popBackStack() }, - onNavigateToVaultEditItem = { - navController.navigateToVaultAddEdit(VaultAddEditType.EditItem(it)) + onNavigateToVaultEditItem = { vaultItemId, isClone -> + navController.navigateToVaultAddEdit( + if (isClone) { + VaultAddEditType.CloneItem(vaultItemId) + } else { + VaultAddEditType.EditItem(vaultItemId) + }, + + ) }, onNavigateToMoveToOrganization = { navController.navigateToVaultMoveToOrganization(it) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/di/PlatformUiManagerModule.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/di/PlatformUiManagerModule.kt index 33dd5dd0ca..98803b644e 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/di/PlatformUiManagerModule.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/di/PlatformUiManagerModule.kt @@ -3,11 +3,14 @@ package com.x8bit.bitwarden.ui.platform.manager.di import android.content.Context import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManagerImpl +import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager +import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManagerImpl import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton /** * Provides UI-based managers in the platform package. @@ -15,11 +18,18 @@ import dagger.hilt.components.SingletonComponent @Module @InstallIn(SingletonComponent::class) class PlatformUiManagerModule { + @Provides + @Singleton fun provideIntentManager( @ApplicationContext context: Context, ): IntentManager = IntentManagerImpl( context = context, ) + + @Provides + @Singleton + fun provideResourceManager(@ApplicationContext context: Context): ResourceManager = + ResourceManagerImpl(context = context) } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/resource/ResourceManager.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/resource/ResourceManager.kt new file mode 100644 index 0000000000..a6933bd57d --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/resource/ResourceManager.kt @@ -0,0 +1,19 @@ +package com.x8bit.bitwarden.ui.platform.manager.resource + +import androidx.annotation.StringRes + +/** + * Interface for managing resources. + */ +interface ResourceManager { + + /** + * Method for returning a permission string from a [resId]. + */ + fun getString(@StringRes resId: Int): String + + /** + * Method for returning a permission string from a [resId] with [formatArgs]. + */ + fun getString(@StringRes resId: Int, vararg formatArgs: Any): String +} diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/resource/ResourceManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/resource/ResourceManagerImpl.kt new file mode 100644 index 0000000000..f98d4abe1c --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/resource/ResourceManagerImpl.kt @@ -0,0 +1,17 @@ +package com.x8bit.bitwarden.ui.platform.manager.resource + +import android.content.Context +import androidx.annotation.StringRes +import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage + +/** + * Primary implementation of [ResourceManager]. + */ +@OmitFromCoverage +class ResourceManagerImpl(private val context: Context) : ResourceManager { + override fun getString(@StringRes resId: Int): String = + context.getString(resId) + + override fun getString(@StringRes resId: Int, vararg formatArgs: Any): String = + context.getString(resId, *formatArgs) +} diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditNavigation.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditNavigation.kt index 7e8b5d72e4..7af5093e50 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditNavigation.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditNavigation.kt @@ -13,6 +13,7 @@ import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType private const val ADD_TYPE: String = "add" private const val EDIT_TYPE: String = "edit" +private const val CLONE_TYPE: String = "clone" private const val EDIT_ITEM_ID: String = "vault_edit_id" private const val ADD_EDIT_ITEM_PREFIX: String = "vault_add_edit_item" @@ -32,6 +33,7 @@ data class VaultAddEditArgs( vaultAddEditType = when (requireNotNull(savedStateHandle[ADD_EDIT_ITEM_TYPE])) { ADD_TYPE -> VaultAddEditType.AddItem EDIT_TYPE -> VaultAddEditType.EditItem(requireNotNull(savedStateHandle[EDIT_ITEM_ID])) + CLONE_TYPE -> VaultAddEditType.CloneItem(requireNotNull(savedStateHandle[EDIT_ITEM_ID])) else -> throw IllegalStateException("Unknown VaultAddEditType.") }, ) @@ -79,7 +81,12 @@ private fun VaultAddEditType.toTypeString(): String = when (this) { is VaultAddEditType.AddItem -> ADD_TYPE is VaultAddEditType.EditItem -> EDIT_TYPE + is VaultAddEditType.CloneItem -> CLONE_TYPE } private fun VaultAddEditType.toIdOrNull(): String? = - (this as? VaultAddEditType.EditItem)?.vaultItemId + when (this) { + is VaultAddEditType.AddItem -> null + is VaultAddEditType.CloneItem -> vaultItemId + is VaultAddEditType.EditItem -> vaultItemId + } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt index 044849e35c..1c5342fb9f 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt @@ -18,6 +18,7 @@ import com.x8bit.bitwarden.ui.platform.base.BaseViewModel import com.x8bit.bitwarden.ui.platform.base.util.Text import com.x8bit.bitwarden.ui.platform.base.util.asText import com.x8bit.bitwarden.ui.platform.base.util.concat +import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorMode import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldType import com.x8bit.bitwarden.ui.vault.feature.addedit.model.toCustomField @@ -56,6 +57,7 @@ class VaultAddEditViewModel @Inject constructor( private val clipboardManager: BitwardenClipboardManager, private val vaultRepository: VaultRepository, private val generatorRepository: GeneratorRepository, + private val resourceManager: ResourceManager, ) : BaseViewModel( // We load the state from the savedStateHandle for testing purposes. initialState = savedStateHandle[KEY_STATE] @@ -70,6 +72,7 @@ class VaultAddEditViewModel @Inject constructor( ) is VaultAddEditType.EditItem -> VaultAddEditState.ViewState.Loading + is VaultAddEditType.CloneItem -> VaultAddEditState.ViewState.Loading }, dialog = null, ) @@ -79,18 +82,18 @@ class VaultAddEditViewModel @Inject constructor( //region Initialization and Overrides init { - when (val vaultAddEditType = state.vaultAddEditType) { - VaultAddEditType.AddItem -> Unit - is VaultAddEditType.EditItem -> { + state + .vaultAddEditType + .vaultItemId + ?.let { itemId -> vaultRepository - .getVaultItemStateFlow(vaultAddEditType.vaultItemId) + .getVaultItemStateFlow(itemId) // We'll stop getting updates as soon as we get some loaded data. .takeUntilLoaded() .map { VaultAddEditAction.Internal.VaultDataReceive(it) } .onEach(::sendAction) .launchIn(viewModelScope) } - } vaultRepository .totpCodeFlow @@ -240,6 +243,11 @@ class VaultAddEditViewModel @Inject constructor( ) sendAction(VaultAddEditAction.Internal.UpdateCipherResultReceive(result)) } + + is VaultAddEditType.CloneItem -> { + val result = vaultRepository.createCipher(cipherView = content.toCipherView()) + sendAction(VaultAddEditAction.Internal.CreateCipherResultReceive(result)) + } } } } @@ -810,6 +818,7 @@ class VaultAddEditViewModel @Inject constructor( } } + @Suppress("LongMethod") private fun handleVaultDataReceive(action: VaultAddEditAction.Internal.VaultDataReceive) { when (val vaultDataState = action.vaultDataState) { is DataState.Error -> { @@ -827,7 +836,10 @@ class VaultAddEditViewModel @Inject constructor( it.copy( viewState = vaultDataState .data - ?.toViewState() + ?.toViewState( + isClone = it.isCloneMode, + resourceManager = resourceManager, + ) ?: VaultAddEditState.ViewState.Error( message = R.string.generic_error_message.asText(), ), @@ -858,7 +870,10 @@ class VaultAddEditViewModel @Inject constructor( it.copy( viewState = vaultDataState .data - ?.toViewState() + ?.toViewState( + isClone = it.isCloneMode, + resourceManager = resourceManager, + ) ?: VaultAddEditState.ViewState.Error( message = R.string.generic_error_message.asText(), ), @@ -1041,6 +1056,7 @@ data class VaultAddEditState( get() = when (vaultAddEditType) { VaultAddEditType.AddItem -> R.string.add_item.asText() is VaultAddEditType.EditItem -> R.string.edit_item.asText() + is VaultAddEditType.CloneItem -> R.string.add_item.asText() } /** @@ -1048,6 +1064,11 @@ data class VaultAddEditState( */ val isAddItemMode: Boolean get() = vaultAddEditType == VaultAddEditType.AddItem + /** + * Helper to determine if the UI should display the content in clone mode. + */ + val isCloneMode: Boolean get() = vaultAddEditType is VaultAddEditType.CloneItem + /** * Enum representing the main type options for the vault, such as LOGIN, CARD, etc. * diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/CipherViewExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/CipherViewExtensions.kt index 3fe9916993..0c45bf4b1d 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/CipherViewExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/CipherViewExtensions.kt @@ -7,6 +7,7 @@ import com.bitwarden.core.FieldType import com.bitwarden.core.FieldView import com.x8bit.bitwarden.R import com.x8bit.bitwarden.ui.platform.base.util.asText +import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand import com.x8bit.bitwarden.ui.vault.model.VaultCardExpirationMonth @@ -18,7 +19,10 @@ import java.util.UUID /** * Transforms [CipherView] into [VaultAddEditState.ViewState]. */ -fun CipherView.toViewState(): VaultAddEditState.ViewState = +fun CipherView.toViewState( + isClone: Boolean, + resourceManager: ResourceManager, +): VaultAddEditState.ViewState = VaultAddEditState.ViewState.Content( type = when (type) { CipherType.LOGIN -> { @@ -63,7 +67,10 @@ fun CipherView.toViewState(): VaultAddEditState.ViewState = }, common = VaultAddEditState.ViewState.Content.Common( originalCipher = this, - name = this.name, + name = name.appendCloneTextIfRequired( + isClone = isClone, + resourceManager = resourceManager, + ), favorite = this.favorite, masterPasswordReprompt = this.reprompt == CipherRepromptType.PASSWORD, notes = this.notes.orEmpty(), @@ -121,3 +128,13 @@ private fun String?.toExpirationMonthOrDefault(): VaultCardExpirationMonth = .entries .find { it.number == this } ?: VaultCardExpirationMonth.SELECT + +private fun String.appendCloneTextIfRequired( + isClone: Boolean, + resourceManager: ResourceManager, +): String = + if (isClone) { + plus(" - ${resourceManager.getString(R.string.clone)}") + } else { + this + } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemNavigation.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemNavigation.kt index 92f1a88fda..78463666d2 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemNavigation.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemNavigation.kt @@ -28,7 +28,7 @@ data class VaultItemArgs(val vaultItemId: String) { */ fun NavGraphBuilder.vaultItemDestination( onNavigateBack: () -> Unit, - onNavigateToVaultEditItem: (vaultItemId: String) -> Unit, + onNavigateToVaultEditItem: (vaultItemId: String, isClone: Boolean) -> Unit, onNavigateToMoveToOrganization: (vaultItemId: String) -> Unit, ) { composableWithSlideTransitions( diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreen.kt index 6bacb7b0ba..00669e234a 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreen.kt @@ -59,7 +59,7 @@ fun VaultItemScreen( viewModel: VaultItemViewModel = hiltViewModel(), intentManager: IntentManager = LocalIntentManager.current, onNavigateBack: () -> Unit, - onNavigateToVaultAddEditItem: (vaultItemId: String) -> Unit, + onNavigateToVaultAddEditItem: (vaultItemId: String, isClone: Boolean) -> Unit, onNavigateToMoveToOrganization: (vaultItemId: String) -> Unit, ) { val state by viewModel.stateFlow.collectAsStateWithLifecycle() @@ -75,8 +75,7 @@ fun VaultItemScreen( VaultItemEvent.NavigateBack -> onNavigateBack() is VaultItemEvent.NavigateToAddEdit -> { - // TODO Implement cloning in BIT-526 - onNavigateToVaultAddEditItem(event.itemId) + onNavigateToVaultAddEditItem(event.itemId, event.isClone) } is VaultItemEvent.NavigateToPasswordHistory -> { diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/model/VaultAddEditType.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/model/VaultAddEditType.kt index 08331f043f..8ed396a917 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/model/VaultAddEditType.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/model/VaultAddEditType.kt @@ -7,19 +7,34 @@ import kotlinx.parcelize.Parcelize * Represents the difference between create a completely new cipher and editing an existing one. */ sealed class VaultAddEditType : Parcelable { + + /** + * The ID of the vault item (nullable). + */ + abstract val vaultItemId: String? + /** * Indicates that we want to create a completely new vault item. */ @Parcelize - data object AddItem : VaultAddEditType() + data object AddItem : VaultAddEditType() { + override val vaultItemId: String? + get() = null + } /** * Indicates that we want to edit an existing item. - * - * @param vaultItemId The ID of the vault item to edit. */ @Parcelize data class EditItem( - val vaultItemId: String, + override val vaultItemId: String, + ) : VaultAddEditType() + + /** + * Indicates that we want to clone an existing item. + */ + @Parcelize + data class CloneItem( + override val vaultItemId: String, ) : VaultAddEditType() } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt index 441e6f1706..879a68d470 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt @@ -16,6 +16,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest import com.x8bit.bitwarden.ui.platform.base.util.Text import com.x8bit.bitwarden.ui.platform.base.util.asText +import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorMode import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldType import com.x8bit.bitwarden.ui.vault.feature.addedit.model.toCustomField @@ -58,7 +59,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { private val totpTestCodeFlow: MutableSharedFlow = bufferedMutableSharedFlow() private val mutableVaultItemFlow = MutableStateFlow>(DataState.Loading) - + private val resourceManager: ResourceManager = mockk() private val clipboardManager: BitwardenClipboardManager = mockk() private val vaultRepository: VaultRepository = mockk { every { getVaultItemStateFlow(DEFAULT_EDIT_ITEM_ID) } returns mutableVaultItemFlow @@ -131,6 +132,25 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { } } + @Test + fun `initial clone state should be correct`() = runTest { + val vaultAddEditType = VaultAddEditType.CloneItem(DEFAULT_EDIT_ITEM_ID) + val initState = createVaultAddItemState(vaultAddEditType = vaultAddEditType) + val viewModel = createAddVaultItemViewModel( + savedStateHandle = createSavedStateHandleWithState( + state = initState, + vaultAddEditType = vaultAddEditType, + ), + ) + assertEquals( + initState.copy(viewState = VaultAddEditState.ViewState.Loading), + viewModel.stateFlow.value, + ) + verify(exactly = 1) { + vaultRepository.getVaultItemStateFlow(DEFAULT_EDIT_ITEM_ID) + } + } + @Test fun `CloseClick should emit NavigateBack`() = runTest { val viewModel = createAddVaultItemViewModel() @@ -254,7 +274,12 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { name = "mockName", ), ) - every { cipherView.toViewState() } returns stateWithName.viewState + every { + cipherView.toViewState( + isClone = false, + resourceManager = resourceManager, + ) + } returns stateWithName.viewState mutableVaultItemFlow.value = DataState.Loaded(cipherView) val viewModel = createAddVaultItemViewModel( @@ -276,7 +301,10 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { } coVerify(exactly = 1) { - cipherView.toViewState() + cipherView.toViewState( + isClone = false, + resourceManager = resourceManager, + ) vaultRepository.updateCipher(DEFAULT_EDIT_ITEM_ID, any()) } } @@ -294,7 +322,12 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { ), ) - every { cipherView.toViewState() } returns stateWithName.viewState + every { + cipherView.toViewState( + isClone = false, + resourceManager = resourceManager, + ) + } returns stateWithName.viewState coEvery { vaultRepository.updateCipher(DEFAULT_EDIT_ITEM_ID, any()) } returns UpdateCipherResult.Error(errorMessage = null) @@ -336,7 +369,12 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { ) val errorMessage = "You do not have permission to edit this." - every { cipherView.toViewState() } returns stateWithName.viewState + every { + cipherView.toViewState( + isClone = false, + resourceManager = resourceManager, + ) + } returns stateWithName.viewState coEvery { vaultRepository.updateCipher(DEFAULT_EDIT_ITEM_ID, any()) } returns UpdateCipherResult.Error(errorMessage = errorMessage) @@ -1192,6 +1230,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { clipboardManager = clipboardManager, vaultRepository = vaultRepository, generatorRepository = generatorRepository, + resourceManager = resourceManager, ) } @@ -1484,6 +1523,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { when (vaultAddEditType) { VaultAddEditType.AddItem -> "add" is VaultAddEditType.EditItem -> "edit" + is VaultAddEditType.CloneItem -> "clone" }, ) set("vault_edit_id", (vaultAddEditType as? VaultAddEditType.EditItem)?.vaultItemId) @@ -1494,12 +1534,14 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { bitwardenClipboardManager: BitwardenClipboardManager = clipboardManager, vaultRepo: VaultRepository = vaultRepository, generatorRepo: GeneratorRepository = generatorRepository, + bitwardenResourceManager: ResourceManager = resourceManager, ): VaultAddEditViewModel = VaultAddEditViewModel( savedStateHandle = savedStateHandle, clipboardManager = bitwardenClipboardManager, vaultRepository = vaultRepo, generatorRepository = generatorRepo, + resourceManager = bitwardenResourceManager, ) /** diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/CipherViewExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/CipherViewExtensionsTest.kt index c8cd12b590..9a5cce6b66 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/CipherViewExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/CipherViewExtensionsTest.kt @@ -14,10 +14,12 @@ import com.bitwarden.core.SecureNoteType import com.bitwarden.core.SecureNoteView import com.x8bit.bitwarden.R import com.x8bit.bitwarden.ui.platform.base.util.asText +import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType import io.mockk.every +import io.mockk.mockk import io.mockk.mockkStatic import io.mockk.unmockkStatic import org.junit.jupiter.api.AfterEach @@ -29,6 +31,10 @@ import java.util.UUID class CipherViewExtensionsTest { + private val resourceManager: ResourceManager = mockk { + every { getString(R.string.clone) } returns "Clone" + } + @BeforeEach fun setup() { mockkStatic(UUID::randomUUID) @@ -44,7 +50,10 @@ class CipherViewExtensionsTest { fun `toViewState should create a Card ViewState`() { val cipherView = DEFAULT_CARD_CIPHER_VIEW - val result = cipherView.toViewState() + val result = cipherView.toViewState( + isClone = false, + resourceManager = resourceManager, + ) assertEquals( VaultAddEditState.ViewState.Content( @@ -85,7 +94,10 @@ class CipherViewExtensionsTest { fun `toViewState should create a Identity ViewState`() { val cipherView = DEFAULT_IDENTITY_CIPHER_VIEW - val result = cipherView.toViewState() + val result = cipherView.toViewState( + isClone = false, + resourceManager = resourceManager, + ) assertEquals( VaultAddEditState.ViewState.Content( @@ -131,7 +143,10 @@ class CipherViewExtensionsTest { fun `toViewState should create a Login ViewState`() { val cipherView = DEFAULT_LOGIN_CIPHER_VIEW - val result = cipherView.toViewState() + val result = cipherView.toViewState( + isClone = false, + resourceManager = resourceManager, + ) assertEquals( VaultAddEditState.ViewState.Content( @@ -172,7 +187,10 @@ class CipherViewExtensionsTest { fun `toViewState should create a Secure Notes ViewState`() { val cipherView = DEFAULT_SECURE_NOTES_CIPHER_VIEW - val result = cipherView.toViewState() + val result = cipherView.toViewState( + isClone = false, + resourceManager = resourceManager, + ) assertEquals( VaultAddEditState.ViewState.Content( @@ -197,6 +215,39 @@ class CipherViewExtensionsTest { result, ) } + + @Test + fun `toViewState with isClone true should append clone text to the cipher name`() { + val cipherView = DEFAULT_SECURE_NOTES_CIPHER_VIEW + + val result = cipherView.toViewState( + isClone = true, + resourceManager = resourceManager, + ) + + assertEquals( + VaultAddEditState.ViewState.Content( + common = VaultAddEditState.ViewState.Content.Common( + originalCipher = cipherView, + name = "cipher - Clone", + folderName = R.string.folder_none.asText(), + favorite = false, + masterPasswordReprompt = true, + notes = "Lots of notes", + ownership = "", + customFieldData = listOf( + VaultAddEditState.Custom.BooleanField(TEST_ID, "TestBoolean", false), + VaultAddEditState.Custom.TextField(TEST_ID, "TestText", "TestText"), + VaultAddEditState.Custom.HiddenField(TEST_ID, "TestHidden", "TestHidden"), + ), + availableFolders = emptyList(), + availableOwners = emptyList(), + ), + type = VaultAddEditState.ViewState.Content.ItemType.SecureNotes, + ), + result, + ) + } } private val DEFAULT_BASE_CIPHER_VIEW: CipherView = CipherView( diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt index 712489bb5e..b15afb6b75 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt @@ -70,7 +70,7 @@ class VaultItemScreenTest : BaseComposeTest() { VaultItemScreen( viewModel = viewModel, onNavigateBack = { onNavigateBackCalled = true }, - onNavigateToVaultAddEditItem = { onNavigateToVaultEditItemId = it }, + onNavigateToVaultAddEditItem = { id, _ -> onNavigateToVaultEditItemId = id }, onNavigateToMoveToOrganization = { onNavigateToMoveToOrganizationItemId = it }, intentManager = intentManager, )