mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 15:15:34 +03:00
Move to organization navigation (#620)
This commit is contained in:
parent
df3a6598b3
commit
0f0fe81f41
9 changed files with 423 additions and 2 deletions
|
@ -21,6 +21,8 @@ import com.x8bit.bitwarden.ui.vault.feature.item.navigateToVaultItem
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.item.vaultItemDestination
|
import com.x8bit.bitwarden.ui.vault.feature.item.vaultItemDestination
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.manualcodeentry.navigateToManualCodeEntryScreen
|
import com.x8bit.bitwarden.ui.vault.feature.manualcodeentry.navigateToManualCodeEntryScreen
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.manualcodeentry.vaultManualCodeEntryDestination
|
import com.x8bit.bitwarden.ui.vault.feature.manualcodeentry.vaultManualCodeEntryDestination
|
||||||
|
import com.x8bit.bitwarden.ui.vault.feature.movetoorganization.navigateToVaultMoveToOrganization
|
||||||
|
import com.x8bit.bitwarden.ui.vault.feature.movetoorganization.vaultMoveToOrganizationDestination
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.qrcodescan.navigateToQrCodeScanScreen
|
import com.x8bit.bitwarden.ui.vault.feature.qrcodescan.navigateToQrCodeScanScreen
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.qrcodescan.vaultQrCodeScanDestination
|
import com.x8bit.bitwarden.ui.vault.feature.qrcodescan.vaultQrCodeScanDestination
|
||||||
import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType
|
import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType
|
||||||
|
@ -37,6 +39,7 @@ fun NavController.navigateToVaultUnlockedGraph(navOptions: NavOptions? = null) {
|
||||||
/**
|
/**
|
||||||
* Add vault unlocked destinations to the root nav graph.
|
* Add vault unlocked destinations to the root nav graph.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("LongMethod")
|
||||||
fun NavGraphBuilder.vaultUnlockedGraph(
|
fun NavGraphBuilder.vaultUnlockedGraph(
|
||||||
navController: NavController,
|
navController: NavController,
|
||||||
) {
|
) {
|
||||||
|
@ -68,11 +71,17 @@ fun NavGraphBuilder.vaultUnlockedGraph(
|
||||||
},
|
},
|
||||||
onNavigateBack = { navController.popBackStack() },
|
onNavigateBack = { navController.popBackStack() },
|
||||||
)
|
)
|
||||||
|
vaultMoveToOrganizationDestination(
|
||||||
|
onNavigateBack = { navController.popBackStack() },
|
||||||
|
)
|
||||||
vaultItemDestination(
|
vaultItemDestination(
|
||||||
onNavigateBack = { navController.popBackStack() },
|
onNavigateBack = { navController.popBackStack() },
|
||||||
onNavigateToVaultEditItem = {
|
onNavigateToVaultEditItem = {
|
||||||
navController.navigateToVaultAddEdit(VaultAddEditType.EditItem(it))
|
navController.navigateToVaultAddEdit(VaultAddEditType.EditItem(it))
|
||||||
},
|
},
|
||||||
|
onNavigateToMoveToOrganization = {
|
||||||
|
navController.navigateToVaultMoveToOrganization(it)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
vaultQrCodeScanDestination(
|
vaultQrCodeScanDestination(
|
||||||
onNavigateToManualCodeEntryScreen = {
|
onNavigateToManualCodeEntryScreen = {
|
||||||
|
|
|
@ -29,6 +29,7 @@ data class VaultItemArgs(val vaultItemId: String) {
|
||||||
fun NavGraphBuilder.vaultItemDestination(
|
fun NavGraphBuilder.vaultItemDestination(
|
||||||
onNavigateBack: () -> Unit,
|
onNavigateBack: () -> Unit,
|
||||||
onNavigateToVaultEditItem: (vaultItemId: String) -> Unit,
|
onNavigateToVaultEditItem: (vaultItemId: String) -> Unit,
|
||||||
|
onNavigateToMoveToOrganization: (vaultItemId: String) -> Unit,
|
||||||
) {
|
) {
|
||||||
composableWithSlideTransitions(
|
composableWithSlideTransitions(
|
||||||
route = VAULT_ITEM_ROUTE,
|
route = VAULT_ITEM_ROUTE,
|
||||||
|
@ -39,6 +40,7 @@ fun NavGraphBuilder.vaultItemDestination(
|
||||||
VaultItemScreen(
|
VaultItemScreen(
|
||||||
onNavigateBack = onNavigateBack,
|
onNavigateBack = onNavigateBack,
|
||||||
onNavigateToVaultAddEditItem = onNavigateToVaultEditItem,
|
onNavigateToVaultAddEditItem = onNavigateToVaultEditItem,
|
||||||
|
onNavigateToMoveToOrganization = onNavigateToMoveToOrganization,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,7 @@ fun VaultItemScreen(
|
||||||
intentHandler: IntentHandler = IntentHandler(context = LocalContext.current),
|
intentHandler: IntentHandler = IntentHandler(context = LocalContext.current),
|
||||||
onNavigateBack: () -> Unit,
|
onNavigateBack: () -> Unit,
|
||||||
onNavigateToVaultAddEditItem: (vaultItemId: String) -> Unit,
|
onNavigateToVaultAddEditItem: (vaultItemId: String) -> Unit,
|
||||||
|
onNavigateToMoveToOrganization: (vaultItemId: String) -> Unit,
|
||||||
) {
|
) {
|
||||||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
@ -82,8 +83,7 @@ fun VaultItemScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
is VaultItemEvent.NavigateToMoveToOrganization -> {
|
is VaultItemEvent.NavigateToMoveToOrganization -> {
|
||||||
// TODO Implement move to organization in BIT-844
|
onNavigateToMoveToOrganization(event.itemId)
|
||||||
Toast.makeText(context, "Not yet implemented.", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
is VaultItemEvent.ShowToast -> {
|
is VaultItemEvent.ShowToast -> {
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
package com.x8bit.bitwarden.ui.vault.feature.movetoorganization
|
||||||
|
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import androidx.navigation.NavGraphBuilder
|
||||||
|
import androidx.navigation.NavOptions
|
||||||
|
import androidx.navigation.NavType
|
||||||
|
import androidx.navigation.navArgument
|
||||||
|
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||||
|
|
||||||
|
private const val VAULT_MOVE_TO_ORGANIZATION_PREFIX = "vault_move_to_organization"
|
||||||
|
private const val VAULT_MOVE_TO_ORGANIZATION_ID = "vault_move_to_organization_id"
|
||||||
|
private const val VAULT_MOVE_TO_ORGANIZATION_ROUTE =
|
||||||
|
"$VAULT_MOVE_TO_ORGANIZATION_PREFIX/{$VAULT_MOVE_TO_ORGANIZATION_ID}"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to retrieve vault move to organization arguments from the [SavedStateHandle].
|
||||||
|
*/
|
||||||
|
@OmitFromCoverage
|
||||||
|
data class VaultMoveToOrganizationArgs(val vaultItemId: String) {
|
||||||
|
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||||
|
checkNotNull(savedStateHandle[VAULT_MOVE_TO_ORGANIZATION_ID]) as String,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the vault move to organization screen to the nav graph.
|
||||||
|
*/
|
||||||
|
fun NavGraphBuilder.vaultMoveToOrganizationDestination(
|
||||||
|
onNavigateBack: () -> Unit,
|
||||||
|
) {
|
||||||
|
composableWithSlideTransitions(
|
||||||
|
route = VAULT_MOVE_TO_ORGANIZATION_ROUTE,
|
||||||
|
arguments = listOf(
|
||||||
|
navArgument(VAULT_MOVE_TO_ORGANIZATION_ID) { type = NavType.StringType },
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
VaultMoveToOrganizationScreen(
|
||||||
|
onNavigateBack = onNavigateBack,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to the vault move to organization screen.
|
||||||
|
*/
|
||||||
|
fun NavController.navigateToVaultMoveToOrganization(
|
||||||
|
vaultItemId: String,
|
||||||
|
navOptions: NavOptions? = null,
|
||||||
|
) {
|
||||||
|
navigate(
|
||||||
|
route = "$VAULT_MOVE_TO_ORGANIZATION_PREFIX/$vaultItemId",
|
||||||
|
navOptions = navOptions,
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
package com.x8bit.bitwarden.ui.vault.feature.movetoorganization
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.imePadding
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.material3.rememberTopAppBarState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
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.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
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.BitwardenErrorContent
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.BitwardenLoadingContent
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the vault move to organization screen.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun VaultMoveToOrganizationScreen(
|
||||||
|
viewModel: VaultMoveToOrganizationViewModel = hiltViewModel(),
|
||||||
|
onNavigateBack: () -> Unit,
|
||||||
|
) {
|
||||||
|
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||||
|
val context = LocalContext.current
|
||||||
|
val resources = context.resources
|
||||||
|
EventsEffect(viewModel = viewModel) { event ->
|
||||||
|
when (event) {
|
||||||
|
is VaultMoveToOrganizationEvent.NavigateBack -> onNavigateBack()
|
||||||
|
is VaultMoveToOrganizationEvent.ShowToast -> {
|
||||||
|
Toast.makeText(context, event.text(resources), Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VaultMoveToOrganizationScaffold(
|
||||||
|
state = state,
|
||||||
|
closeClick = remember(viewModel) {
|
||||||
|
{ viewModel.trySendAction(VaultMoveToOrganizationAction.BackClick) }
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun VaultMoveToOrganizationScaffold(
|
||||||
|
state: VaultMoveToOrganizationState,
|
||||||
|
closeClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||||
|
BitwardenScaffold(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
|
topBar = {
|
||||||
|
BitwardenTopAppBar(
|
||||||
|
title = stringResource(id = R.string.move_to_organization),
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
navigationIcon = painterResource(id = R.drawable.ic_close),
|
||||||
|
navigationIconContentDescription = stringResource(id = R.string.close),
|
||||||
|
onNavigationIconClick = closeClick,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { innerPadding ->
|
||||||
|
val modifier = Modifier
|
||||||
|
.imePadding()
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(innerPadding)
|
||||||
|
|
||||||
|
when (state.viewState) {
|
||||||
|
is VaultMoveToOrganizationState.ViewState.Content -> {
|
||||||
|
// TODO add real views in BIT-844 UI
|
||||||
|
Text(text = "Content")
|
||||||
|
}
|
||||||
|
is VaultMoveToOrganizationState.ViewState.Error -> {
|
||||||
|
BitwardenErrorContent(
|
||||||
|
message = state.viewState.message(),
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is VaultMoveToOrganizationState.ViewState.Loading -> {
|
||||||
|
BitwardenLoadingContent(modifier = modifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
package com.x8bit.bitwarden.ui.vault.feature.movetoorganization
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private const val KEY_STATE = "state"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewModel responsible for handling user interactions in the [VaultMoveToOrganizationScreen].
|
||||||
|
*
|
||||||
|
* @param savedStateHandle Handles the navigation arguments of this ViewModel.
|
||||||
|
*/
|
||||||
|
@HiltViewModel
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
class VaultMoveToOrganizationViewModel @Inject constructor(
|
||||||
|
savedStateHandle: SavedStateHandle,
|
||||||
|
) : BaseViewModel<VaultMoveToOrganizationState, VaultMoveToOrganizationEvent, VaultMoveToOrganizationAction>(
|
||||||
|
initialState = savedStateHandle[KEY_STATE]
|
||||||
|
?: run {
|
||||||
|
VaultMoveToOrganizationState(
|
||||||
|
vaultItemId = VaultMoveToOrganizationArgs(savedStateHandle).vaultItemId,
|
||||||
|
viewState = VaultMoveToOrganizationState.ViewState.Loading,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
|
||||||
|
override fun handleAction(action: VaultMoveToOrganizationAction) {
|
||||||
|
when (action) {
|
||||||
|
is VaultMoveToOrganizationAction.BackClick -> handleBackClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleBackClick() {
|
||||||
|
sendEvent(VaultMoveToOrganizationEvent.NavigateBack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Models state for the [VaultMoveToOrganizationScreen].
|
||||||
|
*
|
||||||
|
* @property vaultItemId Indicates whether the VM is in add or edit mode.
|
||||||
|
* @property viewState indicates what view state the screen is in.
|
||||||
|
*/
|
||||||
|
@Parcelize
|
||||||
|
data class VaultMoveToOrganizationState(
|
||||||
|
val vaultItemId: String,
|
||||||
|
val viewState: ViewState,
|
||||||
|
) : Parcelable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the specific view states for the [VaultMoveToOrganizationScreen].
|
||||||
|
*/
|
||||||
|
sealed class ViewState : Parcelable {
|
||||||
|
/**
|
||||||
|
* Represents an error state for the [VaultMoveToOrganizationScreen].
|
||||||
|
*
|
||||||
|
* @property message the error message to display.
|
||||||
|
*/
|
||||||
|
@Parcelize
|
||||||
|
data class Error(
|
||||||
|
val message: Text,
|
||||||
|
) : ViewState()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a loading state for the [VaultMoveToOrganizationScreen].
|
||||||
|
*/
|
||||||
|
@Parcelize
|
||||||
|
data object Loading : ViewState()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a loaded content state for the [VaultMoveToOrganizationScreen].
|
||||||
|
*/
|
||||||
|
@Parcelize
|
||||||
|
data object Content : ViewState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Models events for the [VaultMoveToOrganizationScreen].
|
||||||
|
*/
|
||||||
|
sealed class VaultMoveToOrganizationEvent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates back to the previous screen.
|
||||||
|
*/
|
||||||
|
data object NavigateBack : VaultMoveToOrganizationEvent()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a toast with the given message.
|
||||||
|
*
|
||||||
|
* @property text the text to display.
|
||||||
|
*/
|
||||||
|
data class ShowToast(val text: Text) : VaultMoveToOrganizationEvent()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Models actions for the [VaultMoveToOrganizationScreen].
|
||||||
|
*/
|
||||||
|
sealed class VaultMoveToOrganizationAction {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Click the back button.
|
||||||
|
*/
|
||||||
|
data object BackClick : VaultMoveToOrganizationAction()
|
||||||
|
}
|
|
@ -51,6 +51,7 @@ class VaultItemScreenTest : BaseComposeTest() {
|
||||||
|
|
||||||
private var onNavigateBackCalled = false
|
private var onNavigateBackCalled = false
|
||||||
private var onNavigateToVaultEditItemId: String? = null
|
private var onNavigateToVaultEditItemId: String? = null
|
||||||
|
private var onNavigateToMoveToOrganizationItemId: String? = null
|
||||||
|
|
||||||
private val intentHandler = mockk<IntentHandler>()
|
private val intentHandler = mockk<IntentHandler>()
|
||||||
|
|
||||||
|
@ -68,6 +69,7 @@ class VaultItemScreenTest : BaseComposeTest() {
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
onNavigateBack = { onNavigateBackCalled = true },
|
onNavigateBack = { onNavigateBackCalled = true },
|
||||||
onNavigateToVaultAddEditItem = { onNavigateToVaultEditItemId = it },
|
onNavigateToVaultAddEditItem = { onNavigateToVaultEditItemId = it },
|
||||||
|
onNavigateToMoveToOrganization = { onNavigateToMoveToOrganizationItemId = it },
|
||||||
intentHandler = intentHandler,
|
intentHandler = intentHandler,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -81,6 +83,13 @@ class VaultItemScreenTest : BaseComposeTest() {
|
||||||
assertEquals(id, onNavigateToVaultEditItemId)
|
assertEquals(id, onNavigateToVaultEditItemId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `NavigateToMoveToOrganization event should invoke onNavigateToMoveToOrganization`() {
|
||||||
|
val id = "id1234"
|
||||||
|
mutableEventFlow.tryEmit(VaultItemEvent.NavigateToMoveToOrganization(itemId = id))
|
||||||
|
assertEquals(id, onNavigateToMoveToOrganizationItemId)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on close click should send CloseClick`() {
|
fun `on close click should send CloseClick`() {
|
||||||
composeTestRule.onNodeWithContentDescription(label = "Close").performClick()
|
composeTestRule.onNodeWithContentDescription(label = "Close").performClick()
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
package com.x8bit.bitwarden.ui.vault.feature.movetoorganization
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||||
|
import androidx.compose.ui.test.performClick
|
||||||
|
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class VaultMoveToOrganizationScreenTest : BaseComposeTest() {
|
||||||
|
|
||||||
|
private var onNavigateBackCalled = false
|
||||||
|
|
||||||
|
private val mutableEventFlow = bufferedMutableSharedFlow<VaultMoveToOrganizationEvent>()
|
||||||
|
private val mutableStateFlow = MutableStateFlow(createVaultMoveToOrganizationState())
|
||||||
|
|
||||||
|
private val viewModel = mockk<VaultMoveToOrganizationViewModel>(relaxed = true) {
|
||||||
|
every { eventFlow } returns mutableEventFlow
|
||||||
|
every { stateFlow } returns mutableStateFlow
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
composeTestRule.setContent {
|
||||||
|
VaultMoveToOrganizationScreen(
|
||||||
|
onNavigateBack = { onNavigateBackCalled = true },
|
||||||
|
viewModel = viewModel,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `on NavigateBack event should invoke onNavigateBack`() {
|
||||||
|
mutableEventFlow.tryEmit(VaultMoveToOrganizationEvent.NavigateBack)
|
||||||
|
assertTrue(onNavigateBackCalled)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `clicking close button should send BackClick action`() {
|
||||||
|
composeTestRule
|
||||||
|
.onNodeWithContentDescription(label = "Close")
|
||||||
|
.performClick()
|
||||||
|
|
||||||
|
verify {
|
||||||
|
viewModel.trySendAction(
|
||||||
|
VaultMoveToOrganizationAction.BackClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createVaultMoveToOrganizationState(): VaultMoveToOrganizationState =
|
||||||
|
VaultMoveToOrganizationState(
|
||||||
|
vaultItemId = "mockId",
|
||||||
|
viewState = VaultMoveToOrganizationState.ViewState.Content,
|
||||||
|
)
|
|
@ -0,0 +1,77 @@
|
||||||
|
package com.x8bit.bitwarden.ui.vault.feature.movetoorganization
|
||||||
|
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import app.cash.turbine.test
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
class VaultMoveToOrganizationViewModelTest : BaseViewModelTest() {
|
||||||
|
|
||||||
|
private val initialState = createVaultMoveToOrganizationState()
|
||||||
|
private val initialSavedStateHandle = createSavedStateHandleWithState(
|
||||||
|
state = initialState,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `initial state should be correct when state is null`() = runTest {
|
||||||
|
val viewModel = createViewModel(
|
||||||
|
savedStateHandle = createSavedStateHandleWithState(
|
||||||
|
state = null,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
initialState.copy(viewState = VaultMoveToOrganizationState.ViewState.Loading),
|
||||||
|
viewModel.stateFlow.value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `initial state should be correct`() = runTest {
|
||||||
|
val initState = createVaultMoveToOrganizationState()
|
||||||
|
val viewModel = createViewModel(
|
||||||
|
savedStateHandle = createSavedStateHandleWithState(
|
||||||
|
state = initState,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(initState, viewModel.stateFlow.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `CloseClick should emit NavigateBack`() = runTest {
|
||||||
|
val viewModel = createViewModel(
|
||||||
|
savedStateHandle = initialSavedStateHandle,
|
||||||
|
)
|
||||||
|
viewModel.eventFlow.test {
|
||||||
|
viewModel.actionChannel.trySend(VaultMoveToOrganizationAction.BackClick)
|
||||||
|
assertEquals(VaultMoveToOrganizationEvent.NavigateBack, awaitItem())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createViewModel(
|
||||||
|
savedStateHandle: SavedStateHandle = initialSavedStateHandle,
|
||||||
|
): VaultMoveToOrganizationViewModel =
|
||||||
|
VaultMoveToOrganizationViewModel(
|
||||||
|
savedStateHandle = savedStateHandle,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun createSavedStateHandleWithState(
|
||||||
|
state: VaultMoveToOrganizationState? = null,
|
||||||
|
vaultItemId: String = "mockId",
|
||||||
|
) = SavedStateHandle().apply {
|
||||||
|
set("state", state)
|
||||||
|
set("vault_move_to_organization_id", vaultItemId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
private fun createVaultMoveToOrganizationState(
|
||||||
|
viewState: VaultMoveToOrganizationState.ViewState = VaultMoveToOrganizationState.ViewState.Content,
|
||||||
|
vaultItemId: String = "mockId",
|
||||||
|
): VaultMoveToOrganizationState =
|
||||||
|
VaultMoveToOrganizationState(
|
||||||
|
vaultItemId = vaultItemId,
|
||||||
|
viewState = viewState,
|
||||||
|
)
|
||||||
|
}
|
Loading…
Reference in a new issue