mirror of
https://github.com/bitwarden/android.git
synced 2026-05-27 06:54:00 -05:00
BIT-844: Move to Organization UI (#638)
This commit is contained in:
@@ -1,13 +1,28 @@
|
||||
package com.x8bit.bitwarden.ui.vault.feature.movetoorganization
|
||||
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.assertIsNotDisplayed
|
||||
import androidx.compose.ui.test.assertIsOff
|
||||
import androidx.compose.ui.test.assertIsOn
|
||||
import androidx.compose.ui.test.filterToOne
|
||||
import androidx.compose.ui.test.hasAnyAncestor
|
||||
import androidx.compose.ui.test.isDialog
|
||||
import androidx.compose.ui.test.onAllNodesWithText
|
||||
import androidx.compose.ui.test.onLast
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performScrollTo
|
||||
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.util.onNodeWithContentDescriptionAfterScroll
|
||||
import com.x8bit.bitwarden.ui.vault.feature.movetoorganization.util.createMockOrganizationList
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
@@ -52,10 +67,191 @@ class VaultMoveToOrganizationScreenTest : BaseComposeTest() {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking move button should send MoveClick action`() {
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Move")
|
||||
.performClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
VaultMoveToOrganizationAction.MoveClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `selecting an organization should send OrganizationSelect action`() {
|
||||
composeTestRule
|
||||
.onNodeWithContentDescriptionAfterScroll(label = "Organization, Organization 1")
|
||||
.performClick()
|
||||
// Choose the option from the menu
|
||||
composeTestRule
|
||||
.onAllNodesWithText(text = "Organization 2")
|
||||
.onLast()
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
VaultMoveToOrganizationAction.OrganizationSelect(
|
||||
VaultMoveToOrganizationState.ViewState.Content.Organization(
|
||||
id = "2",
|
||||
name = "Organization 2",
|
||||
collections = listOf(
|
||||
VaultMoveToOrganizationState.ViewState.Content.Collection(
|
||||
id = "1",
|
||||
name = "Collection 1",
|
||||
isSelected = true,
|
||||
),
|
||||
VaultMoveToOrganizationState.ViewState.Content.Collection(
|
||||
id = "2",
|
||||
name = "Collection 2",
|
||||
isSelected = false,
|
||||
),
|
||||
VaultMoveToOrganizationState.ViewState.Content.Collection(
|
||||
id = "3",
|
||||
name = "Collection 3",
|
||||
isSelected = false,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the organization option field should display according to state`() {
|
||||
composeTestRule
|
||||
.onNodeWithContentDescriptionAfterScroll(label = "Organization, Organization 1")
|
||||
.assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update { currentState ->
|
||||
currentState.copy(
|
||||
viewState = VaultMoveToOrganizationState.ViewState.Content(
|
||||
organizations = createMockOrganizationList(),
|
||||
selectedOrganizationId = "2",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithContentDescriptionAfterScroll(label = "Organization, Organization 2")
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `selecting a collection should send CollectionSelect action`() {
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Collection 2")
|
||||
.performClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
VaultMoveToOrganizationAction.CollectionSelect(
|
||||
VaultMoveToOrganizationState.ViewState.Content.Collection(
|
||||
id = "2",
|
||||
name = "Collection 2",
|
||||
isSelected = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the collection list should display according to state`() {
|
||||
composeTestRule
|
||||
.onNodeWithText("Collection 1")
|
||||
.assertIsOn()
|
||||
composeTestRule
|
||||
.onNodeWithText("Collection 2")
|
||||
.assertIsOff()
|
||||
composeTestRule
|
||||
.onNodeWithText("Collection 3")
|
||||
.assertIsOff()
|
||||
|
||||
mutableStateFlow.update { currentState ->
|
||||
currentState.copy(
|
||||
viewState = VaultMoveToOrganizationState.ViewState.Content(
|
||||
organizations = createMockOrganizationList()
|
||||
.map { organization ->
|
||||
organization.copy(
|
||||
collections =
|
||||
if (organization.id == "1") {
|
||||
organization
|
||||
.collections
|
||||
.map { collection ->
|
||||
collection.copy(isSelected = collection.id != "1")
|
||||
}
|
||||
} else {
|
||||
organization.collections
|
||||
},
|
||||
)
|
||||
},
|
||||
selectedOrganizationId = "1",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Collection 1")
|
||||
.assertIsOff()
|
||||
composeTestRule
|
||||
.onNodeWithText("Collection 2")
|
||||
.assertIsOn()
|
||||
composeTestRule
|
||||
.onNodeWithText("Collection 3")
|
||||
.assertIsOn()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `loading dialog should display according to state`() {
|
||||
composeTestRule
|
||||
.onAllNodesWithText("loading")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.assertIsNotDisplayed()
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialogState = VaultMoveToOrganizationState.DialogState.Loading("loading".asText()),
|
||||
)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onAllNodesWithText("loading")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `error dialog should display according to state`() {
|
||||
composeTestRule
|
||||
.onAllNodesWithText("error")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.assertIsNotDisplayed()
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialogState = VaultMoveToOrganizationState.DialogState.Error("error".asText()),
|
||||
)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onAllNodesWithText("error")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createVaultMoveToOrganizationState(): VaultMoveToOrganizationState =
|
||||
VaultMoveToOrganizationState(
|
||||
vaultItemId = "mockId",
|
||||
viewState = VaultMoveToOrganizationState.ViewState.Content,
|
||||
viewState = VaultMoveToOrganizationState.ViewState.Content(
|
||||
organizations = createMockOrganizationList(),
|
||||
selectedOrganizationId = "1",
|
||||
),
|
||||
dialogState = null,
|
||||
)
|
||||
|
||||
@@ -2,7 +2,10 @@ package com.x8bit.bitwarden.ui.vault.feature.movetoorganization
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.vault.feature.movetoorganization.util.createMockOrganizationList
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
@@ -50,6 +53,119 @@ class VaultMoveToOrganizationViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `OrganizationSelect should update selected Organization`() = runTest {
|
||||
val viewModel = createViewModel(
|
||||
savedStateHandle = createSavedStateHandleWithState(
|
||||
state = createVaultMoveToOrganizationState(
|
||||
viewState = VaultMoveToOrganizationState.ViewState.Content(
|
||||
organizations = createMockOrganizationList(),
|
||||
selectedOrganizationId = "1",
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
val action = VaultMoveToOrganizationAction.OrganizationSelect(
|
||||
VaultMoveToOrganizationState.ViewState.Content.Organization(
|
||||
id = "3",
|
||||
name = "Organization 3",
|
||||
collections = emptyList(),
|
||||
),
|
||||
)
|
||||
val expectedState = createVaultMoveToOrganizationState(
|
||||
viewState = VaultMoveToOrganizationState.ViewState.Content(
|
||||
organizations = createMockOrganizationList(),
|
||||
selectedOrganizationId = "3",
|
||||
),
|
||||
)
|
||||
|
||||
viewModel.actionChannel.trySend(action)
|
||||
|
||||
assertEquals(
|
||||
expectedState,
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `CollectionSelect should update selected Collections`() = runTest {
|
||||
val viewModel = createViewModel(
|
||||
savedStateHandle = createSavedStateHandleWithState(
|
||||
state = createVaultMoveToOrganizationState(
|
||||
viewState = VaultMoveToOrganizationState.ViewState.Content(
|
||||
organizations = createMockOrganizationList(),
|
||||
selectedOrganizationId = "1",
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
val selectCollection3Action = VaultMoveToOrganizationAction.CollectionSelect(
|
||||
VaultMoveToOrganizationState.ViewState.Content.Collection(
|
||||
id = "3",
|
||||
name = "Collection 3",
|
||||
isSelected = false,
|
||||
),
|
||||
)
|
||||
val unselectCollection1Action = VaultMoveToOrganizationAction.CollectionSelect(
|
||||
VaultMoveToOrganizationState.ViewState.Content.Collection(
|
||||
id = "1",
|
||||
name = "Collection 1",
|
||||
isSelected = true,
|
||||
),
|
||||
)
|
||||
val expectedState = createVaultMoveToOrganizationState(
|
||||
viewState = VaultMoveToOrganizationState.ViewState.Content(
|
||||
organizations = createMockOrganizationList()
|
||||
.map { organization ->
|
||||
organization.copy(
|
||||
collections =
|
||||
if (organization.id == "1") {
|
||||
organization.collections.map {
|
||||
it.copy(isSelected = it.id == "3")
|
||||
}
|
||||
} else {
|
||||
organization.collections
|
||||
},
|
||||
)
|
||||
},
|
||||
selectedOrganizationId = "1",
|
||||
),
|
||||
)
|
||||
|
||||
viewModel.actionChannel.trySend(selectCollection3Action)
|
||||
viewModel.actionChannel.trySend(unselectCollection1Action)
|
||||
|
||||
assertEquals(
|
||||
expectedState,
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `MoveClick should show dialog, and remove it once an item is moved`() = runTest {
|
||||
val viewModel = createViewModel(
|
||||
savedStateHandle = createSavedStateHandleWithState(
|
||||
state = initialState,
|
||||
),
|
||||
)
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(initialState, awaitItem())
|
||||
viewModel.actionChannel.trySend(VaultMoveToOrganizationAction.MoveClick)
|
||||
assertEquals(
|
||||
initialState.copy(
|
||||
dialogState = VaultMoveToOrganizationState.DialogState.Loading(
|
||||
message = R.string.saving.asText(),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
assertEquals(
|
||||
initialState,
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createViewModel(
|
||||
savedStateHandle: SavedStateHandle = initialSavedStateHandle,
|
||||
): VaultMoveToOrganizationViewModel =
|
||||
@@ -67,11 +183,13 @@ class VaultMoveToOrganizationViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
private fun createVaultMoveToOrganizationState(
|
||||
viewState: VaultMoveToOrganizationState.ViewState = VaultMoveToOrganizationState.ViewState.Content,
|
||||
viewState: VaultMoveToOrganizationState.ViewState = VaultMoveToOrganizationState.ViewState.Empty,
|
||||
vaultItemId: String = "mockId",
|
||||
dialogState: VaultMoveToOrganizationState.DialogState? = null,
|
||||
): VaultMoveToOrganizationState =
|
||||
VaultMoveToOrganizationState(
|
||||
vaultItemId = vaultItemId,
|
||||
viewState = viewState,
|
||||
dialogState = dialogState,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.x8bit.bitwarden.ui.vault.feature.movetoorganization.util
|
||||
|
||||
import com.x8bit.bitwarden.ui.vault.feature.movetoorganization.VaultMoveToOrganizationState
|
||||
|
||||
/**
|
||||
* Creates a list of mock organizations.
|
||||
*/
|
||||
fun createMockOrganizationList():
|
||||
List<VaultMoveToOrganizationState.ViewState.Content.Organization> =
|
||||
listOf(
|
||||
VaultMoveToOrganizationState.ViewState.Content.Organization(
|
||||
id = "1",
|
||||
name = "Organization 1",
|
||||
collections = listOf(
|
||||
VaultMoveToOrganizationState.ViewState.Content.Collection(
|
||||
id = "1",
|
||||
name = "Collection 1",
|
||||
isSelected = true,
|
||||
),
|
||||
VaultMoveToOrganizationState.ViewState.Content.Collection(
|
||||
id = "2",
|
||||
name = "Collection 2",
|
||||
isSelected = false,
|
||||
),
|
||||
VaultMoveToOrganizationState.ViewState.Content.Collection(
|
||||
id = "3",
|
||||
name = "Collection 3",
|
||||
isSelected = false,
|
||||
),
|
||||
),
|
||||
),
|
||||
VaultMoveToOrganizationState.ViewState.Content.Organization(
|
||||
id = "2",
|
||||
name = "Organization 2",
|
||||
collections = listOf(
|
||||
VaultMoveToOrganizationState.ViewState.Content.Collection(
|
||||
id = "1",
|
||||
name = "Collection 1",
|
||||
isSelected = true,
|
||||
),
|
||||
VaultMoveToOrganizationState.ViewState.Content.Collection(
|
||||
id = "2",
|
||||
name = "Collection 2",
|
||||
isSelected = false,
|
||||
),
|
||||
VaultMoveToOrganizationState.ViewState.Content.Collection(
|
||||
id = "3",
|
||||
name = "Collection 3",
|
||||
isSelected = false,
|
||||
),
|
||||
),
|
||||
),
|
||||
VaultMoveToOrganizationState.ViewState.Content.Organization(
|
||||
id = "3",
|
||||
name = "Organization 3",
|
||||
collections = emptyList(),
|
||||
),
|
||||
)
|
||||
Reference in New Issue
Block a user