Add sealed class to model reusable overflow actions (#682)

This commit is contained in:
David Perez authored and Álison Fernandes committed 2024-06-20 17:08:07 +01:00
1 parent 6cbfff254c
commit cf930438c2
11 files changed
+400 -288

No files matched your search

@@ -3,6 +3,7 @@ package com.x8bit.bitwarden.ui.tools.feature.send.util
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSendView
import com.x8bit.bitwarden.ui.platform.components.model.IconRes
import com.x8bit.bitwarden.ui.tools.feature.send.model.SendStatusIcon
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import java.time.Clock
@@ -60,6 +61,49 @@ class SendViewExtensionsTest {
assertEquals(emptyList<IconRes>(), result)
}
@Suppress("MaxLineLength")
@Test
fun `toOverflowActions should return overflow options with remove password when there is a password`() {
val baseWebSendUrl = "www.test.com"
val sendView = createMockSendView(number = 1).copy(
// Make sure there is a password for the remove password action
hasPassword = true,
)
val result = sendView.toOverflowActions(baseWebSendUrl = baseWebSendUrl)
assertEquals(ALL_SEND_OVERFLOW_OPTIONS, result)
}
@Suppress("MaxLineLength")
@Test
fun `toOverflowActions should return overflow options without Remove Password when there is no password`() {
val baseWebSendUrl = "www.test.com"
val sendView = createMockSendView(number = 1).copy(
// Make sure there is no password for the remove password action
hasPassword = false,
)
val result = sendView.toOverflowActions(baseWebSendUrl = baseWebSendUrl)
assertEquals(
ALL_SEND_OVERFLOW_OPTIONS.filter {
it !is ListingItemOverflowAction.SendAction.RemovePasswordClick
},
result,
)
}
@Test
fun `toOverflowActions should return no overflow options when the id is null`() {
val baseWebSendUrl = "www.test.com"
val sendView = createMockSendView(number = 1).copy(id = null)
val result = sendView.toOverflowActions(baseWebSendUrl = baseWebSendUrl)
assertEquals(emptyList<ListingItemOverflowAction>(), result)
}
@Test
fun `toSendUrl should create an appropriate url`() {
val sendView = createMockSendView(number = 1)
@@ -92,3 +136,16 @@ private val ALL_SEND_STATUS_ICONS: List<IconRes> = listOf(
contentDescription = SendStatusIcon.PENDING_DELETE.contentDescription,
),
)
private val ALL_SEND_OVERFLOW_OPTIONS: List<ListingItemOverflowAction> =
listOf(
ListingItemOverflowAction.SendAction.EditClick(sendId = "mockId-1"),
ListingItemOverflowAction.SendAction.CopyUrlClick(
sendUrl = "www.test.commockAccessId-1/mockKey-1",
),
ListingItemOverflowAction.SendAction.ShareUrlClick(
sendUrl = "www.test.commockAccessId-1/mockKey-1",
),
ListingItemOverflowAction.SendAction.RemovePasswordClick(sendId = "mockId-1"),
ListingItemOverflowAction.SendAction.DeleteClick(sendId = "mockId-1"),
)
@@ -29,6 +29,7 @@ import com.x8bit.bitwarden.ui.platform.components.model.IconRes
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
import com.x8bit.bitwarden.ui.util.assertNoDialogExists
import com.x8bit.bitwarden.ui.util.isProgressBar
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
import io.mockk.every
import io.mockk.just
@@ -544,7 +545,11 @@ class VaultItemListingScreenTest : BaseComposeTest() {
.performClick()
verify(exactly = 1) {
viewModel.trySendAction(
VaultItemListingsAction.ItemClick(id = "mockId-$number"),
VaultItemListingsAction.OverflowOptionClick(
action = ListingItemOverflowAction.SendAction.EditClick(
sendId = "mockId-$number",
),
),
)
}
@@ -558,7 +563,11 @@ class VaultItemListingScreenTest : BaseComposeTest() {
.performClick()
verify(exactly = 1) {
viewModel.trySendAction(
VaultItemListingsAction.CopySendUrlClick(sendUrl = "www.test.com"),
VaultItemListingsAction.OverflowOptionClick(
action = ListingItemOverflowAction.SendAction.CopyUrlClick(
sendUrl = "www.test.com",
),
),
)
}
@@ -572,7 +581,11 @@ class VaultItemListingScreenTest : BaseComposeTest() {
.performClick()
verify(exactly = 1) {
viewModel.trySendAction(
VaultItemListingsAction.ShareSendUrlClick(sendUrl = "www.test.com"),
VaultItemListingsAction.OverflowOptionClick(
action = ListingItemOverflowAction.SendAction.ShareUrlClick(
sendUrl = "www.test.com",
),
),
)
}
@@ -586,9 +599,29 @@ class VaultItemListingScreenTest : BaseComposeTest() {
.performClick()
verify(exactly = 1) {
viewModel.trySendAction(
VaultItemListingsAction.RemoveSendPasswordClick(sendId = "mockId-$number"),
VaultItemListingsAction.OverflowOptionClick(
action = ListingItemOverflowAction.SendAction.RemovePasswordClick(
sendId = "mockId-$number",
),
),
)
}
}
@Suppress("MaxLineLength")
@Test
fun `on send item delete overflow option click should display delete confirmation dialog and emits DeleteSendConfirmClick on confirmation`() {
val sendId = "mockId-1"
val message = "Are you sure you want to delete this Send?"
mutableStateFlow.update {
it.copy(
viewState = VaultItemListingState.ViewState.Content(
displayItemList = listOf(createDisplayItem(number = 1)),
),
)
}
composeTestRule.onNode(isDialog()).assertDoesNotExist()
composeTestRule.onNodeWithText(message).assertDoesNotExist()
composeTestRule
.onNodeWithContentDescription("Options")
@@ -598,28 +631,6 @@ class VaultItemListingScreenTest : BaseComposeTest() {
.onNodeWithText("Delete")
.assert(hasAnyAncestor(isDialog()))
.performClick()
verify(exactly = 1) {
viewModel.trySendAction(
VaultItemListingsAction.DeleteSendClick(sendId = "mockId-$number"),
)
}
}
@Suppress("MaxLineLength")
@Test
fun `delete send confirmation dialog should be displayed according to state and emits DeleteSendConfirmClick on confirmation`() {
val sendId = "sendId"
val message = "Are you sure you want to delete this Send?"
composeTestRule.onNode(isDialog()).assertDoesNotExist()
composeTestRule.onNodeWithText(message).assertDoesNotExist()
mutableStateFlow.update {
it.copy(
dialogState = VaultItemListingState.DialogState.DeleteSendConfirmation(
sendId = sendId,
),
)
}
composeTestRule
.onNodeWithText(message)
@@ -631,7 +642,11 @@ class VaultItemListingScreenTest : BaseComposeTest() {
.performClick()
verify(exactly = 1) {
viewModel.trySendAction(VaultItemListingsAction.DeleteSendConfirmClick(sendId))
viewModel.trySendAction(
VaultItemListingsAction.OverflowOptionClick(
action = ListingItemOverflowAction.SendAction.DeleteClick(sendId = sendId),
),
)
}
}
@@ -716,25 +731,10 @@ private fun createDisplayItem(number: Int): VaultItemListingState.DisplayItem =
),
),
overflowOptions = listOf(
VaultItemListingState.DisplayItem.OverflowItem(
title = R.string.edit.asText(),
action = VaultItemListingsAction.ItemClick(id = "mockId-$number"),
),
VaultItemListingState.DisplayItem.OverflowItem(
title = R.string.copy_link.asText(),
action = VaultItemListingsAction.CopySendUrlClick(sendUrl = "www.test.com"),
),
VaultItemListingState.DisplayItem.OverflowItem(
title = R.string.share_link.asText(),
action = VaultItemListingsAction.ShareSendUrlClick(sendUrl = "www.test.com"),
),
VaultItemListingState.DisplayItem.OverflowItem(
title = R.string.remove_password.asText(),
action = VaultItemListingsAction.RemoveSendPasswordClick(sendId = "mockId-$number"),
),
VaultItemListingState.DisplayItem.OverflowItem(
title = R.string.delete.asText(),
action = VaultItemListingsAction.DeleteSendClick(sendId = "mockId-$number"),
),
ListingItemOverflowAction.SendAction.EditClick(sendId = "mockId-$number"),
ListingItemOverflowAction.SendAction.CopyUrlClick(sendUrl = "www.test.com"),
ListingItemOverflowAction.SendAction.ShareUrlClick(sendUrl = "www.test.com"),
ListingItemOverflowAction.SendAction.RemovePasswordClick(sendId = "mockId-$number"),
ListingItemOverflowAction.SendAction.DeleteClick(sendId = "mockId-$number"),
),
)
@@ -22,6 +22,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.VaultData
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.base.util.concat
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.createMockDisplayItemForCipher
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
@@ -189,89 +190,105 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
}
@Test
fun `CopySendUrlClick should call setText on clipboardManager`() {
fun `OverflowOptionClick Send EditClick should emit NavigateToSendItem`() = runTest {
val sendId = "sendId"
val viewModel = createVaultItemListingViewModel()
viewModel.eventFlow.test {
viewModel.actionChannel.trySend(
VaultItemListingsAction.OverflowOptionClick(
ListingItemOverflowAction.SendAction.EditClick(sendId = sendId),
),
)
assertEquals(VaultItemListingEvent.NavigateToSendItem(sendId), awaitItem())
}
}
@Test
fun `OverflowOptionClick Send CopyUrlClick should call setText on clipboardManager`() {
val sendUrl = "www.test.com"
every { clipboardManager.setText(sendUrl) } just runs
val viewModel = createVaultItemListingViewModel()
viewModel.actionChannel.trySend(VaultItemListingsAction.CopySendUrlClick(sendUrl = sendUrl))
viewModel.actionChannel.trySend(
VaultItemListingsAction.OverflowOptionClick(
ListingItemOverflowAction.SendAction.CopyUrlClick(sendUrl = sendUrl),
),
)
verify(exactly = 1) {
clipboardManager.setText(text = sendUrl)
}
}
@Test
fun `DeleteSendClick should display delete confirmation dialog`() {
val sendId = "sendId"
val viewModel = createVaultItemListingViewModel()
viewModel.trySendAction(VaultItemListingsAction.DeleteSendClick(sendId))
assertEquals(
initialState.copy(
dialogState = VaultItemListingState.DialogState.DeleteSendConfirmation(
sendId = sendId,
),
),
viewModel.stateFlow.value,
)
}
fun `OverflowOptionClick Send DeleteClick with deleteSend error should display error dialog`() =
runTest {
val sendId = "sendId1234"
coEvery { vaultRepository.deleteSend(sendId) } returns DeleteSendResult.Error
@Test
fun `DeleteSendConfirmClick with deleteSend error should display error dialog`() = runTest {
val sendId = "sendId1234"
coEvery { vaultRepository.deleteSend(sendId) } returns DeleteSendResult.Error
val viewModel = createVaultItemListingViewModel()
viewModel.stateFlow.test {
assertEquals(initialState, awaitItem())
viewModel.trySendAction(VaultItemListingsAction.DeleteSendConfirmClick(sendId))
assertEquals(
initialState.copy(
dialogState = VaultItemListingState.DialogState.Loading(
message = R.string.deleting.asText(),
val viewModel = createVaultItemListingViewModel()
viewModel.stateFlow.test {
assertEquals(initialState, awaitItem())
viewModel.actionChannel.trySend(
VaultItemListingsAction.OverflowOptionClick(
ListingItemOverflowAction.SendAction.DeleteClick(sendId = sendId),
),
),
awaitItem(),
)
assertEquals(
initialState.copy(
dialogState = VaultItemListingState.DialogState.Error(
title = R.string.an_error_has_occurred.asText(),
message = R.string.generic_error_message.asText(),
)
assertEquals(
initialState.copy(
dialogState = VaultItemListingState.DialogState.Loading(
message = R.string.deleting.asText(),
),
),
),
awaitItem(),
)
awaitItem(),
)
assertEquals(
initialState.copy(
dialogState = VaultItemListingState.DialogState.Error(
title = R.string.an_error_has_occurred.asText(),
message = R.string.generic_error_message.asText(),
),
),
awaitItem(),
)
}
}
}
@Test
fun `DeleteSendConfirmClick with deleteSend success should emit ShowToast`() = runTest {
val sendId = "sendId1234"
coEvery { vaultRepository.deleteSend(sendId) } returns DeleteSendResult.Success
fun `OverflowOptionClick Send DeleteClick with deleteSend success should emit ShowToast`() =
runTest {
val sendId = "sendId1234"
coEvery { vaultRepository.deleteSend(sendId) } returns DeleteSendResult.Success
val viewModel = createVaultItemListingViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(VaultItemListingsAction.DeleteSendConfirmClick(sendId))
assertEquals(
VaultItemListingEvent.ShowToast(R.string.send_deleted.asText()),
awaitItem(),
)
val viewModel = createVaultItemListingViewModel()
viewModel.eventFlow.test {
viewModel.actionChannel.trySend(
VaultItemListingsAction.OverflowOptionClick(
ListingItemOverflowAction.SendAction.DeleteClick(sendId = sendId),
),
)
assertEquals(
VaultItemListingEvent.ShowToast(R.string.send_deleted.asText()),
awaitItem(),
)
}
}
}
@Test
fun `ShareSendUrlClick should emit ShowShareSheet`() = runTest {
fun `OverflowOptionClick Send ShareUrlClick should emit ShowShareSheet`() = runTest {
val sendUrl = "www.test.com"
val viewModel = createVaultItemListingViewModel()
viewModel.eventFlow.test {
viewModel.actionChannel.trySend(
VaultItemListingsAction.ShareSendUrlClick(sendUrl = sendUrl),
VaultItemListingsAction.OverflowOptionClick(
ListingItemOverflowAction.SendAction.ShareUrlClick(sendUrl = sendUrl),
),
)
assertEquals(VaultItemListingEvent.ShowShareSheet(sendUrl), awaitItem())
}
}
@Suppress("MaxLineLength")
@Test
fun `RemovePasswordClick with removePasswordSend error should display error dialog`() =
fun `OverflowOptionClick Send RemovePasswordClick with removePasswordSend error should display error dialog`() =
runTest {
val sendId = "sendId1234"
coEvery {
@@ -281,7 +298,11 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
val viewModel = createVaultItemListingViewModel()
viewModel.stateFlow.test {
assertEquals(initialState, awaitItem())
viewModel.trySendAction(VaultItemListingsAction.RemoveSendPasswordClick(sendId))
viewModel.actionChannel.trySend(
VaultItemListingsAction.OverflowOptionClick(
ListingItemOverflowAction.SendAction.RemovePasswordClick(sendId = sendId),
),
)
assertEquals(
initialState.copy(
dialogState = VaultItemListingState.DialogState.Loading(
@@ -302,22 +323,28 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
}
}
@Suppress("MaxLineLength")
@Test
fun `RemovePasswordClick with removePasswordSend success should emit ShowToast`() = runTest {
val sendId = "sendId1234"
coEvery {
vaultRepository.removePasswordSend(sendId)
} returns RemovePasswordSendResult.Success(mockk())
fun `OverflowOptionClick Send RemovePasswordClick with removePasswordSend success should emit ShowToast`() =
runTest {
val sendId = "sendId1234"
coEvery {
vaultRepository.removePasswordSend(sendId)
} returns RemovePasswordSendResult.Success(mockk())
val viewModel = createVaultItemListingViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(VaultItemListingsAction.RemoveSendPasswordClick(sendId))
assertEquals(
VaultItemListingEvent.ShowToast(R.string.send_password_removed.asText()),
awaitItem(),
)
val viewModel = createVaultItemListingViewModel()
viewModel.eventFlow.test {
viewModel.actionChannel.trySend(
VaultItemListingsAction.OverflowOptionClick(
ListingItemOverflowAction.SendAction.RemovePasswordClick(sendId = sendId),
),
)
assertEquals(
VaultItemListingEvent.ShowToast(R.string.send_password_removed.asText()),
awaitItem(),
)
}
}
}
@Test
fun `vaultDataStateFlow Loaded with items should update ViewState to Content`() =
@@ -7,7 +7,7 @@ import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.components.model.IconData
import com.x8bit.bitwarden.ui.platform.components.model.IconRes
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.VaultItemListingState
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.VaultItemListingsAction
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
/**
* Create a mock [VaultItemListingState.DisplayItem] with a given [number].
@@ -90,33 +90,16 @@ fun createMockDisplayItemForSend(
contentDescription = R.string.maximum_access_count_reached.asText(),
),
),
overflowOptions = listOfNotNull(
VaultItemListingState.DisplayItem.OverflowItem(
title = R.string.edit.asText(),
action = VaultItemListingsAction.ItemClick(id = "mockId-$number"),
overflowOptions = listOf(
ListingItemOverflowAction.SendAction.EditClick(sendId = "mockId-$number"),
ListingItemOverflowAction.SendAction.CopyUrlClick(
sendUrl = "https://vault.bitwarden.com/#/send/mockAccessId-$number/mockKey-$number",
),
VaultItemListingState.DisplayItem.OverflowItem(
title = R.string.copy_link.asText(),
action = VaultItemListingsAction.CopySendUrlClick(
sendUrl = "https://vault.bitwarden.com/#/send/mockAccessId-$number/mockKey-$number",
),
),
VaultItemListingState.DisplayItem.OverflowItem(
title = R.string.share_link.asText(),
action = VaultItemListingsAction.ShareSendUrlClick(
sendUrl = "https://vault.bitwarden.com/#/send/mockAccessId-$number/mockKey-$number",
),
),
VaultItemListingState.DisplayItem.OverflowItem(
title = R.string.remove_password.asText(),
action = VaultItemListingsAction.RemoveSendPasswordClick(
sendId = "mockId-$number",
),
),
VaultItemListingState.DisplayItem.OverflowItem(
title = R.string.delete.asText(),
action = VaultItemListingsAction.DeleteSendClick(sendId = "mockId-$number"),
ListingItemOverflowAction.SendAction.ShareUrlClick(
sendUrl = "https://vault.bitwarden.com/#/send/mockAccessId-$number/mockKey-$number",
),
ListingItemOverflowAction.SendAction.RemovePasswordClick(sendId = "mockId-$number"),
ListingItemOverflowAction.SendAction.DeleteClick(sendId = "mockId-$number"),
),
)
}
@@ -137,33 +120,16 @@ fun createMockDisplayItemForSend(
contentDescription = R.string.maximum_access_count_reached.asText(),
),
),
overflowOptions = listOfNotNull(
VaultItemListingState.DisplayItem.OverflowItem(
title = R.string.edit.asText(),
action = VaultItemListingsAction.ItemClick(id = "mockId-$number"),
overflowOptions = listOf(
ListingItemOverflowAction.SendAction.EditClick(sendId = "mockId-$number"),
ListingItemOverflowAction.SendAction.CopyUrlClick(
sendUrl = "https://vault.bitwarden.com/#/send/mockAccessId-$number/mockKey-$number",
),
VaultItemListingState.DisplayItem.OverflowItem(
title = R.string.copy_link.asText(),
action = VaultItemListingsAction.CopySendUrlClick(
sendUrl = "https://vault.bitwarden.com/#/send/mockAccessId-$number/mockKey-$number",
),
),
VaultItemListingState.DisplayItem.OverflowItem(
title = R.string.share_link.asText(),
action = VaultItemListingsAction.ShareSendUrlClick(
sendUrl = "https://vault.bitwarden.com/#/send/mockAccessId-$number/mockKey-$number",
),
),
VaultItemListingState.DisplayItem.OverflowItem(
title = R.string.remove_password.asText(),
action = VaultItemListingsAction.RemoveSendPasswordClick(
sendId = "mockId-$number",
),
),
VaultItemListingState.DisplayItem.OverflowItem(
title = R.string.delete.asText(),
action = VaultItemListingsAction.DeleteSendClick(sendId = "mockId-$number"),
ListingItemOverflowAction.SendAction.ShareUrlClick(
sendUrl = "https://vault.bitwarden.com/#/send/mockAccessId-$number/mockKey-$number",
),
ListingItemOverflowAction.SendAction.RemovePasswordClick(sendId = "mockId-$number"),
ListingItemOverflowAction.SendAction.DeleteClick(sendId = "mockId-$number"),
),
)
}