BIT-1518: Process shared sends (#698)

This commit is contained in:
David Perez authored and Álison Fernandes committed 2024-06-20 17:08:07 +01:00
1 parent 4510695f76
commit c9d7a48598
14 files changed
+462 -60

No files matched your search

@@ -23,6 +23,7 @@ import androidx.compose.ui.test.performTextInput
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.manager.exit.ExitManager
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
import com.x8bit.bitwarden.ui.platform.manager.permissions.FakePermissionManager
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.model.AddSendType
@@ -45,6 +46,9 @@ class AddSendScreenTest : BaseComposeTest() {
private var onNavigateBackCalled = false
private val exitManager: ExitManager = mockk(relaxed = true) {
every { exitApplication() } just runs
}
private val permissionsManager = FakePermissionManager()
private val intentManager: IntentManager = mockk(relaxed = true) {
every { shareText(any()) } just runs
@@ -61,6 +65,7 @@ class AddSendScreenTest : BaseComposeTest() {
composeTestRule.setContent {
AddSendScreen(
viewModel = viewModel,
exitManager = exitManager,
intentManager = intentManager,
permissionsManager = permissionsManager,
onNavigateBack = { onNavigateBackCalled = true },
@@ -74,6 +79,14 @@ class AddSendScreenTest : BaseComposeTest() {
assert(onNavigateBackCalled)
}
@Test
fun `ExitApp should call exitApplication on ExitManager`() {
mutableEventFlow.tryEmit(AddSendEvent.ExitApp)
verify {
exitManager.exitApplication()
}
}
@Test
fun `on ShowShareSheet should call shareText on IntentManager`() {
val text = "sharable stuff"
@@ -91,6 +104,14 @@ class AddSendScreenTest : BaseComposeTest() {
verify { viewModel.trySendAction(AddSendAction.CloseClick) }
}
@Test
fun `display navigation icon according to state`() {
mutableStateFlow.update { it.copy(isShared = false) }
composeTestRule.onNodeWithContentDescription("Close").assertIsDisplayed()
mutableStateFlow.update { it.copy(isShared = true) }
composeTestRule.onNodeWithContentDescription("Close").assertDoesNotExist()
}
@Test
fun `on save click should send SaveClick`() {
composeTestRule
@@ -282,7 +303,25 @@ class AddSendScreenTest : BaseComposeTest() {
@Test
fun `segmented buttons should appear based on state`() {
mutableStateFlow.update { it.copy(addSendType = AddSendType.AddItem) }
mutableStateFlow.update { it.copy(isShared = true) }
composeTestRule
.onNodeWithText("Type")
.assertDoesNotExist()
composeTestRule
.onAllNodesWithText("File")
.filterToOne(!isEditableText)
.assertDoesNotExist()
composeTestRule
.onAllNodesWithText("Text")
.filterToOne(!isEditableText)
.assertDoesNotExist()
mutableStateFlow.update {
it.copy(
isShared = false,
addSendType = AddSendType.AddItem,
)
}
composeTestRule
.onNodeWithText("Type")
.performScrollTo()
@@ -889,6 +928,8 @@ class AddSendScreenTest : BaseComposeTest() {
addSendType = AddSendType.AddItem,
viewState = DEFAULT_VIEW_STATE,
dialogState = null,
shouldFinishOnComplete = false,
isShared = false,
isPremiumUser = false,
baseWebSendUrl = "https://vault.bitwarden.com/#/send/",
)
@@ -56,6 +56,8 @@ class AddSendViewModelTest : BaseViewModelTest() {
}
private val mutableUserStateFlow = MutableStateFlow<UserState?>(DEFAULT_USER_STATE)
private val authRepository: AuthRepository = mockk {
every { specialCircumstance } returns null
every { specialCircumstance = null } just runs
every { userStateFlow } returns mutableUserStateFlow
}
private val environmentRepository: EnvironmentRepository = mockk {
@@ -108,8 +110,9 @@ class AddSendViewModelTest : BaseViewModelTest() {
}
}
@Suppress("MaxLineLength")
@Test
fun `SaveClick with createSend success should emit NavigateBack and ShowShareSheet`() =
fun `SaveClick with createSend success should emit NavigateBack and ShowShareSheet when not an external shared`() =
runTest {
val viewState = DEFAULT_VIEW_STATE.copy(
common = DEFAULT_COMMON_STATE.copy(name = "input"),
@@ -137,6 +140,76 @@ class AddSendViewModelTest : BaseViewModelTest() {
}
}
@Suppress("MaxLineLength")
@Test
fun `SaveClick with createSend success should copy the send URL to the clipboard and emit NavigateBack`() =
runTest {
val viewState = DEFAULT_VIEW_STATE.copy(
common = DEFAULT_COMMON_STATE.copy(name = "input"),
)
val initialState = DEFAULT_STATE.copy(
shouldFinishOnComplete = false,
isShared = true,
viewState = viewState,
)
val mockSendView = mockk<SendView>()
every { viewState.toSendView(clock) } returns mockSendView
val sendUrl = "www.test.com/send/test"
val resultSendView = mockk<SendView> {
every { toSendUrl(DEFAULT_ENVIRONMENT_URL) } returns sendUrl
}
coEvery {
vaultRepository.createSend(sendView = mockSendView, fileUri = null)
} returns CreateSendResult.Success(sendView = resultSendView)
val viewModel = createViewModel(initialState)
viewModel.eventFlow.test {
viewModel.trySendAction(AddSendAction.SaveClick)
assertEquals(AddSendEvent.NavigateBack, awaitItem())
}
assertEquals(initialState, viewModel.stateFlow.value)
coVerify(exactly = 1) {
vaultRepository.createSend(sendView = mockSendView, fileUri = null)
authRepository.specialCircumstance = null
clipboardManager.setText(sendUrl)
}
}
@Suppress("MaxLineLength")
@Test
fun `SaveClick with createSend success should copy the send URL to the clipboard and emit ExitApp`() =
runTest {
val viewState = DEFAULT_VIEW_STATE.copy(
common = DEFAULT_COMMON_STATE.copy(name = "input"),
)
val initialState = DEFAULT_STATE.copy(
shouldFinishOnComplete = true,
isShared = true,
viewState = viewState,
)
val mockSendView = mockk<SendView>()
every { viewState.toSendView(clock) } returns mockSendView
val sendUrl = "www.test.com/send/test"
val resultSendView = mockk<SendView> {
every { toSendUrl(DEFAULT_ENVIRONMENT_URL) } returns sendUrl
}
coEvery {
vaultRepository.createSend(sendView = mockSendView, fileUri = null)
} returns CreateSendResult.Success(sendView = resultSendView)
val viewModel = createViewModel(initialState)
viewModel.eventFlow.test {
viewModel.trySendAction(AddSendAction.SaveClick)
assertEquals(AddSendEvent.ExitApp, awaitItem())
}
assertEquals(initialState, viewModel.stateFlow.value)
coVerify(exactly = 1) {
vaultRepository.createSend(sendView = mockSendView, fileUri = null)
authRepository.specialCircumstance = null
clipboardManager.setText(sendUrl)
}
}
@Test
fun `SaveClick with createSend failure should show error dialog`() = runTest {
val viewState = DEFAULT_VIEW_STATE.copy(
@@ -915,6 +988,8 @@ class AddSendViewModelTest : BaseViewModelTest() {
addSendType = AddSendType.AddItem,
viewState = DEFAULT_VIEW_STATE,
dialogState = null,
shouldFinishOnComplete = false,
isShared = false,
isPremiumUser = false,
baseWebSendUrl = DEFAULT_ENVIRONMENT_URL,
)
@@ -0,0 +1,135 @@
package com.x8bit.bitwarden.ui.tools.feature.send.addsend.util
import android.net.Uri
import com.x8bit.bitwarden.data.auth.repository.model.UserState
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.AddSendState
import io.mockk.mockk
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
class SpecialCircumstanceExtensionsTest {
@Test
fun `toSendType with TextSend should return Text SendType with correct text`() {
val text = "Share Text"
val expected = AddSendState.ViewState.Content.SendType.Text(
input = text,
isHideByDefaultChecked = false,
)
val specialCircumstance = UserState.SpecialCircumstance.ShareNewSend(
data = IntentManager.ShareData.TextSend(
subject = "",
text = text,
),
shouldFinishWhenComplete = false,
)
val result = specialCircumstance.toSendType()
assertEquals(expected, result)
}
@Test
fun `toSendType with FileSend should return File SendType with correct data`() {
val uri = mockk<Uri>()
val fileName = "Share Name"
val sizeBytes = 100L
val expected = AddSendState.ViewState.Content.SendType.File(
uri = uri,
name = fileName,
sizeBytes = sizeBytes,
displaySize = null,
)
val specialCircumstance = UserState.SpecialCircumstance.ShareNewSend(
data = IntentManager.ShareData.FileSend(
fileData = IntentManager.FileData(
fileName = fileName,
uri = uri,
sizeBytes = sizeBytes,
),
),
shouldFinishWhenComplete = false,
)
val result = specialCircumstance.toSendType()
assertEquals(expected, result)
}
@Test
fun `toSendType with null SpecialCircumstance should return null`() {
val specialCircumstance: UserState.SpecialCircumstance? = null
assertNull(specialCircumstance.toSendType())
}
@Test
fun `toSendName with TextSend should return subject`() {
val subject = "Subject"
val specialCircumstance = UserState.SpecialCircumstance.ShareNewSend(
data = IntentManager.ShareData.TextSend(
subject = subject,
text = "",
),
shouldFinishWhenComplete = false,
)
val result = specialCircumstance.toSendName()
assertEquals(subject, result)
}
@Test
fun `toSendName with FileSend should return file name`() {
val fileName = "File Name"
val specialCircumstance = UserState.SpecialCircumstance.ShareNewSend(
data = IntentManager.ShareData.FileSend(
fileData = IntentManager.FileData(
fileName = fileName,
uri = mockk(),
sizeBytes = 0L,
),
),
shouldFinishWhenComplete = false,
)
val result = specialCircumstance.toSendName()
assertEquals(fileName, result)
}
@Test
fun `toSendName with null SpecialCircumstance should return null`() {
val specialCircumstance: UserState.SpecialCircumstance? = null
assertNull(specialCircumstance.toSendName())
}
@Suppress("MaxLineLength")
@Test
fun `shouldFinishOnComplete with ShareNewSend shouldFinishWhenComplete true should return true`() {
val specialCircumstance = UserState.SpecialCircumstance.ShareNewSend(
data = mockk(),
shouldFinishWhenComplete = true,
)
assertTrue(specialCircumstance.shouldFinishOnComplete())
}
@Suppress("MaxLineLength")
@Test
fun `shouldFinishOnComplete with ShareNewSend shouldFinishWhenComplete false should return false`() {
val specialCircumstance = UserState.SpecialCircumstance.ShareNewSend(
data = mockk(),
shouldFinishWhenComplete = false,
)
assertFalse(specialCircumstance.shouldFinishOnComplete())
}
@Test
fun `shouldFinishOnComplete with null SpecialCircumstance should return false`() {
val specialCircumstance: UserState.SpecialCircumstance? = null
assertFalse(specialCircumstance.shouldFinishOnComplete())
}
}
@@ -22,7 +22,7 @@ import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFl
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
import com.x8bit.bitwarden.ui.platform.manager.exit.ExitManager
import com.x8bit.bitwarden.ui.util.assertLockOrLogoutDialogIsDisplayed
import com.x8bit.bitwarden.ui.util.assertLogoutConfirmationDialogIsDisplayed
import com.x8bit.bitwarden.ui.util.assertNoDialogExists
@@ -61,7 +61,7 @@ class VaultScreenTest : BaseComposeTest() {
private var onNavigateToVaultItemListingType: VaultItemListingType? = null
private var onDimBottomNavBarRequestCalled = false
private var onNavigateToVerificationCodeScreen = false
private val intentManager = mockk<IntentManager>(relaxed = true)
private val exitManager = mockk<ExitManager>(relaxed = true)
private val mutableEventFlow = bufferedMutableSharedFlow<VaultEvent>()
private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
@@ -81,7 +81,7 @@ class VaultScreenTest : BaseComposeTest() {
onNavigateToVaultItemListingScreen = { onNavigateToVaultItemListingType = it },
onDimBottomNavBarRequest = { onDimBottomNavBarRequestCalled = true },
onNavigateToVerificationCodeScreen = { onNavigateToVerificationCodeScreen = true },
intentManager = intentManager,
exitManager = exitManager,
)
}
}
@@ -646,9 +646,9 @@ class VaultScreenTest : BaseComposeTest() {
}
@Test
fun `NavigateOutOfApp event should call exitApplication on the IntentManager`() {
fun `NavigateOutOfApp event should call exitApplication on the ExitManager`() {
mutableEventFlow.tryEmit(VaultEvent.NavigateOutOfApp)
verify { intentManager.exitApplication() }
verify { exitManager.exitApplication() }
}
@Test